From 13a8dff5536617f1e76fc181f31450971ec996d0 Mon Sep 17 00:00:00 2001 From: Jun Pataleta Date: Fri, 18 Aug 2023 16:28:45 +0800 Subject: [PATCH] MDL-79063 course: Use .text-body for 'disabled' elements in move modal Using the .disabled class for the course module or section that is being moved does not have sufficient colour contrast against the background, especially when it is being highlighted. So instead of using .disabled, just use Bootstrap classes .text-body and .font-italic which renders the cm/section to be moved in normal font colour while preserving its italicised look. --- course/format/amd/build/local/content/actions.min.js | 2 +- course/format/amd/build/local/content/actions.min.js.map | 2 +- course/format/amd/src/local/content/actions.js | 5 ++++- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/course/format/amd/build/local/content/actions.min.js b/course/format/amd/build/local/content/actions.min.js index dd33654d675..1c1cb1fed3a 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",cmNoGroups:"cmNoGroups",cmSeparateGroups:"cmSeparateGroups",cmVisibleGroups:"cmVisibleGroups"};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 pendingModalReady=new _pending.default("courseformat/actions:prepareMoveSectionModal"),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)))})),pendingModalReady.resolve()}async _requestMoveCm(target,event){const cmIds=this._getTargetIds(target);if(0==cmIds.length)return;event.preventDefault();const pendingModalReady=new _pending.default("courseformat/actions:prepareMoveCmModal"),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)})),pendingModalReady.resolve()}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,event){(0,_bulkselection.toggleBulkSelectionAction)(this.reactive,target,event,"cm")}async _requestToggleSelectionSection(target,event){(0,_bulkselection.toggleBulkSelectionAction)(this.reactive,target,event,"section")}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})); + */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",cmNoGroups:"cmNoGroups",cmSeparateGroups:"cmSeparateGroups",cmVisibleGroups:"cmVisibleGroups"};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:"text-body",ITALIC:"font-italic"}}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 pendingModalReady=new _pending.default("courseformat/actions:prepareMoveSectionModal"),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)))})),pendingModalReady.resolve()}async _requestMoveCm(target,event){const cmIds=this._getTargetIds(target);if(0==cmIds.length)return;event.preventDefault();const pendingModalReady=new _pending.default("courseformat/actions:prepareMoveCmModal"),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)})),pendingModalReady.resolve()}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,event){(0,_bulkselection.toggleBulkSelectionAction)(this.reactive,target,event,"cm")}async _requestToggleSelectionSection(target,event){(0,_bulkselection.toggleBulkSelectionAction)(this.reactive,target,event,"section")}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),element.classList.toggle(this.classes.ITALIC,locked),this.setElementLocked(element,locked)}))}_disableLink(element){element&&(element.style.pointerEvents="none",element.style.userSelect="none",element.classList.add(this.classes.DISABLED),element.classList.add(this.classes.ITALIC),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 f70af8db6e5..d943e45e56c 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 {toggleBulkSelectionAction} from 'core_courseformat/local/content/actions/bulkselection';\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 cmNoGroups: 'cmNoGroups',\n cmSeparateGroups: 'cmSeparateGroups',\n cmVisibleGroups: 'cmVisibleGroups',\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 const pendingModalReady = new Pending(`courseformat/actions:prepareMoveSectionModal`);\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 pendingModalReady.resolve();\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 const pendingModalReady = new Pending(`courseformat/actions:prepareMoveCmModal`);\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 pendingModalReady.resolve();\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 * @param {Event} event the triggered event\n */\n async _requestToggleSelectionCm(target, event) {\n toggleBulkSelectionAction(this.reactive, target, event, 'cm');\n }\n\n /**\n * Handle a toggle section selection.\n *\n * @param {Element} target the dispatch action element\n * @param {Event} event the triggered event\n */\n async _requestToggleSelectionSection(target, event) {\n toggleBulkSelectionAction(this.reactive, target, event, 'section');\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","cmNoGroups","cmSeparateGroups","cmVisibleGroups","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","pendingModalReady","Pending","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","resolve","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","mutationName","modname","allowstealth","canUseStealth","saveButtonText","SAVE_CANCEL","_setupMutationRadioButtonModal","setButtonDisabled","submitFunction","radio","mutation","value","querySelectorAll","parentNode","checked","dbClickEvent","save","locked","getElements","toggle","setElementLocked","style","pointerEvents","userSelect","add","setAttribute","Promise","reject","then","setRemoveOnClose","bodyRendered","setSaveButtonText","deleteButtonText","setDeleteButtonText","show","catch","hide","pendingDestroy","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 {toggleBulkSelectionAction} from 'core_courseformat/local/content/actions/bulkselection';\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 cmNoGroups: 'cmNoGroups',\n cmSeparateGroups: 'cmSeparateGroups',\n cmVisibleGroups: 'cmVisibleGroups',\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: `text-body`,\n ITALIC: `font-italic`,\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 const pendingModalReady = new Pending(`courseformat/actions:prepareMoveSectionModal`);\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 pendingModalReady.resolve();\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 const pendingModalReady = new Pending(`courseformat/actions:prepareMoveCmModal`);\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 pendingModalReady.resolve();\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 * @param {Event} event the triggered event\n */\n async _requestToggleSelectionCm(target, event) {\n toggleBulkSelectionAction(this.reactive, target, event, 'cm');\n }\n\n /**\n * Handle a toggle section selection.\n *\n * @param {Element} target the dispatch action element\n * @param {Event} event the triggered event\n */\n async _requestToggleSelectionSection(target, event) {\n toggleBulkSelectionAction(this.reactive, target, event, 'section');\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 element.classList.toggle(this.classes.ITALIC, 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.classList.add(this.classes.ITALIC);\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","cmNoGroups","cmSeparateGroups","cmVisibleGroups","BaseComponent","create","name","selectors","ACTIONLINK","SECTIONLINK","CMLINK","SECTIONNODE","MODALTOGGLER","ADDSECTION","CONTENTTREE","ACTIONMENU","ACTIONMENUTOGGLER","OPTIONSRADIO","classes","DISABLED","ITALIC","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","pendingModalReady","Pending","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","resolve","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","mutationName","modname","allowstealth","canUseStealth","saveButtonText","SAVE_CANCEL","_setupMutationRadioButtonModal","setButtonDisabled","submitFunction","radio","mutation","value","querySelectorAll","parentNode","checked","dbClickEvent","save","locked","getElements","toggle","setElementLocked","style","pointerEvents","userSelect","add","setAttribute","Promise","reject","then","setRemoveOnClose","bodyRendered","setSaveButtonText","deleteButtonText","setDeleteButtonText","show","catch","hide","pendingDestroy","focus","setTimeout","actionMenu"],"mappings":"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"} \ 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 6bf38661db7..239ce065e56 100644 --- a/course/format/amd/src/local/content/actions.js +++ b/course/format/amd/src/local/content/actions.js @@ -83,7 +83,8 @@ export default class extends BaseComponent { }; // Component css classes. this.classes = { - DISABLED: `disabled`, + DISABLED: `text-body`, + ITALIC: `font-italic`, }; } @@ -673,6 +674,7 @@ export default class extends BaseComponent { const targets = this.getElements(this.selectors.ADDSECTION); targets.forEach(element => { element.classList.toggle(this.classes.DISABLED, locked); + element.classList.toggle(this.classes.ITALIC, locked); this.setElementLocked(element, locked); }); } @@ -687,6 +689,7 @@ export default class extends BaseComponent { element.style.pointerEvents = 'none'; element.style.userSelect = 'none'; element.classList.add(this.classes.DISABLED); + element.classList.add(this.classes.ITALIC); element.setAttribute('aria-disabled', true); element.addEventListener('click', event => event.preventDefault()); }