From ae799fad7d40b4eb47f27635c91482ef73cd667c Mon Sep 17 00:00:00 2001 From: Ferran Recio Date: Mon, 6 Mar 2023 11:29:06 +0100 Subject: [PATCH] MDL-76905 core_courseformat: match prototype visual style --- .../amd/build/local/content/actions.min.js | 2 +- .../build/local/content/actions.min.js.map | 2 +- .../build/local/content/bulkedittools.min.js | 2 +- .../local/content/bulkedittools.min.js.map | 2 +- .../format/amd/src/local/content/actions.js | 3 +- .../amd/src/local/content/bulkedittools.js | 4 +- course/format/classes/base.php | 2 + .../output/local/content/bulkedittools.php | 2 +- .../local/content/bulkedittoggler.mustache | 2 +- .../local/content/bulkedittools.mustache | 6 +- .../tests/behat/bulk_section_actions.feature | 20 +++---- .../format/topics/lang/en/format_topics.php | 3 + course/format/weeks/lang/en/format_weeks.php | 3 + .../weeks/tests/behat/bulk_actions.feature | 58 ++++++++++++++++++- lang/en/courseformat.php | 8 ++- 15 files changed, 93 insertions(+), 26 deletions(-) diff --git a/course/format/amd/build/local/content/actions.min.js b/course/format/amd/build/local/content/actions.min.js index aa5c0aac5da..f4f1d071ac2 100644 --- a/course/format/amd/build/local/content/actions.min.js +++ b/course/format/amd/build/local/content/actions.min.js @@ -9,6 +9,6 @@ define("core_courseformat/local/content/actions",["exports","core/reactive","cor * @class core_courseformat/local/content/actions * @copyright 2021 Ferran Recio * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.default=void 0,_modal_factory=_interopRequireDefault(_modal_factory),_modal_events=_interopRequireDefault(_modal_events),_templates=_interopRequireDefault(_templates),CourseEvents=function(obj,nodeInterop){if(!nodeInterop&&obj&&obj.__esModule)return obj;if(null===obj||"object"!=typeof obj&&"function"!=typeof obj)return{default:obj};var cache=_getRequireWildcardCache(nodeInterop);if(cache&&cache.has(obj))return cache.get(obj);var newObj={},hasPropertyDescriptor=Object.defineProperty&&Object.getOwnPropertyDescriptor;for(var key in obj)if("default"!==key&&Object.prototype.hasOwnProperty.call(obj,key)){var desc=hasPropertyDescriptor?Object.getOwnPropertyDescriptor(obj,key):null;desc&&(desc.get||desc.set)?Object.defineProperty(newObj,key,desc):newObj[key]=obj[key]}newObj.default=obj,cache&&cache.set(obj,newObj);return newObj}(CourseEvents),_pending=_interopRequireDefault(_pending),_contenttree=_interopRequireDefault(_contenttree),_jquery=_interopRequireDefault(_jquery),(0,_prefetch.prefetchStrings)("core",["movecoursesection","movecoursemodule","confirm","delete"]);const directMutations={sectionHide:"sectionHide",sectionShow:"sectionShow",cmHide:"cmHide",cmShow:"cmShow",cmStealth:"cmStealth",cmMoveRight:"cmMoveRight",cmMoveLeft:"cmMoveLeft"};class _default extends _reactive.BaseComponent{create(){this.name="content_actions",this.selectors={ACTIONLINK:"[data-action]",SECTIONLINK:"[data-for='section']",CMLINK:"[data-for='cm']",SECTIONNODE:"[data-for='sectionnode']",MODALTOGGLER:"[data-toggle='collapse']",ADDSECTION:"[data-action='addSection']",CONTENTTREE:"#destination-selector",ACTIONMENU:".action-menu",ACTIONMENUTOGGLER:'[data-toggle="dropdown"]',OPTIONSRADIO:"[type='radio']"},this.classes={DISABLED:"disabled"}}static addActions(actions){for(const[action,mutationReference]of Object.entries(actions)){if("function"!=typeof mutationReference&&"string"!=typeof mutationReference)throw new Error("".concat(action," action must be a mutation name or a function"));directMutations[action]=mutationReference}}stateReady(state){this.addEventListener(this.element,"click",this._dispatchClick),this._checkSectionlist({state:state}),this.addEventListener(this.element,CourseEvents.sectionRefreshed,(()=>this._checkSectionlist({state:state})))}getWatchers(){return[{watch:"course.sectionlist:updated",handler:this._checkSectionlist}]}_dispatchClick(event){const target=event.target.closest(this.selectors.ACTIONLINK);if(!target)return;if(target.classList.contains(this.classes.DISABLED))return void event.preventDefault();const actionName=target.dataset.action,methodName=this._actionMethodName(actionName);if(void 0===this[methodName])return void 0!==directMutations[actionName]?"function"==typeof directMutations[actionName]?void directMutations[actionName](target,event):void this._requestMutationAction(target,event,directMutations[actionName]):void 0;this[methodName](target,event)}_actionMethodName(name){const requestName=name.charAt(0).toUpperCase()+name.slice(1);return"_request".concat(requestName)}_checkSectionlist(_ref){let{state:state}=_ref;this._setAddSectionLocked(state.course.sectionlist.length>state.course.maxsections)}_getTargetIds(target){var _target$dataset,_target$dataset2;let ids=[];null!=target&&null!==(_target$dataset=target.dataset)&&void 0!==_target$dataset&&_target$dataset.id&&ids.push(target.dataset.id);const bulkType=null==target||null===(_target$dataset2=target.dataset)||void 0===_target$dataset2?void 0:_target$dataset2.bulk;if(!bulkType)return ids;const bulk=this.reactive.get("bulk");return bulk.enabled&&bulk.selectedType===bulkType&&(ids=[...ids,...bulk.selection]),ids}async _requestMoveSection(target,event){const sectionIds=this._getTargetIds(target);if(0==sectionIds.length)return;event.preventDefault();const editTools=this._getClosestActionMenuToogler(target),data=this.reactive.getExporter().course(this.reactive.state);let titleText=null,sectionInfo=null;1==sectionIds.length?(sectionInfo=this.reactive.get("section",sectionIds[0]),data.sectionid=sectionInfo.id,data.sectiontitle=sectionInfo.title,data.information=await this.reactive.getFormatString("sectionmove_info",data.sectiontitle),titleText=this.reactive.getFormatString("sectionmove_title")):(data.information=await this.reactive.getFormatString("sectionsmove_info",sectionIds.length),titleText=this.reactive.getFormatString("sectionsmove_title"));const modalParams={title:titleText,body:_templates.default.render("core_courseformat/local/content/movesection",data)},modal=await this._modalBodyRenderedPromise(modalParams),modalBody=(0,_normalise.getFirst)(modal.getBody());sectionIds.forEach((sectionId=>{const currentElement=modalBody.querySelector("".concat(this.selectors.SECTIONLINK,"[data-id='").concat(sectionId,"']"));this._disableLink(currentElement)})),new _contenttree.default(modalBody.querySelector(this.selectors.CONTENTTREE),{SECTION:this.selectors.SECTIONNODE,TOGGLER:this.selectors.MODALTOGGLER,COLLAPSE:this.selectors.MODALTOGGLER},!0),modalBody.addEventListener("click",(event=>{const target=event.target;target.matches("a")&&"section"==target.dataset.for&&void 0!==target.dataset.id&&(target.getAttribute("aria-disabled")||(event.preventDefault(),this.reactive.dispatch("sectionMoveAfter",sectionIds,target.dataset.id),this._destroyModal(modal,editTools)))}))}async _requestMoveCm(target,event){const cmIds=this._getTargetIds(target);if(0==cmIds.length)return;event.preventDefault();const editTools=this._getClosestActionMenuToogler(target),exporter=this.reactive.getExporter(),data=exporter.course(this.reactive.state);let titleText=null;if(1==cmIds.length){const cmInfo=this.reactive.get("cm",cmIds[0]);data.cmid=cmInfo.id,data.cmname=cmInfo.name,data.information=await this.reactive.getFormatString("cmmove_info",data.cmname),titleText=this.reactive.getFormatString("cmmove_title")}else data.information=await this.reactive.getFormatString("cmsmove_info",cmIds.length),titleText=this.reactive.getFormatString("cmsmove_title");const modalParams={title:titleText,body:_templates.default.render("core_courseformat/local/content/movecm",data)},modal=await this._modalBodyRenderedPromise(modalParams),modalBody=(0,_normalise.getFirst)(modal.getBody());cmIds.forEach((cmId=>{const currentElement=modalBody.querySelector("".concat(this.selectors.CMLINK,"[data-id='").concat(cmId,"']"));this._disableLink(currentElement)})),new _contenttree.default(modalBody.querySelector(this.selectors.CONTENTTREE),{SECTION:this.selectors.SECTIONNODE,TOGGLER:this.selectors.MODALTOGGLER,COLLAPSE:this.selectors.MODALTOGGLER,ENTER:this.selectors.SECTIONLINK}),cmIds.forEach((cmId=>{var _toggler$data;const sectionnode=modalBody.querySelector("".concat(this.selectors.CMLINK,"[data-id='").concat(cmId,"']")).closest(this.selectors.SECTIONNODE),toggler=(0,_jquery.default)(sectionnode).find(this.selectors.MODALTOGGLER);let collapsibleId=null!==(_toggler$data=toggler.data("target"))&&void 0!==_toggler$data?_toggler$data:toggler.attr("href");if(collapsibleId){collapsibleId=collapsibleId.replace("#","");const expandNode=modalBody.querySelector("#".concat(collapsibleId));(0,_jquery.default)(expandNode).collapse("show")}})),modalBody.addEventListener("click",(event=>{const target=event.target;if(!target.matches("a")||void 0===target.dataset.for||void 0===target.dataset.id)return;if(target.getAttribute("aria-disabled"))return;let targetSectionId,targetCmId;if(event.preventDefault(),"cm"==target.dataset.for){const dropData=exporter.cmDraggableData(this.reactive.state,target.dataset.id);targetSectionId=dropData.sectionid,targetCmId=dropData.nextcmid}else{const section=this.reactive.get("section",target.dataset.id);targetSectionId=target.dataset.id,targetCmId=null==section?void 0:section.cmlist[0]}this.reactive.dispatch("cmMove",cmIds,targetSectionId,targetCmId),this._destroyModal(modal,editTools)}))}async _requestAddSection(target,event){var _target$dataset$id;event.preventDefault(),this.reactive.dispatch("addSection",null!==(_target$dataset$id=target.dataset.id)&&void 0!==_target$dataset$id?_target$dataset$id:0)}async _requestDeleteSection(target,event){const sectionIds=this._getTargetIds(target);if(0==sectionIds.length)return;if(event.preventDefault(),!sectionIds.some((sectionId=>{var _sectionInfo$cmlist;const sectionInfo=this.reactive.get("section",sectionId);return(null!==(_sectionInfo$cmlist=sectionInfo.cmlist)&&void 0!==_sectionInfo$cmlist?_sectionInfo$cmlist:[]).length||sectionInfo.hassummary||sectionInfo.rawtitle})))return void this.reactive.dispatch("sectionDelete",sectionIds);let bodyText=null,titleText=null;if(1==sectionIds.length){titleText=this.reactive.getFormatString("sectiondelete_title");const sectionInfo=this.reactive.get("section",sectionIds[0]);bodyText=this.reactive.getFormatString("sectiondelete_info",{name:sectionInfo.title})}else titleText=this.reactive.getFormatString("sectionsdelete_title"),bodyText=this.reactive.getFormatString("sectionsdelete_info",{count:sectionIds.length});const modalParams={title:titleText,body:bodyText,type:_modal_factory.default.types.DELETE_CANCEL},modal=await this._modalBodyRenderedPromise(modalParams);modal.getRoot().on(_modal_events.default.delete,(e=>{e.preventDefault(),modal.destroy(),this.reactive.dispatch("sectionDelete",sectionIds)}))}async _requestToggleSelectionCm(target){var _target$checked;const cmId=target.dataset.id;if(!cmId)return;const mutation=null!==(_target$checked=target.checked)&&void 0!==_target$checked&&_target$checked?"cmSelect":"cmUnselect";this.reactive.dispatch(mutation,[cmId])}async _requestToggleSelectionSection(target){var _target$checked2;const sectionId=target.dataset.id;if(!sectionId)return;const mutation=null!==(_target$checked2=target.checked)&&void 0!==_target$checked2&&_target$checked2?"sectionSelect":"sectionUnselect";this.reactive.dispatch(mutation,[sectionId])}async _requestMutationAction(target,event,mutationName){target.dataset.id&&(event.preventDefault(),this.reactive.dispatch(mutationName,[target.dataset.id]))}async _requestCmDuplicate(target,event){var _target$dataset$secti;const cmIds=this._getTargetIds(target);if(0==cmIds.length)return;const sectionId=null!==(_target$dataset$secti=target.dataset.sectionid)&&void 0!==_target$dataset$secti?_target$dataset$secti:null;event.preventDefault(),this.reactive.dispatch("cmDuplicate",cmIds,sectionId)}async _requestCmDelete(target,event){const cmIds=this._getTargetIds(target);if(0==cmIds.length)return;event.preventDefault();let bodyText=null,titleText=null;if(1==cmIds.length){const cmInfo=this.reactive.get("cm",cmIds[0]);titleText=(0,_str.get_string)("cmdelete_title","core_courseformat"),bodyText=(0,_str.get_string)("cmdelete_info","core_courseformat",{type:cmInfo.modname,name:cmInfo.name})}else titleText=(0,_str.get_string)("cmsdelete_title","core_courseformat"),bodyText=(0,_str.get_string)("cmsdelete_info","core_courseformat",{count:cmIds.length});const modalParams={title:titleText,body:bodyText,type:_modal_factory.default.types.DELETE_CANCEL},modal=await this._modalBodyRenderedPromise(modalParams);modal.getRoot().on(_modal_events.default.delete,(e=>{e.preventDefault(),modal.destroy(),this.reactive.dispatch("cmDelete",cmIds)}))}async _requestCmAvailability(target){const cmIds=this._getTargetIds(target);if(0==cmIds.length)return;const data={allowstealth:this.reactive.getExporter().canUseStealth(this.reactive.state,cmIds)},modalParams={title:(0,_str.get_string)("availability","core"),body:_templates.default.render("core_courseformat/local/content/cm/availabilitymodal",data),saveButtonText:(0,_str.get_string)("apply","core"),type:_modal_factory.default.types.SAVE_CANCEL},modal=await this._modalBodyRenderedPromise(modalParams);this._setupMutationRadioButtonModal(modal,cmIds)}async _requestSectionAvailability(target){const sectionIds=this._getTargetIds(target);if(0==sectionIds.length)return;const modalParams={title:(0,_str.get_string)("availability","core"),body:_templates.default.render("core_courseformat/local/content/section/availabilitymodal",[]),saveButtonText:(0,_str.get_string)("apply","core"),type:_modal_factory.default.types.SAVE_CANCEL},modal=await this._modalBodyRenderedPromise(modalParams);this._setupMutationRadioButtonModal(modal,sectionIds)}_setupMutationRadioButtonModal(modal,ids){modal.setButtonDisabled("save",!0);const submitFunction=radio=>{const mutation=null==radio?void 0:radio.value;return!!mutation&&(this.reactive.dispatch(mutation,ids),!0)},modalBody=(0,_normalise.getFirst)(modal.getBody());modalBody.querySelectorAll(this.selectors.OPTIONSRADIO).forEach((radio=>{radio.addEventListener("change",(()=>{modal.setButtonDisabled("save",!1)})),radio.parentNode.addEventListener("click",(()=>{radio.checked=!0,modal.setButtonDisabled("save",!1)})),radio.parentNode.addEventListener("dblclick",(dbClickEvent=>{submitFunction(radio)&&(dbClickEvent.preventDefault(),modal.destroy())}))})),modal.getRoot().on(_modal_events.default.save,(()=>{const radio=modalBody.querySelector("".concat(this.selectors.OPTIONSRADIO,":checked"));submitFunction(radio)}))}_setAddSectionLocked(locked){this.getElements(this.selectors.ADDSECTION).forEach((element=>{element.classList.toggle(this.classes.DISABLED,locked),this.setElementLocked(element,locked)}))}_disableLink(element){element&&(element.style.pointerEvents="none",element.style.userSelect="none",element.classList.add(this.classes.DISABLED),element.setAttribute("aria-disabled",!0),element.addEventListener("click",(event=>event.preventDefault())))}_modalBodyRenderedPromise(modalParams){return new Promise(((resolve,reject)=>{_modal_factory.default.create(modalParams).then((modal=>{modal.setRemoveOnClose(!0),modal.getRoot().on(_modal_events.default.bodyRendered,(()=>{resolve(modal)})),void 0!==modalParams.saveButtonText&&modal.setSaveButtonText(modalParams.saveButtonText),void 0!==modalParams.deleteButtonText&&modal.setDeleteButtonText(modalParams.saveButtonText),modal.show()})).catch((()=>{reject("Cannot load modal content")}))}))}_destroyModal(modal,element){modal.hide();const pendingDestroy=new _pending.default("courseformat/actions:destroyModal");element&&element.focus(),setTimeout((()=>{modal.destroy(),pendingDestroy.resolve()}),500)}_getClosestActionMenuToogler(element){const actionMenu=element.closest(this.selectors.ACTIONMENU);if(actionMenu)return actionMenu.querySelector(this.selectors.ACTIONMENUTOGGLER)}}return _exports.default=_default,_exports.default})); + */Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.default=void 0,_modal_factory=_interopRequireDefault(_modal_factory),_modal_events=_interopRequireDefault(_modal_events),_templates=_interopRequireDefault(_templates),CourseEvents=function(obj,nodeInterop){if(!nodeInterop&&obj&&obj.__esModule)return obj;if(null===obj||"object"!=typeof obj&&"function"!=typeof obj)return{default:obj};var cache=_getRequireWildcardCache(nodeInterop);if(cache&&cache.has(obj))return cache.get(obj);var newObj={},hasPropertyDescriptor=Object.defineProperty&&Object.getOwnPropertyDescriptor;for(var key in obj)if("default"!==key&&Object.prototype.hasOwnProperty.call(obj,key)){var desc=hasPropertyDescriptor?Object.getOwnPropertyDescriptor(obj,key):null;desc&&(desc.get||desc.set)?Object.defineProperty(newObj,key,desc):newObj[key]=obj[key]}newObj.default=obj,cache&&cache.set(obj,newObj);return newObj}(CourseEvents),_pending=_interopRequireDefault(_pending),_contenttree=_interopRequireDefault(_contenttree),_jquery=_interopRequireDefault(_jquery),(0,_prefetch.prefetchStrings)("core",["movecoursesection","movecoursemodule","confirm","delete"]);const directMutations={sectionHide:"sectionHide",sectionShow:"sectionShow",cmHide:"cmHide",cmShow:"cmShow",cmStealth:"cmStealth",cmMoveRight:"cmMoveRight",cmMoveLeft:"cmMoveLeft"};class _default extends _reactive.BaseComponent{create(){this.name="content_actions",this.selectors={ACTIONLINK:"[data-action]",SECTIONLINK:"[data-for='section']",CMLINK:"[data-for='cm']",SECTIONNODE:"[data-for='sectionnode']",MODALTOGGLER:"[data-toggle='collapse']",ADDSECTION:"[data-action='addSection']",CONTENTTREE:"#destination-selector",ACTIONMENU:".action-menu",ACTIONMENUTOGGLER:'[data-toggle="dropdown"]',OPTIONSRADIO:"[type='radio']"},this.classes={DISABLED:"disabled"}}static addActions(actions){for(const[action,mutationReference]of Object.entries(actions)){if("function"!=typeof mutationReference&&"string"!=typeof mutationReference)throw new Error("".concat(action," action must be a mutation name or a function"));directMutations[action]=mutationReference}}stateReady(state){this.addEventListener(this.element,"click",this._dispatchClick),this._checkSectionlist({state:state}),this.addEventListener(this.element,CourseEvents.sectionRefreshed,(()=>this._checkSectionlist({state:state})))}getWatchers(){return[{watch:"course.sectionlist:updated",handler:this._checkSectionlist}]}_dispatchClick(event){const target=event.target.closest(this.selectors.ACTIONLINK);if(!target)return;if(target.classList.contains(this.classes.DISABLED))return void event.preventDefault();const actionName=target.dataset.action,methodName=this._actionMethodName(actionName);if(void 0===this[methodName])return void 0!==directMutations[actionName]?"function"==typeof directMutations[actionName]?void directMutations[actionName](target,event):void this._requestMutationAction(target,event,directMutations[actionName]):void 0;this[methodName](target,event)}_actionMethodName(name){const requestName=name.charAt(0).toUpperCase()+name.slice(1);return"_request".concat(requestName)}_checkSectionlist(_ref){let{state:state}=_ref;this._setAddSectionLocked(state.course.sectionlist.length>state.course.maxsections)}_getTargetIds(target){var _target$dataset,_target$dataset2;let ids=[];null!=target&&null!==(_target$dataset=target.dataset)&&void 0!==_target$dataset&&_target$dataset.id&&ids.push(target.dataset.id);const bulkType=null==target||null===(_target$dataset2=target.dataset)||void 0===_target$dataset2?void 0:_target$dataset2.bulk;if(!bulkType)return ids;const bulk=this.reactive.get("bulk");return bulk.enabled&&bulk.selectedType===bulkType&&(ids=[...ids,...bulk.selection]),ids}async _requestMoveSection(target,event){const sectionIds=this._getTargetIds(target);if(0==sectionIds.length)return;event.preventDefault();const editTools=this._getClosestActionMenuToogler(target),data=this.reactive.getExporter().course(this.reactive.state);let titleText=null,sectionInfo=null;1==sectionIds.length?(sectionInfo=this.reactive.get("section",sectionIds[0]),data.sectionid=sectionInfo.id,data.sectiontitle=sectionInfo.title,data.information=await this.reactive.getFormatString("sectionmove_info",data.sectiontitle),titleText=this.reactive.getFormatString("sectionmove_title")):(data.information=await this.reactive.getFormatString("sectionsmove_info",sectionIds.length),titleText=this.reactive.getFormatString("sectionsmove_title"));const modalParams={title:titleText,body:_templates.default.render("core_courseformat/local/content/movesection",data)},modal=await this._modalBodyRenderedPromise(modalParams),modalBody=(0,_normalise.getFirst)(modal.getBody());sectionIds.forEach((sectionId=>{const currentElement=modalBody.querySelector("".concat(this.selectors.SECTIONLINK,"[data-id='").concat(sectionId,"']"));this._disableLink(currentElement)})),new _contenttree.default(modalBody.querySelector(this.selectors.CONTENTTREE),{SECTION:this.selectors.SECTIONNODE,TOGGLER:this.selectors.MODALTOGGLER,COLLAPSE:this.selectors.MODALTOGGLER},!0),modalBody.addEventListener("click",(event=>{const target=event.target;target.matches("a")&&"section"==target.dataset.for&&void 0!==target.dataset.id&&(target.getAttribute("aria-disabled")||(event.preventDefault(),this.reactive.dispatch("sectionMoveAfter",sectionIds,target.dataset.id),this._destroyModal(modal,editTools)))}))}async _requestMoveCm(target,event){const cmIds=this._getTargetIds(target);if(0==cmIds.length)return;event.preventDefault();const editTools=this._getClosestActionMenuToogler(target),exporter=this.reactive.getExporter(),data=exporter.course(this.reactive.state);let titleText=null;if(1==cmIds.length){const cmInfo=this.reactive.get("cm",cmIds[0]);data.cmid=cmInfo.id,data.cmname=cmInfo.name,data.information=await this.reactive.getFormatString("cmmove_info",data.cmname),titleText=this.reactive.getFormatString("cmmove_title")}else data.information=await this.reactive.getFormatString("cmsmove_info",cmIds.length),titleText=this.reactive.getFormatString("cmsmove_title");const modalParams={title:titleText,body:_templates.default.render("core_courseformat/local/content/movecm",data)},modal=await this._modalBodyRenderedPromise(modalParams),modalBody=(0,_normalise.getFirst)(modal.getBody());cmIds.forEach((cmId=>{const currentElement=modalBody.querySelector("".concat(this.selectors.CMLINK,"[data-id='").concat(cmId,"']"));this._disableLink(currentElement)})),new _contenttree.default(modalBody.querySelector(this.selectors.CONTENTTREE),{SECTION:this.selectors.SECTIONNODE,TOGGLER:this.selectors.MODALTOGGLER,COLLAPSE:this.selectors.MODALTOGGLER,ENTER:this.selectors.SECTIONLINK}),cmIds.forEach((cmId=>{var _toggler$data;const sectionnode=modalBody.querySelector("".concat(this.selectors.CMLINK,"[data-id='").concat(cmId,"']")).closest(this.selectors.SECTIONNODE),toggler=(0,_jquery.default)(sectionnode).find(this.selectors.MODALTOGGLER);let collapsibleId=null!==(_toggler$data=toggler.data("target"))&&void 0!==_toggler$data?_toggler$data:toggler.attr("href");if(collapsibleId){collapsibleId=collapsibleId.replace("#","");const expandNode=modalBody.querySelector("#".concat(collapsibleId));(0,_jquery.default)(expandNode).collapse("show")}})),modalBody.addEventListener("click",(event=>{const target=event.target;if(!target.matches("a")||void 0===target.dataset.for||void 0===target.dataset.id)return;if(target.getAttribute("aria-disabled"))return;let targetSectionId,targetCmId;if(event.preventDefault(),"cm"==target.dataset.for){const dropData=exporter.cmDraggableData(this.reactive.state,target.dataset.id);targetSectionId=dropData.sectionid,targetCmId=dropData.nextcmid}else{const section=this.reactive.get("section",target.dataset.id);targetSectionId=target.dataset.id,targetCmId=null==section?void 0:section.cmlist[0]}this.reactive.dispatch("cmMove",cmIds,targetSectionId,targetCmId),this._destroyModal(modal,editTools)}))}async _requestAddSection(target,event){var _target$dataset$id;event.preventDefault(),this.reactive.dispatch("addSection",null!==(_target$dataset$id=target.dataset.id)&&void 0!==_target$dataset$id?_target$dataset$id:0)}async _requestDeleteSection(target,event){const sectionIds=this._getTargetIds(target);if(0==sectionIds.length)return;if(event.preventDefault(),!sectionIds.some((sectionId=>{var _sectionInfo$cmlist;const sectionInfo=this.reactive.get("section",sectionId);return(null!==(_sectionInfo$cmlist=sectionInfo.cmlist)&&void 0!==_sectionInfo$cmlist?_sectionInfo$cmlist:[]).length||sectionInfo.hassummary||sectionInfo.rawtitle})))return void this.reactive.dispatch("sectionDelete",sectionIds);let bodyText=null,titleText=null;if(1==sectionIds.length){titleText=this.reactive.getFormatString("sectiondelete_title");const sectionInfo=this.reactive.get("section",sectionIds[0]);bodyText=this.reactive.getFormatString("sectiondelete_info",{name:sectionInfo.title})}else titleText=this.reactive.getFormatString("sectionsdelete_title"),bodyText=this.reactive.getFormatString("sectionsdelete_info",{count:sectionIds.length});const modalParams={title:titleText,body:bodyText,type:_modal_factory.default.types.DELETE_CANCEL},modal=await this._modalBodyRenderedPromise(modalParams);modal.getRoot().on(_modal_events.default.delete,(e=>{e.preventDefault(),modal.destroy(),this.reactive.dispatch("sectionDelete",sectionIds)}))}async _requestToggleSelectionCm(target){var _target$checked;const cmId=target.dataset.id;if(!cmId)return;const mutation=null!==(_target$checked=target.checked)&&void 0!==_target$checked&&_target$checked?"cmSelect":"cmUnselect";this.reactive.dispatch(mutation,[cmId])}async _requestToggleSelectionSection(target){var _target$checked2;const sectionId=target.dataset.id;if(!sectionId)return;const mutation=null!==(_target$checked2=target.checked)&&void 0!==_target$checked2&&_target$checked2?"sectionSelect":"sectionUnselect";this.reactive.dispatch(mutation,[sectionId])}async _requestMutationAction(target,event,mutationName){target.dataset.id&&(event.preventDefault(),this.reactive.dispatch(mutationName,[target.dataset.id]))}async _requestCmDuplicate(target,event){var _target$dataset$secti;const cmIds=this._getTargetIds(target);if(0==cmIds.length)return;const sectionId=null!==(_target$dataset$secti=target.dataset.sectionid)&&void 0!==_target$dataset$secti?_target$dataset$secti:null;event.preventDefault(),this.reactive.dispatch("cmDuplicate",cmIds,sectionId)}async _requestCmDelete(target,event){const cmIds=this._getTargetIds(target);if(0==cmIds.length)return;event.preventDefault();let bodyText=null,titleText=null;if(1==cmIds.length){const cmInfo=this.reactive.get("cm",cmIds[0]);titleText=(0,_str.get_string)("cmdelete_title","core_courseformat"),bodyText=(0,_str.get_string)("cmdelete_info","core_courseformat",{type:cmInfo.modname,name:cmInfo.name})}else titleText=(0,_str.get_string)("cmsdelete_title","core_courseformat"),bodyText=(0,_str.get_string)("cmsdelete_info","core_courseformat",{count:cmIds.length});const modalParams={title:titleText,body:bodyText,type:_modal_factory.default.types.DELETE_CANCEL},modal=await this._modalBodyRenderedPromise(modalParams);modal.getRoot().on(_modal_events.default.delete,(e=>{e.preventDefault(),modal.destroy(),this.reactive.dispatch("cmDelete",cmIds)}))}async _requestCmAvailability(target){const cmIds=this._getTargetIds(target);if(0==cmIds.length)return;const data={allowstealth:this.reactive.getExporter().canUseStealth(this.reactive.state,cmIds)},modalParams={title:(0,_str.get_string)("availability","core"),body:_templates.default.render("core_courseformat/local/content/cm/availabilitymodal",data),saveButtonText:(0,_str.get_string)("apply","core"),type:_modal_factory.default.types.SAVE_CANCEL},modal=await this._modalBodyRenderedPromise(modalParams);this._setupMutationRadioButtonModal(modal,cmIds)}async _requestSectionAvailability(target){const sectionIds=this._getTargetIds(target);if(0==sectionIds.length)return;const title=1==sectionIds.length?"sectionavailability_title":"sectionsavailability_title",modalParams={title:this.reactive.getFormatString(title),body:_templates.default.render("core_courseformat/local/content/section/availabilitymodal",[]),saveButtonText:(0,_str.get_string)("apply","core"),type:_modal_factory.default.types.SAVE_CANCEL},modal=await this._modalBodyRenderedPromise(modalParams);this._setupMutationRadioButtonModal(modal,sectionIds)}_setupMutationRadioButtonModal(modal,ids){modal.setButtonDisabled("save",!0);const submitFunction=radio=>{const mutation=null==radio?void 0:radio.value;return!!mutation&&(this.reactive.dispatch(mutation,ids),!0)},modalBody=(0,_normalise.getFirst)(modal.getBody());modalBody.querySelectorAll(this.selectors.OPTIONSRADIO).forEach((radio=>{radio.addEventListener("change",(()=>{modal.setButtonDisabled("save",!1)})),radio.parentNode.addEventListener("click",(()=>{radio.checked=!0,modal.setButtonDisabled("save",!1)})),radio.parentNode.addEventListener("dblclick",(dbClickEvent=>{submitFunction(radio)&&(dbClickEvent.preventDefault(),modal.destroy())}))})),modal.getRoot().on(_modal_events.default.save,(()=>{const radio=modalBody.querySelector("".concat(this.selectors.OPTIONSRADIO,":checked"));submitFunction(radio)}))}_setAddSectionLocked(locked){this.getElements(this.selectors.ADDSECTION).forEach((element=>{element.classList.toggle(this.classes.DISABLED,locked),this.setElementLocked(element,locked)}))}_disableLink(element){element&&(element.style.pointerEvents="none",element.style.userSelect="none",element.classList.add(this.classes.DISABLED),element.setAttribute("aria-disabled",!0),element.addEventListener("click",(event=>event.preventDefault())))}_modalBodyRenderedPromise(modalParams){return new Promise(((resolve,reject)=>{_modal_factory.default.create(modalParams).then((modal=>{modal.setRemoveOnClose(!0),modal.getRoot().on(_modal_events.default.bodyRendered,(()=>{resolve(modal)})),void 0!==modalParams.saveButtonText&&modal.setSaveButtonText(modalParams.saveButtonText),void 0!==modalParams.deleteButtonText&&modal.setDeleteButtonText(modalParams.saveButtonText),modal.show()})).catch((()=>{reject("Cannot load modal content")}))}))}_destroyModal(modal,element){modal.hide();const pendingDestroy=new _pending.default("courseformat/actions:destroyModal");element&&element.focus(),setTimeout((()=>{modal.destroy(),pendingDestroy.resolve()}),500)}_getClosestActionMenuToogler(element){const actionMenu=element.closest(this.selectors.ACTIONMENU);if(actionMenu)return actionMenu.querySelector(this.selectors.ACTIONMENUTOGGLER)}}return _exports.default=_default,_exports.default})); //# sourceMappingURL=actions.min.js.map \ No newline at end of file diff --git a/course/format/amd/build/local/content/actions.min.js.map b/course/format/amd/build/local/content/actions.min.js.map index 880a2e82357..45751691b44 100644 --- a/course/format/amd/build/local/content/actions.min.js.map +++ b/course/format/amd/build/local/content/actions.min.js.map @@ -1 +1 @@ -{"version":3,"file":"actions.min.js","sources":["../../../src/local/content/actions.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * Course state actions dispatcher.\n *\n * This module captures all data-dispatch links in the course content and dispatch the proper\n * state mutation, including any confirmation and modal required.\n *\n * @module core_courseformat/local/content/actions\n * @class core_courseformat/local/content/actions\n * @copyright 2021 Ferran Recio \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport {BaseComponent} from 'core/reactive';\nimport 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 {getFirst} from 'core/normalise';\nimport * as CourseEvents from 'core_course/events';\nimport Pending from 'core/pending';\nimport ContentTree from 'core_courseformat/local/courseeditor/contenttree';\n// The jQuery module is only used for interacting with Boostrap 4. It can we removed when MDL-71979 is integrated.\nimport jQuery from 'jquery';\n\n// Load global strings.\nprefetchStrings('core', ['movecoursesection', 'movecoursemodule', 'confirm', 'delete']);\n\n// Mutations are dispatched by the course content actions.\n// Formats can use this module addActions static method to add custom actions.\n// Direct mutations can be simple strings (mutation) name or functions.\nconst directMutations = {\n sectionHide: 'sectionHide',\n sectionShow: 'sectionShow',\n cmHide: 'cmHide',\n cmShow: 'cmShow',\n cmStealth: 'cmStealth',\n cmMoveRight: 'cmMoveRight',\n cmMoveLeft: 'cmMoveLeft',\n};\n\nexport default class extends BaseComponent {\n\n /**\n * Constructor hook.\n */\n create() {\n // Optional component name for debugging.\n this.name = 'content_actions';\n // Default query selectors.\n this.selectors = {\n ACTIONLINK: `[data-action]`,\n // Move modal selectors.\n SECTIONLINK: `[data-for='section']`,\n CMLINK: `[data-for='cm']`,\n SECTIONNODE: `[data-for='sectionnode']`,\n MODALTOGGLER: `[data-toggle='collapse']`,\n ADDSECTION: `[data-action='addSection']`,\n CONTENTTREE: `#destination-selector`,\n ACTIONMENU: `.action-menu`,\n ACTIONMENUTOGGLER: `[data-toggle=\"dropdown\"]`,\n // Availability modal selectors.\n OPTIONSRADIO: `[type='radio']`,\n };\n // Component css classes.\n this.classes = {\n DISABLED: `disabled`,\n };\n }\n\n /**\n * Add extra actions to the module.\n *\n * @param {array} actions array of methods to execute\n */\n static addActions(actions) {\n for (const [action, mutationReference] of Object.entries(actions)) {\n if (typeof mutationReference !== 'function' && typeof mutationReference !== 'string') {\n throw new Error(`${action} action must be a mutation name or a function`);\n }\n directMutations[action] = mutationReference;\n }\n }\n\n /**\n * Initial state ready method.\n *\n * @param {Object} state the state data.\n *\n */\n stateReady(state) {\n // Delegate dispatch clicks.\n this.addEventListener(\n this.element,\n 'click',\n this._dispatchClick\n );\n // Check section limit.\n this._checkSectionlist({state});\n // Add an Event listener to recalculate limits it if a section HTML is altered.\n this.addEventListener(\n this.element,\n CourseEvents.sectionRefreshed,\n () => this._checkSectionlist({state})\n );\n }\n\n /**\n * Return the component watchers.\n *\n * @returns {Array} of watchers\n */\n getWatchers() {\n return [\n // Check section limit.\n {watch: `course.sectionlist:updated`, handler: this._checkSectionlist},\n ];\n }\n\n _dispatchClick(event) {\n const target = event.target.closest(this.selectors.ACTIONLINK);\n if (!target) {\n return;\n }\n if (target.classList.contains(this.classes.DISABLED)) {\n event.preventDefault();\n return;\n }\n\n // Invoke proper method.\n const actionName = target.dataset.action;\n const methodName = this._actionMethodName(actionName);\n\n if (this[methodName] !== undefined) {\n this[methodName](target, event);\n return;\n }\n\n // Check direct mutations or mutations handlers.\n if (directMutations[actionName] !== undefined) {\n if (typeof directMutations[actionName] === 'function') {\n directMutations[actionName](target, event);\n return;\n }\n this._requestMutationAction(target, event, directMutations[actionName]);\n return;\n }\n }\n\n _actionMethodName(name) {\n const requestName = name.charAt(0).toUpperCase() + name.slice(1);\n return `_request${requestName}`;\n }\n\n /**\n * Check the section list and disable some options if needed.\n *\n * @param {Object} detail the update details.\n * @param {Object} detail.state the state object.\n */\n _checkSectionlist({state}) {\n // Disable \"add section\" actions if the course max sections has been exceeded.\n this._setAddSectionLocked(state.course.sectionlist.length > state.course.maxsections);\n }\n\n /**\n * Return the ids represented by this element.\n *\n * Depending on the dataset attributes the action could represent a single id\n * or a bulk actions with all the current selected ids.\n *\n * @param {HTMLElement} target\n * @returns {Number[]} array of Ids\n */\n _getTargetIds(target) {\n let ids = [];\n if (target?.dataset?.id) {\n ids.push(target.dataset.id);\n }\n const bulkType = target?.dataset?.bulk;\n if (!bulkType) {\n return ids;\n }\n const bulk = this.reactive.get('bulk');\n if (bulk.enabled && bulk.selectedType === bulkType) {\n ids = [...ids, ...bulk.selection];\n }\n return ids;\n }\n\n /**\n * Handle a move section request.\n *\n * @param {Element} target the dispatch action element\n * @param {Event} event the triggered event\n */\n async _requestMoveSection(target, event) {\n // Check we have an id.\n const sectionIds = this._getTargetIds(target);\n if (sectionIds.length == 0) {\n return;\n }\n\n event.preventDefault();\n\n // The section edit menu to refocus on end.\n const editTools = this._getClosestActionMenuToogler(target);\n\n // Collect section information from the state.\n const exporter = this.reactive.getExporter();\n const data = exporter.course(this.reactive.state);\n let titleText = null;\n\n // Add the target section id and title.\n let sectionInfo = null;\n if (sectionIds.length == 1) {\n sectionInfo = this.reactive.get('section', sectionIds[0]);\n data.sectionid = sectionInfo.id;\n data.sectiontitle = sectionInfo.title;\n data.information = await this.reactive.getFormatString('sectionmove_info', data.sectiontitle);\n titleText = this.reactive.getFormatString('sectionmove_title');\n } else {\n data.information = await this.reactive.getFormatString('sectionsmove_info', sectionIds.length);\n titleText = this.reactive.getFormatString('sectionsmove_title');\n }\n\n\n // Build the modal parameters from the event data.\n const modalParams = {\n title: titleText,\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 = getFirst(modal.getBody());\n\n // Disable current selected section ids.\n sectionIds.forEach(sectionId => {\n const currentElement = modalBody.querySelector(`${this.selectors.SECTIONLINK}[data-id='${sectionId}']`);\n this._disableLink(currentElement);\n });\n\n // Setup keyboard navigation.\n new ContentTree(\n modalBody.querySelector(this.selectors.CONTENTTREE),\n {\n SECTION: this.selectors.SECTIONNODE,\n TOGGLER: this.selectors.MODALTOGGLER,\n COLLAPSE: this.selectors.MODALTOGGLER,\n },\n true\n );\n\n // Capture click.\n modalBody.addEventListener('click', (event) => {\n const target = event.target;\n if (!target.matches('a') || target.dataset.for != 'section' || target.dataset.id === undefined) {\n return;\n }\n if (target.getAttribute('aria-disabled')) {\n return;\n }\n event.preventDefault();\n this.reactive.dispatch('sectionMoveAfter', sectionIds, target.dataset.id);\n this._destroyModal(modal, editTools);\n });\n }\n\n /**\n * Handle a move cm request.\n *\n * @param {Element} target the dispatch action element\n * @param {Event} event the triggered event\n */\n async _requestMoveCm(target, event) {\n // Check we have an id.\n const cmIds = this._getTargetIds(target);\n if (cmIds.length == 0) {\n return;\n }\n\n event.preventDefault();\n\n // The section edit menu to refocus on end.\n const editTools = this._getClosestActionMenuToogler(target);\n\n // Collect information from the state.\n const exporter = this.reactive.getExporter();\n const data = exporter.course(this.reactive.state);\n\n let titleText = null;\n if (cmIds.length == 1) {\n const cmInfo = this.reactive.get('cm', cmIds[0]);\n data.cmid = cmInfo.id;\n data.cmname = cmInfo.name;\n data.information = await this.reactive.getFormatString('cmmove_info', data.cmname);\n titleText = this.reactive.getFormatString('cmmove_title');\n } else {\n data.information = await this.reactive.getFormatString('cmsmove_info', cmIds.length);\n titleText = this.reactive.getFormatString('cmsmove_title');\n }\n\n // Build the modal parameters from the event data.\n const modalParams = {\n title: titleText,\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 = getFirst(modal.getBody());\n\n // Disable current selected section ids.\n cmIds.forEach(cmId => {\n const currentElement = modalBody.querySelector(`${this.selectors.CMLINK}[data-id='${cmId}']`);\n this._disableLink(currentElement);\n });\n\n // Setup keyboard navigation.\n new ContentTree(\n modalBody.querySelector(this.selectors.CONTENTTREE),\n {\n SECTION: this.selectors.SECTIONNODE,\n TOGGLER: this.selectors.MODALTOGGLER,\n COLLAPSE: this.selectors.MODALTOGGLER,\n ENTER: this.selectors.SECTIONLINK,\n }\n );\n\n // Open the cm section node if possible (Bootstrap 4 uses jQuery to interact with collapsibles).\n // All jQuery in this code can be replaced when MDL-71979 is integrated.\n cmIds.forEach(cmId => {\n const currentElement = modalBody.querySelector(`${this.selectors.CMLINK}[data-id='${cmId}']`);\n const sectionnode = currentElement.closest(this.selectors.SECTIONNODE);\n const toggler = jQuery(sectionnode).find(this.selectors.MODALTOGGLER);\n let collapsibleId = toggler.data('target') ?? toggler.attr('href');\n if (collapsibleId) {\n // We cannot be sure we have # in the id element name.\n collapsibleId = collapsibleId.replace('#', '');\n const expandNode = modalBody.querySelector(`#${collapsibleId}`);\n jQuery(expandNode).collapse('show');\n }\n });\n\n modalBody.addEventListener('click', (event) => {\n const target = event.target;\n if (!target.matches('a') || target.dataset.for === undefined || target.dataset.id === undefined) {\n return;\n }\n if (target.getAttribute('aria-disabled')) {\n return;\n }\n event.preventDefault();\n\n let targetSectionId;\n let targetCmId;\n 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 this.reactive.dispatch('cmMove', cmIds, targetSectionId, targetCmId);\n this._destroyModal(modal, editTools);\n });\n }\n\n /**\n * Handle a create section request.\n *\n * @param {Element} target the dispatch action element\n * @param {Event} event the triggered event\n */\n async _requestAddSection(target, event) {\n event.preventDefault();\n this.reactive.dispatch('addSection', target.dataset.id ?? 0);\n }\n\n /**\n * Handle a delete section request.\n *\n * @param {Element} target the dispatch action element\n * @param {Event} event the triggered event\n */\n async _requestDeleteSection(target, event) {\n const sectionIds = this._getTargetIds(target);\n if (sectionIds.length == 0) {\n return;\n }\n\n event.preventDefault();\n\n // We don't need confirmation to delete empty sections.\n let needsConfirmation = sectionIds.some(sectionId => {\n const sectionInfo = this.reactive.get('section', sectionId);\n const cmList = sectionInfo.cmlist ?? [];\n return (cmList.length || sectionInfo.hassummary || sectionInfo.rawtitle);\n });\n if (!needsConfirmation) {\n this.reactive.dispatch('sectionDelete', sectionIds);\n return;\n }\n\n let bodyText = null;\n let titleText = null;\n if (sectionIds.length == 1) {\n titleText = this.reactive.getFormatString('sectiondelete_title');\n const sectionInfo = this.reactive.get('section', sectionIds[0]);\n bodyText = this.reactive.getFormatString('sectiondelete_info', {name: sectionInfo.title});\n } else {\n titleText = this.reactive.getFormatString('sectionsdelete_title');\n bodyText = this.reactive.getFormatString('sectionsdelete_info', {count: sectionIds.length});\n }\n\n const modalParams = {\n title: titleText,\n body: bodyText,\n type: ModalFactory.types.DELETE_CANCEL,\n };\n\n const modal = await this._modalBodyRenderedPromise(modalParams);\n\n modal.getRoot().on(\n ModalEvents.delete,\n e => {\n // Stop the default save button behaviour which is to close the modal.\n e.preventDefault();\n modal.destroy();\n this.reactive.dispatch('sectionDelete', sectionIds);\n }\n );\n }\n\n /**\n * Handle a toggle cm selection.\n *\n * @param {Element} target the dispatch action element\n */\n async _requestToggleSelectionCm(target) {\n const cmId = target.dataset.id;\n if (!cmId) {\n return;\n }\n const value = target.checked ?? false;\n const mutation = (value) ? 'cmSelect' : 'cmUnselect';\n this.reactive.dispatch(mutation, [cmId]);\n }\n\n /**\n * Handle a toggle section selection.\n *\n * @param {Element} target the dispatch action element\n */\n async _requestToggleSelectionSection(target) {\n const sectionId = target.dataset.id;\n if (!sectionId) {\n return;\n }\n const value = target.checked ?? false;\n const mutation = (value) ? 'sectionSelect' : 'sectionUnselect';\n this.reactive.dispatch(mutation, [sectionId]);\n }\n\n /**\n * Basic mutation action helper.\n *\n * @param {Element} target the dispatch action element\n * @param {Event} event the triggered event\n * @param {string} mutationName the mutation name\n */\n async _requestMutationAction(target, event, mutationName) {\n if (!target.dataset.id) {\n return;\n }\n event.preventDefault();\n this.reactive.dispatch(mutationName, [target.dataset.id]);\n }\n\n /**\n * Handle a course module duplicate request.\n *\n * @param {Element} target the dispatch action element\n * @param {Event} event the triggered event\n */\n async _requestCmDuplicate(target, event) {\n const cmIds = this._getTargetIds(target);\n if (cmIds.length == 0) {\n return;\n }\n const sectionId = target.dataset.sectionid ?? null;\n event.preventDefault();\n this.reactive.dispatch('cmDuplicate', cmIds, sectionId);\n }\n\n /**\n * Handle a delete cm request.\n *\n * @param {Element} target the dispatch action element\n * @param {Event} event the triggered event\n */\n async _requestCmDelete(target, event) {\n const cmIds = this._getTargetIds(target);\n if (cmIds.length == 0) {\n return;\n }\n\n event.preventDefault();\n\n let bodyText = null;\n let titleText = null;\n if (cmIds.length == 1) {\n const cmInfo = this.reactive.get('cm', cmIds[0]);\n titleText = getString('cmdelete_title', 'core_courseformat');\n bodyText = getString(\n 'cmdelete_info',\n 'core_courseformat',\n {\n type: cmInfo.modname,\n name: cmInfo.name,\n }\n );\n } else {\n titleText = getString('cmsdelete_title', 'core_courseformat');\n bodyText = getString(\n 'cmsdelete_info',\n 'core_courseformat',\n {count: cmIds.length}\n );\n }\n\n const modalParams = {\n title: titleText,\n body: bodyText,\n type: ModalFactory.types.DELETE_CANCEL,\n };\n\n const modal = await this._modalBodyRenderedPromise(modalParams);\n\n modal.getRoot().on(\n ModalEvents.delete,\n e => {\n // Stop the default save button behaviour which is to close the modal.\n e.preventDefault();\n modal.destroy();\n this.reactive.dispatch('cmDelete', cmIds);\n }\n );\n }\n\n /**\n * Handle a cm availability change request.\n *\n * @param {Element} target the dispatch action element\n */\n async _requestCmAvailability(target) {\n const cmIds = this._getTargetIds(target);\n if (cmIds.length == 0) {\n return;\n }\n // Show the availability modal to decide which action to trigger.\n const exporter = this.reactive.getExporter();\n const data = {\n allowstealth: exporter.canUseStealth(this.reactive.state, cmIds),\n };\n const modalParams = {\n title: getString('availability', 'core'),\n body: Templates.render('core_courseformat/local/content/cm/availabilitymodal', data),\n saveButtonText: getString('apply', 'core'),\n type: ModalFactory.types.SAVE_CANCEL,\n };\n const modal = await this._modalBodyRenderedPromise(modalParams);\n\n this._setupMutationRadioButtonModal(modal, cmIds);\n }\n\n /**\n * Handle a section availability change request.\n *\n * @param {Element} target the dispatch action element\n */\n async _requestSectionAvailability(target) {\n const sectionIds = this._getTargetIds(target);\n if (sectionIds.length == 0) {\n return;\n }\n // Show the availability modal to decide which action to trigger.\n const modalParams = {\n title: getString('availability', 'core'),\n body: Templates.render('core_courseformat/local/content/section/availabilitymodal', []),\n saveButtonText: getString('apply', 'core'),\n type: ModalFactory.types.SAVE_CANCEL,\n };\n const modal = await this._modalBodyRenderedPromise(modalParams);\n\n this._setupMutationRadioButtonModal(modal, sectionIds);\n }\n\n /**\n * Add events to a mutation selector radio buttons modal.\n * @param {Modal} modal\n * @param {Number[]} ids the section or cm ids to apply the mutation\n */\n _setupMutationRadioButtonModal(modal, ids) {\n // The save button is not enabled until the user selects an option.\n modal.setButtonDisabled('save', true);\n\n const submitFunction = (radio) => {\n const mutation = radio?.value;\n if (!mutation) {\n return false;\n }\n this.reactive.dispatch(mutation, ids);\n return true;\n };\n\n const modalBody = getFirst(modal.getBody());\n const radioOptions = modalBody.querySelectorAll(this.selectors.OPTIONSRADIO);\n radioOptions.forEach(radio => {\n radio.addEventListener('change', () => {\n modal.setButtonDisabled('save', false);\n });\n radio.parentNode.addEventListener('click', () => {\n radio.checked = true;\n modal.setButtonDisabled('save', false);\n });\n radio.parentNode.addEventListener('dblclick', dbClickEvent => {\n if (submitFunction(radio)) {\n dbClickEvent.preventDefault();\n modal.destroy();\n }\n });\n });\n\n modal.getRoot().on(\n ModalEvents.save,\n () => {\n const radio = modalBody.querySelector(`${this.selectors.OPTIONSRADIO}:checked`);\n submitFunction(radio);\n }\n );\n }\n\n /**\n * Disable all add sections actions.\n *\n * @param {boolean} locked the new locked value.\n */\n _setAddSectionLocked(locked) {\n const targets = this.getElements(this.selectors.ADDSECTION);\n targets.forEach(element => {\n element.classList.toggle(this.classes.DISABLED, locked);\n this.setElementLocked(element, locked);\n });\n }\n\n /**\n * Replace an element with a copy with a different tag name.\n *\n * @param {Element} element the original element\n */\n _disableLink(element) {\n if (element) {\n element.style.pointerEvents = 'none';\n element.style.userSelect = 'none';\n element.classList.add(this.classes.DISABLED);\n element.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 modal.setRemoveOnClose(true);\n // Handle body loading event.\n modal.getRoot().on(ModalEvents.bodyRendered, () => {\n resolve(modal);\n });\n // Configure some extra modal params.\n if (modalParams.saveButtonText !== undefined) {\n modal.setSaveButtonText(modalParams.saveButtonText);\n }\n if (modalParams.deleteButtonText !== undefined) {\n modal.setDeleteButtonText(modalParams.saveButtonText);\n }\n modal.show();\n return;\n }).catch(() => {\n reject(`Cannot load modal content`);\n });\n });\n }\n\n /**\n * Hide and later destroy a modal.\n *\n * Behat will fail if we remove the modal while some boostrap collapse is executing.\n *\n * @param {Modal} modal\n * @param {HTMLElement} element the dom element to focus on.\n */\n _destroyModal(modal, element) {\n modal.hide();\n const pendingDestroy = new Pending(`courseformat/actions:destroyModal`);\n if (element) {\n element.focus();\n }\n setTimeout(() =>{\n modal.destroy();\n pendingDestroy.resolve();\n }, 500);\n }\n\n /**\n * Get the closest actions menu toggler to an action element.\n *\n * @param {HTMLElement} element the action link element\n * @returns {HTMLElement|undefined}\n */\n _getClosestActionMenuToogler(element) {\n const actionMenu = element.closest(this.selectors.ACTIONMENU);\n if (!actionMenu) {\n return undefined;\n }\n return actionMenu.querySelector(this.selectors.ACTIONMENUTOGGLER);\n }\n}\n"],"names":["directMutations","sectionHide","sectionShow","cmHide","cmShow","cmStealth","cmMoveRight","cmMoveLeft","BaseComponent","create","name","selectors","ACTIONLINK","SECTIONLINK","CMLINK","SECTIONNODE","MODALTOGGLER","ADDSECTION","CONTENTTREE","ACTIONMENU","ACTIONMENUTOGGLER","OPTIONSRADIO","classes","DISABLED","actions","action","mutationReference","Object","entries","Error","stateReady","state","addEventListener","this","element","_dispatchClick","_checkSectionlist","CourseEvents","sectionRefreshed","getWatchers","watch","handler","event","target","closest","classList","contains","preventDefault","actionName","dataset","methodName","_actionMethodName","undefined","_requestMutationAction","requestName","charAt","toUpperCase","slice","_setAddSectionLocked","course","sectionlist","length","maxsections","_getTargetIds","ids","_target$dataset","id","push","bulkType","_target$dataset2","bulk","reactive","get","enabled","selectedType","selection","sectionIds","editTools","_getClosestActionMenuToogler","data","getExporter","titleText","sectionInfo","sectionid","sectiontitle","title","information","getFormatString","modalParams","body","Templates","render","modal","_modalBodyRenderedPromise","modalBody","getBody","forEach","sectionId","currentElement","querySelector","_disableLink","ContentTree","SECTION","TOGGLER","COLLAPSE","matches","for","getAttribute","dispatch","_destroyModal","cmIds","exporter","cmInfo","cmid","cmname","cmId","ENTER","sectionnode","toggler","find","collapsibleId","attr","replace","expandNode","collapse","targetSectionId","targetCmId","dropData","cmDraggableData","nextcmid","section","cmlist","some","hassummary","rawtitle","bodyText","count","type","ModalFactory","types","DELETE_CANCEL","getRoot","on","ModalEvents","delete","e","destroy","mutation","checked","mutationName","modname","allowstealth","canUseStealth","saveButtonText","SAVE_CANCEL","_setupMutationRadioButtonModal","setButtonDisabled","submitFunction","radio","value","querySelectorAll","parentNode","dbClickEvent","save","locked","getElements","toggle","setElementLocked","style","pointerEvents","userSelect","add","setAttribute","Promise","resolve","reject","then","setRemoveOnClose","bodyRendered","setSaveButtonText","deleteButtonText","setDeleteButtonText","show","catch","hide","pendingDestroy","Pending","focus","setTimeout","actionMenu"],"mappings":"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"} \ No newline at end of file +{"version":3,"file":"actions.min.js","sources":["../../../src/local/content/actions.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * Course state actions dispatcher.\n *\n * This module captures all data-dispatch links in the course content and dispatch the proper\n * state mutation, including any confirmation and modal required.\n *\n * @module core_courseformat/local/content/actions\n * @class core_courseformat/local/content/actions\n * @copyright 2021 Ferran Recio \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport {BaseComponent} from 'core/reactive';\nimport 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 {getFirst} from 'core/normalise';\nimport * as CourseEvents from 'core_course/events';\nimport Pending from 'core/pending';\nimport ContentTree from 'core_courseformat/local/courseeditor/contenttree';\n// The jQuery module is only used for interacting with Boostrap 4. It can we removed when MDL-71979 is integrated.\nimport jQuery from 'jquery';\n\n// Load global strings.\nprefetchStrings('core', ['movecoursesection', 'movecoursemodule', 'confirm', 'delete']);\n\n// Mutations are dispatched by the course content actions.\n// Formats can use this module addActions static method to add custom actions.\n// Direct mutations can be simple strings (mutation) name or functions.\nconst directMutations = {\n sectionHide: 'sectionHide',\n sectionShow: 'sectionShow',\n cmHide: 'cmHide',\n cmShow: 'cmShow',\n cmStealth: 'cmStealth',\n cmMoveRight: 'cmMoveRight',\n cmMoveLeft: 'cmMoveLeft',\n};\n\nexport default class extends BaseComponent {\n\n /**\n * Constructor hook.\n */\n create() {\n // Optional component name for debugging.\n this.name = 'content_actions';\n // Default query selectors.\n this.selectors = {\n ACTIONLINK: `[data-action]`,\n // Move modal selectors.\n SECTIONLINK: `[data-for='section']`,\n CMLINK: `[data-for='cm']`,\n SECTIONNODE: `[data-for='sectionnode']`,\n MODALTOGGLER: `[data-toggle='collapse']`,\n ADDSECTION: `[data-action='addSection']`,\n CONTENTTREE: `#destination-selector`,\n ACTIONMENU: `.action-menu`,\n ACTIONMENUTOGGLER: `[data-toggle=\"dropdown\"]`,\n // Availability modal selectors.\n OPTIONSRADIO: `[type='radio']`,\n };\n // Component css classes.\n this.classes = {\n DISABLED: `disabled`,\n };\n }\n\n /**\n * Add extra actions to the module.\n *\n * @param {array} actions array of methods to execute\n */\n static addActions(actions) {\n for (const [action, mutationReference] of Object.entries(actions)) {\n if (typeof mutationReference !== 'function' && typeof mutationReference !== 'string') {\n throw new Error(`${action} action must be a mutation name or a function`);\n }\n directMutations[action] = mutationReference;\n }\n }\n\n /**\n * Initial state ready method.\n *\n * @param {Object} state the state data.\n *\n */\n stateReady(state) {\n // Delegate dispatch clicks.\n this.addEventListener(\n this.element,\n 'click',\n this._dispatchClick\n );\n // Check section limit.\n this._checkSectionlist({state});\n // Add an Event listener to recalculate limits it if a section HTML is altered.\n this.addEventListener(\n this.element,\n CourseEvents.sectionRefreshed,\n () => this._checkSectionlist({state})\n );\n }\n\n /**\n * Return the component watchers.\n *\n * @returns {Array} of watchers\n */\n getWatchers() {\n return [\n // Check section limit.\n {watch: `course.sectionlist:updated`, handler: this._checkSectionlist},\n ];\n }\n\n _dispatchClick(event) {\n const target = event.target.closest(this.selectors.ACTIONLINK);\n if (!target) {\n return;\n }\n if (target.classList.contains(this.classes.DISABLED)) {\n event.preventDefault();\n return;\n }\n\n // Invoke proper method.\n const actionName = target.dataset.action;\n const methodName = this._actionMethodName(actionName);\n\n if (this[methodName] !== undefined) {\n this[methodName](target, event);\n return;\n }\n\n // Check direct mutations or mutations handlers.\n if (directMutations[actionName] !== undefined) {\n if (typeof directMutations[actionName] === 'function') {\n directMutations[actionName](target, event);\n return;\n }\n this._requestMutationAction(target, event, directMutations[actionName]);\n return;\n }\n }\n\n _actionMethodName(name) {\n const requestName = name.charAt(0).toUpperCase() + name.slice(1);\n return `_request${requestName}`;\n }\n\n /**\n * Check the section list and disable some options if needed.\n *\n * @param {Object} detail the update details.\n * @param {Object} detail.state the state object.\n */\n _checkSectionlist({state}) {\n // Disable \"add section\" actions if the course max sections has been exceeded.\n this._setAddSectionLocked(state.course.sectionlist.length > state.course.maxsections);\n }\n\n /**\n * Return the ids represented by this element.\n *\n * Depending on the dataset attributes the action could represent a single id\n * or a bulk actions with all the current selected ids.\n *\n * @param {HTMLElement} target\n * @returns {Number[]} array of Ids\n */\n _getTargetIds(target) {\n let ids = [];\n if (target?.dataset?.id) {\n ids.push(target.dataset.id);\n }\n const bulkType = target?.dataset?.bulk;\n if (!bulkType) {\n return ids;\n }\n const bulk = this.reactive.get('bulk');\n if (bulk.enabled && bulk.selectedType === bulkType) {\n ids = [...ids, ...bulk.selection];\n }\n return ids;\n }\n\n /**\n * Handle a move section request.\n *\n * @param {Element} target the dispatch action element\n * @param {Event} event the triggered event\n */\n async _requestMoveSection(target, event) {\n // Check we have an id.\n const sectionIds = this._getTargetIds(target);\n if (sectionIds.length == 0) {\n return;\n }\n\n event.preventDefault();\n\n // The section edit menu to refocus on end.\n const editTools = this._getClosestActionMenuToogler(target);\n\n // Collect section information from the state.\n const exporter = this.reactive.getExporter();\n const data = exporter.course(this.reactive.state);\n let titleText = null;\n\n // Add the target section id and title.\n let sectionInfo = null;\n if (sectionIds.length == 1) {\n sectionInfo = this.reactive.get('section', sectionIds[0]);\n data.sectionid = sectionInfo.id;\n data.sectiontitle = sectionInfo.title;\n data.information = await this.reactive.getFormatString('sectionmove_info', data.sectiontitle);\n titleText = this.reactive.getFormatString('sectionmove_title');\n } else {\n data.information = await this.reactive.getFormatString('sectionsmove_info', sectionIds.length);\n titleText = this.reactive.getFormatString('sectionsmove_title');\n }\n\n\n // Build the modal parameters from the event data.\n const modalParams = {\n title: titleText,\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 = getFirst(modal.getBody());\n\n // Disable current selected section ids.\n sectionIds.forEach(sectionId => {\n const currentElement = modalBody.querySelector(`${this.selectors.SECTIONLINK}[data-id='${sectionId}']`);\n this._disableLink(currentElement);\n });\n\n // Setup keyboard navigation.\n new ContentTree(\n modalBody.querySelector(this.selectors.CONTENTTREE),\n {\n SECTION: this.selectors.SECTIONNODE,\n TOGGLER: this.selectors.MODALTOGGLER,\n COLLAPSE: this.selectors.MODALTOGGLER,\n },\n true\n );\n\n // Capture click.\n modalBody.addEventListener('click', (event) => {\n const target = event.target;\n if (!target.matches('a') || target.dataset.for != 'section' || target.dataset.id === undefined) {\n return;\n }\n if (target.getAttribute('aria-disabled')) {\n return;\n }\n event.preventDefault();\n this.reactive.dispatch('sectionMoveAfter', sectionIds, target.dataset.id);\n this._destroyModal(modal, editTools);\n });\n }\n\n /**\n * Handle a move cm request.\n *\n * @param {Element} target the dispatch action element\n * @param {Event} event the triggered event\n */\n async _requestMoveCm(target, event) {\n // Check we have an id.\n const cmIds = this._getTargetIds(target);\n if (cmIds.length == 0) {\n return;\n }\n\n event.preventDefault();\n\n // The section edit menu to refocus on end.\n const editTools = this._getClosestActionMenuToogler(target);\n\n // Collect information from the state.\n const exporter = this.reactive.getExporter();\n const data = exporter.course(this.reactive.state);\n\n let titleText = null;\n if (cmIds.length == 1) {\n const cmInfo = this.reactive.get('cm', cmIds[0]);\n data.cmid = cmInfo.id;\n data.cmname = cmInfo.name;\n data.information = await this.reactive.getFormatString('cmmove_info', data.cmname);\n titleText = this.reactive.getFormatString('cmmove_title');\n } else {\n data.information = await this.reactive.getFormatString('cmsmove_info', cmIds.length);\n titleText = this.reactive.getFormatString('cmsmove_title');\n }\n\n // Build the modal parameters from the event data.\n const modalParams = {\n title: titleText,\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 = getFirst(modal.getBody());\n\n // Disable current selected section ids.\n cmIds.forEach(cmId => {\n const currentElement = modalBody.querySelector(`${this.selectors.CMLINK}[data-id='${cmId}']`);\n this._disableLink(currentElement);\n });\n\n // Setup keyboard navigation.\n new ContentTree(\n modalBody.querySelector(this.selectors.CONTENTTREE),\n {\n SECTION: this.selectors.SECTIONNODE,\n TOGGLER: this.selectors.MODALTOGGLER,\n COLLAPSE: this.selectors.MODALTOGGLER,\n ENTER: this.selectors.SECTIONLINK,\n }\n );\n\n // Open the cm section node if possible (Bootstrap 4 uses jQuery to interact with collapsibles).\n // All jQuery in this code can be replaced when MDL-71979 is integrated.\n cmIds.forEach(cmId => {\n const currentElement = modalBody.querySelector(`${this.selectors.CMLINK}[data-id='${cmId}']`);\n const sectionnode = currentElement.closest(this.selectors.SECTIONNODE);\n const toggler = jQuery(sectionnode).find(this.selectors.MODALTOGGLER);\n let collapsibleId = toggler.data('target') ?? toggler.attr('href');\n if (collapsibleId) {\n // We cannot be sure we have # in the id element name.\n collapsibleId = collapsibleId.replace('#', '');\n const expandNode = modalBody.querySelector(`#${collapsibleId}`);\n jQuery(expandNode).collapse('show');\n }\n });\n\n modalBody.addEventListener('click', (event) => {\n const target = event.target;\n if (!target.matches('a') || target.dataset.for === undefined || target.dataset.id === undefined) {\n return;\n }\n if (target.getAttribute('aria-disabled')) {\n return;\n }\n event.preventDefault();\n\n let targetSectionId;\n let targetCmId;\n 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 this.reactive.dispatch('cmMove', cmIds, targetSectionId, targetCmId);\n this._destroyModal(modal, editTools);\n });\n }\n\n /**\n * Handle a create section request.\n *\n * @param {Element} target the dispatch action element\n * @param {Event} event the triggered event\n */\n async _requestAddSection(target, event) {\n event.preventDefault();\n this.reactive.dispatch('addSection', target.dataset.id ?? 0);\n }\n\n /**\n * Handle a delete section request.\n *\n * @param {Element} target the dispatch action element\n * @param {Event} event the triggered event\n */\n async _requestDeleteSection(target, event) {\n const sectionIds = this._getTargetIds(target);\n if (sectionIds.length == 0) {\n return;\n }\n\n event.preventDefault();\n\n // We don't need confirmation to delete empty sections.\n let needsConfirmation = sectionIds.some(sectionId => {\n const sectionInfo = this.reactive.get('section', sectionId);\n const cmList = sectionInfo.cmlist ?? [];\n return (cmList.length || sectionInfo.hassummary || sectionInfo.rawtitle);\n });\n if (!needsConfirmation) {\n this.reactive.dispatch('sectionDelete', sectionIds);\n return;\n }\n\n let bodyText = null;\n let titleText = null;\n if (sectionIds.length == 1) {\n titleText = this.reactive.getFormatString('sectiondelete_title');\n const sectionInfo = this.reactive.get('section', sectionIds[0]);\n bodyText = this.reactive.getFormatString('sectiondelete_info', {name: sectionInfo.title});\n } else {\n titleText = this.reactive.getFormatString('sectionsdelete_title');\n bodyText = this.reactive.getFormatString('sectionsdelete_info', {count: sectionIds.length});\n }\n\n const modalParams = {\n title: titleText,\n body: bodyText,\n type: ModalFactory.types.DELETE_CANCEL,\n };\n\n const modal = await this._modalBodyRenderedPromise(modalParams);\n\n modal.getRoot().on(\n ModalEvents.delete,\n e => {\n // Stop the default save button behaviour which is to close the modal.\n e.preventDefault();\n modal.destroy();\n this.reactive.dispatch('sectionDelete', sectionIds);\n }\n );\n }\n\n /**\n * Handle a toggle cm selection.\n *\n * @param {Element} target the dispatch action element\n */\n async _requestToggleSelectionCm(target) {\n const cmId = target.dataset.id;\n if (!cmId) {\n return;\n }\n const value = target.checked ?? false;\n const mutation = (value) ? 'cmSelect' : 'cmUnselect';\n this.reactive.dispatch(mutation, [cmId]);\n }\n\n /**\n * Handle a toggle section selection.\n *\n * @param {Element} target the dispatch action element\n */\n async _requestToggleSelectionSection(target) {\n const sectionId = target.dataset.id;\n if (!sectionId) {\n return;\n }\n const value = target.checked ?? false;\n const mutation = (value) ? 'sectionSelect' : 'sectionUnselect';\n this.reactive.dispatch(mutation, [sectionId]);\n }\n\n /**\n * Basic mutation action helper.\n *\n * @param {Element} target the dispatch action element\n * @param {Event} event the triggered event\n * @param {string} mutationName the mutation name\n */\n async _requestMutationAction(target, event, mutationName) {\n if (!target.dataset.id) {\n return;\n }\n event.preventDefault();\n this.reactive.dispatch(mutationName, [target.dataset.id]);\n }\n\n /**\n * Handle a course module duplicate request.\n *\n * @param {Element} target the dispatch action element\n * @param {Event} event the triggered event\n */\n async _requestCmDuplicate(target, event) {\n const cmIds = this._getTargetIds(target);\n if (cmIds.length == 0) {\n return;\n }\n const sectionId = target.dataset.sectionid ?? null;\n event.preventDefault();\n this.reactive.dispatch('cmDuplicate', cmIds, sectionId);\n }\n\n /**\n * Handle a delete cm request.\n *\n * @param {Element} target the dispatch action element\n * @param {Event} event the triggered event\n */\n async _requestCmDelete(target, event) {\n const cmIds = this._getTargetIds(target);\n if (cmIds.length == 0) {\n return;\n }\n\n event.preventDefault();\n\n let bodyText = null;\n let titleText = null;\n if (cmIds.length == 1) {\n const cmInfo = this.reactive.get('cm', cmIds[0]);\n titleText = getString('cmdelete_title', 'core_courseformat');\n bodyText = getString(\n 'cmdelete_info',\n 'core_courseformat',\n {\n type: cmInfo.modname,\n name: cmInfo.name,\n }\n );\n } else {\n titleText = getString('cmsdelete_title', 'core_courseformat');\n bodyText = getString(\n 'cmsdelete_info',\n 'core_courseformat',\n {count: cmIds.length}\n );\n }\n\n const modalParams = {\n title: titleText,\n body: bodyText,\n type: ModalFactory.types.DELETE_CANCEL,\n };\n\n const modal = await this._modalBodyRenderedPromise(modalParams);\n\n modal.getRoot().on(\n ModalEvents.delete,\n e => {\n // Stop the default save button behaviour which is to close the modal.\n e.preventDefault();\n modal.destroy();\n this.reactive.dispatch('cmDelete', cmIds);\n }\n );\n }\n\n /**\n * Handle a cm availability change request.\n *\n * @param {Element} target the dispatch action element\n */\n async _requestCmAvailability(target) {\n const cmIds = this._getTargetIds(target);\n if (cmIds.length == 0) {\n return;\n }\n // Show the availability modal to decide which action to trigger.\n const exporter = this.reactive.getExporter();\n const data = {\n allowstealth: exporter.canUseStealth(this.reactive.state, cmIds),\n };\n const modalParams = {\n title: getString('availability', 'core'),\n body: Templates.render('core_courseformat/local/content/cm/availabilitymodal', data),\n saveButtonText: getString('apply', 'core'),\n type: ModalFactory.types.SAVE_CANCEL,\n };\n const modal = await this._modalBodyRenderedPromise(modalParams);\n\n this._setupMutationRadioButtonModal(modal, cmIds);\n }\n\n /**\n * Handle a section availability change request.\n *\n * @param {Element} target the dispatch action element\n */\n async _requestSectionAvailability(target) {\n const sectionIds = this._getTargetIds(target);\n if (sectionIds.length == 0) {\n return;\n }\n const title = (sectionIds.length == 1) ? 'sectionavailability_title' : 'sectionsavailability_title';\n // Show the availability modal to decide which action to trigger.\n const modalParams = {\n title: this.reactive.getFormatString(title),\n body: Templates.render('core_courseformat/local/content/section/availabilitymodal', []),\n saveButtonText: getString('apply', 'core'),\n type: ModalFactory.types.SAVE_CANCEL,\n };\n const modal = await this._modalBodyRenderedPromise(modalParams);\n\n this._setupMutationRadioButtonModal(modal, sectionIds);\n }\n\n /**\n * Add events to a mutation selector radio buttons modal.\n * @param {Modal} modal\n * @param {Number[]} ids the section or cm ids to apply the mutation\n */\n _setupMutationRadioButtonModal(modal, ids) {\n // The save button is not enabled until the user selects an option.\n modal.setButtonDisabled('save', true);\n\n const submitFunction = (radio) => {\n const mutation = radio?.value;\n if (!mutation) {\n return false;\n }\n this.reactive.dispatch(mutation, ids);\n return true;\n };\n\n const modalBody = getFirst(modal.getBody());\n const radioOptions = modalBody.querySelectorAll(this.selectors.OPTIONSRADIO);\n radioOptions.forEach(radio => {\n radio.addEventListener('change', () => {\n modal.setButtonDisabled('save', false);\n });\n radio.parentNode.addEventListener('click', () => {\n radio.checked = true;\n modal.setButtonDisabled('save', false);\n });\n radio.parentNode.addEventListener('dblclick', dbClickEvent => {\n if (submitFunction(radio)) {\n dbClickEvent.preventDefault();\n modal.destroy();\n }\n });\n });\n\n modal.getRoot().on(\n ModalEvents.save,\n () => {\n const radio = modalBody.querySelector(`${this.selectors.OPTIONSRADIO}:checked`);\n submitFunction(radio);\n }\n );\n }\n\n /**\n * Disable all add sections actions.\n *\n * @param {boolean} locked the new locked value.\n */\n _setAddSectionLocked(locked) {\n const targets = this.getElements(this.selectors.ADDSECTION);\n targets.forEach(element => {\n element.classList.toggle(this.classes.DISABLED, locked);\n this.setElementLocked(element, locked);\n });\n }\n\n /**\n * Replace an element with a copy with a different tag name.\n *\n * @param {Element} element the original element\n */\n _disableLink(element) {\n if (element) {\n element.style.pointerEvents = 'none';\n element.style.userSelect = 'none';\n element.classList.add(this.classes.DISABLED);\n element.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 modal.setRemoveOnClose(true);\n // Handle body loading event.\n modal.getRoot().on(ModalEvents.bodyRendered, () => {\n resolve(modal);\n });\n // Configure some extra modal params.\n if (modalParams.saveButtonText !== undefined) {\n modal.setSaveButtonText(modalParams.saveButtonText);\n }\n if (modalParams.deleteButtonText !== undefined) {\n modal.setDeleteButtonText(modalParams.saveButtonText);\n }\n modal.show();\n return;\n }).catch(() => {\n reject(`Cannot load modal content`);\n });\n });\n }\n\n /**\n * Hide and later destroy a modal.\n *\n * Behat will fail if we remove the modal while some boostrap collapse is executing.\n *\n * @param {Modal} modal\n * @param {HTMLElement} element the dom element to focus on.\n */\n _destroyModal(modal, element) {\n modal.hide();\n const pendingDestroy = new Pending(`courseformat/actions:destroyModal`);\n if (element) {\n element.focus();\n }\n setTimeout(() =>{\n modal.destroy();\n pendingDestroy.resolve();\n }, 500);\n }\n\n /**\n * Get the closest actions menu toggler to an action element.\n *\n * @param {HTMLElement} element the action link element\n * @returns {HTMLElement|undefined}\n */\n _getClosestActionMenuToogler(element) {\n const actionMenu = element.closest(this.selectors.ACTIONMENU);\n if (!actionMenu) {\n return undefined;\n }\n return actionMenu.querySelector(this.selectors.ACTIONMENUTOGGLER);\n }\n}\n"],"names":["directMutations","sectionHide","sectionShow","cmHide","cmShow","cmStealth","cmMoveRight","cmMoveLeft","BaseComponent","create","name","selectors","ACTIONLINK","SECTIONLINK","CMLINK","SECTIONNODE","MODALTOGGLER","ADDSECTION","CONTENTTREE","ACTIONMENU","ACTIONMENUTOGGLER","OPTIONSRADIO","classes","DISABLED","actions","action","mutationReference","Object","entries","Error","stateReady","state","addEventListener","this","element","_dispatchClick","_checkSectionlist","CourseEvents","sectionRefreshed","getWatchers","watch","handler","event","target","closest","classList","contains","preventDefault","actionName","dataset","methodName","_actionMethodName","undefined","_requestMutationAction","requestName","charAt","toUpperCase","slice","_setAddSectionLocked","course","sectionlist","length","maxsections","_getTargetIds","ids","_target$dataset","id","push","bulkType","_target$dataset2","bulk","reactive","get","enabled","selectedType","selection","sectionIds","editTools","_getClosestActionMenuToogler","data","getExporter","titleText","sectionInfo","sectionid","sectiontitle","title","information","getFormatString","modalParams","body","Templates","render","modal","_modalBodyRenderedPromise","modalBody","getBody","forEach","sectionId","currentElement","querySelector","_disableLink","ContentTree","SECTION","TOGGLER","COLLAPSE","matches","for","getAttribute","dispatch","_destroyModal","cmIds","exporter","cmInfo","cmid","cmname","cmId","ENTER","sectionnode","toggler","find","collapsibleId","attr","replace","expandNode","collapse","targetSectionId","targetCmId","dropData","cmDraggableData","nextcmid","section","cmlist","some","hassummary","rawtitle","bodyText","count","type","ModalFactory","types","DELETE_CANCEL","getRoot","on","ModalEvents","delete","e","destroy","mutation","checked","mutationName","modname","allowstealth","canUseStealth","saveButtonText","SAVE_CANCEL","_setupMutationRadioButtonModal","setButtonDisabled","submitFunction","radio","value","querySelectorAll","parentNode","dbClickEvent","save","locked","getElements","toggle","setElementLocked","style","pointerEvents","userSelect","add","setAttribute","Promise","resolve","reject","then","setRemoveOnClose","bodyRendered","setSaveButtonText","deleteButtonText","setDeleteButtonText","show","catch","hide","pendingDestroy","Pending","focus","setTimeout","actionMenu"],"mappings":"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"} \ No newline at end of file diff --git a/course/format/amd/build/local/content/bulkedittools.min.js b/course/format/amd/build/local/content/bulkedittools.min.js index 8c1323f39d8..e05e6654c7a 100644 --- a/course/format/amd/build/local/content/bulkedittools.min.js +++ b/course/format/amd/build/local/content/bulkedittools.min.js @@ -6,6 +6,6 @@ define("core_courseformat/local/content/bulkedittools",["exports","core/reactive * @class core_courseformat/local/content/bulkedittools * @copyright 2023 Ferran Recio * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.default=void 0,_pending=(obj=_pending)&&obj.__esModule?obj:{default:obj},(0,_prefetch.prefetchStrings)("core_courseformat",["bulkselection"]);class Component extends _reactive.BaseComponent{create(){this.name="bulk_editor_tools",this.selectors={ACTIONS:'[data-for="bulkaction"]',ACTIONTOOL:'[data-for="bulkactions"] li',CANCEL:'[data-for="bulkcancel"]',COUNT:"[data-for='bulkcount']",SELECTABLE:"[data-bulkcheckbox][data-is-selectable]",SELECTALL:'[data-for="selectall"]',BULKBTN:'[data-for="enableBulk"]'},this.classes={HIDE:"d-none",DISABLED:"disabled"}}static init(target,selectors){return new this({element:document.querySelector(target),reactive:(0,_courseeditor.getCurrentCourseEditor)(),selectors:selectors})}stateReady(){const cancelBtn=this.getElement(this.selectors.CANCEL);cancelBtn&&this.addEventListener(cancelBtn,"click",this._cancelBulk);const selectAll=this.getElement(this.selectors.SELECTALL);selectAll&&this.addEventListener(selectAll,"change",this._selectAllClick)}getWatchers(){return[{watch:"bulk.enabled:updated",handler:this._refreshEnabled},{watch:"bulk:updated",handler:this._refreshTools}]}_refreshEnabled(_ref){let{element:element}=_ref;element.enabled?(0,_stickyFooter.enableStickyFooter)():(0,_stickyFooter.disableStickyFooter)()}_refreshTools(param){this._refreshSelectCount(param),this._refreshSelectAll(param),this._refreshActions(param)}async _refreshSelectCount(_ref2){let{element:bulk}=_ref2;const selectedCount=await(0,_str.get_string)("bulkselection","core_courseformat",bulk.selection.length),selectedElement=this.getElement(this.selectors.COUNT);selectedElement&&(selectedElement.innerHTML=selectedCount)}_refreshSelectAll(_ref3){let{element:bulk}=_ref3;const selectall=this.getElement(this.selectors.SELECTALL);if(!selectall)return;if(""===bulk.selectedType)return selectall.checked=!1,void(selectall.disabled=!0);selectall.disabled=!1;const maxSelection=document.querySelectorAll(this.selectors.SELECTABLE).length;selectall.checked=bulk.selection.length==maxSelection}_refreshActions(_ref4){let{element:bulk}=_ref4;const displayType="section"==bulk.selectedType?"section":"cm",enabled=""!==bulk.selectedType;this.getElements(this.selectors.ACTIONS).forEach((action=>{action.classList.toggle(this.classes.DISABLED,!enabled);const actionTool=action.closest(this.selectors.ACTIONTOOL),isHidden=action.dataset.bulk!=displayType;null==actionTool||actionTool.classList.toggle(this.classes.HIDE,isHidden)}))}_cancelBulk(){const pending=new _pending.default("courseformat/content:bulktoggle_off");this.reactive.dispatch("bulkEnable",!1),setTimeout((()=>{var _document$querySelect;null===(_document$querySelect=document.querySelector(this.selectors.BULKBTN))||void 0===_document$querySelect||_document$querySelect.focus(),pending.resolve()}),150)}_selectAllClick(event){const target=event.target,bulk=this.reactive.get("bulk");""!==bulk.selectedType&&(target.checked?this._handleSelectAll(bulk):this._handleUnselectAll())}_handleUnselectAll(){const pending=new _pending.default("courseformat/content:bulktUnselectAll");this.reactive.dispatch("bulkEnable",!0),setTimeout((()=>{var _document$querySelect2;null===(_document$querySelect2=document.querySelector(this.selectors.SELECTABLE))||void 0===_document$querySelect2||_document$querySelect2.focus(),pending.resolve()}),150)}_handleSelectAll(bulk){const selectableIds=[],selectables=document.querySelectorAll(this.selectors.SELECTABLE);if(0==selectables.length)return;selectables.forEach((selectable=>{selectableIds.push(selectable.dataset.id)}));const mutation="cm"===bulk.selectedType?"cmSelect":"sectionSelect";this.reactive.dispatch(mutation,selectableIds)}}return _exports.default=Component,_exports.default})); + */Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.default=void 0,_pending=(obj=_pending)&&obj.__esModule?obj:{default:obj},(0,_prefetch.prefetchStrings)("core_courseformat",["bulkselection"]);class Component extends _reactive.BaseComponent{create(){this.name="bulk_editor_tools",this.selectors={ACTIONS:'[data-for="bulkaction"]',ACTIONTOOL:'[data-for="bulkactions"] li',CANCEL:'[data-for="bulkcancel"]',COUNT:"[data-for='bulkcount']",SELECTABLE:"[data-bulkcheckbox][data-is-selectable]",SELECTALL:'[data-for="selectall"]',BULKBTN:'[data-for="enableBulk"]'},this.classes={HIDE:"d-none",DISABLED:"disabled"}}static init(target,selectors){return new this({element:document.querySelector(target),reactive:(0,_courseeditor.getCurrentCourseEditor)(),selectors:selectors})}stateReady(){const cancelBtn=this.getElement(this.selectors.CANCEL);cancelBtn&&this.addEventListener(cancelBtn,"click",this._cancelBulk);const selectAll=this.getElement(this.selectors.SELECTALL);selectAll&&this.addEventListener(selectAll,"change",this._selectAllClick)}getWatchers(){return[{watch:"bulk.enabled:updated",handler:this._refreshEnabled},{watch:"bulk:updated",handler:this._refreshTools}]}_refreshEnabled(_ref){let{element:element}=_ref;element.enabled?(0,_stickyFooter.enableStickyFooter)():(0,_stickyFooter.disableStickyFooter)()}_refreshTools(param){this._refreshSelectCount(param),this._refreshSelectAll(param),this._refreshActions(param)}async _refreshSelectCount(_ref2){let{element:bulk}=_ref2;const stringName=bulk.selection.length>1?"bulkselection_plural":"bulkselection",selectedCount=await(0,_str.get_string)(stringName,"core_courseformat",bulk.selection.length),selectedElement=this.getElement(this.selectors.COUNT);selectedElement&&(selectedElement.innerHTML=selectedCount)}_refreshSelectAll(_ref3){let{element:bulk}=_ref3;const selectall=this.getElement(this.selectors.SELECTALL);if(!selectall)return;if(""===bulk.selectedType)return selectall.checked=!1,void(selectall.disabled=!0);selectall.disabled=!1;const maxSelection=document.querySelectorAll(this.selectors.SELECTABLE).length;selectall.checked=bulk.selection.length==maxSelection}_refreshActions(_ref4){let{element:bulk}=_ref4;const displayType="section"==bulk.selectedType?"section":"cm",enabled=""!==bulk.selectedType;this.getElements(this.selectors.ACTIONS).forEach((action=>{action.classList.toggle(this.classes.DISABLED,!enabled),action.tabIndex=enabled?0:-1;const actionTool=action.closest(this.selectors.ACTIONTOOL),isHidden=action.dataset.bulk!=displayType;null==actionTool||actionTool.classList.toggle(this.classes.HIDE,isHidden)}))}_cancelBulk(){const pending=new _pending.default("courseformat/content:bulktoggle_off");this.reactive.dispatch("bulkEnable",!1),setTimeout((()=>{var _document$querySelect;null===(_document$querySelect=document.querySelector(this.selectors.BULKBTN))||void 0===_document$querySelect||_document$querySelect.focus(),pending.resolve()}),150)}_selectAllClick(event){const target=event.target,bulk=this.reactive.get("bulk");""!==bulk.selectedType&&(target.checked?this._handleSelectAll(bulk):this._handleUnselectAll())}_handleUnselectAll(){const pending=new _pending.default("courseformat/content:bulktUnselectAll");this.reactive.dispatch("bulkEnable",!0),setTimeout((()=>{var _document$querySelect2;null===(_document$querySelect2=document.querySelector(this.selectors.SELECTABLE))||void 0===_document$querySelect2||_document$querySelect2.focus(),pending.resolve()}),150)}_handleSelectAll(bulk){const selectableIds=[],selectables=document.querySelectorAll(this.selectors.SELECTABLE);if(0==selectables.length)return;selectables.forEach((selectable=>{selectableIds.push(selectable.dataset.id)}));const mutation="cm"===bulk.selectedType?"cmSelect":"sectionSelect";this.reactive.dispatch(mutation,selectableIds)}}return _exports.default=Component,_exports.default})); //# sourceMappingURL=bulkedittools.min.js.map \ No newline at end of file diff --git a/course/format/amd/build/local/content/bulkedittools.min.js.map b/course/format/amd/build/local/content/bulkedittools.min.js.map index 35b2b824b8e..fc9b7d1c986 100644 --- a/course/format/amd/build/local/content/bulkedittools.min.js.map +++ b/course/format/amd/build/local/content/bulkedittools.min.js.map @@ -1 +1 @@ -{"version":3,"file":"bulkedittools.min.js","sources":["../../../src/local/content/bulkedittools.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * The bulk editor tools bar.\n *\n * @module core_courseformat/local/content/bulkedittools\n * @class core_courseformat/local/content/bulkedittools\n * @copyright 2023 Ferran Recio \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport {BaseComponent} from 'core/reactive';\nimport {disableStickyFooter, enableStickyFooter} from 'core/sticky-footer';\nimport {getCurrentCourseEditor} from 'core_courseformat/courseeditor';\nimport {get_string as getString} from 'core/str';\nimport Pending from 'core/pending';\nimport {prefetchStrings} from 'core/prefetch';\n\n// Load global strings.\nprefetchStrings(\n 'core_courseformat',\n ['bulkselection']\n);\n\nexport default class Component extends BaseComponent {\n\n /**\n * Constructor hook.\n */\n create() {\n // Optional component name for debugging.\n this.name = 'bulk_editor_tools';\n // Default query selectors.\n this.selectors = {\n ACTIONS: `[data-for=\"bulkaction\"]`,\n ACTIONTOOL: `[data-for=\"bulkactions\"] li`,\n CANCEL: `[data-for=\"bulkcancel\"]`,\n COUNT: `[data-for='bulkcount']`,\n SELECTABLE: `[data-bulkcheckbox][data-is-selectable]`,\n SELECTALL: `[data-for=\"selectall\"]`,\n BULKBTN: `[data-for=\"enableBulk\"]`,\n };\n // Most classes will be loaded later by DndCmItem.\n this.classes = {\n HIDE: 'd-none',\n DISABLED: 'disabled',\n };\n }\n\n /**\n * Static method to create a component instance from the mustache template.\n *\n * @param {string} target optional altentative DOM main element CSS selector\n * @param {object} selectors optional css selector overrides\n * @return {Component}\n */\n static init(target, selectors) {\n return new this({\n element: document.querySelector(target),\n reactive: getCurrentCourseEditor(),\n selectors\n });\n }\n\n /**\n * Initial state ready method.\n */\n stateReady() {\n const cancelBtn = this.getElement(this.selectors.CANCEL);\n if (cancelBtn) {\n this.addEventListener(cancelBtn, 'click', this._cancelBulk);\n }\n const selectAll = this.getElement(this.selectors.SELECTALL);\n if (selectAll) {\n this.addEventListener(selectAll, 'change', this._selectAllClick);\n }\n }\n\n /**\n * Component watchers.\n *\n * @returns {Array} of watchers\n */\n getWatchers() {\n return [\n {watch: `bulk.enabled:updated`, handler: this._refreshEnabled},\n {watch: `bulk:updated`, handler: this._refreshTools},\n ];\n }\n\n /**\n * Hide and show the bulk edit tools.\n *\n * @param {object} param\n * @param {Object} param.element details the update details (state.bulk in this case).\n */\n _refreshEnabled({element}) {\n if (element.enabled) {\n enableStickyFooter();\n } else {\n disableStickyFooter();\n }\n }\n\n /**\n * Refresh the tools depending on the current selection.\n *\n * @param {object} param the state watcher information\n * @param {Object} param.state the full state data.\n * @param {Object} param.element the affected element (bulk in this case).\n */\n _refreshTools(param) {\n this._refreshSelectCount(param);\n this._refreshSelectAll(param);\n this._refreshActions(param);\n }\n\n /**\n * Refresh the selection count.\n *\n * @param {object} param\n * @param {Object} param.element the affected element (bulk in this case).\n */\n async _refreshSelectCount({element: bulk}) {\n const selectedCount = await getString('bulkselection', 'core_courseformat', bulk.selection.length);\n const selectedElement = this.getElement(this.selectors.COUNT);\n if (selectedElement) {\n selectedElement.innerHTML = selectedCount;\n }\n }\n\n /**\n * Refresh the select all element.\n *\n * @param {object} param\n * @param {Object} param.element the affected element (bulk in this case).\n */\n _refreshSelectAll({element: bulk}) {\n const selectall = this.getElement(this.selectors.SELECTALL);\n if (!selectall) {\n return;\n }\n if (bulk.selectedType === '') {\n selectall.checked = false;\n selectall.disabled = true;\n return;\n }\n\n selectall.disabled = false;\n const maxSelection = document.querySelectorAll(this.selectors.SELECTABLE).length;\n selectall.checked = (bulk.selection.length == maxSelection);\n }\n\n /**\n * Refresh the visible action buttons depending on the selection type.\n *\n * @param {object} param\n * @param {Object} param.element the affected element (bulk in this case).\n */\n _refreshActions({element: bulk}) {\n // By default, we show the cm options.\n const displayType = (bulk.selectedType == 'section') ? 'section' : 'cm';\n const enabled = (bulk.selectedType !== '');\n this.getElements(this.selectors.ACTIONS).forEach(action => {\n action.classList.toggle(this.classes.DISABLED, !enabled);\n\n const actionTool = action.closest(this.selectors.ACTIONTOOL);\n const isHidden = (action.dataset.bulk != displayType);\n actionTool?.classList.toggle(this.classes.HIDE, isHidden);\n });\n }\n\n /**\n * Cancel bulk handler.\n */\n _cancelBulk() {\n const pending = new Pending(`courseformat/content:bulktoggle_off`);\n this.reactive.dispatch('bulkEnable', false);\n // Wait for a while and focus on enable bulk button.\n setTimeout(() => {\n document.querySelector(this.selectors.BULKBTN)?.focus();\n pending.resolve();\n }, 150);\n }\n\n /**\n * Select all elements click handler.\n * @param {Event} event\n */\n _selectAllClick(event) {\n const target = event.target;\n const bulk = this.reactive.get('bulk');\n if (bulk.selectedType === '') {\n return;\n }\n if (!target.checked) {\n this._handleUnselectAll();\n return;\n }\n this._handleSelectAll(bulk);\n }\n\n /**\n * Process unselect all elements.\n */\n _handleUnselectAll() {\n const pending = new Pending(`courseformat/content:bulktUnselectAll`);\n // Re-enable bulk will clean the selection and the selection type.\n this.reactive.dispatch('bulkEnable', true);\n // Wait for a while and focus on the first checkbox.\n setTimeout(() => {\n document.querySelector(this.selectors.SELECTABLE)?.focus();\n pending.resolve();\n }, 150);\n }\n\n /**\n * Process a select all selectable elements.\n * @param {Object} bulk the state bulk data\n * @param {String} bulk.selectedType the current selected type (section/cm)\n */\n _handleSelectAll(bulk) {\n const selectableIds = [];\n const selectables = document.querySelectorAll(this.selectors.SELECTABLE);\n if (selectables.length == 0) {\n return;\n }\n selectables.forEach(selectable => {\n selectableIds.push(selectable.dataset.id);\n });\n const mutation = (bulk.selectedType === 'cm') ? 'cmSelect' : 'sectionSelect';\n this.reactive.dispatch(mutation, selectableIds);\n }\n}\n"],"names":["Component","BaseComponent","create","name","selectors","ACTIONS","ACTIONTOOL","CANCEL","COUNT","SELECTABLE","SELECTALL","BULKBTN","classes","HIDE","DISABLED","target","this","element","document","querySelector","reactive","stateReady","cancelBtn","getElement","addEventListener","_cancelBulk","selectAll","_selectAllClick","getWatchers","watch","handler","_refreshEnabled","_refreshTools","enabled","param","_refreshSelectCount","_refreshSelectAll","_refreshActions","bulk","selectedCount","selection","length","selectedElement","innerHTML","selectall","selectedType","checked","disabled","maxSelection","querySelectorAll","displayType","getElements","forEach","action","classList","toggle","actionTool","closest","isHidden","dataset","pending","Pending","dispatch","setTimeout","focus","resolve","event","get","_handleSelectAll","_handleUnselectAll","selectableIds","selectables","selectable","push","id","mutation"],"mappings":";;;;;;;;6KAiCI,oBACA,CAAC,wBAGgBA,kBAAkBC,wBAKnCC,cAESC,KAAO,yBAEPC,UAAY,CACbC,kCACAC,yCACAC,iCACAC,+BACAC,qDACAC,mCACAC,wCAGCC,QAAU,CACXC,KAAM,SACNC,SAAU,wBAWNC,OAAQX,kBACT,IAAIY,KAAK,CACZC,QAASC,SAASC,cAAcJ,QAChCK,UAAU,0CACVhB,UAAAA,YAORiB,mBACUC,UAAYN,KAAKO,WAAWP,KAAKZ,UAAUG,QAC7Ce,gBACKE,iBAAiBF,UAAW,QAASN,KAAKS,mBAE7CC,UAAYV,KAAKO,WAAWP,KAAKZ,UAAUM,WAC7CgB,gBACKF,iBAAiBE,UAAW,SAAUV,KAAKW,iBASxDC,oBACW,CACH,CAACC,6BAA+BC,QAASd,KAAKe,iBAC9C,CAACF,qBAAuBC,QAASd,KAAKgB,gBAU9CD,0BAAgBd,QAACA,cACTA,QAAQgB,uFAchBD,cAAcE,YACLC,oBAAoBD,YACpBE,kBAAkBF,YAClBG,gBAAgBH,4CASEjB,QAASqB,kBAC1BC,oBAAsB,mBAAU,gBAAiB,oBAAqBD,KAAKE,UAAUC,QACrFC,gBAAkB1B,KAAKO,WAAWP,KAAKZ,UAAUI,OACnDkC,kBACAA,gBAAgBC,UAAYJ,eAUpCH,6BAAmBnB,QAASqB,kBAClBM,UAAY5B,KAAKO,WAAWP,KAAKZ,UAAUM,eAC5CkC,oBAGqB,KAAtBN,KAAKO,oBACLD,UAAUE,SAAU,OACpBF,UAAUG,UAAW,GAIzBH,UAAUG,UAAW,QACfC,aAAe9B,SAAS+B,iBAAiBjC,KAAKZ,UAAUK,YAAYgC,OAC1EG,UAAUE,QAAWR,KAAKE,UAAUC,QAAUO,aASlDX,2BAAiBpB,QAASqB,kBAEhBY,YAAoC,WAArBZ,KAAKO,aAA6B,UAAY,KAC7DZ,QAAiC,KAAtBK,KAAKO,kBACjBM,YAAYnC,KAAKZ,UAAUC,SAAS+C,SAAQC,SAC7CA,OAAOC,UAAUC,OAAOvC,KAAKJ,QAAQE,UAAWmB,eAE1CuB,WAAaH,OAAOI,QAAQzC,KAAKZ,UAAUE,YAC3CoD,SAAYL,OAAOM,QAAQrB,MAAQY,YACzCM,MAAAA,YAAAA,WAAYF,UAAUC,OAAOvC,KAAKJ,QAAQC,KAAM6C,aAOxDjC,oBACUmC,QAAU,IAAIC,6DACfzC,SAAS0C,SAAS,cAAc,GAErCC,YAAW,6DACP7C,SAASC,cAAcH,KAAKZ,UAAUO,iEAAUqD,QAChDJ,QAAQK,YACT,KAOPtC,gBAAgBuC,aACNnD,OAASmD,MAAMnD,OACfuB,KAAOtB,KAAKI,SAAS+C,IAAI,QACL,KAAtB7B,KAAKO,eAGJ9B,OAAO+B,aAIPsB,iBAAiB9B,WAHb+B,sBASbA,2BACUT,QAAU,IAAIC,+DAEfzC,SAAS0C,SAAS,cAAc,GAErCC,YAAW,+DACP7C,SAASC,cAAcH,KAAKZ,UAAUK,sEAAauD,QACnDJ,QAAQK,YACT,KAQPG,iBAAiB9B,YACPgC,cAAgB,GAChBC,YAAcrD,SAAS+B,iBAAiBjC,KAAKZ,UAAUK,eACnC,GAAtB8D,YAAY9B,cAGhB8B,YAAYnB,SAAQoB,aAChBF,cAAcG,KAAKD,WAAWb,QAAQe,aAEpCC,SAAkC,OAAtBrC,KAAKO,aAAyB,WAAa,qBACxDzB,SAAS0C,SAASa,SAAUL"} \ No newline at end of file +{"version":3,"file":"bulkedittools.min.js","sources":["../../../src/local/content/bulkedittools.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * The bulk editor tools bar.\n *\n * @module core_courseformat/local/content/bulkedittools\n * @class core_courseformat/local/content/bulkedittools\n * @copyright 2023 Ferran Recio \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport {BaseComponent} from 'core/reactive';\nimport {disableStickyFooter, enableStickyFooter} from 'core/sticky-footer';\nimport {getCurrentCourseEditor} from 'core_courseformat/courseeditor';\nimport {get_string as getString} from 'core/str';\nimport Pending from 'core/pending';\nimport {prefetchStrings} from 'core/prefetch';\n\n// Load global strings.\nprefetchStrings(\n 'core_courseformat',\n ['bulkselection']\n);\n\nexport default class Component extends BaseComponent {\n\n /**\n * Constructor hook.\n */\n create() {\n // Optional component name for debugging.\n this.name = 'bulk_editor_tools';\n // Default query selectors.\n this.selectors = {\n ACTIONS: `[data-for=\"bulkaction\"]`,\n ACTIONTOOL: `[data-for=\"bulkactions\"] li`,\n CANCEL: `[data-for=\"bulkcancel\"]`,\n COUNT: `[data-for='bulkcount']`,\n SELECTABLE: `[data-bulkcheckbox][data-is-selectable]`,\n SELECTALL: `[data-for=\"selectall\"]`,\n BULKBTN: `[data-for=\"enableBulk\"]`,\n };\n // Most classes will be loaded later by DndCmItem.\n this.classes = {\n HIDE: 'd-none',\n DISABLED: 'disabled',\n };\n }\n\n /**\n * Static method to create a component instance from the mustache template.\n *\n * @param {string} target optional altentative DOM main element CSS selector\n * @param {object} selectors optional css selector overrides\n * @return {Component}\n */\n static init(target, selectors) {\n return new this({\n element: document.querySelector(target),\n reactive: getCurrentCourseEditor(),\n selectors\n });\n }\n\n /**\n * Initial state ready method.\n */\n stateReady() {\n const cancelBtn = this.getElement(this.selectors.CANCEL);\n if (cancelBtn) {\n this.addEventListener(cancelBtn, 'click', this._cancelBulk);\n }\n const selectAll = this.getElement(this.selectors.SELECTALL);\n if (selectAll) {\n this.addEventListener(selectAll, 'change', this._selectAllClick);\n }\n }\n\n /**\n * Component watchers.\n *\n * @returns {Array} of watchers\n */\n getWatchers() {\n return [\n {watch: `bulk.enabled:updated`, handler: this._refreshEnabled},\n {watch: `bulk:updated`, handler: this._refreshTools},\n ];\n }\n\n /**\n * Hide and show the bulk edit tools.\n *\n * @param {object} param\n * @param {Object} param.element details the update details (state.bulk in this case).\n */\n _refreshEnabled({element}) {\n if (element.enabled) {\n enableStickyFooter();\n } else {\n disableStickyFooter();\n }\n }\n\n /**\n * Refresh the tools depending on the current selection.\n *\n * @param {object} param the state watcher information\n * @param {Object} param.state the full state data.\n * @param {Object} param.element the affected element (bulk in this case).\n */\n _refreshTools(param) {\n this._refreshSelectCount(param);\n this._refreshSelectAll(param);\n this._refreshActions(param);\n }\n\n /**\n * Refresh the selection count.\n *\n * @param {object} param\n * @param {Object} param.element the affected element (bulk in this case).\n */\n async _refreshSelectCount({element: bulk}) {\n const stringName = (bulk.selection.length > 1) ? 'bulkselection_plural' : 'bulkselection';\n const selectedCount = await getString(stringName, 'core_courseformat', bulk.selection.length);\n const selectedElement = this.getElement(this.selectors.COUNT);\n if (selectedElement) {\n selectedElement.innerHTML = selectedCount;\n }\n }\n\n /**\n * Refresh the select all element.\n *\n * @param {object} param\n * @param {Object} param.element the affected element (bulk in this case).\n */\n _refreshSelectAll({element: bulk}) {\n const selectall = this.getElement(this.selectors.SELECTALL);\n if (!selectall) {\n return;\n }\n if (bulk.selectedType === '') {\n selectall.checked = false;\n selectall.disabled = true;\n return;\n }\n\n selectall.disabled = false;\n const maxSelection = document.querySelectorAll(this.selectors.SELECTABLE).length;\n selectall.checked = (bulk.selection.length == maxSelection);\n }\n\n /**\n * Refresh the visible action buttons depending on the selection type.\n *\n * @param {object} param\n * @param {Object} param.element the affected element (bulk in this case).\n */\n _refreshActions({element: bulk}) {\n // By default, we show the cm options.\n const displayType = (bulk.selectedType == 'section') ? 'section' : 'cm';\n const enabled = (bulk.selectedType !== '');\n this.getElements(this.selectors.ACTIONS).forEach(action => {\n action.classList.toggle(this.classes.DISABLED, !enabled);\n action.tabIndex = (enabled) ? 0 : -1;\n\n const actionTool = action.closest(this.selectors.ACTIONTOOL);\n const isHidden = (action.dataset.bulk != displayType);\n actionTool?.classList.toggle(this.classes.HIDE, isHidden);\n });\n }\n\n /**\n * Cancel bulk handler.\n */\n _cancelBulk() {\n const pending = new Pending(`courseformat/content:bulktoggle_off`);\n this.reactive.dispatch('bulkEnable', false);\n // Wait for a while and focus on enable bulk button.\n setTimeout(() => {\n document.querySelector(this.selectors.BULKBTN)?.focus();\n pending.resolve();\n }, 150);\n }\n\n /**\n * Select all elements click handler.\n * @param {Event} event\n */\n _selectAllClick(event) {\n const target = event.target;\n const bulk = this.reactive.get('bulk');\n if (bulk.selectedType === '') {\n return;\n }\n if (!target.checked) {\n this._handleUnselectAll();\n return;\n }\n this._handleSelectAll(bulk);\n }\n\n /**\n * Process unselect all elements.\n */\n _handleUnselectAll() {\n const pending = new Pending(`courseformat/content:bulktUnselectAll`);\n // Re-enable bulk will clean the selection and the selection type.\n this.reactive.dispatch('bulkEnable', true);\n // Wait for a while and focus on the first checkbox.\n setTimeout(() => {\n document.querySelector(this.selectors.SELECTABLE)?.focus();\n pending.resolve();\n }, 150);\n }\n\n /**\n * Process a select all selectable elements.\n * @param {Object} bulk the state bulk data\n * @param {String} bulk.selectedType the current selected type (section/cm)\n */\n _handleSelectAll(bulk) {\n const selectableIds = [];\n const selectables = document.querySelectorAll(this.selectors.SELECTABLE);\n if (selectables.length == 0) {\n return;\n }\n selectables.forEach(selectable => {\n selectableIds.push(selectable.dataset.id);\n });\n const mutation = (bulk.selectedType === 'cm') ? 'cmSelect' : 'sectionSelect';\n this.reactive.dispatch(mutation, selectableIds);\n }\n}\n"],"names":["Component","BaseComponent","create","name","selectors","ACTIONS","ACTIONTOOL","CANCEL","COUNT","SELECTABLE","SELECTALL","BULKBTN","classes","HIDE","DISABLED","target","this","element","document","querySelector","reactive","stateReady","cancelBtn","getElement","addEventListener","_cancelBulk","selectAll","_selectAllClick","getWatchers","watch","handler","_refreshEnabled","_refreshTools","enabled","param","_refreshSelectCount","_refreshSelectAll","_refreshActions","bulk","stringName","selection","length","selectedCount","selectedElement","innerHTML","selectall","selectedType","checked","disabled","maxSelection","querySelectorAll","displayType","getElements","forEach","action","classList","toggle","tabIndex","actionTool","closest","isHidden","dataset","pending","Pending","dispatch","setTimeout","focus","resolve","event","get","_handleSelectAll","_handleUnselectAll","selectableIds","selectables","selectable","push","id","mutation"],"mappings":";;;;;;;;6KAiCI,oBACA,CAAC,wBAGgBA,kBAAkBC,wBAKnCC,cAESC,KAAO,yBAEPC,UAAY,CACbC,kCACAC,yCACAC,iCACAC,+BACAC,qDACAC,mCACAC,wCAGCC,QAAU,CACXC,KAAM,SACNC,SAAU,wBAWNC,OAAQX,kBACT,IAAIY,KAAK,CACZC,QAASC,SAASC,cAAcJ,QAChCK,UAAU,0CACVhB,UAAAA,YAORiB,mBACUC,UAAYN,KAAKO,WAAWP,KAAKZ,UAAUG,QAC7Ce,gBACKE,iBAAiBF,UAAW,QAASN,KAAKS,mBAE7CC,UAAYV,KAAKO,WAAWP,KAAKZ,UAAUM,WAC7CgB,gBACKF,iBAAiBE,UAAW,SAAUV,KAAKW,iBASxDC,oBACW,CACH,CAACC,6BAA+BC,QAASd,KAAKe,iBAC9C,CAACF,qBAAuBC,QAASd,KAAKgB,gBAU9CD,0BAAgBd,QAACA,cACTA,QAAQgB,uFAchBD,cAAcE,YACLC,oBAAoBD,YACpBE,kBAAkBF,YAClBG,gBAAgBH,4CASEjB,QAASqB,kBAC1BC,WAAcD,KAAKE,UAAUC,OAAS,EAAK,uBAAyB,gBACpEC,oBAAsB,mBAAUH,WAAY,oBAAqBD,KAAKE,UAAUC,QAChFE,gBAAkB3B,KAAKO,WAAWP,KAAKZ,UAAUI,OACnDmC,kBACAA,gBAAgBC,UAAYF,eAUpCN,6BAAmBnB,QAASqB,kBAClBO,UAAY7B,KAAKO,WAAWP,KAAKZ,UAAUM,eAC5CmC,oBAGqB,KAAtBP,KAAKQ,oBACLD,UAAUE,SAAU,OACpBF,UAAUG,UAAW,GAIzBH,UAAUG,UAAW,QACfC,aAAe/B,SAASgC,iBAAiBlC,KAAKZ,UAAUK,YAAYgC,OAC1EI,UAAUE,QAAWT,KAAKE,UAAUC,QAAUQ,aASlDZ,2BAAiBpB,QAASqB,kBAEhBa,YAAoC,WAArBb,KAAKQ,aAA6B,UAAY,KAC7Db,QAAiC,KAAtBK,KAAKQ,kBACjBM,YAAYpC,KAAKZ,UAAUC,SAASgD,SAAQC,SAC7CA,OAAOC,UAAUC,OAAOxC,KAAKJ,QAAQE,UAAWmB,SAChDqB,OAAOG,SAAYxB,QAAW,GAAK,QAE7ByB,WAAaJ,OAAOK,QAAQ3C,KAAKZ,UAAUE,YAC3CsD,SAAYN,OAAOO,QAAQvB,MAAQa,YACzCO,MAAAA,YAAAA,WAAYH,UAAUC,OAAOxC,KAAKJ,QAAQC,KAAM+C,aAOxDnC,oBACUqC,QAAU,IAAIC,6DACf3C,SAAS4C,SAAS,cAAc,GAErCC,YAAW,6DACP/C,SAASC,cAAcH,KAAKZ,UAAUO,iEAAUuD,QAChDJ,QAAQK,YACT,KAOPxC,gBAAgByC,aACNrD,OAASqD,MAAMrD,OACfuB,KAAOtB,KAAKI,SAASiD,IAAI,QACL,KAAtB/B,KAAKQ,eAGJ/B,OAAOgC,aAIPuB,iBAAiBhC,WAHbiC,sBASbA,2BACUT,QAAU,IAAIC,+DAEf3C,SAAS4C,SAAS,cAAc,GAErCC,YAAW,+DACP/C,SAASC,cAAcH,KAAKZ,UAAUK,sEAAayD,QACnDJ,QAAQK,YACT,KAQPG,iBAAiBhC,YACPkC,cAAgB,GAChBC,YAAcvD,SAASgC,iBAAiBlC,KAAKZ,UAAUK,eACnC,GAAtBgE,YAAYhC,cAGhBgC,YAAYpB,SAAQqB,aAChBF,cAAcG,KAAKD,WAAWb,QAAQe,aAEpCC,SAAkC,OAAtBvC,KAAKQ,aAAyB,WAAa,qBACxD1B,SAAS4C,SAASa,SAAUL"} \ No newline at end of file diff --git a/course/format/amd/src/local/content/actions.js b/course/format/amd/src/local/content/actions.js index 213256a449c..3780a6e3e5f 100644 --- a/course/format/amd/src/local/content/actions.js +++ b/course/format/amd/src/local/content/actions.js @@ -604,9 +604,10 @@ export default class extends BaseComponent { if (sectionIds.length == 0) { return; } + const title = (sectionIds.length == 1) ? 'sectionavailability_title' : 'sectionsavailability_title'; // Show the availability modal to decide which action to trigger. const modalParams = { - title: getString('availability', 'core'), + title: this.reactive.getFormatString(title), body: Templates.render('core_courseformat/local/content/section/availabilitymodal', []), saveButtonText: getString('apply', 'core'), type: ModalFactory.types.SAVE_CANCEL, diff --git a/course/format/amd/src/local/content/bulkedittools.js b/course/format/amd/src/local/content/bulkedittools.js index 75cf06e8bc5..2301639f8a5 100644 --- a/course/format/amd/src/local/content/bulkedittools.js +++ b/course/format/amd/src/local/content/bulkedittools.js @@ -135,7 +135,8 @@ export default class Component extends BaseComponent { * @param {Object} param.element the affected element (bulk in this case). */ async _refreshSelectCount({element: bulk}) { - const selectedCount = await getString('bulkselection', 'core_courseformat', bulk.selection.length); + const stringName = (bulk.selection.length > 1) ? 'bulkselection_plural' : 'bulkselection'; + const selectedCount = await getString(stringName, 'core_courseformat', bulk.selection.length); const selectedElement = this.getElement(this.selectors.COUNT); if (selectedElement) { selectedElement.innerHTML = selectedCount; @@ -176,6 +177,7 @@ export default class Component extends BaseComponent { const enabled = (bulk.selectedType !== ''); this.getElements(this.selectors.ACTIONS).forEach(action => { action.classList.toggle(this.classes.DISABLED, !enabled); + action.tabIndex = (enabled) ? 0 : -1; const actionTool = action.closest(this.selectors.ACTIONTOOL); const isHidden = (action.dataset.bulk != displayType); diff --git a/course/format/classes/base.php b/course/format/classes/base.php index b6e2875767b..a4c8d45b2ad 100644 --- a/course/format/classes/base.php +++ b/course/format/classes/base.php @@ -808,12 +808,14 @@ abstract class base { $stringmanager = get_string_manager(); $component = 'format_' . $this->format; $formatoverridbles = [ + 'sectionavailability_title', 'sectiondelete_title', 'sectiondelete_info', 'sectionsdelete_title', 'sectionsdelete_info', 'sectionmove_title', 'sectionmove_info', + 'sectionsavailability_title', 'sectionsmove_title', 'sectionsmove_info', 'selectsection' diff --git a/course/format/classes/output/local/content/bulkedittools.php b/course/format/classes/output/local/content/bulkedittools.php index 47dab958e5d..69dbe3f6638 100644 --- a/course/format/classes/output/local/content/bulkedittools.php +++ b/course/format/classes/output/local/content/bulkedittools.php @@ -155,7 +155,7 @@ class bulkedittools implements named_templatable, renderable { 'icon' => 't/show', 'action' => 'sectionAvailability', 'name' => get_string('availability'), - 'title' => get_string('sectionavailability', 'core_courseformat'), + 'title' => $this->format->get_format_string('sectionsavailability'), 'bulk' => 'section', ]; } diff --git a/course/format/templates/local/content/bulkedittoggler.mustache b/course/format/templates/local/content/bulkedittoggler.mustache index 46d59598971..8cde7db232c 100644 --- a/course/format/templates/local/content/bulkedittoggler.mustache +++ b/course/format/templates/local/content/bulkedittoggler.mustache @@ -25,7 +25,7 @@ }} {{/actions}} @@ -74,7 +74,7 @@ class="btn pr-0 pb-0" data-action="bulkcancel" data-for="bulkcancel" - title="{{#str}} bulkeditoff, core_courseformat {{/str}}" + title="{{#str}} bulkcancel, core_courseformat {{/str}}" > {{#pix}} e/cancel, core {{/pix}} diff --git a/course/format/tests/behat/bulk_section_actions.feature b/course/format/tests/behat/bulk_section_actions.feature index 20b8bdf40b1..b3fe7e3997f 100644 --- a/course/format/tests/behat/bulk_section_actions.feature +++ b/course/format/tests/behat/bulk_section_actions.feature @@ -41,9 +41,9 @@ Feature: Bulk course section actions. When I click on "Select topic Topic 1" "checkbox" And I click on "Select topic Topic 2" "checkbox" And I should see "2 selected" in the "sticky-footer" "region" - And I click on "Section availability" "button" in the "sticky-footer" "region" - And I click on "Hide on course page" "radio" in the "Availability" "dialogue" - And I click on "Apply" "button" in the "Availability" "dialogue" + And I click on "Topics availability" "button" in the "sticky-footer" "region" + And I click on "Hide on course page" "radio" in the "Topics availability" "dialogue" + And I click on "Apply" "button" in the "Topics availability" "dialogue" Then I should see "Hidden from students" in the "Activity sample 1" "activity" And I should see "Hidden from students" in the "Activity sample 2" "activity" And I should see "Hidden from students" in the "Activity sample 3" "activity" @@ -56,10 +56,10 @@ Feature: Bulk course section actions. Scenario: Bulk show sections Given I click on "Select topic Topic 1" "checkbox" - Given I click on "Select topic Topic 3" "checkbox" - And I click on "Section availability" "button" in the "sticky-footer" "region" - And I click on "Hide on course page" "radio" in the "Availability" "dialogue" - And I click on "Apply" "button" in the "Availability" "dialogue" + And I click on "Select topic Topic 3" "checkbox" + And I click on "Topics availability" "button" in the "sticky-footer" "region" + And I click on "Hide on course page" "radio" in the "Topics availability" "dialogue" + And I click on "Apply" "button" in the "Topics availability" "dialogue" And I should see "Hidden from students" in the "Activity sample 1" "activity" And I should see "Hidden from students" in the "Activity sample 2" "activity" And I should not see "Hidden from students" in the "Activity sample 3" "activity" @@ -71,9 +71,9 @@ Feature: Bulk course section actions. When I click on "Select topic Topic 1" "checkbox" And I click on "Select topic Topic 2" "checkbox" And I should see "2 selected" in the "sticky-footer" "region" - And I click on "Section availability" "button" in the "sticky-footer" "region" - And I click on "Show on course page" "radio" in the "Availability" "dialogue" - And I click on "Apply" "button" in the "Availability" "dialogue" + And I click on "Topics availability" "button" in the "sticky-footer" "region" + And I click on "Show on course page" "radio" in the "Topics availability" "dialogue" + And I click on "Apply" "button" in the "Topics availability" "dialogue" Then I should not see "Hidden from students" in the "Activity sample 1" "activity" And I should not see "Hidden from students" in the "Activity sample 2" "activity" And I should not see "Hidden from students" in the "Activity sample 3" "activity" diff --git a/course/format/topics/lang/en/format_topics.php b/course/format/topics/lang/en/format_topics.php index 91d010ba5e9..a4ae079074b 100644 --- a/course/format/topics/lang/en/format_topics.php +++ b/course/format/topics/lang/en/format_topics.php @@ -36,9 +36,12 @@ $string['privacy:metadata'] = 'The Topics format plugin does not store any perso $string['indentation'] = 'Allow indentation on course page'; $string['indentation_help'] = 'Allow teachers, and other users with the manage activities capability, to indent items on the course page.'; $string['section0name'] = 'General'; +$string['sectionavailability_title'] = 'Topic availability'; $string['sectiondelete_title'] = 'Delete topic?'; $string['sectionmove_title'] = 'Move topic'; $string['sectionname'] = 'Topic'; +$string['sectionsavailability'] = 'Topics availability'; +$string['sectionsavailability_title'] = 'Topics availability'; $string['sectionsdelete'] = 'Delete topics'; $string['sectionsdelete_info'] = 'This will delete {$a->count} topics and all the activities they contain.'; $string['sectionsdelete_title'] = 'Delete selected topics?'; diff --git a/course/format/weeks/lang/en/format_weeks.php b/course/format/weeks/lang/en/format_weeks.php index 7fe4be1ac7d..f2065a7d968 100644 --- a/course/format/weeks/lang/en/format_weeks.php +++ b/course/format/weeks/lang/en/format_weeks.php @@ -38,9 +38,12 @@ $string['privacy:metadata'] = 'The Weekly format plugin does not store any perso $string['indentation'] = 'Allow indentation on course page'; $string['indentation_help'] = 'Allow teachers, and other users with the manage activities capability, to indent items on the course page.'; $string['section0name'] = 'General'; +$string['sectionavailability_title'] = 'Week availability'; $string['sectiondelete_title'] = 'Delete week?'; $string['sectionmove_title'] = 'Move section'; $string['sectionname'] = 'Week'; +$string['sectionsavailability'] = 'Weeks availability'; +$string['sectionsavailability_title'] = 'Weeks availability'; $string['sectionsdelete'] = 'Delete weeks'; $string['sectionsdelete_info'] = 'This will delete {$a->count} weeks and all the activities they contain.'; $string['sectionsdelete_title'] = 'Delete selected weeks?'; diff --git a/course/format/weeks/tests/behat/bulk_actions.feature b/course/format/weeks/tests/behat/bulk_actions.feature index 2c24bcbe58a..d8c57ff2f0b 100644 --- a/course/format/weeks/tests/behat/bulk_actions.feature +++ b/course/format/weeks/tests/behat/bulk_actions.feature @@ -29,6 +29,60 @@ Feature: Weeks format bulk activity actions. And I click on "Bulk edit" "button" And I should see "0 selected" in the "sticky-footer" "region" + Scenario: Bulk hide weeks + Given I should not see "Hidden from students" in the "Activity sample 1" "activity" + And I should not see "Hidden from students" in the "Activity sample 2" "activity" + And I should not see "Hidden from students" in the "Activity sample 3" "activity" + And I should not see "Hidden from students" in the "Activity sample 4" "activity" + And I should not see "Hidden from students" in the "1 May - 7 May" "section" + And I should not see "Hidden from students" in the "8 May - 14 May" "section" + And I should not see "Hidden from students" in the "15 May - 21 May" "section" + And I should not see "Hidden from students" in the "22 May - 28 May" "section" + When I click on "Select week 1 May - 7 May" "checkbox" + And I click on "Select week 8 May - 14 May" "checkbox" + And I should see "2 selected" in the "sticky-footer" "region" + And I click on "Weeks availability" "button" in the "sticky-footer" "region" + And I click on "Hide on course page" "radio" in the "Weeks availability" "dialogue" + And I click on "Apply" "button" in the "Weeks availability" "dialogue" + Then I should see "Hidden from students" in the "Activity sample 1" "activity" + And I should see "Hidden from students" in the "Activity sample 2" "activity" + And I should see "Hidden from students" in the "Activity sample 3" "activity" + And I should see "Hidden from students" in the "Activity sample 4" "activity" + And I should see "Hidden from students" in the "1 May - 7 May" "section" + And I should see "Hidden from students" in the "8 May - 14 May" "section" + And I should not see "Hidden from students" in the "15 May - 21 May" "section" + And I should not see "Hidden from students" in the "22 May - 28 May" "section" + And I should see "0 selected" in the "sticky-footer" "region" + + Scenario: Bulk show weeks + Given I click on "Select week 1 May - 7 May" "checkbox" + And I click on "Select week 15 May - 21 May" "checkbox" + And I click on "Weeks availability" "button" in the "sticky-footer" "region" + And I click on "Hide on course page" "radio" in the "Weeks availability" "dialogue" + And I click on "Apply" "button" in the "Weeks availability" "dialogue" + And I should see "Hidden from students" in the "Activity sample 1" "activity" + And I should see "Hidden from students" in the "Activity sample 2" "activity" + And I should not see "Hidden from students" in the "Activity sample 3" "activity" + And I should not see "Hidden from students" in the "Activity sample 4" "activity" + And I should see "Hidden from students" in the "1 May - 7 May" "section" + And I should not see "Hidden from students" in the "8 May - 14 May" "section" + And I should see "Hidden from students" in the "15 May - 21 May" "section" + And I should not see "Hidden from students" in the "22 May - 28 May" "section" + When I click on "Select week 1 May - 7 May" "checkbox" + And I click on "Select week 8 May - 14 May" "checkbox" + And I should see "2 selected" in the "sticky-footer" "region" + And I click on "Weeks availability" "button" in the "sticky-footer" "region" + And I click on "Show on course page" "radio" in the "Weeks availability" "dialogue" + And I click on "Apply" "button" in the "Weeks availability" "dialogue" + Then I should not see "Hidden from students" in the "Activity sample 1" "activity" + And I should not see "Hidden from students" in the "Activity sample 2" "activity" + And I should not see "Hidden from students" in the "Activity sample 3" "activity" + And I should not see "Hidden from students" in the "Activity sample 4" "activity" + And I should not see "Hidden from students" in the "1 May - 7 May" "section" + And I should not see "Hidden from students" in the "8 May - 14 May" "section" + And I should see "Hidden from students" in the "15 May - 21 May" "section" + And I should not see "Hidden from students" in the "22 May - 28 May" "section" + Scenario: Delete a single week using bulk action Given I should see "1 May - 7 May" in the "region-main" "region" And I should see "8 May - 14 May" in the "region-main" "region" @@ -42,7 +96,7 @@ Feature: Weeks format bulk activity actions. And I click on "Select week 8 May - 14 May" "checkbox" And I should see "2 selected" in the "sticky-footer" "region" When I click on "Delete weeks" "button" in the "sticky-footer" "region" - And I click on "Delete" "button" in the ".modal" "css_element" + And I click on "Delete" "button" in the "Delete selected weeks?" "dialogue" Then I should see "1 May - 7 May" in the "region-main" "region" And I should see "8 May - 14 May" in the "region-main" "region" And I should not see "15 May - 21 May" in the "region-main" "region" @@ -66,7 +120,7 @@ Feature: Weeks format bulk activity actions. And I click on "Select week 15 May - 21 May" "checkbox" And I should see "2 selected" in the "sticky-footer" "region" When I click on "Delete weeks" "button" in the "sticky-footer" "region" - And I click on "Delete" "button" in the ".modal" "css_element" + And I click on "Delete" "button" in the "Delete selected weeks?" "dialogue" Then I should see "1 May - 7 May" in the "region-main" "region" And I should see "8 May - 14 May" in the "region-main" "region" And I should not see "15 May - 21 May" in the "region-main" "region" diff --git a/lang/en/courseformat.php b/lang/en/courseformat.php index b032186d01d..57e79b7b25f 100644 --- a/lang/en/courseformat.php +++ b/lang/en/courseformat.php @@ -29,9 +29,9 @@ $string['availability_hide_help'] = 'Not available to students.'; $string['availability_stealth'] = 'Make available but don\'t show on course page'; $string['availability_stealth_help'] = 'Available to students if you provide a link. Activities will still appear in the gradebook and other reports.'; $string['bulkedit'] = 'Bulk edit'; -$string['bulkeditoff'] = 'Close bulk edit'; -$string['bulkcancel'] = 'Close bulk editing'; +$string['bulkcancel'] = 'Close bulk edit'; $string['bulkselection'] = '{$a} selected'; +$string['bulkselection_plural'] = '{$a} selected'; $string['cmavailability'] = 'Activity availability'; $string['cmdelete_info'] = 'This will delete "{$a->name}" and any user data it contains'; $string['cmdelete_title'] = 'Delete activity?'; @@ -47,9 +47,11 @@ $string['courseindex'] = 'Course index'; $string['nobulkaction'] = 'No bulk actions available'; $string['preference:coursesectionspreferences'] = 'Section user preferences for course {$a}'; $string['privacy:metadata:preference:coursesectionspreferences'] = 'Section user preferences like collapsed and expanded.'; -$string['sectionavailability'] = 'Section availability'; +$string['sectionavailability_title'] = 'Section availability'; $string['sectiondelete_info'] = 'This will delete "{$a->name}" and all the activities it contains.'; $string['sectiondelete_title'] = 'Delete section?'; +$string['sectionsavailability'] = 'Sections availability'; +$string['sectionsavailability_title'] = 'Sections availability'; $string['sectionsdelete'] = 'Delete sections'; $string['sectionmove_info'] = 'Move "{$a}" after'; $string['sectionmove_title'] = 'Move section';