diff --git a/course/amd/build/actions.min.js b/course/amd/build/actions.min.js index 2c3fda01b4e..611546dbe2e 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();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}}); +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=["moveSection","moveCm"],n=l.getCurrentCourseEditor(),o={EDITINPROGRESS:"editinprogress",SECTIONDRAGGABLE:"sectiondraggable",EDITINGMOVE:"editing_move"},p={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){p.SECTIONLI=a}});var q=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},r=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=n.state,d=q(a);if(!b&&c&&d){var e;b=null===(e=c.cm.get(d))||void 0===e?void 0:e.name}return b},s=function(a){a.addClass(o.EDITINPROGRESS);var b=a.find(p.ACTIONAREA).get(0);if(b){var c=M.util.add_spinner(g,g.Node(b));c.show();if(a.data("id")!==void 0){n.dispatch("cmLock",[a.data("id")],!0)}return c}return null},t=function(a){a.addClass(o.EDITINPROGRESS);var b=a.find(p.SECTIONACTIONMENU).get(0);if(b){var c=M.util.add_spinner(g,g.Node(b));c.show();if(a.data("id")!==void 0){n.dispatch("sectionLock",[a.data("id")],!0)}return c}return null},u=function(a){var b=M.util.add_lightbox(g,g.Node(a.get(0)));b.show();return b},v=function(a,b,c){window.setTimeout(function(){a.removeClass(o.EDITINPROGRESS);if(b){b.hide()}if(a.data("id")!==void 0){var c="section"===a.data("for")?"sectionLock":"cmLock";n.dispatch(c,[a.data("id")],!1)}},c)},w=function(a,b){if(a){window.setTimeout(function(){a.hide()},b)}},x=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))}},y=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(p.MENU).find(p.TOGGLE).focus()}},z=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},A=function(c,e,f){var g=f.attr("data-action"),h=s(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=u(f.closest(p.SECTIONLI))}a.when.apply(a,i).done(function(b){var d=z(c);c.replaceWith(b);var f=[];a("
"+b+"
").find(p.ACTIVITYLI).each(function(b){x(a(this).attr("id"));if(0===b){y(a(this).attr("id"),g);d=null}f.push(q(a(this)))});if(d){d.focus()}v(c,h,400);w(j,400);c.trigger(a.Event("coursemoduleedited",{ajaxreturn:b,action:g}));n.dispatch("legacyActivityAction",g,e,f)}).fail(function(b){v(c,h);w(j);var f=a.Event("coursemoduleeditfailed",{exception:b,action:g});c.trigger(f);if(!f.isDefaultPrevented()){d.exception(b)}})},B=function(c,d,e){var f=a(c),g=s(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){v(f,g,400);G(a);b(a)}).fail(function(){v(f,g);c()})})},C=function(a,b){var c=a.attr("class").match(/modtype_([^\s]*)/)[1],f=r(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)})})},D=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)})},E=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)},F=function(b,c,d,e,f){var g=c.attr("data-action");if("hide"===g||"show"===g){if("hide"===g){b.addClass("hidden");E(c,"i/show","showfromothers","format_"+e,"show")}else{b.removeClass("hidden");E(c,"i/hide","hidefromothers","format_"+e,"hide")}if(d.modules!==void 0){for(var h in d.modules){G(d.modules[h])}}if(d.section_availability!==void 0){b.find(".section_availability").first().replaceWith(d.section_availability)}var k=n.state.section.get(f);if(k!==void 0){n.dispatch("sectionState",[f])}}else if("setmarker"===g){var i=a(p.SECTIONLI+".current"),j=i.find(p.SECTIONACTIONMENU+" a[data-action=removemarker]");i.removeClass("current");E(j,"i/marker","highlight","core","setmarker");b.addClass("current");E(c,"i/marked","highlightoff","core","removemarker");n.dispatch("legacySectionAction",g,f)}else if("removemarker"===g){b.removeClass("current");E(c,"i/marker","highlight","core","setmarker");n.dispatch("legacySectionAction",g,f)}},G=function(b){a("
"+b+"
").find(p.ACTIVITYLI).each(function(){var c=a(this).attr("id");a(p.ACTIVITYLI+"#"+c).replaceWith(b);x(c)})},H=function(c,e,f,g){var h=f.attr("data-action"),i=f.attr("data-sectionreturn")?f.attr("data-sectionreturn"):0;if(n.supportComponents&&m.includes(h)){return!1}var j=t(c),k=b.call([{methodname:"core_course_edit_section",args:{id:e,action:h,sectionreturn:i}}],!0),l=u(c);a.when.apply(a,k).done(function(b){var d=a.parseJSON(b);v(c,j);w(l);c.find(p.SECTIONACTIONMENU).find(p.TOGGLE).focus();var i=a.Event("coursesectionedited",{ajaxreturn:d,action:h});c.trigger(i);if(!i.isDefaultPrevented()){F(c,f,d,g,e)}}).fail(function(b){v(c,j);w(l);var f=a.Event("coursesectioneditfailed",{exception:b,action:h});c.trigger(f);if(!f.isDefaultPrevented()){d.exception(b)}});return!0};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=q(c);if(d){var e=c.find("."+o.EDITINGMOVE).attr("data-sectionreturn");B(c,d,e)}},updateMovedCmState:function updateMovedCmState(a){var b=n.state,c=b.cm.get(a.cmid);if(c!==void 0){n.dispatch("sectionState",[c.sectionid])}n.dispatch("cmState",[a.cmid])},updateMovedSectionState:function updateMovedSectionState(){n.dispatch("courseState")}})});n.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":n.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",p.ACTIVITYLI+" "+p.ACTIVITYACTION+"[data-action]",function(b){if("keypress"===b.type&&13!==b.keyCode){return}var c=a(this),d=c.closest(p.ACTIVITYLI),e=c.attr("data-action"),f=q(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){C(d,function(){A(d,f,c)})}else{A(d,f,c)}});a("body").on("click keypress",p.SECTIONLI+" "+p.SECTIONACTIONMENU+"[data-sectionid] a[data-action]",function(c){if("keypress"===c.type&&13!==c.keyCode){return}var d=a(this),e=d.closest(p.SECTIONLI),f=d.closest(p.SECTIONACTIONMENU).attr("data-sectionid"),g=!0;if(d.attr("data-confirm")){D(d.attr("data-confirm"),function(){g=H(e,f,d,b)})}else{g=H(e,f,d,b)}if(g){c.preventDefault()}});a("body").on("updated","".concat(p.SECTIONLI," [data-inplaceeditable]"),function(a){if(a.ajaxreturn&&a.ajaxreturn.itemid){var b=n.state,c=b.section.get(a.ajaxreturn.itemid);if(c!==void 0){n.dispatch("sectionState",[a.ajaxreturn.itemid])}}});a("body").on("updated","".concat(p.ACTIVITYLI," [data-inplaceeditable]"),function(a){if(a.ajaxreturn&&a.ajaxreturn.itemid){n.dispatch("cmState",[a.ajaxreturn.itemid])}});e.get_string("numberweeks").done(function(b){var c=a(p.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(p.SECTIONACTIONMENU+" "+b);E(g,c,d,e,f)},refreshModule:B}}); //# sourceMappingURL=actions.min.js.map diff --git a/course/amd/build/actions.min.js.map b/course/amd/build/actions.min.js.map index ce14cf77970..77f0685d3bb 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","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":"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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 * 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 +{"version":3,"sources":["../src/actions.js"],"names":["define","$","ajax","templates","notification","str","url","Y","ModalFactory","ModalEvents","KeyCodes","log","editor","componentActions","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","supportComponents","includes","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","isExecuted","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":"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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 * 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 // Eventually, core_courseformat/local/content/actions will handle all actions for\n // component compatible formats and the default actions.js won't be necessary anymore.\n // Meanwhile, we filter the migrated actions.\n const componentActions = ['moveSection', 'moveCm'];\n\n // The course reactive instance.\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 * @return {boolean} true the action call is sent to the server or false if it is ignored.\n */\n var editSection = function(sectionElement, sectionid, target, courseformat) {\n var action = target.attr('data-action'),\n sectionreturn = target.attr('data-sectionreturn') ? target.attr('data-sectionreturn') : 0;\n\n // Filter direct component handled actions.\n if (courseeditor.supportComponents && componentActions.includes(action)) {\n return false;\n }\n\n var spinner = addSectionSpinner(sectionElement);\n var promises = ajax.call([{\n methodname: 'core_course_edit_section',\n args: {id: sectionid, action: action, sectionreturn: sectionreturn}\n }], true);\n\n var lightbox = addSectionLightbox(sectionElement);\n $.when.apply($, promises)\n .done(function(dataencoded) {\n var data = $.parseJSON(dataencoded);\n removeSpinner(sectionElement, spinner);\n removeLightbox(lightbox);\n sectionElement.find(SELECTOR.SECTIONACTIONMENU).find(SELECTOR.TOGGLE).focus();\n // Trigger event that can be observed by course formats.\n var e = $.Event('coursesectionedited', {ajaxreturn: data, action: action});\n sectionElement.trigger(e);\n if (!e.isDefaultPrevented()) {\n defaultEditSectionHandler(sectionElement, target, data, courseformat, sectionid);\n }\n }).fail(function(ex) {\n // Remove spinner and lightbox.\n removeSpinner(sectionElement, spinner);\n removeLightbox(lightbox);\n // Trigger event that can be observed by course formats.\n var e = $.Event('coursesectioneditfailed', {exception: ex, action: action});\n sectionElement.trigger(e);\n if (!e.isDefaultPrevented()) {\n notification.exception(ex);\n }\n });\n return true;\n };\n\n // 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\n let isExecuted = true;\n if (actionItem.attr('data-confirm')) {\n // Action requires confirmation.\n confirmEditSection(actionItem.attr('data-confirm'), function() {\n isExecuted = editSection(sectionElement, sectionId, actionItem, courseformat);\n });\n } else {\n isExecuted = editSection(sectionElement, sectionId, actionItem, courseformat);\n }\n // Prevent any other module from capturing the action if it is already in execution.\n if (isExecuted) {\n e.preventDefault();\n }\n });\n\n // The section and activity names are edited using inplace editable.\n // The \"update\" jQuery event must be captured in order to update the course state.\n $('body').on('updated', `${SELECTOR.SECTIONLI} [data-inplaceeditable]`, function(e) {\n if (e.ajaxreturn && e.ajaxreturn.itemid) {\n const state = courseeditor.state;\n const section = state.section.get(e.ajaxreturn.itemid);\n if (section !== undefined) {\n courseeditor.dispatch('sectionState', [e.ajaxreturn.itemid]);\n }\n }\n });\n $('body').on('updated', `${SELECTOR.ACTIVITYLI} [data-inplaceeditable]`, function(e) {\n if (e.ajaxreturn && e.ajaxreturn.itemid) {\n courseeditor.dispatch('cmState', [e.ajaxreturn.itemid]);\n }\n });\n\n // 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 3f2fdb0b6aa..41f1fed0f6f 100644 --- a/course/amd/src/actions.js +++ b/course/amd/src/actions.js @@ -25,6 +25,12 @@ define(['jquery', 'core/ajax', 'core/templates', 'core/notification', 'core/str' 'core/modal_factory', 'core/modal_events', 'core/key_codes', 'core/log', 'core_courseformat/courseeditor'], function($, ajax, templates, notification, str, url, Y, ModalFactory, ModalEvents, KeyCodes, log, editor) { + // Eventually, core_courseformat/local/content/actions will handle all actions for + // component compatible formats and the default actions.js won't be necessary anymore. + // Meanwhile, we filter the migrated actions. + const componentActions = ['moveSection', 'moveCm']; + + // The course reactive instance. const courseeditor = editor.getCurrentCourseEditor(); var CSS = { @@ -488,10 +494,17 @@ define(['jquery', 'core/ajax', 'core/templates', 'core/notification', 'core/str' * @param {Nunmber} sectionid * @param {JQuery} target the element (menu item) that was clicked * @param {String} courseformat + * @return {boolean} true the action call is sent to the server or false if it is ignored. */ var editSection = function(sectionElement, sectionid, target, courseformat) { var action = target.attr('data-action'), sectionreturn = target.attr('data-sectionreturn') ? target.attr('data-sectionreturn') : 0; + + // Filter direct component handled actions. + if (courseeditor.supportComponents && componentActions.includes(action)) { + return false; + } + var spinner = addSectionSpinner(sectionElement); var promises = ajax.call([{ methodname: 'core_course_edit_section', @@ -522,6 +535,7 @@ define(['jquery', 'core/ajax', 'core/templates', 'core/notification', 'core/str' notification.exception(ex); } }); + return true; }; // Register a function to be executed after D&D of an activity. @@ -735,14 +749,19 @@ define(['jquery', 'core/ajax', 'core/templates', 'core/notification', 'core/str' var actionItem = $(this), sectionElement = actionItem.closest(SELECTOR.SECTIONLI), sectionId = actionItem.closest(SELECTOR.SECTIONACTIONMENU).attr('data-sectionid'); - e.preventDefault(); + + let isExecuted = true; if (actionItem.attr('data-confirm')) { // Action requires confirmation. confirmEditSection(actionItem.attr('data-confirm'), function() { - editSection(sectionElement, sectionId, actionItem, courseformat); + isExecuted = editSection(sectionElement, sectionId, actionItem, courseformat); }); } else { - editSection(sectionElement, sectionId, actionItem, courseformat); + isExecuted = editSection(sectionElement, sectionId, actionItem, courseformat); + } + // Prevent any other module from capturing the action if it is already in execution. + if (isExecuted) { + e.preventDefault(); } }); diff --git a/course/format/amd/build/local/content.min.js b/course/format/amd/build/local/content.min.js index 57cbdf6d817..9c2da155362 100644 --- a/course/format/amd/build/local/content.min.js +++ b/course/format/amd/build/local/content.min.js @@ -1,2 +1,2 @@ -define ("core_courseformat/local/content",["exports","core/reactive","core_courseformat/courseeditor","core/inplace_editable","core_courseformat/local/content/section","core_courseformat/local/content/section/cmitem","core_course/actions"],function(a,b,c,d,e,f,g){"use strict";Object.defineProperty(a,"__esModule",{value:!0});a.default=void 0;d=h(d);e=h(e);f=h(f);g=h(g);function h(a){return a&&a.__esModule?a:{default:a}}function i(a){"@babel/helpers - typeof";if("function"==typeof Symbol&&"symbol"==typeof Symbol.iterator){i=function(a){return typeof a}}else{i=function(a){return a&&"function"==typeof Symbol&&a.constructor===Symbol&&a!==Symbol.prototype?"symbol":typeof a}}return i(a)}function j(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 k(a){for(var b=1,c;bb.length){var i=a.lastChild;if(i.classList.contains("dndupload-preview")){f=i}else{var g,h;d[null!==(g=null===i||void 0===i?void 0:null===(h=i.dataset)||void 0===h?void 0:h.id)&&void 0!==g?g:0]=i}a.removeChild(i)}if(f){a.append(f)}}}],[{key:"init",value:function init(a,d){return new b({element:document.getElementById(a),reactive:(0,c.getCurrentCourseEditor)(),selectors:d})}}]);return b}(b.BaseComponent);a.default=w;return a.default}); +define ("core_courseformat/local/content",["exports","core/reactive","core_courseformat/courseeditor","core/inplace_editable","core_courseformat/local/content/section","core_courseformat/local/content/section/cmitem","core_course/actions","core_courseformat/local/content/actions"],function(a,b,c,d,e,f,g,h){"use strict";Object.defineProperty(a,"__esModule",{value:!0});a.default=void 0;d=i(d);e=i(e);f=i(f);g=i(g);h=i(h);function i(a){return a&&a.__esModule?a:{default:a}}function j(a){"@babel/helpers - typeof";if("function"==typeof Symbol&&"symbol"==typeof Symbol.iterator){j=function(a){return typeof a}}else{j=function(a){return a&&"function"==typeof Symbol&&a.constructor===Symbol&&a!==Symbol.prototype?"symbol":typeof a}}return j(a)}function k(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 l(a){return function(){var b=this,c=arguments;return new Promise(function(d,e){var h=a.apply(b,c);function f(a){k(h,d,e,f,g,"next",a)}function g(a){k(h,d,e,f,g,"throw",a)}f(void 0)})}}function m(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 n(a){for(var b=1,c;bc.length){j=b.lastChild;if(null===j||void 0===j?void 0:null===(i=j.classList)||void 0===i?void 0:i.contains("dndupload-preview")){h=j}else{e[null!==(k=null===j||void 0===j?void 0:null===(l=j.dataset)||void 0===l?void 0:l.id)&&void 0!==k?k:0]=j}b.removeChild(j)}if(h){b.append(h)}case 10:case"end":return a.stop();}}},a)}));return function _fixOrder(){return a.apply(this,arguments)}}()}],[{key:"init",value:function init(a,d){return new b({element:document.getElementById(a),reactive:(0,c.getCurrentCourseEditor)(),selectors:d})}}]);return b}(b.BaseComponent);a.default=z;return a.default}); //# sourceMappingURL=content.min.js.map diff --git a/course/format/amd/build/local/content.min.js.map b/course/format/amd/build/local/content.min.js.map index 9ea4db4516e..730f12a220d 100644 --- a/course/format/amd/build/local/content.min.js.map +++ b/course/format/amd/build/local/content.min.js.map @@ -1 +1 @@ -{"version":3,"sources":["../../src/local/content.js"],"names":["Component","name","selectors","SECTION","SECTION_ITEM","SECTION_CMLIST","COURSE_SECTIONLIST","CM","dettachedCms","dettachedSections","sections","cms","_indexContents","reactive","supportComponents","watch","handler","_reloadCm","_refreshSectionNumber","_startProcessing","_refreshCourseSectionlist","_refreshSectionCmlist","element","cmitem","getElement","id","courseActions","refreshModule","target","Error","number","dataset","sectionid","inplace","inplaceeditable","getInplaceEditable","querySelector","currentvalue","getValue","currentitemid","getItemId","rawtitle","setValue","cmlist","section","listparent","_fixOrder","sectionlist","_scanIndex","item","Section","CmItem","selector","index","creationhandler","items","getElements","forEach","unregister","indexed","promise","then","catch","container","neworder","dettachedelements","length","classList","add","innerHTML","remove","itemid","currentitem","children","append","insertBefore","dndFakeActivity","lastchild","lastChild","contains","removeChild","document","getElementById","BaseComponent"],"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 * Course index main component.\n *\n * @module core_courseformat/local/content\n * @class core_courseformat/local/content\n * @copyright 2020 Ferran Recio \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport {BaseComponent} from 'core/reactive';\nimport {getCurrentCourseEditor} from 'core_courseformat/courseeditor';\nimport inplaceeditable from 'core/inplace_editable';\nimport Section from 'core_courseformat/local/content/section';\nimport CmItem from 'core_courseformat/local/content/section/cmitem';\n// Course actions is needed for actions that are not migrated to components.\nimport courseActions from 'core_course/actions';\n\nexport default class Component extends BaseComponent {\n\n /**\n * Constructor hook.\n */\n create() {\n // Optional component name for debugging.\n this.name = 'course_format';\n // Default query selectors.\n this.selectors = {\n SECTION: `[data-for='section']`,\n SECTION_ITEM: `[data-for='section_title']`,\n SECTION_CMLIST: `[data-for='cmlist']`,\n COURSE_SECTIONLIST: `[data-for='course_sectionlist']`,\n CM: `[data-for='cmitem']`,\n };\n // Array to save dettached elements during element resorting.\n this.dettachedCms = {};\n this.dettachedSections = {};\n // Index of sections and cms components.\n this.sections = {};\n this.cms = {};\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 reactive: getCurrentCourseEditor(),\n selectors,\n });\n }\n\n /**\n * Initial state ready method.\n *\n * Course content elements could not provide JS Components because the elements HTML is applied\n * directly from the course actions. To keep internal components updated this module keeps\n * a list of the active components and mark them as \"indexed\". This way when any action replace\n * the HTML this component will recreate the components an add any necessary event listener.\n *\n */\n stateReady() {\n this._indexContents();\n }\n\n /**\n * Return the component watchers.\n *\n * @returns {Array} of watchers\n */\n getWatchers() {\n // Check if the course format is compatible with reactive components.\n if (!this.reactive.supportComponents) {\n return [];\n }\n return [\n // State changes that require to reload some course modules.\n {watch: `cm.visible:updated`, handler: this._reloadCm},\n // Update section number and title.\n {watch: `section.number:updated`, handler: this._refreshSectionNumber},\n // Sections and cm sorting.\n {watch: `transaction:start`, handler: this._startProcessing},\n {watch: `course.sectionlist:updated`, handler: this._refreshCourseSectionlist},\n {watch: `section.cmlist:updated`, handler: this._refreshSectionCmlist},\n // Reindex sections and cms.\n {watch: `state:updated`, handler: this._indexContents},\n // State changes thaty require to reload course modules.\n {watch: `cm.visible:updated`, handler: this._reloadCm},\n {watch: `cm.sectionid:updated`, handler: this._reloadCm},\n ];\n }\n\n /**\n * Reload a course module.\n *\n * Most course module HTML is still strongly backend dependant.\n * Some changes require to get a new version af the module.\n *\n * @param {Object} update the state update data\n */\n _reloadCm({element}) {\n const cmitem = this.getElement(this.selectors.CM, element.id);\n if (cmitem) {\n courseActions.refreshModule(cmitem, element.id);\n }\n }\n\n /**\n * Setup the component to start a transaction.\n *\n * Some of the course actions replaces the current DOM element with a new one before updating the\n * course state. This means the component cannot preload any index properly until the transaction starts.\n *\n */\n _startProcessing() {\n // During a section or cm sorting, some elements could be dettached from the DOM and we\n // need to store somewhare in case they are needed later.\n this.dettachedCms = {};\n this.dettachedSections = {};\n }\n\n /**\n * Update a course section when the section number changes.\n *\n * The courseActions module used for most course section tools still depends on css classes and\n * section numbers (not id). To prevent inconsistencies when a section is moved, we need to refresh\n * the\n *\n * Course formats can override the section title rendering so the frontend depends heavily on backend\n * rendering. Luckily in edit mode we can trigger a title update using the inplace_editable module.\n *\n * @param {Object} details the update details.\n */\n _refreshSectionNumber({element}) {\n // Find the element.\n const target = this.getElement(this.selectors.SECTION, element.id);\n if (!target) {\n throw new Error(`Unkown section with ID ${element.id}`);\n }\n // Update section numbers in all data, css and YUI attributes.\n target.id = `section-${element.number}`;\n // YUI uses section number as section id in data-sectionid, in principle if a format use components\n // don't need this sectionid attribute anymore, but we keep the compatibility in case some plugin\n // use it for legacy purposes.\n target.dataset.sectionid = element.number;\n // The data-number is the attribute used by components to store the section number.\n target.dataset.number = element.number;\n\n // Update title and title inplace editable, if any.\n const inplace = inplaceeditable.getInplaceEditable(target.querySelector(this.selectors.SECTION_ITEM));\n if (inplace) {\n // The course content HTML can be modified at any moment, so the function need to do some checkings\n // to make sure the inplace editable still represents the same itemid.\n const currentvalue = inplace.getValue();\n const currentitemid = inplace.getItemId();\n // Unnamed sections must be recalculated.\n if (inplace.getValue() === '') {\n // The value to send can be an empty value if it is a default name.\n if (currentitemid == element.id && (currentvalue != element.rawtitle || element.rawtitle == '')) {\n inplace.setValue(element.rawtitle);\n }\n }\n }\n }\n\n /**\n * Refresh a section cm list.\n *\n * @param {Object} details the update details.\n */\n _refreshSectionCmlist({element}) {\n const cmlist = element.cmlist ?? [];\n const section = this.getElement(this.selectors.SECTION, element.id);\n const listparent = section?.querySelector(this.selectors.SECTION_CMLIST);\n if (listparent) {\n this._fixOrder(listparent, cmlist, this.selectors.CM, this.dettachedCms);\n }\n }\n\n /**\n * Refresh the section list.\n *\n * @param {Object} details the update details.\n */\n _refreshCourseSectionlist({element}) {\n const sectionlist = element.sectionlist ?? [];\n const listparent = this.getElement(this.selectors.COURSE_SECTIONLIST);\n if (listparent) {\n this._fixOrder(listparent, sectionlist, this.selectors.SECTION, this.dettachedSections);\n }\n }\n\n /**\n * Regenerate content indexes.\n *\n * This method is used when a legacy action refresh some content element.\n */\n _indexContents() {\n // Find unindexed sections.\n this._scanIndex(\n this.selectors.SECTION,\n this.sections,\n (item) => {\n return new Section(item);\n }\n );\n\n // Find unindexed cms.\n this._scanIndex(\n this.selectors.CM,\n this.cms,\n (item) => {\n return new CmItem(item);\n }\n );\n }\n\n /**\n * Reindex a content (section or cm) of the course content.\n *\n * This method is used internally by _indexContents.\n *\n * @param {string} selector the DOM selector to scan\n * @param {*} index the index attribute to update\n * @param {*} creationhandler method to create a new indexed element\n */\n _scanIndex(selector, index, creationhandler) {\n const items = this.getElements(`${selector}:not([data-indexed])`);\n items.forEach((item) => {\n if (!item?.dataset?.id) {\n return;\n }\n // Delete previous item component.\n if (index[item.dataset.id] !== undefined) {\n index[item.dataset.id].unregister();\n }\n // Create the new component.\n index[item.dataset.id] = creationhandler({\n ...this,\n element: item,\n });\n // Mark as indexed.\n item.dataset.indexed = true;\n });\n }\n\n /**\n * Reload a course module contents.\n *\n * @param {details} param0 the watcher details\n * @property {object} param0.element the state object\n */\n _reloadCm({element}) {\n const cmitem = this.getElement(this.selectors.CM, element.id);\n if (cmitem) {\n const promise = courseActions.refreshModule(cmitem, element.id);\n promise.then(() => {\n this._indexContents();\n return;\n }).catch();\n }\n }\n\n /**\n * Fix/reorder the section or cms order.\n *\n * @param {Element} container the HTML element to reorder.\n * @param {Array} neworder an array with the ids order\n * @param {string} selector the element selector\n * @param {Object} dettachedelements a list of dettached elements\n */\n _fixOrder(container, neworder, selector, dettachedelements) {\n\n // Empty lists should not be visible.\n if (!neworder.length) {\n container.classList.add('hidden');\n container.innerHTML = '';\n return;\n }\n\n // Grant the list is visible (in case it was empty).\n container.classList.remove('hidden');\n\n // Move the elements in order at the beginning of the list.\n neworder.forEach((itemid, index) => {\n const item = this.getElement(selector, itemid) ?? dettachedelements[itemid];\n // Get the current elemnt at that position.\n const currentitem = container.children[index];\n if (currentitem === undefined) {\n container.append(item);\n return;\n }\n if (currentitem !== item) {\n container.insertBefore(item, currentitem);\n }\n });\n\n // Dndupload add a fake element we need to keep.\n let dndFakeActivity;\n\n // Remove the remaining elements.\n while (container.children.length > neworder.length) {\n const lastchild = container.lastChild;\n if (lastchild.classList.contains('dndupload-preview')) {\n dndFakeActivity = lastchild;\n } else {\n dettachedelements[lastchild?.dataset?.id ?? 0] = lastchild;\n }\n container.removeChild(lastchild);\n }\n // Restore dndupload fake element.\n if (dndFakeActivity) {\n container.append(dndFakeActivity);\n }\n }\n}\n"],"file":"content.min.js"} \ No newline at end of file +{"version":3,"sources":["../../src/local/content.js"],"names":["Component","name","selectors","SECTION","SECTION_ITEM","SECTION_CMLIST","COURSE_SECTIONLIST","CM","ACTIVITYTAG","classes","STATEDREADY","ACTIVITY","dettachedCms","dettachedSections","sections","cms","_indexContents","reactive","supportComponents","isEditing","DispatchActions","element","classList","add","watch","handler","_reloadCm","_refreshSectionNumber","_startProcessing","_refreshCourseSectionlist","_refreshSectionCmlist","target","getElement","id","number","dataset","sectionid","inplace","inplaceeditable","getInplaceEditable","querySelector","currentvalue","getValue","currentitemid","getItemId","rawtitle","setValue","cmlist","section","listparent","createCm","_createCmItem","bind","_fixOrder","sectionlist","createSection","_scanIndex","item","Section","CmItem","selector","index","creationhandler","items","getElements","forEach","unregister","indexed","cmitem","promise","courseActions","refreshModule","then","catch","container","cmid","newItem","document","createElement","for","append","get","neworder","dettachedelements","createMethod","length","innerHTML","remove","itemid","currentitem","children","insertBefore","lastchild","lastChild","contains","dndFakeActivity","removeChild","getElementById","BaseComponent"],"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 * Course index main component.\n *\n * @module core_courseformat/local/content\n * @class core_courseformat/local/content\n * @copyright 2020 Ferran Recio \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport {BaseComponent} from 'core/reactive';\nimport {getCurrentCourseEditor} from 'core_courseformat/courseeditor';\nimport inplaceeditable from 'core/inplace_editable';\nimport Section from 'core_courseformat/local/content/section';\nimport CmItem from 'core_courseformat/local/content/section/cmitem';\n// Course actions is needed for actions that are not migrated to components.\nimport courseActions from 'core_course/actions';\nimport DispatchActions from 'core_courseformat/local/content/actions';\n\nexport default class Component extends BaseComponent {\n\n /**\n * Constructor hook.\n */\n create() {\n // Optional component name for debugging.\n this.name = 'course_format';\n // Default query selectors.\n this.selectors = {\n SECTION: `[data-for='section']`,\n SECTION_ITEM: `[data-for='section_title']`,\n SECTION_CMLIST: `[data-for='cmlist']`,\n COURSE_SECTIONLIST: `[data-for='course_sectionlist']`,\n CM: `[data-for='cmitem']`,\n // Formats can override the activity tag but a default one is needed to create new elements.\n ACTIVITYTAG: 'li',\n };\n // Course content classes.\n this.classes = {\n STATEDREADY: `stateready`,\n ACTIVITY: `activity`,\n };\n // Array to save dettached elements during element resorting.\n this.dettachedCms = {};\n this.dettachedSections = {};\n // Index of sections and cms components.\n this.sections = {};\n this.cms = {};\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 reactive: getCurrentCourseEditor(),\n selectors,\n });\n }\n\n /**\n * Initial state ready method.\n *\n * Course content elements could not provide JS Components because the elements HTML is applied\n * directly from the course actions. To keep internal components updated this module keeps\n * a list of the active components and mark them as \"indexed\". This way when any action replace\n * the HTML this component will recreate the components an add any necessary event listener.\n *\n * Format plugins can override this method to provide extra logic to the course frontend.\n *\n */\n stateReady() {\n this._indexContents();\n\n if (this.reactive.supportComponents) {\n // Actions are only available in edit mode.\n if (this.reactive.isEditing) {\n new DispatchActions(this);\n }\n\n // Mark content as state ready.\n this.element.classList.add(this.classes.STATEDREADY);\n }\n }\n\n /**\n * Return the component watchers.\n *\n * @returns {Array} of watchers\n */\n getWatchers() {\n // Check if the course format is compatible with reactive components.\n if (!this.reactive.supportComponents) {\n return [];\n }\n return [\n // State changes that require to reload some course modules.\n {watch: `cm.visible:updated`, handler: this._reloadCm},\n // Update section number and title.\n {watch: `section.number:updated`, handler: this._refreshSectionNumber},\n // Sections and cm sorting.\n {watch: `transaction:start`, handler: this._startProcessing},\n {watch: `course.sectionlist:updated`, handler: this._refreshCourseSectionlist},\n {watch: `section.cmlist:updated`, handler: this._refreshSectionCmlist},\n // Reindex sections and cms.\n {watch: `state:updated`, handler: this._indexContents},\n // State changes thaty require to reload course modules.\n {watch: `cm.visible:updated`, handler: this._reloadCm},\n {watch: `cm.sectionid:updated`, handler: this._reloadCm},\n ];\n }\n\n /**\n * Setup the component to start a transaction.\n *\n * Some of the course actions replaces the current DOM element with a new one before updating the\n * course state. This means the component cannot preload any index properly until the transaction starts.\n *\n */\n _startProcessing() {\n // During a section or cm sorting, some elements could be dettached from the DOM and we\n // need to store somewhare in case they are needed later.\n this.dettachedCms = {};\n this.dettachedSections = {};\n }\n\n /**\n * Update a course section when the section number changes.\n *\n * The courseActions module used for most course section tools still depends on css classes and\n * section numbers (not id). To prevent inconsistencies when a section is moved, we need to refresh\n * the\n *\n * Course formats can override the section title rendering so the frontend depends heavily on backend\n * rendering. Luckily in edit mode we can trigger a title update using the inplace_editable module.\n *\n * @param {Object} details the update details.\n */\n _refreshSectionNumber({element}) {\n // Find the element.\n const target = this.getElement(this.selectors.SECTION, element.id);\n if (!target) {\n // Job done. Nothing to refresh.\n return;\n }\n // Update section numbers in all data, css and YUI attributes.\n target.id = `section-${element.number}`;\n // YUI uses section number as section id in data-sectionid, in principle if a format use components\n // don't need this sectionid attribute anymore, but we keep the compatibility in case some plugin\n // use it for legacy purposes.\n target.dataset.sectionid = element.number;\n // The data-number is the attribute used by components to store the section number.\n target.dataset.number = element.number;\n\n // Update title and title inplace editable, if any.\n const inplace = inplaceeditable.getInplaceEditable(target.querySelector(this.selectors.SECTION_ITEM));\n if (inplace) {\n // The course content HTML can be modified at any moment, so the function need to do some checkings\n // to make sure the inplace editable still represents the same itemid.\n const currentvalue = inplace.getValue();\n const currentitemid = inplace.getItemId();\n // Unnamed sections must be recalculated.\n if (inplace.getValue() === '') {\n // The value to send can be an empty value if it is a default name.\n if (currentitemid == element.id && (currentvalue != element.rawtitle || element.rawtitle == '')) {\n inplace.setValue(element.rawtitle);\n }\n }\n }\n }\n\n /**\n * Refresh a section cm list.\n *\n * @param {details} details the update details\n * @property {object} details.element the state object\n */\n _refreshSectionCmlist({element}) {\n const cmlist = element.cmlist ?? [];\n const section = this.getElement(this.selectors.SECTION, element.id);\n const listparent = section?.querySelector(this.selectors.SECTION_CMLIST);\n // A method to create a fake element to be replaced when the item is ready.\n const createCm = this._createCmItem.bind(this);\n if (listparent) {\n this._fixOrder(listparent, cmlist, this.selectors.CM, this.dettachedCms, createCm);\n }\n }\n\n /**\n * Refresh the section list.\n *\n * @param {Object} details the update details.\n */\n _refreshCourseSectionlist({element}) {\n const sectionlist = element.sectionlist ?? [];\n const listparent = this.getElement(this.selectors.COURSE_SECTIONLIST);\n // For now section cannot be created at a frontend level.\n const createSection = () => undefined;\n if (listparent) {\n this._fixOrder(listparent, sectionlist, this.selectors.SECTION, this.dettachedSections, createSection);\n }\n }\n\n /**\n * Regenerate content indexes.\n *\n * This method is used when a legacy action refresh some content element.\n */\n _indexContents() {\n // Find unindexed sections.\n this._scanIndex(\n this.selectors.SECTION,\n this.sections,\n (item) => {\n return new Section(item);\n }\n );\n\n // Find unindexed cms.\n this._scanIndex(\n this.selectors.CM,\n this.cms,\n (item) => {\n return new CmItem(item);\n }\n );\n }\n\n /**\n * Reindex a content (section or cm) of the course content.\n *\n * This method is used internally by _indexContents.\n *\n * @param {string} selector the DOM selector to scan\n * @param {*} index the index attribute to update\n * @param {*} creationhandler method to create a new indexed element\n */\n _scanIndex(selector, index, creationhandler) {\n const items = this.getElements(`${selector}:not([data-indexed])`);\n items.forEach((item) => {\n if (!item?.dataset?.id) {\n return;\n }\n // Delete previous item component.\n if (index[item.dataset.id] !== undefined) {\n index[item.dataset.id].unregister();\n }\n // Create the new component.\n index[item.dataset.id] = creationhandler({\n ...this,\n element: item,\n });\n // Mark as indexed.\n item.dataset.indexed = true;\n });\n }\n\n /**\n * Reload a course module contents.\n *\n * Most course module HTML is still strongly backend dependant.\n * Some changes require to get a new version of the module.\n *\n * @param {details} param0 the watcher details\n * @property {object} param0.element the state object\n */\n _reloadCm({element}) {\n const cmitem = this.getElement(this.selectors.CM, element.id);\n if (cmitem) {\n const promise = courseActions.refreshModule(cmitem, element.id);\n promise.then(() => {\n this._indexContents();\n return;\n }).catch();\n }\n }\n\n /**\n * Create a new course module item in a section.\n *\n * Thos method will append a fake item in the container and trigger an ajax request to\n * replace the fake element by the real content.\n *\n * @param {Element} container the container element (section)\n * @param {Number} cmid the course-module ID\n * @returns {Element} the created element\n */\n _createCmItem(container, cmid) {\n const newItem = document.createElement(this.selectors.ACTIVITYTAG);\n newItem.dataset.for = 'cmitem';\n newItem.dataset.id = cmid;\n // The legacy actions.js requires a specific ID and class to refresh the CM.\n newItem.id = `module-${cmid}`;\n newItem.classList.add(this.classes.ACTIVITY);\n container.append(newItem);\n this._reloadCm({\n element: this.reactive.get('cm', cmid),\n });\n return newItem;\n }\n\n /**\n * Fix/reorder the section or cms order.\n *\n * @param {Element} container the HTML element to reorder.\n * @param {Array} neworder an array with the ids order\n * @param {string} selector the element selector\n * @param {Object} dettachedelements a list of dettached elements\n * @param {function} createMethod method to create missing elements\n */\n async _fixOrder(container, neworder, selector, dettachedelements, createMethod) {\n if (container === undefined) {\n return;\n }\n\n // Empty lists should not be visible.\n if (!neworder.length) {\n container.classList.add('hidden');\n container.innerHTML = '';\n return;\n }\n\n // Grant the list is visible (in case it was empty).\n container.classList.remove('hidden');\n\n // Move the elements in order at the beginning of the list.\n neworder.forEach((itemid, index) => {\n let item = this.getElement(selector, itemid) ?? dettachedelements[itemid] ?? createMethod(container, itemid);\n if (item === undefined) {\n // Missing elements cannot be sorted.\n return;\n }\n // Get the current elemnt at that position.\n const currentitem = container.children[index];\n if (currentitem === undefined) {\n container.append(item);\n return;\n }\n if (currentitem !== item) {\n container.insertBefore(item, currentitem);\n }\n });\n\n // Dndupload add a fake element we need to keep.\n let dndFakeActivity;\n\n // Remove the remaining elements.\n while (container.children.length > neworder.length) {\n const lastchild = container.lastChild;\n if (lastchild?.classList?.contains('dndupload-preview')) {\n dndFakeActivity = lastchild;\n } else {\n dettachedelements[lastchild?.dataset?.id ?? 0] = lastchild;\n }\n container.removeChild(lastchild);\n }\n // Restore dndupload fake element.\n if (dndFakeActivity) {\n container.append(dndFakeActivity);\n }\n }\n}\n"],"file":"content.min.js"} \ No newline at end of file diff --git a/course/format/amd/build/local/content/actions.min.js b/course/format/amd/build/local/content/actions.min.js new file mode 100644 index 00000000000..6269fe683d4 --- /dev/null +++ b/course/format/amd/build/local/content/actions.min.js @@ -0,0 +1,2 @@ +define ("core_courseformat/local/content/actions",["exports","core/reactive","core/modal_factory","core/modal_events","core/templates","core/prefetch","core/str","core/normalise"],function(a,b,c,d,e,f,g,h){"use strict";Object.defineProperty(a,"__esModule",{value:!0});a.default=void 0;c=i(c);d=i(d);e=i(e);function i(a){return a&&a.__esModule?a:{default:a}}function j(a){"@babel/helpers - typeof";if("function"==typeof Symbol&&"symbol"==typeof Symbol.iterator){j=function(a){return typeof a}}else{j=function(a){return a&&"function"==typeof Symbol&&a.constructor===Symbol&&a!==Symbol.prototype?"symbol":typeof a}}return j(a)}function k(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 l(a){return function(){var b=this,c=arguments;return new Promise(function(d,e){var h=a.apply(b,c);function f(a){k(h,d,e,f,g,"next",a)}function g(a){k(h,d,e,f,g,"throw",a)}f(void 0)})}}function m(a,b){if(!(a instanceof b)){throw new TypeError("Cannot call a class as a function")}}function n(a,b){for(var c=0,d;c.\n\n/**\n * Course state actions dispatcher.\n *\n * This module captures all data-dispatch links in the course content and dispatch the proper\n * state mutation, including any confirmation and modal required.\n *\n * @module core_courseformat/local/content/actions\n * @class core_courseformat/local/content/actions\n * @copyright 2021 Ferran Recio \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport {BaseComponent} from 'core/reactive';\nimport ModalFactory from 'core/modal_factory';\nimport ModalEvents from 'core/modal_events';\nimport Templates from 'core/templates';\nimport {prefetchStrings} from 'core/prefetch';\nimport {get_string as getString} from 'core/str';\nimport {getList} from 'core/normalise';\n\n// Load global strings.\nprefetchStrings('core', ['movecoursesection', 'movecoursemodule']);\n\nexport default class extends BaseComponent {\n\n /**\n * Constructor hook.\n */\n create() {\n // Optional component name for debugging.\n this.name = 'content_actions';\n // Default query selectors.\n this.selectors = {\n ACTIONLINK: `[data-action]`,\n SECTIONLINK: `[data-for='section']`,\n CMLINK: `[data-for='cm']`,\n SECTIONNODE: `[data-for='sectionnode']`,\n TOGGLER: `[data-toggle='collapse']`,\n };\n // Component css classes.\n this.classes = {\n DISABLED: `disabled`,\n };\n }\n\n /**\n * Initial state ready method.\n *\n */\n stateReady() {\n // Delegate dispatch clicks.\n this.addEventListener(\n this.element,\n 'click',\n this._dispatchClick\n );\n }\n\n _dispatchClick(event) {\n const target = event.target.closest(this.selectors.ACTIONLINK);\n if (!target) {\n return;\n }\n\n // Invoke proper method.\n const methodName = this._actionMethodName(target.dataset.action);\n\n if (this[methodName] !== undefined) {\n this[methodName](target, event);\n }\n }\n\n _actionMethodName(name) {\n const requestName = name.charAt(0).toUpperCase() + name.slice(1);\n return `_request${requestName}`;\n }\n\n /**\n * Handle a move section request.\n *\n * @param {Element} target the dispatch action element\n * @param {Event} event the triggered event\n */\n async _requestMoveSection(target, event) {\n // Check we have an id.\n const sectionId = target.dataset.id;\n if (!sectionId) {\n return;\n }\n const sectionInfo = this.reactive.get('section', sectionId);\n\n event.preventDefault();\n\n // Collect section information from the state.\n const exporter = this.reactive.getExporter();\n const data = exporter.course(this.reactive.state);\n\n // Add the target section id and title.\n data.sectionid = sectionInfo.id;\n data.sectiontitle = sectionInfo.title;\n\n // Build the modal parameters from the event data.\n const modalParams = {\n title: getString('movecoursesection', 'core'),\n body: Templates.render('core_courseformat/local/content/movesection', data),\n };\n\n // Create the modal.\n const modal = await this._modalBodyRenderedPromise(modalParams);\n\n const modalBody = getList(modal.getBody())[0];\n\n // Disable current element and section zero.\n const currentElement = modalBody.querySelector(`${this.selectors.SECTIONLINK}[data-id='${sectionId}']`);\n this._disableLink(currentElement);\n const generalSection = modalBody.querySelector(`${this.selectors.SECTIONLINK}[data-number='0']`);\n this._disableLink(generalSection);\n\n // Capture click.\n modalBody.addEventListener('click', (event) => {\n const target = event.target;\n if (!target.matches('a') || target.dataset.for != 'section' || target.dataset.id === undefined) {\n return;\n }\n if (target.getAttribute('aria-disabled')) {\n return;\n }\n event.preventDefault();\n this.reactive.dispatch('sectionMove', [sectionId], target.dataset.id);\n modal.destroy();\n });\n }\n\n /**\n * Handle a move cm request.\n *\n * @param {Element} target the dispatch action element\n * @param {Event} event the triggered event\n */\n async _requestMoveCm(target, event) {\n // Check we have an id.\n const cmId = target.dataset.id;\n if (!cmId) {\n return;\n }\n const cmInfo = this.reactive.get('cm', cmId);\n\n event.preventDefault();\n\n // Collect section information from the state.\n const exporter = this.reactive.getExporter();\n const data = exporter.course(this.reactive.state);\n\n // Add the target cm info.\n data.cmid = cmInfo.id;\n data.cmname = cmInfo.name;\n\n // Build the modal parameters from the event data.\n const modalParams = {\n title: getString('movecoursemodule', 'core'),\n body: Templates.render('core_courseformat/local/content/movecm', data),\n };\n\n // Create the modal.\n const modal = await this._modalBodyRenderedPromise(modalParams);\n\n const modalBody = getList(modal.getBody())[0];\n\n // Disable current element.\n let currentElement = modalBody.querySelector(`${this.selectors.CMLINK}[data-id='${cmId}']`);\n this._disableLink(currentElement);\n\n // Open the cm section node if possible.\n currentElement.closest(this.selectors.SECTIONNODE)?.querySelector(this.selectors.TOGGLER)?.click();\n\n // Capture click.\n modalBody.addEventListener('click', (event) => {\n const target = event.target;\n if (!target.matches('a') || target.dataset.for === undefined || target.dataset.id === undefined) {\n return;\n }\n if (target.getAttribute('aria-disabled')) {\n return;\n }\n event.preventDefault();\n\n // Get draggable data from cm or section to dispatch.\n let targetSectionId;\n let targetCmId;\n if (target.dataset.for == 'cm') {\n const dropData = exporter.cmDraggableData(this.reactive.state, target.dataset.id);\n targetSectionId = dropData.sectionid;\n targetCmId = dropData.nextcmid;\n } else {\n const section = this.reactive.get('section', target.dataset.id);\n targetSectionId = target.dataset.id;\n targetCmId = section?.cmlist[0];\n }\n\n this.reactive.dispatch('cmMove', [cmId], targetSectionId, targetCmId);\n modal.destroy();\n });\n }\n\n /**\n * Replace an element with a copy with a different tag name.\n *\n * @param {Element} element the original element\n */\n _disableLink(element) {\n if (element) {\n element.style.pointerEvents = 'none';\n element.style.userSelect = 'none';\n element.classList.add(this.classes.DISABLED);\n element.setAttribute('aria-disabled', true);\n element.addEventListener('click', event => event.preventDefault());\n }\n }\n\n /**\n * Render a modal and return a body ready promise.\n *\n * @param {object} modalParams the modal params\n * @return {Promise} the modal body ready promise\n */\n _modalBodyRenderedPromise(modalParams) {\n return new Promise((resolve, reject) => {\n ModalFactory.create(modalParams).then((modal) => {\n // Handle body loading event.\n modal.getRoot().on(ModalEvents.bodyRendered, () => {\n resolve(modal);\n });\n modal.show();\n return;\n }).catch(() => {\n reject(`Cannot load modal content`);\n });\n });\n }\n}\n"],"file":"actions.min.js"} \ No newline at end of file diff --git a/course/format/amd/build/local/courseeditor/exporter.min.js b/course/format/amd/build/local/courseeditor/exporter.min.js index 97028838082..8a00724239f 100644 --- a/course/format/amd/build/local/courseeditor/exporter.min.js +++ b/course/format/amd/build/local/courseeditor/exporter.min.js @@ -1,2 +1,2 @@ -define ("core_courseformat/local/courseeditor/exporter",["exports"],function(a){"use strict";Object.defineProperty(a,"__esModule",{value:!0});a.default=void 0;function b(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 c(a){for(var c=1,e;c.\n\n/**\n * Module to export parts of the state and transform them to be used in templates\n * and as draggable data.\n *\n * @module core_courseformat/local/courseeditor/exporter\n * @class core_courseformat/local/courseeditor/exporter\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 /**\n * Class constructor.\n *\n * @param {CourseEditor} reactive the course editor object\n */\n constructor(reactive) {\n this.reactive = reactive;\n }\n\n /**\n * Generate the course export data from the state.\n *\n * @param {Object} state the current state.\n * @returns {Object}\n */\n course(state) {\n // Collect section information from the state.\n const data = {\n sections: [],\n editmode: this.reactive.isEditing,\n highlighted: state.course.highlighted ?? '',\n };\n const sectionlist = state.course.sectionlist ?? [];\n sectionlist.forEach(sectionid => {\n const sectioninfo = state.section.get(sectionid) ?? {};\n const section = this.section(state, sectioninfo);\n data.sections.push(section);\n });\n data.hassections = (data.sections.length != 0);\n\n return data;\n }\n\n /**\n * Generate a section export data from the state.\n *\n * @param {Object} state the current state.\n * @param {Object} sectioninfo the section state data.\n * @returns {Object}\n */\n section(state, sectioninfo) {\n const section = {\n ...sectioninfo,\n cms: [],\n isactive: true,\n };\n const cmlist = sectioninfo.cmlist ?? [];\n cmlist.forEach(cmid => {\n const cminfo = state.cm.get(cmid);\n const cm = this.cm(state, cminfo);\n section.cms.push(cm);\n });\n section.hascms = (section.cms.length != 0);\n\n return section;\n }\n\n /**\n * Generate a cm export data from the state.\n *\n * @param {Object} state the current state.\n * @param {Object} cminfo the course module state data.\n * @returns {Object}\n */\n cm(state, cminfo) {\n const cm = {\n ...cminfo,\n isactive: false,\n };\n return cm;\n }\n\n /**\n * Generate a dragable cm data structure.\n *\n * This method is used by any draggable course module element to generate drop data\n * for its reactive/dragdrop instance.\n *\n * @param {*} state the state object\n * @param {*} cmid the cours emodule id\n * @returns {Object|null}\n */\n cmDraggableData(state, cmid) {\n const cminfo = state.cm.get(cmid);\n if (!cminfo) {\n return null;\n }\n\n // Drop an activity over the next activity is the same as doing anything.\n let nextcmid;\n const section = state.section.get(cminfo.sectionid);\n const currentindex = section?.cmlist.indexOf(cminfo.id);\n if (currentindex !== undefined) {\n nextcmid = section?.cmlist[currentindex + 1];\n }\n\n return {\n type: 'cm',\n id: cminfo.id,\n name: cminfo.name,\n nextcmid,\n };\n }\n\n /**\n * Generate a dragable cm data structure.\n *\n * This method is used by any draggable section element to generate drop data\n * for its reactive/dragdrop instance.\n *\n * @param {*} state the state object\n * @param {*} sectionid the cours section id\n * @returns {Object|null}\n */\n sectionDraggableData(state, sectionid) {\n const sectioninfo = state.section.get(sectionid);\n if (!sectioninfo) {\n return null;\n }\n return {\n type: 'section',\n id: sectioninfo.id,\n name: sectioninfo.name,\n number: sectioninfo.number,\n };\n }\n}\n"],"file":"exporter.min.js"} \ No newline at end of file +{"version":3,"sources":["../../../src/local/courseeditor/exporter.js"],"names":["reactive","state","data","sections","editmode","isEditing","highlighted","course","sectionlist","forEach","sectionid","sectioninfo","section","get","push","hassections","length","cms","isactive","cmlist","cmid","cminfo","cm","hascms","nextcmid","currentindex","indexOf","id","type","name","number"],"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 * Module to export parts of the state and transform them to be used in templates\n * and as draggable data.\n *\n * @module core_courseformat/local/courseeditor/exporter\n * @class core_courseformat/local/courseeditor/exporter\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 /**\n * Class constructor.\n *\n * @param {CourseEditor} reactive the course editor object\n */\n constructor(reactive) {\n this.reactive = reactive;\n }\n\n /**\n * Generate the course export data from the state.\n *\n * @param {Object} state the current state.\n * @returns {Object}\n */\n course(state) {\n // Collect section information from the state.\n const data = {\n sections: [],\n editmode: this.reactive.isEditing,\n highlighted: state.course.highlighted ?? '',\n };\n const sectionlist = state.course.sectionlist ?? [];\n sectionlist.forEach(sectionid => {\n const sectioninfo = state.section.get(sectionid) ?? {};\n const section = this.section(state, sectioninfo);\n data.sections.push(section);\n });\n data.hassections = (data.sections.length != 0);\n\n return data;\n }\n\n /**\n * Generate a section export data from the state.\n *\n * @param {Object} state the current state.\n * @param {Object} sectioninfo the section state data.\n * @returns {Object}\n */\n section(state, sectioninfo) {\n const section = {\n ...sectioninfo,\n cms: [],\n isactive: true,\n };\n const cmlist = sectioninfo.cmlist ?? [];\n cmlist.forEach(cmid => {\n const cminfo = state.cm.get(cmid);\n const cm = this.cm(state, cminfo);\n section.cms.push(cm);\n });\n section.hascms = (section.cms.length != 0);\n\n return section;\n }\n\n /**\n * Generate a cm export data from the state.\n *\n * @param {Object} state the current state.\n * @param {Object} cminfo the course module state data.\n * @returns {Object}\n */\n cm(state, cminfo) {\n const cm = {\n ...cminfo,\n isactive: false,\n };\n return cm;\n }\n\n /**\n * Generate a dragable cm data structure.\n *\n * This method is used by any draggable course module element to generate drop data\n * for its reactive/dragdrop instance.\n *\n * @param {*} state the state object\n * @param {*} cmid the cours emodule id\n * @returns {Object|null}\n */\n cmDraggableData(state, cmid) {\n const cminfo = state.cm.get(cmid);\n if (!cminfo) {\n return null;\n }\n\n // Drop an activity over the next activity is the same as doing anything.\n let nextcmid;\n const section = state.section.get(cminfo.sectionid);\n const currentindex = section?.cmlist.indexOf(cminfo.id);\n if (currentindex !== undefined) {\n nextcmid = section?.cmlist[currentindex + 1];\n }\n\n return {\n type: 'cm',\n id: cminfo.id,\n name: cminfo.name,\n sectionid: cminfo.sectionid,\n nextcmid,\n };\n }\n\n /**\n * Generate a dragable cm data structure.\n *\n * This method is used by any draggable section element to generate drop data\n * for its reactive/dragdrop instance.\n *\n * @param {*} state the state object\n * @param {*} sectionid the cours section id\n * @returns {Object|null}\n */\n sectionDraggableData(state, sectionid) {\n const sectioninfo = state.section.get(sectionid);\n if (!sectioninfo) {\n return null;\n }\n return {\n type: 'section',\n id: sectioninfo.id,\n name: sectioninfo.name,\n number: sectioninfo.number,\n };\n }\n}\n"],"file":"exporter.min.js"} \ No newline at end of file diff --git a/course/format/amd/src/local/content.js b/course/format/amd/src/local/content.js index d2f36906f6f..79a13d26cde 100644 --- a/course/format/amd/src/local/content.js +++ b/course/format/amd/src/local/content.js @@ -29,6 +29,7 @@ import Section from 'core_courseformat/local/content/section'; import CmItem from 'core_courseformat/local/content/section/cmitem'; // Course actions is needed for actions that are not migrated to components. import courseActions from 'core_course/actions'; +import DispatchActions from 'core_courseformat/local/content/actions'; export default class Component extends BaseComponent { @@ -45,6 +46,13 @@ export default class Component extends BaseComponent { SECTION_CMLIST: `[data-for='cmlist']`, COURSE_SECTIONLIST: `[data-for='course_sectionlist']`, CM: `[data-for='cmitem']`, + // Formats can override the activity tag but a default one is needed to create new elements. + ACTIVITYTAG: 'li', + }; + // Course content classes. + this.classes = { + STATEDREADY: `stateready`, + ACTIVITY: `activity`, }; // Array to save dettached elements during element resorting. this.dettachedCms = {}; @@ -77,9 +85,21 @@ export default class Component extends BaseComponent { * a list of the active components and mark them as "indexed". This way when any action replace * the HTML this component will recreate the components an add any necessary event listener. * + * Format plugins can override this method to provide extra logic to the course frontend. + * */ stateReady() { this._indexContents(); + + if (this.reactive.supportComponents) { + // Actions are only available in edit mode. + if (this.reactive.isEditing) { + new DispatchActions(this); + } + + // Mark content as state ready. + this.element.classList.add(this.classes.STATEDREADY); + } } /** @@ -109,21 +129,6 @@ export default class Component extends BaseComponent { ]; } - /** - * Reload a course module. - * - * Most course module HTML is still strongly backend dependant. - * Some changes require to get a new version af the module. - * - * @param {Object} update the state update data - */ - _reloadCm({element}) { - const cmitem = this.getElement(this.selectors.CM, element.id); - if (cmitem) { - courseActions.refreshModule(cmitem, element.id); - } - } - /** * Setup the component to start a transaction. * @@ -154,7 +159,8 @@ export default class Component extends BaseComponent { // Find the element. const target = this.getElement(this.selectors.SECTION, element.id); if (!target) { - throw new Error(`Unkown section with ID ${element.id}`); + // Job done. Nothing to refresh. + return; } // Update section numbers in all data, css and YUI attributes. target.id = `section-${element.number}`; @@ -185,14 +191,17 @@ export default class Component extends BaseComponent { /** * Refresh a section cm list. * - * @param {Object} details the update details. + * @param {details} details the update details + * @property {object} details.element the state object */ _refreshSectionCmlist({element}) { const cmlist = element.cmlist ?? []; const section = this.getElement(this.selectors.SECTION, element.id); const listparent = section?.querySelector(this.selectors.SECTION_CMLIST); + // A method to create a fake element to be replaced when the item is ready. + const createCm = this._createCmItem.bind(this); if (listparent) { - this._fixOrder(listparent, cmlist, this.selectors.CM, this.dettachedCms); + this._fixOrder(listparent, cmlist, this.selectors.CM, this.dettachedCms, createCm); } } @@ -204,8 +213,10 @@ export default class Component extends BaseComponent { _refreshCourseSectionlist({element}) { const sectionlist = element.sectionlist ?? []; const listparent = this.getElement(this.selectors.COURSE_SECTIONLIST); + // For now section cannot be created at a frontend level. + const createSection = () => undefined; if (listparent) { - this._fixOrder(listparent, sectionlist, this.selectors.SECTION, this.dettachedSections); + this._fixOrder(listparent, sectionlist, this.selectors.SECTION, this.dettachedSections, createSection); } } @@ -266,6 +277,9 @@ export default class Component extends BaseComponent { /** * Reload a course module contents. * + * Most course module HTML is still strongly backend dependant. + * Some changes require to get a new version of the module. + * * @param {details} param0 the watcher details * @property {object} param0.element the state object */ @@ -280,6 +294,30 @@ export default class Component extends BaseComponent { } } + /** + * Create a new course module item in a section. + * + * Thos method will append a fake item in the container and trigger an ajax request to + * replace the fake element by the real content. + * + * @param {Element} container the container element (section) + * @param {Number} cmid the course-module ID + * @returns {Element} the created element + */ + _createCmItem(container, cmid) { + const newItem = document.createElement(this.selectors.ACTIVITYTAG); + newItem.dataset.for = 'cmitem'; + newItem.dataset.id = cmid; + // The legacy actions.js requires a specific ID and class to refresh the CM. + newItem.id = `module-${cmid}`; + newItem.classList.add(this.classes.ACTIVITY); + container.append(newItem); + this._reloadCm({ + element: this.reactive.get('cm', cmid), + }); + return newItem; + } + /** * Fix/reorder the section or cms order. * @@ -287,8 +325,12 @@ export default class Component extends BaseComponent { * @param {Array} neworder an array with the ids order * @param {string} selector the element selector * @param {Object} dettachedelements a list of dettached elements + * @param {function} createMethod method to create missing elements */ - _fixOrder(container, neworder, selector, dettachedelements) { + async _fixOrder(container, neworder, selector, dettachedelements, createMethod) { + if (container === undefined) { + return; + } // Empty lists should not be visible. if (!neworder.length) { @@ -302,7 +344,11 @@ export default class Component extends BaseComponent { // Move the elements in order at the beginning of the list. neworder.forEach((itemid, index) => { - const item = this.getElement(selector, itemid) ?? dettachedelements[itemid]; + let item = this.getElement(selector, itemid) ?? dettachedelements[itemid] ?? createMethod(container, itemid); + if (item === undefined) { + // Missing elements cannot be sorted. + return; + } // Get the current elemnt at that position. const currentitem = container.children[index]; if (currentitem === undefined) { @@ -320,7 +366,7 @@ export default class Component extends BaseComponent { // Remove the remaining elements. while (container.children.length > neworder.length) { const lastchild = container.lastChild; - if (lastchild.classList.contains('dndupload-preview')) { + if (lastchild?.classList?.contains('dndupload-preview')) { dndFakeActivity = lastchild; } else { dettachedelements[lastchild?.dataset?.id ?? 0] = lastchild; diff --git a/course/format/amd/src/local/content/actions.js b/course/format/amd/src/local/content/actions.js new file mode 100644 index 00000000000..b1db6e8ca5a --- /dev/null +++ b/course/format/amd/src/local/content/actions.js @@ -0,0 +1,255 @@ +// This file is part of Moodle - http://moodle.org/ +// +// Moodle is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Moodle is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Moodle. If not, see . + +/** + * Course state actions dispatcher. + * + * This module captures all data-dispatch links in the course content and dispatch the proper + * state mutation, including any confirmation and modal required. + * + * @module core_courseformat/local/content/actions + * @class core_courseformat/local/content/actions + * @copyright 2021 Ferran Recio + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +import {BaseComponent} from 'core/reactive'; +import ModalFactory from 'core/modal_factory'; +import ModalEvents from 'core/modal_events'; +import Templates from 'core/templates'; +import {prefetchStrings} from 'core/prefetch'; +import {get_string as getString} from 'core/str'; +import {getList} from 'core/normalise'; + +// Load global strings. +prefetchStrings('core', ['movecoursesection', 'movecoursemodule']); + +export default class extends BaseComponent { + + /** + * Constructor hook. + */ + create() { + // Optional component name for debugging. + this.name = 'content_actions'; + // Default query selectors. + this.selectors = { + ACTIONLINK: `[data-action]`, + SECTIONLINK: `[data-for='section']`, + CMLINK: `[data-for='cm']`, + SECTIONNODE: `[data-for='sectionnode']`, + TOGGLER: `[data-toggle='collapse']`, + }; + // Component css classes. + this.classes = { + DISABLED: `disabled`, + }; + } + + /** + * Initial state ready method. + * + */ + stateReady() { + // Delegate dispatch clicks. + this.addEventListener( + this.element, + 'click', + this._dispatchClick + ); + } + + _dispatchClick(event) { + const target = event.target.closest(this.selectors.ACTIONLINK); + if (!target) { + return; + } + + // Invoke proper method. + const methodName = this._actionMethodName(target.dataset.action); + + if (this[methodName] !== undefined) { + this[methodName](target, event); + } + } + + _actionMethodName(name) { + const requestName = name.charAt(0).toUpperCase() + name.slice(1); + return `_request${requestName}`; + } + + /** + * Handle a move section request. + * + * @param {Element} target the dispatch action element + * @param {Event} event the triggered event + */ + async _requestMoveSection(target, event) { + // Check we have an id. + const sectionId = target.dataset.id; + if (!sectionId) { + return; + } + const sectionInfo = this.reactive.get('section', sectionId); + + event.preventDefault(); + + // Collect section information from the state. + const exporter = this.reactive.getExporter(); + const data = exporter.course(this.reactive.state); + + // Add the target section id and title. + data.sectionid = sectionInfo.id; + data.sectiontitle = sectionInfo.title; + + // Build the modal parameters from the event data. + const modalParams = { + title: getString('movecoursesection', 'core'), + body: Templates.render('core_courseformat/local/content/movesection', data), + }; + + // Create the modal. + const modal = await this._modalBodyRenderedPromise(modalParams); + + const modalBody = getList(modal.getBody())[0]; + + // Disable current element and section zero. + const currentElement = modalBody.querySelector(`${this.selectors.SECTIONLINK}[data-id='${sectionId}']`); + this._disableLink(currentElement); + const generalSection = modalBody.querySelector(`${this.selectors.SECTIONLINK}[data-number='0']`); + this._disableLink(generalSection); + + // Capture click. + modalBody.addEventListener('click', (event) => { + const target = event.target; + if (!target.matches('a') || target.dataset.for != 'section' || target.dataset.id === undefined) { + return; + } + if (target.getAttribute('aria-disabled')) { + return; + } + event.preventDefault(); + this.reactive.dispatch('sectionMove', [sectionId], target.dataset.id); + modal.destroy(); + }); + } + + /** + * Handle a move cm request. + * + * @param {Element} target the dispatch action element + * @param {Event} event the triggered event + */ + async _requestMoveCm(target, event) { + // Check we have an id. + const cmId = target.dataset.id; + if (!cmId) { + return; + } + const cmInfo = this.reactive.get('cm', cmId); + + event.preventDefault(); + + // Collect section information from the state. + const exporter = this.reactive.getExporter(); + const data = exporter.course(this.reactive.state); + + // Add the target cm info. + data.cmid = cmInfo.id; + data.cmname = cmInfo.name; + + // Build the modal parameters from the event data. + const modalParams = { + title: getString('movecoursemodule', 'core'), + body: Templates.render('core_courseformat/local/content/movecm', data), + }; + + // Create the modal. + const modal = await this._modalBodyRenderedPromise(modalParams); + + const modalBody = getList(modal.getBody())[0]; + + // Disable current element. + let currentElement = modalBody.querySelector(`${this.selectors.CMLINK}[data-id='${cmId}']`); + this._disableLink(currentElement); + + // Open the cm section node if possible. + currentElement.closest(this.selectors.SECTIONNODE)?.querySelector(this.selectors.TOGGLER)?.click(); + + // Capture click. + modalBody.addEventListener('click', (event) => { + const target = event.target; + if (!target.matches('a') || target.dataset.for === undefined || target.dataset.id === undefined) { + return; + } + if (target.getAttribute('aria-disabled')) { + return; + } + event.preventDefault(); + + // Get draggable data from cm or section to dispatch. + let targetSectionId; + let targetCmId; + if (target.dataset.for == 'cm') { + const dropData = exporter.cmDraggableData(this.reactive.state, target.dataset.id); + targetSectionId = dropData.sectionid; + targetCmId = dropData.nextcmid; + } else { + const section = this.reactive.get('section', target.dataset.id); + targetSectionId = target.dataset.id; + targetCmId = section?.cmlist[0]; + } + + this.reactive.dispatch('cmMove', [cmId], targetSectionId, targetCmId); + modal.destroy(); + }); + } + + /** + * Replace an element with a copy with a different tag name. + * + * @param {Element} element the original element + */ + _disableLink(element) { + if (element) { + element.style.pointerEvents = 'none'; + element.style.userSelect = 'none'; + element.classList.add(this.classes.DISABLED); + element.setAttribute('aria-disabled', true); + element.addEventListener('click', event => event.preventDefault()); + } + } + + /** + * Render a modal and return a body ready promise. + * + * @param {object} modalParams the modal params + * @return {Promise} the modal body ready promise + */ + _modalBodyRenderedPromise(modalParams) { + return new Promise((resolve, reject) => { + ModalFactory.create(modalParams).then((modal) => { + // Handle body loading event. + modal.getRoot().on(ModalEvents.bodyRendered, () => { + resolve(modal); + }); + modal.show(); + return; + }).catch(() => { + reject(`Cannot load modal content`); + }); + }); + } +} diff --git a/course/format/amd/src/local/courseeditor/exporter.js b/course/format/amd/src/local/courseeditor/exporter.js index 60fde91d220..0fe93849bf4 100644 --- a/course/format/amd/src/local/courseeditor/exporter.js +++ b/course/format/amd/src/local/courseeditor/exporter.js @@ -124,6 +124,7 @@ export default class { type: 'cm', id: cminfo.id, name: cminfo.name, + sectionid: cminfo.sectionid, nextcmid, }; } diff --git a/course/format/classes/output/local/content/cm.php b/course/format/classes/output/local/content/cm.php index 59ea3b6f415..84132e7f93a 100644 --- a/course/format/classes/output/local/content/cm.php +++ b/course/format/classes/output/local/content/cm.php @@ -192,7 +192,12 @@ class cm implements renderable, templatable { $data->controlmenu = $controlmenu->export_for_template($output); // Move and select options. - $data->moveicon = course_get_cm_move($mod, $returnsection); + if ($format->supports_components()) { + $data->moveicon = $output->pix_icon('i/dragdrop', '', 'moodle', ['class' => 'editing_move dragicon']); + } else { + // Add the legacy YUI move link. + $data->moveicon = course_get_cm_move($mod, $returnsection); + } } return $data; diff --git a/course/format/classes/output/local/content/section/controlmenu.php b/course/format/classes/output/local/content/section/controlmenu.php index 04efbd5152a..9f6b91a72fb 100644 --- a/course/format/classes/output/local/content/section/controlmenu.php +++ b/course/format/classes/output/local/content/section/controlmenu.php @@ -85,11 +85,11 @@ class controlmenu implements renderable, templatable { $url = empty($value['url']) ? '' : $value['url']; $icon = empty($value['icon']) ? '' : $value['icon']; $name = empty($value['name']) ? '' : $value['name']; - $attr = empty($value['attr']) ? array() : $value['attr']; + $attr = empty($value['attr']) ? [] : $value['attr']; $class = empty($value['pixattr']['class']) ? '' : $value['pixattr']['class']; $al = new action_menu_link_secondary( new moodle_url($url), - new pix_icon($icon, '', null, array('class' => "smallicon " . $class)), + new pix_icon($icon, '', null, ['class' => "smallicon " . $class]), $name, $attr ); @@ -124,6 +124,7 @@ class controlmenu implements renderable, templatable { $sectionreturn = $format->get_section_number(); $user = $USER; + $usecomponents = $format->supports_components(); $coursecontext = context_course::instance($course->id); $numsections = $format->get_last_section_number(); $isstealth = $section->section > $numsections; @@ -141,12 +142,13 @@ class controlmenu implements renderable, templatable { $streditsection = get_string('editsection'); } - $controls['edit'] = array( - 'url' => new moodle_url('/course/editsection.php', array('id' => $section->id, 'sr' => $sectionreturn)), + $controls['edit'] = [ + 'url' => new moodle_url('/course/editsection.php', ['id' => $section->id, 'sr' => $sectionreturn]), 'icon' => 'i/settings', 'name' => $streditsection, - 'pixattr' => array('class' => ''), - 'attr' => array('class' => 'icon edit')); + 'pixattr' => ['class' => ''], + 'attr' => ['class' => 'icon edit'], + ]; } if ($section->section) { @@ -156,53 +158,79 @@ class controlmenu implements renderable, templatable { if ($section->visible) { // Show the hide/show eye. $strhidefromothers = get_string('hidefromothers', 'format_'.$course->format); $url->param('hide', $section->section); - $controls['visiblity'] = array( + $controls['visiblity'] = [ 'url' => $url, 'icon' => 'i/hide', 'name' => $strhidefromothers, - 'pixattr' => array('class' => ''), - 'attr' => array('class' => 'icon editing_showhide', - 'data-sectionreturn' => $sectionreturn, 'data-action' => 'hide')); + 'pixattr' => ['class' => ''], + 'attr' => [ + 'class' => 'icon editing_showhide', + 'data-sectionreturn' => $sectionreturn, + 'data-action' => 'hide', + ], + ]; } else { $strshowfromothers = get_string('showfromothers', 'format_'.$course->format); $url->param('show', $section->section); - $controls['visiblity'] = array( + $controls['visiblity'] = [ 'url' => $url, 'icon' => 'i/show', 'name' => $strshowfromothers, - 'pixattr' => array('class' => ''), - 'attr' => array('class' => 'icon editing_showhide', - 'data-sectionreturn' => $sectionreturn, 'data-action' => 'show')); + 'pixattr' => ['class' => ''], + 'attr' => [ + 'class' => 'icon editing_showhide', + 'data-sectionreturn' => $sectionreturn, + 'data-action' => 'show', + ], + ]; } } - if (!$sectionreturn) { - if (has_capability('moodle/course:movesections', $coursecontext, $user)) { - $url = clone($baseurl); - if ($section->section > 1) { // Add a arrow to move section up. - $url->param('section', $section->section); - $url->param('move', -1); - $strmoveup = get_string('moveup'); - $controls['moveup'] = array( - 'url' => $url, - 'icon' => 'i/up', - 'name' => $strmoveup, - 'pixattr' => array('class' => ''), - 'attr' => array('class' => 'icon moveup')); - } + if (!$sectionreturn && has_capability('moodle/course:movesections', $coursecontext, $user)) { + if ($usecomponents) { + // This tool will appear only when the state is ready. + $url = clone ($baseurl); + $url->param('movesection', $section->section); + $url->param('section', $section->section); + $controls['movesection'] = [ + 'url' => $url, + 'icon' => 'i/dragdrop', + 'name' => get_string('move', 'moodle'), + 'pixattr' => ['class' => ''], + 'attr' => [ + 'class' => 'icon move waitstate', + 'data-action' => 'moveSection', + 'data-id' => $section->id, + ], + ]; + } + // Legacy move up and down links for non component-based formats. + $url = clone($baseurl); + if ($section->section > 1) { // Add a arrow to move section up. + $url->param('section', $section->section); + $url->param('move', -1); + $strmoveup = get_string('moveup'); + $controls['moveup'] = [ + 'url' => $url, + 'icon' => 'i/up', + 'name' => $strmoveup, + 'pixattr' => ['class' => ''], + 'attr' => ['class' => 'icon moveup whilenostate'], + ]; + } - $url = clone($baseurl); - if ($section->section < $numsections) { // Add a arrow to move section down. - $url->param('section', $section->section); - $url->param('move', 1); - $strmovedown = get_string('movedown'); - $controls['movedown'] = array( - 'url' => $url, - 'icon' => 'i/down', - 'name' => $strmovedown, - 'pixattr' => array('class' => ''), - 'attr' => array('class' => 'icon movedown')); - } + $url = clone($baseurl); + if ($section->section < $numsections) { // Add a arrow to move section down. + $url->param('section', $section->section); + $url->param('move', 1); + $strmovedown = get_string('movedown'); + $controls['movedown'] = [ + 'url' => $url, + 'icon' => 'i/down', + 'name' => $strmovedown, + 'pixattr' => ['class' => ''], + 'attr' => ['class' => 'icon movedown whilenostate'], + ]; } } } @@ -213,17 +241,22 @@ class controlmenu implements renderable, templatable { } else { $strdelete = get_string('deletesection'); } - $url = new moodle_url('/course/editsection.php', array( - 'id' => $section->id, - 'sr' => $sectionreturn, - 'delete' => 1, - 'sesskey' => sesskey())); - $controls['delete'] = array( + $url = new moodle_url( + '/course/editsection.php', + [ + 'id' => $section->id, + 'sr' => $sectionreturn, + 'delete' => 1, + 'sesskey' => sesskey(), + ] + ); + $controls['delete'] = [ 'url' => $url, 'icon' => 'i/delete', 'name' => $strdelete, - 'pixattr' => array('class' => ''), - 'attr' => array('class' => 'icon editing_delete')); + 'pixattr' => ['class' => ''], + 'attr' => ['class' => 'icon editing_delete'], + ]; } } diff --git a/course/format/templates/local/content/movecm.mustache b/course/format/templates/local/content/movecm.mustache new file mode 100644 index 00000000000..a913662f68d --- /dev/null +++ b/course/format/templates/local/content/movecm.mustache @@ -0,0 +1,133 @@ +{{! + This file is part of Moodle - http://moodle.org/ + + Moodle is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Moodle is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Moodle. If not, see . +}} +{{! + @template core_courseformat/local/movemodal/movecm + + Displays the course index. + + Example context (json): + { + "cmname": "Activity name", + "cmid": 42, + "sections": [ + { + "title": "General", + "id": 42, + "number": 1, + "sectionurl": "#", + "hascms": true, + "cms": [ + { + "name": "Glossary of characters", + "id": "10", + "url": "#" + }, + { + "name": "World Cinema forum", + "id": "11", + "url": "#" + }, + { + "name": "Announcements", + "id": "12", + "url": "#" + } + ] + }, + { + "title": "City of God or Cidade de Deus", + "id": "43", + "number": "2", + "sectionurl": "#", + "hascms": true, + "cms": [ + { + "name": "Resources", + "id": "13", + "url": "#" + }, + { + "name": "Studying City of God by Stephen Smith Bergman-Messerschmidt", + "id": "14", + "url": "#" + }, + { + "name": "Film education study guide", + "id": "15", + "url": "#" + } + ] + } + ] + } + +}} +

{{#str}} movefull, moodle, {{cmname}} {{/str}}:

+
diff --git a/course/format/templates/local/content/movesection.mustache b/course/format/templates/local/content/movesection.mustache new file mode 100644 index 00000000000..348070b6979 --- /dev/null +++ b/course/format/templates/local/content/movesection.mustache @@ -0,0 +1,61 @@ +{{! + This file is part of Moodle - http://moodle.org/ + + Moodle is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Moodle is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Moodle. If not, see . +}} +{{! + @template core_courseformat/local/movemodal/movesection + + Displays the course index. + + Example context (json): + { + "sectionid": 23, + "sectiontitle": "Section title", + "sections": [ + { + "title": "General", + "id": 42, + "number": 1, + "sectionurl": "#", + "isactive": 1 + }, + { + "title": "City of God or Cidade de Deus", + "id": "43", + "number": "2", + "sectionurl": "#", + "isactive": 0 + } + ] + } + +}} +

{{#str}} movefull, moodle, {{sectiontitle}} {{/str}}:

+ diff --git a/course/lib.php b/course/lib.php index dd60dcfdbd2..43733242a18 100644 --- a/course/lib.php +++ b/course/lib.php @@ -1820,6 +1820,23 @@ function course_get_cm_edit_actions(cm_info $mod, $indent = -1, $sr = null) { ); } + // Move (only for component compatible formats). + if ($courseformat->supports_components()) { + $actions['move'] = new action_menu_link_secondary( + new moodle_url($baseurl, [ + 'sesskey' => sesskey(), + 'copy' => $mod->id, + ]), + new pix_icon('i/dragdrop', '', 'moodle', ['class' => 'iconsmall']), + $str->move, + [ + 'class' => 'editing_movecm', + 'data-action' => 'moveCm', + 'data-id' => $mod->id, + ] + ); + } + // Indent. if ($hasmanageactivities && $indent >= 0) { $indentlimits = new stdClass(); @@ -3217,17 +3234,17 @@ function include_course_ajax($course, $usedmodules = array(), $enabledmodules = $config = new stdClass(); } - // The URL to use for resource changes + // The URL to use for resource changes. if (!isset($config->resourceurl)) { $config->resourceurl = '/course/rest.php'; } - // The URL to use for section changes + // The URL to use for section changes. if (!isset($config->sectionurl)) { $config->sectionurl = '/course/rest.php'; } - // Any additional parameters which need to be included on page submission + // Any additional parameters which need to be included on page submission. if (!isset($config->pageparams)) { $config->pageparams = array(); } diff --git a/course/tests/behat/behat_course.php b/course/tests/behat/behat_course.php index bc3d23ba331..6cb2402191c 100644 --- a/course/tests/behat/behat_course.php +++ b/course/tests/behat/behat_course.php @@ -762,38 +762,84 @@ class behat_course extends behat_base { } /** - * Moves the specified activity to the first slot of a section. This step is experimental when using it in Javascript tests. Editing mode should be on. + * Moves the specified activity to the first slot of a section. + * + * Editing mode should be on. * * @Given /^I move "(?P(?:[^"]|\\")*)" activity to section "(?P\d+)"$/ * @param string $activityname The activity name * @param int $sectionnumber The number of section */ - public function i_move_activity_to_section($activityname, $sectionnumber) { + public function i_move_activity_to_section($activityname, $sectionnumber): void { + // Ensure the destination is valid. + $sectionxpath = $this->section_exists($sectionnumber); + // Not all formats are compatible with the move tool. + $activitynode = $this->get_activity_node($activityname); + if (!$activitynode->find('css', "[data-action='moveCm']", false, false, 0)) { + // Execute the legacy YUI move option. + $this->i_move_activity_to_section_yui($activityname, $sectionnumber); + return; + } + + // JS enabled. + if ($this->running_javascript()) { + $this->i_open_actions_menu($activityname); + $this->execute( + 'behat_course::i_click_on_in_the_activity', + [get_string('move'), "link", $this->escape($activityname)] + ); + $this->execute("behat_general::i_click_on_in_the", [ + "[data-for='section'][data-number='$sectionnumber']", + 'css_element', + "[data-region='modal-container']", + 'css_element' + ]); + } else { + $this->execute( + 'behat_course::i_click_on_in_the_activity', + [get_string('move'), "link", $this->escape($activityname)] + ); + $this->execute( + 'behat_general::i_click_on_in_the', + ["li.movehere a", "css_element", $this->escape($sectionxpath), "xpath_element"] + ); + } + } + + /** + * Moves the specified activity to the first slot of a section using the YUI course format. + * + * This step is experimental when using it in Javascript tests. Editing mode should be on. + * + * @param string $activityname The activity name + * @param int $sectionnumber The number of section + */ + public function i_move_activity_to_section_yui($activityname, $sectionnumber): void { // Ensure the destination is valid. $sectionxpath = $this->section_exists($sectionnumber); // JS enabled. if ($this->running_javascript()) { - $activitynode = $this->get_activity_element('Move', 'icon', $activityname); $destinationxpath = $sectionxpath . "/descendant::ul[contains(concat(' ', normalize-space(@class), ' '), ' yui3-dd-drop ')]"; - - $this->execute("behat_general::i_drag_and_i_drop_it_in", - array($this->escape($activitynode->getXpath()), "xpath_element", - $this->escape($destinationxpath), "xpath_element") + $this->execute( + "behat_general::i_drag_and_i_drop_it_in", + [ + $this->escape($activitynode->getXpath()), "xpath_element", + $this->escape($destinationxpath), "xpath_element", + ] ); - } else { // Following links with no-JS. - // Moving to the fist spot of the section (before all other section's activities). - $this->execute('behat_course::i_click_on_in_the_activity', - array("a.editing_move", "css_element", $this->escape($activityname)) + $this->execute( + 'behat_course::i_click_on_in_the_activity', + ["a.editing_move", "css_element", $this->escape($activityname)] ); - - $this->execute('behat_general::i_click_on_in_the', - array("li.movehere a", "css_element", $this->escape($sectionxpath), "xpath_element") + $this->execute( + 'behat_general::i_click_on_in_the', + ["li.movehere a", "css_element", $this->escape($sectionxpath), "xpath_element"] ); } } diff --git a/course/tests/behat/move_activities.feature b/course/tests/behat/move_activities.feature index 1be73ff8c90..7e85daa5155 100644 --- a/course/tests/behat/move_activities.feature +++ b/course/tests/behat/move_activities.feature @@ -23,9 +23,6 @@ Feature: Activities can be moved between sections | section | 1 | And I log in as "teacher1" And I am on "Course 1" course homepage with editing mode on - And I add the "Recent activity" block - And I follow "Delete Recent activity block" - And I press "Yes" Scenario: Move activities in a single page course with Javascript disabled When I move "Test forum name" activity to section "2" @@ -56,3 +53,8 @@ Feature: Activities can be moved between sections And I follow "Topic 1" When I move "Second forum name" activity to section "1" Then "Second forum name" "link" should appear before "Test forum name" "link" + + @javascript + Scenario: Move activity with javascript + When I move "Test forum name" activity to section "3" + Then I should see "Test forum name" in the "Topic 3" "section" diff --git a/course/tests/behat/move_sections.feature b/course/tests/behat/move_sections.feature index 3e9780ad5e0..e6087c17027 100644 --- a/course/tests/behat/move_sections.feature +++ b/course/tests/behat/move_sections.feature @@ -6,32 +6,28 @@ Feature: Sections can be moved Background: Given the following "users" exist: - | username | firstname | lastname | email | - | teacher1 | Teacher | 1 | teacher1@example.com | + | username | firstname | lastname | email | + | teacher1 | Teacher | 1 | teacher1@example.com | And the following "courses" exist: | fullname | shortname | format | coursedisplay | numsections | - | Course 1 | C1 | topics | 0 | 5 | + | Course 1 | C1 | topics | 0 | 5 | And the following "course enrolments" exist: - | user | course | role | - | teacher1 | C1 | editingteacher | + | user | course | role | + | teacher1 | C1 | editingteacher | + And the following "activities" exist: + | activity | name | intro | course | idnumber | section | + | forum | Test forum name | Test forum name description | C1 | forum1 | 1 | And I log in as "teacher1" + And I am on "Course 1" course homepage with editing mode on Scenario: Move up and down a section with Javascript disabled in a single page course - Given the following "activities" exist: - | activity | name | intro | course | idnumber | section | - | forum | Test forum name | Test forum name description | C1 | forum1 | 1 | - And I am on "Course 1" course homepage with editing mode on When I move down section "1" Then I should see "Test forum name" in the "Topic 2" "section" And I move up section "2" And I should see "Test forum name" in the "Topic 1" "section" Scenario: Move up and down a section with Javascript disabled in the course home of a course using paged mode - Given the following "activities" exist: - | activity | name | intro | course | idnumber | section | - | forum | Test forum name | Test forum name description | C1 | forum1 | 1 | - And I am on "Course 1" course homepage with editing mode on - And I navigate to "Settings" in current page administration + Given I navigate to "Settings" in current page administration And I set the following fields to these values: | Course layout | Show one section per page | And I press "Save and display" @@ -41,11 +37,7 @@ Feature: Sections can be moved And I should see "Test forum name" in the "Topic 1" "section" Scenario: Sections can not be moved with Javascript disabled in a section page of a course using paged mode - Given the following "activities" exist: - | activity | name | intro | course | idnumber | section | - | forum | Test forum name | Test forum name description | C1 | forum1 | 2 | - And I am on "Course 1" course homepage with editing mode on - And I navigate to "Settings" in current page administration + Given I navigate to "Settings" in current page administration And I set the following fields to these values: | Course layout | Show one section per page | And I press "Save and display" @@ -54,3 +46,10 @@ Feature: Sections can be moved And "Topic 3" "section" should not exist And "Move down" "link" should not exist And "Move up" "link" should not exist + + @javascript + Scenario: Move section with javascript + When I open section "1" edit menu + And I click on "Move" "link" in the "Topic 1" "section" + And I click on "Topic 3" "link" in the ".modal-body" "css_element" + Then I should see "Test forum name" in the "Topic 3" "section" diff --git a/lib/amd/build/local/reactive/reactive.min.js b/lib/amd/build/local/reactive/reactive.min.js index 1d8b80b51e2..b5df25f4ede 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 // Check if we have a debug instance to register the instance.\n if (M.reactive !== undefined) {\n M.reactive.registerNewInstance(this);\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 // Dispatch an event to communicate the registration to the debug module.\n this.target.dispatchEvent(new CustomEvent('registerComponent:success', {\n bubbles: false,\n detail: {component},\n }));\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","M","reactive","registerNewInstance","event","dispatchEvent","CustomEvent","detail","action","bubbles","stateData","resolve","newFunctions","init","Object","entries","mutation","mutationFunction","manager","id","get","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","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 // Check if we have a debug instance to register the instance.\n if (M.reactive !== undefined) {\n M.reactive.registerNewInstance(this);\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 * Get state data.\n *\n * Components access the state frequently. This convenience method is a shortcut to\n * this.reactive.state.stateManager.get() method.\n *\n * @param {String} name the state object name\n * @param {*} id an optional object id for state maps.\n * @return {Object|undefined} the state object found\n */\n get(name, id) {\n return this.stateManager.get(name, id);\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 // Dispatch an event to communicate the registration to the debug module.\n this.target.dispatchEvent(new CustomEvent('registerComponent:success', {\n bubbles: false,\n detail: {component},\n }));\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/src/local/reactive/reactive.js b/lib/amd/src/local/reactive/reactive.js index 119f2fef98b..154ec42aca5 100644 --- a/lib/amd/src/local/reactive/reactive.js +++ b/lib/amd/src/local/reactive/reactive.js @@ -191,6 +191,20 @@ export default class { return this.stateManager.state; } + /** + * Get state data. + * + * Components access the state frequently. This convenience method is a shortcut to + * this.reactive.state.stateManager.get() method. + * + * @param {String} name the state object name + * @param {*} id an optional object id for state maps. + * @return {Object|undefined} the state object found + */ + get(name, id) { + return this.stateManager.get(name, id); + } + /** * Return the initial state promise. * diff --git a/theme/boost/scss/moodle/core.scss b/theme/boost/scss/moodle/core.scss index b99b369d43a..6fd222e7d92 100644 --- a/theme/boost/scss/moodle/core.scss +++ b/theme/boost/scss/moodle/core.scss @@ -7,6 +7,11 @@ $bg-inverse-link-color: #fff !default; $dropzone-border: $gray-900 !default; +$collapse-list-item-padding-y: 0.5rem !default; +$collapse-list-item-padding-x: 1rem !default; +$collapse-list-item-hover-bg: theme-color-level('info', -11) !default; +$collapse-list-item-hover-border: theme-color-level('info', -9) !default; + $font-size-xs: ($font-size-base * .75) !default; #region-main { @@ -2829,3 +2834,35 @@ body.dragging { visibility: visible; cursor: move; } + +// Generic classes reactive components can use. + +.waitstate { + display: none; +} + +.stateready { + .waitstate { + display: inherit; + } + .whilenostate { + display: none; + } +} + +// Collapsible list. + +.collapse-list { + .collapse-list-item { + padding: $collapse-list-item-padding-y $collapse-list-item-padding-x; + @include hover-focus() { + background-color: $collapse-list-item-hover-bg; + border-color: $collapse-list-item-hover-border; + } + } + .collapse-list-item-content { + .collapse-list-item { + padding-left: calc(#{$collapse-list-item-padding-x} * 3); + } + } +} diff --git a/theme/boost/style/moodle.css b/theme/boost/style/moodle.css index 00f26e0bcae..6fc253bdc1b 100644 --- a/theme/boost/style/moodle.css +++ b/theme/boost/style/moodle.css @@ -12187,6 +12187,24 @@ body.dragging .dragging { visibility: visible; cursor: move; } +.waitstate { + display: none; } + +.stateready .waitstate { + display: inherit; } + +.stateready .whilenostate { + display: none; } + +.collapse-list .collapse-list-item { + padding: 0.5rem 1rem; } + .collapse-list .collapse-list-item:hover, .collapse-list .collapse-list-item:focus { + background-color: #e0f0f2; + border-color: #b8dce2; } + +.collapse-list .collapse-list-item-content .collapse-list-item { + padding-left: calc(1rem * 3); } + .icon { font-size: 16px; width: 16px; diff --git a/theme/classic/style/moodle.css b/theme/classic/style/moodle.css index ef5babe5def..def19cbcebe 100644 --- a/theme/classic/style/moodle.css +++ b/theme/classic/style/moodle.css @@ -12187,6 +12187,24 @@ body.dragging .dragging { visibility: visible; cursor: move; } +.waitstate { + display: none; } + +.stateready .waitstate { + display: inherit; } + +.stateready .whilenostate { + display: none; } + +.collapse-list .collapse-list-item { + padding: 0.5rem 1rem; } + .collapse-list .collapse-list-item:hover, .collapse-list .collapse-list-item:focus { + background-color: #e0f0f2; + border-color: #b8dce2; } + +.collapse-list .collapse-list-item-content .collapse-list-item { + padding-left: calc(1rem * 3); } + .icon { font-size: 16px; width: 16px;