diff --git a/course/format/amd/build/local/content/actions.min.js b/course/format/amd/build/local/content/actions.min.js index f4f1d071ac2..44ac911febd 100644 --- a/course/format/amd/build/local/content/actions.min.js +++ b/course/format/amd/build/local/content/actions.min.js @@ -1,4 +1,4 @@ -define("core_courseformat/local/content/actions",["exports","core/reactive","core/modal_factory","core/modal_events","core/templates","core/prefetch","core/str","core/normalise","core_course/events","core/pending","core_courseformat/local/courseeditor/contenttree","jquery"],(function(_exports,_reactive,_modal_factory,_modal_events,_templates,_prefetch,_str,_normalise,CourseEvents,_pending,_contenttree,_jquery){function _getRequireWildcardCache(nodeInterop){if("function"!=typeof WeakMap)return null;var cacheBabelInterop=new WeakMap,cacheNodeInterop=new WeakMap;return(_getRequireWildcardCache=function(nodeInterop){return nodeInterop?cacheNodeInterop:cacheBabelInterop})(nodeInterop)}function _interopRequireDefault(obj){return obj&&obj.__esModule?obj:{default:obj}} +define("core_courseformat/local/content/actions",["exports","core/reactive","core/modal_factory","core/modal_events","core/templates","core/prefetch","core/str","core/normalise","core_courseformat/local/content/actions/bulkselection","core_course/events","core/pending","core_courseformat/local/courseeditor/contenttree","jquery"],(function(_exports,_reactive,_modal_factory,_modal_events,_templates,_prefetch,_str,_normalise,_bulkselection,CourseEvents,_pending,_contenttree,_jquery){function _getRequireWildcardCache(nodeInterop){if("function"!=typeof WeakMap)return null;var cacheBabelInterop=new WeakMap,cacheNodeInterop=new WeakMap;return(_getRequireWildcardCache=function(nodeInterop){return nodeInterop?cacheNodeInterop:cacheBabelInterop})(nodeInterop)}function _interopRequireDefault(obj){return obj&&obj.__esModule?obj:{default:obj}} /** * Course state actions dispatcher. * @@ -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 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"};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,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})); //# 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 45751691b44..7e55ac3e80b 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 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 +{"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};\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 * @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","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","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","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/actions/bulkselection.min.js b/course/format/amd/build/local/content/actions/bulkselection.min.js new file mode 100644 index 00000000000..88d6c3d276d --- /dev/null +++ b/course/format/amd/build/local/content/actions/bulkselection.min.js @@ -0,0 +1,12 @@ +define("core_courseformat/local/content/actions/bulkselection",["exports"],(function(_exports){Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.toggleBulkSelectionAction=_exports.switchBulkSelection=_exports.selectAllBulk=_exports.checkAllBulkSelected=void 0; +/** + * Bulk selection auxiliar methods. + * + * @module core_courseformat/local/content/actions/bulkselection + * @class core_courseformat/local/content/actions/bulkselection + * @copyright 2023 Ferran Recio + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class BulkSelector{constructor(courseEditor){this.courseEditor=courseEditor,this.selectors={BULKCMCHECKBOX:"[data-bulkcheckbox][data-action='toggleSelectionCm']",BULKSECTIONCHECKBOX:"[data-bulkcheckbox][data-action='toggleSelectionSection']",CONTENT:"#region-main"}}processNewSelection(id,elementType,settings){const value=!this._isBulkSelected(id,elementType);settings.all&&settings.range?this.switchCurrentSelection():this._isSelectable(id,elementType)&&(settings.all?"cm"==elementType?this._updateBulkCmSiblings(id,value):this._updateBulkSelectionAll(elementType,value):settings.range?this._updateBulkSelectionRange(id,elementType,value):this._updateBulkSelection([id],elementType,value))}switchCurrentSelection(){const bulk=this.courseEditor.get("bulk");if(""===bulk.selectedType||0==bulk.selection.length)return;const newSelectedType="section"===bulk.selectedType?"cm":"section";let newSelectedIds;newSelectedIds="section"===bulk.selectedType?this._getCmIdsFromSections(bulk.selection):this._getSectionIdsFromCms(bulk.selection);const affectedIds=[];newSelectedIds.forEach((newId=>{this._getSelector(newId,newSelectedType)&&affectedIds.push(newId)})),this.courseEditor.dispatch("bulkEnable",!0),0!=affectedIds.length&&this._updateBulkSelection(affectedIds,newSelectedType,!0)}selectAll(value){const bulk=this.courseEditor.get("bulk");if(""==bulk.selectedType)return;if(!value)return void this.courseEditor.dispatch("bulkEnable",!0);const elementType=bulk.selectedType;this._updateBulkSelectionAll(elementType,value)}checkAllSelected(){const bulk=this.courseEditor.get("bulk");return""!=bulk.selectedType&&this._getContentCheckboxes(bulk.selectedType).every((bulkSelect=>{if(bulkSelect.disabled)return!1;if("section"==bulk.selectedType){if(0==this.courseEditor.get("section",bulkSelect.dataset.id).number)return!0}return bulk.selection.includes(bulkSelect.dataset.id)}))}_isBulkSelected(id,elementType){const bulk=this.courseEditor.get("bulk");return bulk.selectedType===elementType&&bulk.selection.includes(id)}_updateBulkSelection(ids,elementType,value){let mutation=elementType;mutation+=value?"Select":"Unselect",this.courseEditor.dispatch(mutation,ids)}_getContentCheckboxes(elementType){const selector="cm"==elementType?this.selectors.BULKCMCHECKBOX:this.selectors.BULKSECTIONCHECKBOX;return[...document.querySelectorAll("".concat(this.selectors.CONTENT," ").concat(selector))]}_isSelectable(id,elementType){const bulkSelect=this._getSelector(id,elementType);return!(!bulkSelect||bulkSelect.disabled)}_getSelector(id,elementType){let selector="cm"==elementType?this.selectors.BULKCMCHECKBOX:this.selectors.BULKSECTIONCHECKBOX;return selector+="[data-id='".concat(id,"']"),document.querySelector("".concat(this.selectors.CONTENT," ").concat(selector))}_updateBulkSelectionRange(id,elementType,value){const bulk=this.courseEditor.get("bulk");let lastSelectedId=bulk.selection.at(-1);if(bulk.selectedType!==elementType||lastSelectedId==id)return void this._updateBulkSelection([id],elementType,value);const affectedIds=[];let found=0;this._getContentCheckboxes(elementType).every((bulkSelect=>!!bulkSelect.disabled||(bulkSelect.dataset.id!=id&&bulkSelect.dataset.id!=lastSelectedId||found++,0==found||(affectedIds.push(bulkSelect.dataset.id),2!=found)))),this._updateBulkSelection(affectedIds,elementType,value)}_updateBulkCmSiblings(cmId,value){if("section"===this.courseEditor.get("bulk").selectedType)return;const cm=this.courseEditor.get("cm",cmId),section=this.courseEditor.get("section",cm.sectionid),affectedIds=[];section.cmlist.forEach((sectionCmId=>{this._isSelectable(sectionCmId,"cm")&&affectedIds.push(sectionCmId)})),this._updateBulkSelection(affectedIds,"cm",value)}_updateBulkSelectionAll(elementType,value){const affectedIds=[];this._getContentCheckboxes(elementType).forEach((bulkSelect=>{if(!bulkSelect.disabled){if("section"==elementType){const section=this.courseEditor.get("section",bulkSelect.dataset.id);if(0==(null==section?void 0:section.number))return}affectedIds.push(bulkSelect.dataset.id)}})),this._updateBulkSelection(affectedIds,elementType,value)}_getCmIdsFromSections(sectionIds){const result=[];return sectionIds.forEach((sectionId=>{const section=this.courseEditor.get("section",sectionId);result.push(...section.cmlist)})),result}_getSectionIdsFromCms(cmIds){const result=new Set;return cmIds.forEach((cmId=>{const cm=this.courseEditor.get("cm",cmId);0!=cm.sectionnumber&&result.add(cm.sectionid)})),[...result]}}_exports.toggleBulkSelectionAction=function(courseEditor,target,event,elementType){const id=target.dataset.id;if(!id)return;target.dataset.preventDefault&&event.preventDefault(),document.getSelection().removeAllRanges();new BulkSelector(courseEditor).processNewSelection(id,elementType,{range:event.shiftKey,all:event.altKey})};_exports.switchBulkSelection=function(courseEditor){new BulkSelector(courseEditor).switchCurrentSelection()};_exports.selectAllBulk=function(courseEditor,value){new BulkSelector(courseEditor).selectAll(value)};_exports.checkAllBulkSelected=function(courseEditor){return new BulkSelector(courseEditor).checkAllSelected()}})); + +//# sourceMappingURL=bulkselection.min.js.map \ No newline at end of file diff --git a/course/format/amd/build/local/content/actions/bulkselection.min.js.map b/course/format/amd/build/local/content/actions/bulkselection.min.js.map new file mode 100644 index 00000000000..dc1307009bb --- /dev/null +++ b/course/format/amd/build/local/content/actions/bulkselection.min.js.map @@ -0,0 +1 @@ +{"version":3,"file":"bulkselection.min.js","sources":["../../../../src/local/content/actions/bulkselection.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 * Bulk selection auxiliar methods.\n *\n * @module core_courseformat/local/content/actions/bulkselection\n * @class core_courseformat/local/content/actions/bulkselection\n * @copyright 2023 Ferran Recio \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\nclass BulkSelector {\n\n /**\n * The class constructor.\n * @param {CourseEditor} courseEditor the original actions component.\n */\n constructor(courseEditor) {\n this.courseEditor = courseEditor;\n this.selectors = {\n BULKCMCHECKBOX: `[data-bulkcheckbox][data-action='toggleSelectionCm']`,\n BULKSECTIONCHECKBOX: `[data-bulkcheckbox][data-action='toggleSelectionSection']`,\n CONTENT: `#region-main`,\n };\n }\n\n /**\n * Process a new selection.\n * @param {Number} id\n * @param {String} elementType cm or section\n * @param {Object} settings special selection settings\n * @param {Boolean} settings.all if the action is over all elements of the same type\n * @param {Boolean} settings.range if the action is over a range of elements\n */\n processNewSelection(id, elementType, settings) {\n const value = !this._isBulkSelected(id, elementType);\n if (settings.all && settings.range) {\n this.switchCurrentSelection();\n return;\n }\n if (!this._isSelectable(id, elementType)) {\n return;\n }\n if (settings.all) {\n if (elementType == 'cm') {\n this._updateBulkCmSiblings(id, value);\n } else {\n this._updateBulkSelectionAll(elementType, value);\n }\n return;\n }\n if (settings.range) {\n this._updateBulkSelectionRange(id, elementType, value);\n return;\n }\n this._updateBulkSelection([id], elementType, value);\n }\n\n /**\n * Switch between section and cm selection.\n */\n switchCurrentSelection() {\n const bulk = this.courseEditor.get('bulk');\n if (bulk.selectedType === '' || bulk.selection.length == 0) {\n return;\n }\n const newSelectedType = (bulk.selectedType === 'section') ? 'cm' : 'section';\n let newSelectedIds;\n if (bulk.selectedType === 'section') {\n newSelectedIds = this._getCmIdsFromSections(bulk.selection);\n } else {\n newSelectedIds = this._getSectionIdsFromCms(bulk.selection);\n }\n // Formats can display only a few activities of the section,\n // We need to select on the activities present in the page.\n const affectedIds = [];\n newSelectedIds.forEach(newId => {\n if (this._getSelector(newId, newSelectedType)) {\n affectedIds.push(newId);\n }\n });\n this.courseEditor.dispatch('bulkEnable', true);\n if (affectedIds.length != 0) {\n this._updateBulkSelection(affectedIds, newSelectedType, true);\n }\n }\n\n /**\n * Select all elements of the current type.\n * @param {Boolean} value the wanted selected value\n */\n selectAll(value) {\n const bulk = this.courseEditor.get('bulk');\n if (bulk.selectedType == '') {\n return;\n }\n if (!value) {\n this.courseEditor.dispatch('bulkEnable', true);\n return;\n }\n const elementType = bulk.selectedType;\n this._updateBulkSelectionAll(elementType, value);\n }\n\n /**\n * Checks if all selectable elements are selected.\n * @returns {Boolean} true if all are selected\n */\n checkAllSelected() {\n const bulk = this.courseEditor.get('bulk');\n if (bulk.selectedType == '') {\n return false;\n }\n return this._getContentCheckboxes(bulk.selectedType).every(bulkSelect => {\n if (bulkSelect.disabled) {\n return false;\n }\n // Section zero is never selectale for bulk actions.\n if (bulk.selectedType == 'section') {\n const section = this.courseEditor.get('section', bulkSelect.dataset.id);\n if (section.number == 0) {\n return true;\n }\n }\n return bulk.selection.includes(bulkSelect.dataset.id);\n });\n }\n\n /**\n * Check if the id is part of the current bulk selection.\n * @private\n * @param {Number} id\n * @param {String} elementType\n * @returns {Boolean} if the element is present in the current selection.\n */\n _isBulkSelected(id, elementType) {\n const bulk = this.courseEditor.get('bulk');\n if (bulk.selectedType !== elementType) {\n return false;\n }\n return bulk.selection.includes(id);\n }\n\n /**\n * Update the current bulk selection removing or adding Ids.\n * @private\n * @param {Number[]} ids the user selected element id\n * @param {String} elementType cm or section\n * @param {Boolean} value the wanted selected value\n */\n _updateBulkSelection(ids, elementType, value) {\n let mutation = elementType;\n mutation += (value) ? 'Select' : 'Unselect';\n this.courseEditor.dispatch(mutation, ids);\n }\n\n /**\n * Get all content bulk selector checkboxes of one type (section/cm).\n * @private\n * @param {String} elementType section or cm\n * @returns {HTMLElement[]} an array with all checkboxes\n */\n _getContentCheckboxes(elementType) {\n const selector = (elementType == 'cm') ? this.selectors.BULKCMCHECKBOX : this.selectors.BULKSECTIONCHECKBOX;\n const checkboxes = document.querySelectorAll(`${this.selectors.CONTENT} ${selector}`);\n // Converting to array because NodeList has less iteration methods.\n return [...checkboxes];\n }\n\n /**\n * Validate if an element is selectable in the current page.\n * @private\n * @param {Number} id the user selected element id\n * @param {String} elementType cm or section\n * @return {Boolean}\n */\n _isSelectable(id, elementType) {\n const bulkSelect = this._getSelector(id, elementType);\n if (!bulkSelect || bulkSelect.disabled) {\n return false;\n }\n return true;\n }\n\n /**\n * Get as specific element checkbox.\n * @private\n * @param {Number} id\n * @param {String} elementType cm or section\n * @returns {HTMLElement|undefined}\n */\n _getSelector(id, elementType) {\n let selector = (elementType == 'cm') ? this.selectors.BULKCMCHECKBOX : this.selectors.BULKSECTIONCHECKBOX;\n selector += `[data-id='${id}']`;\n return document.querySelector(`${this.selectors.CONTENT} ${selector}`);\n }\n\n /**\n * Update the current bulk selection when a user uses shift to select a range.\n * @private\n * @param {Number} id the user selected element id\n * @param {String} elementType cm or section\n * @param {Boolean} value the wanted selected value\n */\n _updateBulkSelectionRange(id, elementType, value) {\n const bulk = this.courseEditor.get('bulk');\n let lastSelectedId = bulk.selection.at(-1);\n if (bulk.selectedType !== elementType || lastSelectedId == id) {\n this._updateBulkSelection([id], elementType, value);\n return;\n }\n const affectedIds = [];\n let found = 0;\n this._getContentCheckboxes(elementType).every(bulkSelect => {\n if (bulkSelect.disabled) {\n return true;\n }\n if (bulkSelect.dataset.id == id || bulkSelect.dataset.id == lastSelectedId) {\n found++;\n }\n if (found == 0) {\n return true;\n }\n affectedIds.push(bulkSelect.dataset.id);\n return found != 2;\n });\n this._updateBulkSelection(affectedIds, elementType, value);\n }\n\n /**\n * Select or unselect all cm siblings.\n * @private\n * @param {Number} cmId the user selected element id\n * @param {Boolean} value the wanted selected value\n */\n _updateBulkCmSiblings(cmId, value) {\n const bulk = this.courseEditor.get('bulk');\n if (bulk.selectedType === 'section') {\n return;\n }\n const cm = this.courseEditor.get('cm', cmId);\n const section = this.courseEditor.get('section', cm.sectionid);\n // Formats can display only a few activities of the section,\n // We need to select on the activities selectable in the page.\n const affectedIds = [];\n section.cmlist.forEach(sectionCmId => {\n if (this._isSelectable(sectionCmId, 'cm')) {\n affectedIds.push(sectionCmId);\n }\n });\n this._updateBulkSelection(affectedIds, 'cm', value);\n }\n\n /**\n * Select or unselects al elements of the same type.\n * @private\n * @param {String} elementType section or cm\n * @param {Boolean} value if the elements must be selected or unselected.\n */\n _updateBulkSelectionAll(elementType, value) {\n const affectedIds = [];\n this._getContentCheckboxes(elementType).forEach(bulkSelect => {\n if (bulkSelect.disabled) {\n return;\n }\n if (elementType == 'section') {\n const section = this.courseEditor.get('section', bulkSelect.dataset.id);\n if (section?.number == 0) {\n return;\n }\n }\n affectedIds.push(bulkSelect.dataset.id);\n });\n this._updateBulkSelection(affectedIds, elementType, value);\n }\n\n /**\n * Get all cm ids from a specific section ids.\n * @private\n * @param {Number[]} sectionIds\n * @returns {Number[]} the cm ids\n */\n _getCmIdsFromSections(sectionIds) {\n const result = [];\n sectionIds.forEach(sectionId => {\n const section = this.courseEditor.get('section', sectionId);\n result.push(...section.cmlist);\n });\n return result;\n }\n\n /**\n * Get all section ids containing a specific cm ids.\n * @private\n * @param {Number[]} cmIds\n * @returns {Number[]} the section ids\n */\n _getSectionIdsFromCms(cmIds) {\n const result = new Set();\n cmIds.forEach(cmId => {\n const cm = this.courseEditor.get('cm', cmId);\n if (cm.sectionnumber == 0) {\n return;\n }\n result.add(cm.sectionid);\n });\n return [...result];\n }\n}\n\n/**\n * Process a bulk selection toggle action.\n * @method\n * @param {CourseEditor} courseEditor\n * @param {HTMLElement} target the action element\n * @param {Event} event\n * @param {String} elementType cm or section\n */\nexport const toggleBulkSelectionAction = function(courseEditor, target, event, elementType) {\n const id = target.dataset.id;\n if (!id) {\n return;\n }\n // When the action cames from a form element (checkbox) we should not preventDefault.\n // If we do it the changechecker module will execute the state change twice.\n if (target.dataset.preventDefault) {\n event.preventDefault();\n }\n // Using shift or alt key can produce text selection.\n document.getSelection().removeAllRanges();\n\n const bulkSelector = new BulkSelector(courseEditor);\n bulkSelector.processNewSelection(\n id,\n elementType,\n {\n range: event.shiftKey,\n all: event.altKey,\n }\n );\n};\n\n/**\n * Switch the current bulk selection.\n * @method\n * @param {CourseEditor} courseEditor\n */\nexport const switchBulkSelection = function(courseEditor) {\n const bulkSelector = new BulkSelector(courseEditor);\n bulkSelector.switchCurrentSelection();\n};\n\n/**\n * Select/unselect all element of the selected type.\n * @method\n * @param {CourseEditor} courseEditor\n * @param {Boolean} value if the elements must be selected or unselected.\n */\nexport const selectAllBulk = function(courseEditor, value) {\n const bulkSelector = new BulkSelector(courseEditor);\n bulkSelector.selectAll(value);\n};\n\n/**\n * Check if all possible elements are selected.\n * @method\n * @param {CourseEditor} courseEditor\n * @return {Boolean} if all elements of the current type are selected.\n */\nexport const checkAllBulkSelected = function(courseEditor) {\n const bulkSelector = new BulkSelector(courseEditor);\n return bulkSelector.checkAllSelected();\n};\n"],"names":["BulkSelector","constructor","courseEditor","selectors","BULKCMCHECKBOX","BULKSECTIONCHECKBOX","CONTENT","processNewSelection","id","elementType","settings","value","this","_isBulkSelected","all","range","switchCurrentSelection","_isSelectable","_updateBulkCmSiblings","_updateBulkSelectionAll","_updateBulkSelectionRange","_updateBulkSelection","bulk","get","selectedType","selection","length","newSelectedType","newSelectedIds","_getCmIdsFromSections","_getSectionIdsFromCms","affectedIds","forEach","newId","_getSelector","push","dispatch","selectAll","checkAllSelected","_getContentCheckboxes","every","bulkSelect","disabled","dataset","number","includes","ids","mutation","selector","document","querySelectorAll","querySelector","lastSelectedId","at","found","cmId","cm","section","sectionid","cmlist","sectionCmId","sectionIds","result","sectionId","cmIds","Set","sectionnumber","add","target","event","preventDefault","getSelection","removeAllRanges","shiftKey","altKey"],"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 e05e6654c7a..ea2702a0f51 100644 --- a/course/format/amd/build/local/content/bulkedittools.min.js +++ b/course/format/amd/build/local/content/bulkedittools.min.js @@ -1,4 +1,4 @@ -define("core_courseformat/local/content/bulkedittools",["exports","core/reactive","core/sticky-footer","core_courseformat/courseeditor","core/str","core/pending","core/prefetch"],(function(_exports,_reactive,_stickyFooter,_courseeditor,_str,_pending,_prefetch){var obj; +define("core_courseformat/local/content/bulkedittools",["exports","core/reactive","core/sticky-footer","core_courseformat/courseeditor","core/str","core/pending","core/prefetch","core_courseformat/local/content/actions/bulkselection"],(function(_exports,_reactive,_stickyFooter,_courseeditor,_str,_pending,_prefetch,_bulkselection){var obj; /** * The bulk editor tools bar. * @@ -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 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})); + */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,"click",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;selectall.disabled=""===bulk.selectedType;const pending=new _pending.default("courseformat/bulktools:refreshSelectAll");setTimeout((()=>{selectall.checked=(0,_bulkselection.checkAllBulkSelected)(this.reactive),pending.resolve()}),100)}_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){event.preventDefault(),event.altKey?(0,_bulkselection.switchBulkSelection)(this.reactive):(0,_bulkselection.checkAllBulkSelected)(this.reactive)?this._handleUnselectAll():(0,_bulkselection.selectAllBulk)(this.reactive,!0)}_handleUnselectAll(){const pending=new _pending.default("courseformat/content:bulktUnselectAll");(0,_bulkselection.selectAllBulk)(this.reactive,!1),setTimeout((()=>{var _document$querySelect2;null===(_document$querySelect2=document.querySelector(this.selectors.SELECTABLE))||void 0===_document$querySelect2||_document$querySelect2.focus(),pending.resolve()}),150)}}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 fc9b7d1c986..fd5d5eca214 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 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 +{"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';\nimport {\n selectAllBulk,\n switchBulkSelection,\n checkAllBulkSelected\n} from 'core_courseformat/local/content/actions/bulkselection';\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, 'click', 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 selectall.disabled = (bulk.selectedType === '');\n // The changechecker module can prevent the checkbox form changing it's value.\n // To avoid that we leave the sniffer to act before changing the value.\n const pending = new Pending(`courseformat/bulktools:refreshSelectAll`);\n setTimeout(\n () => {\n selectall.checked = checkAllBulkSelected(this.reactive);\n pending.resolve();\n },\n 100\n );\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 * Handle special select all cases.\n * @param {Event} event\n */\n _selectAllClick(event) {\n event.preventDefault();\n if (event.altKey) {\n switchBulkSelection(this.reactive);\n return;\n }\n if (checkAllBulkSelected(this.reactive)) {\n this._handleUnselectAll();\n return;\n }\n selectAllBulk(this.reactive, true);\n }\n\n /**\n * Process unselect all elements.\n */\n _handleUnselectAll() {\n const pending = new Pending(`courseformat/content:bulktUnselectAll`);\n selectAllBulk(this.reactive, false);\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"],"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","disabled","selectedType","pending","Pending","setTimeout","checked","resolve","displayType","getElements","forEach","action","classList","toggle","tabIndex","actionTool","closest","isHidden","dataset","dispatch","focus","event","preventDefault","altKey","_handleUnselectAll"],"mappings":";;;;;;;;6KAsCI,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,QAASV,KAAKW,iBASvDC,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,iBAGLA,UAAUC,SAAkC,KAAtBR,KAAKS,mBAGrBC,QAAU,IAAIC,4DACpBC,YACI,KACIL,UAAUM,SAAU,uCAAqBnC,KAAKI,UAC9C4B,QAAQI,YAEZ,KAURf,2BAAiBpB,QAASqB,kBAEhBe,YAAoC,WAArBf,KAAKS,aAA6B,UAAY,KAC7Dd,QAAiC,KAAtBK,KAAKS,kBACjBO,YAAYtC,KAAKZ,UAAUC,SAASkD,SAAQC,SAC7CA,OAAOC,UAAUC,OAAO1C,KAAKJ,QAAQE,UAAWmB,SAChDuB,OAAOG,SAAY1B,QAAW,GAAK,QAE7B2B,WAAaJ,OAAOK,QAAQ7C,KAAKZ,UAAUE,YAC3CwD,SAAYN,OAAOO,QAAQzB,MAAQe,YACzCO,MAAAA,YAAAA,WAAYH,UAAUC,OAAO1C,KAAKJ,QAAQC,KAAMiD,aAOxDrC,oBACUuB,QAAU,IAAIC,6DACf7B,SAAS4C,SAAS,cAAc,GAErCd,YAAW,6DACPhC,SAASC,cAAcH,KAAKZ,UAAUO,iEAAUsD,QAChDjB,QAAQI,YACT,KAOPzB,gBAAgBuC,OACZA,MAAMC,iBACFD,MAAME,8CACcpD,KAAKI,WAGzB,uCAAqBJ,KAAKI,eACrBiD,sDAGKrD,KAAKI,UAAU,GAMjCiD,2BACUrB,QAAU,IAAIC,2FACNjC,KAAKI,UAAU,GAE7B8B,YAAW,+DACPhC,SAASC,cAAcH,KAAKZ,UAAUK,sEAAawD,QACnDjB,QAAQI,YACT"} \ No newline at end of file diff --git a/course/format/amd/build/local/content/section/cmitem.min.js b/course/format/amd/build/local/content/section/cmitem.min.js index 5dadc8d7944..b94fdae26d3 100644 --- a/course/format/amd/build/local/content/section/cmitem.min.js +++ b/course/format/amd/build/local/content/section/cmitem.min.js @@ -8,6 +8,6 @@ define("core_courseformat/local/content/section/cmitem",["exports","core_coursef * @class core_courseformat/local/content/section/cmitem * @copyright 2021 Ferran Recio * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.default=void 0,_dndcmitem=(obj=_dndcmitem)&&obj.__esModule?obj:{default:obj};class _default extends _dndcmitem.default{create(){this.name="content_section_cmitem",this.selectors={BULKSELECT:"[data-for='cmBulkSelect']",BULKCHECKBOX:"[data-bulkcheckbox]",CARD:".activity-item",DRAGICON:".editing_move",INPLACEEDITABLE:"[data-inplaceeditablelink]"},this.classes={LOCKED:"editinprogress",HIDE:"d-none",SELECTED:"selected"},this.id=this.element.dataset.id}stateReady(state){var _this$getElement;this.configDragDrop(this.id),null===(_this$getElement=this.getElement(this.selectors.DRAGICON))||void 0===_this$getElement||_this$getElement.classList.add(this.classes.DRAGICON),this._refreshBulk({state:state}),this.addEventListener(this.element,"click",this._handleBulkModeClick)}getWatchers(){return[{watch:"cm[".concat(this.id,"]:deleted"),handler:this.unregister},{watch:"cm[".concat(this.id,"]:updated"),handler:this._refreshCm},{watch:"bulk:updated",handler:this._refreshBulk}]}_refreshCm(_ref){var _element$dragging,_element$locked;let{element:element}=_ref;this.element.classList.toggle(this.classes.DRAGGING,null!==(_element$dragging=element.dragging)&&void 0!==_element$dragging&&_element$dragging),this.element.classList.toggle(this.classes.LOCKED,null!==(_element$locked=element.locked)&&void 0!==_element$locked&&_element$locked),this.locked=element.locked}_refreshBulk(_ref2){var _this$getElement2;let{state:state}=_ref2;const bulk=state.bulk;this.setDraggable(!bulk.enabled),null===(_this$getElement2=this.getElement(this.selectors.BULKSELECT))||void 0===_this$getElement2||_this$getElement2.classList.toggle(this.classes.HIDE,!bulk.enabled);const disabled=!this._isCmBulkEnabled(bulk),selected=this._isSelected(bulk);this._refreshActivityCard(bulk,selected),this._setCheckboxValue(selected,disabled)}_refreshActivityCard(bulk,selected){var _this$getElement3,_this$getElement4;null===(_this$getElement3=this.getElement(this.selectors.INPLACEEDITABLE))||void 0===_this$getElement3||_this$getElement3.classList.toggle(this.classes.HIDE,bulk.enabled),null===(_this$getElement4=this.getElement(this.selectors.CARD))||void 0===_this$getElement4||_this$getElement4.classList.toggle(this.classes.SELECTED,selected),this.element.classList.toggle(this.classes.SELECTED,selected)}_setCheckboxValue(checked,disabled){const checkbox=this.getElement(this.selectors.BULKCHECKBOX);checkbox&&(checkbox.checked=checked,checkbox.disabled=disabled,disabled?checkbox.removeAttribute("data-is-selectable"):checkbox.dataset.isSelectable=1)}_handleBulkModeClick(event){if(event.target.closest(this.selectors.BULKSELECT))return;const bulk=this.reactive.get("bulk");if(!this._isCmBulkEnabled(bulk))return;event.preventDefault();const mutation=this._isSelected(bulk)?"cmUnselect":"cmSelect";this.reactive.dispatch(mutation,[this.id])}_isCmBulkEnabled(bulk){return!!bulk.enabled&&(""===bulk.selectedType||"cm"===bulk.selectedType)}_isSelected(bulk){return"cm"===bulk.selectedType&&bulk.selection.includes(this.id)}}return _exports.default=_default,_exports.default})); + */Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.default=void 0,_dndcmitem=(obj=_dndcmitem)&&obj.__esModule?obj:{default:obj};class _default extends _dndcmitem.default{create(){this.name="content_section_cmitem",this.selectors={BULKSELECT:"[data-for='cmBulkSelect']",BULKCHECKBOX:"[data-bulkcheckbox]",CARD:".activity-item",DRAGICON:".editing_move",INPLACEEDITABLE:"[data-inplaceeditablelink]"},this.classes={LOCKED:"editinprogress",HIDE:"d-none",SELECTED:"selected"},this.id=this.element.dataset.id}stateReady(state){var _this$getElement;this.configDragDrop(this.id),null===(_this$getElement=this.getElement(this.selectors.DRAGICON))||void 0===_this$getElement||_this$getElement.classList.add(this.classes.DRAGICON),this._refreshBulk({state:state})}getWatchers(){return[{watch:"cm[".concat(this.id,"]:deleted"),handler:this.unregister},{watch:"cm[".concat(this.id,"]:updated"),handler:this._refreshCm},{watch:"bulk:updated",handler:this._refreshBulk}]}_refreshCm(_ref){var _element$dragging,_element$locked;let{element:element}=_ref;this.element.classList.toggle(this.classes.DRAGGING,null!==(_element$dragging=element.dragging)&&void 0!==_element$dragging&&_element$dragging),this.element.classList.toggle(this.classes.LOCKED,null!==(_element$locked=element.locked)&&void 0!==_element$locked&&_element$locked),this.locked=element.locked}_refreshBulk(_ref2){var _this$getElement2;let{state:state}=_ref2;const bulk=state.bulk;this.setDraggable(!bulk.enabled),bulk.enabled?(this.element.dataset.action="toggleSelectionCm",this.element.dataset.preventDefault=1):(this.element.removeAttribute("data-action"),this.element.removeAttribute("data-preventDefault")),null===(_this$getElement2=this.getElement(this.selectors.BULKSELECT))||void 0===_this$getElement2||_this$getElement2.classList.toggle(this.classes.HIDE,!bulk.enabled);const disabled=!this._isCmBulkEnabled(bulk),selected=this._isSelected(bulk);this._refreshActivityCard(bulk,selected),this._setCheckboxValue(selected,disabled)}_refreshActivityCard(bulk,selected){var _this$getElement3,_this$getElement4;null===(_this$getElement3=this.getElement(this.selectors.INPLACEEDITABLE))||void 0===_this$getElement3||_this$getElement3.classList.toggle(this.classes.HIDE,bulk.enabled),null===(_this$getElement4=this.getElement(this.selectors.CARD))||void 0===_this$getElement4||_this$getElement4.classList.toggle(this.classes.SELECTED,selected),this.element.classList.toggle(this.classes.SELECTED,selected)}_setCheckboxValue(checked,disabled){const checkbox=this.getElement(this.selectors.BULKCHECKBOX);checkbox&&(checkbox.checked=checked,checkbox.disabled=disabled,disabled?checkbox.removeAttribute("data-is-selectable"):checkbox.dataset.isSelectable=1)}_isCmBulkEnabled(bulk){return!!bulk.enabled&&(""===bulk.selectedType||"cm"===bulk.selectedType)}_isSelected(bulk){return"cm"===bulk.selectedType&&bulk.selection.includes(this.id)}}return _exports.default=_default,_exports.default})); //# sourceMappingURL=cmitem.min.js.map \ No newline at end of file diff --git a/course/format/amd/build/local/content/section/cmitem.min.js.map b/course/format/amd/build/local/content/section/cmitem.min.js.map index 559f7b5952d..1a1be46227d 100644 --- a/course/format/amd/build/local/content/section/cmitem.min.js.map +++ b/course/format/amd/build/local/content/section/cmitem.min.js.map @@ -1 +1 @@ -{"version":3,"file":"cmitem.min.js","sources":["../../../../src/local/content/section/cmitem.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * Course course module item component.\n *\n * This component is used to control specific course modules interactions like drag and drop.\n *\n * @module core_courseformat/local/content/section/cmitem\n * @class core_courseformat/local/content/section/cmitem\n * @copyright 2021 Ferran Recio \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport DndCmItem from 'core_courseformat/local/courseeditor/dndcmitem';\n\nexport default class extends DndCmItem {\n\n /**\n * Constructor hook.\n */\n create() {\n // Optional component name for debugging.\n this.name = 'content_section_cmitem';\n // Default query selectors.\n this.selectors = {\n BULKSELECT: `[data-for='cmBulkSelect']`,\n BULKCHECKBOX: `[data-bulkcheckbox]`,\n CARD: `.activity-item`,\n DRAGICON: `.editing_move`,\n INPLACEEDITABLE: `[data-inplaceeditablelink]`,\n };\n // Most classes will be loaded later by DndCmItem.\n this.classes = {\n LOCKED: 'editinprogress',\n HIDE: 'd-none',\n SELECTED: 'selected',\n };\n // We need our id to watch specific events.\n this.id = this.element.dataset.id;\n }\n\n /**\n * Initial state ready method.\n * @param {Object} state the state data\n */\n stateReady(state) {\n this.configDragDrop(this.id);\n this.getElement(this.selectors.DRAGICON)?.classList.add(this.classes.DRAGICON);\n this._refreshBulk({state});\n this.addEventListener(this.element, 'click', this._handleBulkModeClick);\n }\n\n /**\n * Component watchers.\n *\n * @returns {Array} of watchers\n */\n getWatchers() {\n return [\n {watch: `cm[${this.id}]:deleted`, handler: this.unregister},\n {watch: `cm[${this.id}]:updated`, handler: this._refreshCm},\n {watch: `bulk:updated`, handler: this._refreshBulk},\n ];\n }\n\n /**\n * Update a course index cm using the state information.\n *\n * @param {object} param\n * @param {Object} param.element details the update details.\n */\n _refreshCm({element}) {\n // Update classes.\n this.element.classList.toggle(this.classes.DRAGGING, element.dragging ?? false);\n this.element.classList.toggle(this.classes.LOCKED, element.locked ?? false);\n this.locked = element.locked;\n }\n\n /**\n * Update the bulk editing interface.\n *\n * @param {object} param\n * @param {Object} param.state the state data\n */\n _refreshBulk({state}) {\n const bulk = state.bulk;\n // For now, dragging elements in bulk is not possible.\n this.setDraggable(!bulk.enabled);\n\n this.getElement(this.selectors.BULKSELECT)?.classList.toggle(this.classes.HIDE, !bulk.enabled);\n\n const disabled = !this._isCmBulkEnabled(bulk);\n const selected = this._isSelected(bulk);\n this._refreshActivityCard(bulk, selected);\n this._setCheckboxValue(selected, disabled);\n }\n\n /**\n * Update the activity card depending on the bulk selection.\n *\n * @param {Object} bulk the current bulk state data\n * @param {Boolean} selected if the activity is selected.\n */\n _refreshActivityCard(bulk, selected) {\n this.getElement(this.selectors.INPLACEEDITABLE)?.classList.toggle(this.classes.HIDE, bulk.enabled);\n this.getElement(this.selectors.CARD)?.classList.toggle(this.classes.SELECTED, selected);\n this.element.classList.toggle(this.classes.SELECTED, selected);\n }\n\n /**\n * Modify the checkbox element.\n * @param {Boolean} checked the new checked value\n * @param {Boolean} disabled the new disabled value\n */\n _setCheckboxValue(checked, disabled) {\n const checkbox = this.getElement(this.selectors.BULKCHECKBOX);\n if (!checkbox) {\n return;\n }\n checkbox.checked = checked;\n checkbox.disabled = disabled;\n // Is selectable is used to easily scan the page for bulk checkboxes.\n if (disabled) {\n checkbox.removeAttribute('data-is-selectable');\n } else {\n checkbox.dataset.isSelectable = 1;\n }\n }\n\n /**\n * Handle the activity card click in bulk mode.\n * @param {Event} event the click event\n */\n _handleBulkModeClick(event) {\n const selectElement = event.target.closest(this.selectors.BULKSELECT);\n if (selectElement) {\n // The select element checkbox execute a normal content action as\n // any regular action button. This is because the chengechecker module\n // is sniffing any form element and will with the checked value\n // changing it twice.\n return;\n }\n const bulk = this.reactive.get('bulk');\n if (!this._isCmBulkEnabled(bulk)) {\n return;\n }\n event.preventDefault();\n const mutation = (this._isSelected(bulk)) ? 'cmUnselect' : 'cmSelect';\n this.reactive.dispatch(mutation, [this.id]);\n }\n\n /**\n * Check if cm bulk selection is available.\n * @param {Object} bulk the current state bulk attribute\n * @returns {Boolean}\n */\n _isCmBulkEnabled(bulk) {\n if (!bulk.enabled) {\n return false;\n }\n return (bulk.selectedType === '' || bulk.selectedType === 'cm');\n }\n\n /**\n * Check if the cm id is part of the current bulk selection.\n * @param {Object} bulk the current state bulk attribute\n * @returns {Boolean}\n */\n _isSelected(bulk) {\n if (bulk.selectedType !== 'cm') {\n return false;\n }\n return bulk.selection.includes(this.id);\n }\n}\n"],"names":["DndCmItem","create","name","selectors","BULKSELECT","BULKCHECKBOX","CARD","DRAGICON","INPLACEEDITABLE","classes","LOCKED","HIDE","SELECTED","id","this","element","dataset","stateReady","state","configDragDrop","getElement","classList","add","_refreshBulk","addEventListener","_handleBulkModeClick","getWatchers","watch","handler","unregister","_refreshCm","toggle","DRAGGING","dragging","locked","bulk","setDraggable","enabled","disabled","_isCmBulkEnabled","selected","_isSelected","_refreshActivityCard","_setCheckboxValue","checked","checkbox","removeAttribute","isSelectable","event","target","closest","reactive","get","preventDefault","mutation","dispatch","selectedType","selection","includes"],"mappings":";;;;;;;;;;0KA4B6BA,mBAKzBC,cAESC,KAAO,8BAEPC,UAAY,CACbC,uCACAC,mCACAC,sBACAC,yBACAC,mDAGCC,QAAU,CACXC,OAAQ,iBACRC,KAAM,SACNC,SAAU,iBAGTC,GAAKC,KAAKC,QAAQC,QAAQH,GAOnCI,WAAWC,iCACFC,eAAeL,KAAKD,kCACpBO,WAAWN,KAAKX,UAAUI,wDAAWc,UAAUC,IAAIR,KAAKL,QAAQF,eAChEgB,aAAa,CAACL,MAAAA,aACdM,iBAAiBV,KAAKC,QAAS,QAASD,KAAKW,sBAQtDC,oBACW,CACH,CAACC,mBAAab,KAAKD,gBAAee,QAASd,KAAKe,YAChD,CAACF,mBAAab,KAAKD,gBAAee,QAASd,KAAKgB,YAChD,CAACH,qBAAuBC,QAASd,KAAKS,eAU9CO,2DAAWf,QAACA,mBAEHA,QAAQM,UAAUU,OAAOjB,KAAKL,QAAQuB,mCAAUjB,QAAQkB,+DACxDlB,QAAQM,UAAUU,OAAOjB,KAAKL,QAAQC,+BAAQK,QAAQmB,yDACtDA,OAASnB,QAAQmB,OAS1BX,8CAAaL,MAACA,mBACJiB,KAAOjB,MAAMiB,UAEdC,cAAcD,KAAKE,wCAEnBjB,WAAWN,KAAKX,UAAUC,4DAAaiB,UAAUU,OAAOjB,KAAKL,QAAQE,MAAOwB,KAAKE,eAEhFC,UAAYxB,KAAKyB,iBAAiBJ,MAClCK,SAAW1B,KAAK2B,YAAYN,WAC7BO,qBAAqBP,KAAMK,eAC3BG,kBAAkBH,SAAUF,UASrCI,qBAAqBP,KAAMK,iFAClBpB,WAAWN,KAAKX,UAAUK,iEAAkBa,UAAUU,OAAOjB,KAAKL,QAAQE,KAAMwB,KAAKE,wCACrFjB,WAAWN,KAAKX,UAAUG,sDAAOe,UAAUU,OAAOjB,KAAKL,QAAQG,SAAU4B,eACzEzB,QAAQM,UAAUU,OAAOjB,KAAKL,QAAQG,SAAU4B,UAQzDG,kBAAkBC,QAASN,gBACjBO,SAAW/B,KAAKM,WAAWN,KAAKX,UAAUE,cAC3CwC,WAGLA,SAASD,QAAUA,QACnBC,SAASP,SAAWA,SAEhBA,SACAO,SAASC,gBAAgB,sBAEzBD,SAAS7B,QAAQ+B,aAAe,GAQxCtB,qBAAqBuB,UACKA,MAAMC,OAAOC,QAAQpC,KAAKX,UAAUC,yBAQpD+B,KAAOrB,KAAKqC,SAASC,IAAI,YAC1BtC,KAAKyB,iBAAiBJ,aAG3Ba,MAAMK,uBACAC,SAAYxC,KAAK2B,YAAYN,MAAS,aAAe,gBACtDgB,SAASI,SAASD,SAAU,CAACxC,KAAKD,KAQ3C0B,iBAAiBJ,cACRA,KAAKE,UAGoB,KAAtBF,KAAKqB,cAA6C,OAAtBrB,KAAKqB,cAQ7Cf,YAAYN,YACkB,OAAtBA,KAAKqB,cAGFrB,KAAKsB,UAAUC,SAAS5C,KAAKD"} \ No newline at end of file +{"version":3,"file":"cmitem.min.js","sources":["../../../../src/local/content/section/cmitem.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * Course course module item component.\n *\n * This component is used to control specific course modules interactions like drag and drop.\n *\n * @module core_courseformat/local/content/section/cmitem\n * @class core_courseformat/local/content/section/cmitem\n * @copyright 2021 Ferran Recio \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport DndCmItem from 'core_courseformat/local/courseeditor/dndcmitem';\n\nexport default class extends DndCmItem {\n\n /**\n * Constructor hook.\n */\n create() {\n // Optional component name for debugging.\n this.name = 'content_section_cmitem';\n // Default query selectors.\n this.selectors = {\n BULKSELECT: `[data-for='cmBulkSelect']`,\n BULKCHECKBOX: `[data-bulkcheckbox]`,\n CARD: `.activity-item`,\n DRAGICON: `.editing_move`,\n INPLACEEDITABLE: `[data-inplaceeditablelink]`,\n };\n // Most classes will be loaded later by DndCmItem.\n this.classes = {\n LOCKED: 'editinprogress',\n HIDE: 'd-none',\n SELECTED: 'selected',\n };\n // We need our id to watch specific events.\n this.id = this.element.dataset.id;\n }\n\n /**\n * Initial state ready method.\n * @param {Object} state the state data\n */\n stateReady(state) {\n this.configDragDrop(this.id);\n this.getElement(this.selectors.DRAGICON)?.classList.add(this.classes.DRAGICON);\n this._refreshBulk({state});\n }\n\n /**\n * Component watchers.\n *\n * @returns {Array} of watchers\n */\n getWatchers() {\n return [\n {watch: `cm[${this.id}]:deleted`, handler: this.unregister},\n {watch: `cm[${this.id}]:updated`, handler: this._refreshCm},\n {watch: `bulk:updated`, handler: this._refreshBulk},\n ];\n }\n\n /**\n * Update a course index cm using the state information.\n *\n * @param {object} param\n * @param {Object} param.element details the update details.\n */\n _refreshCm({element}) {\n // Update classes.\n this.element.classList.toggle(this.classes.DRAGGING, element.dragging ?? false);\n this.element.classList.toggle(this.classes.LOCKED, element.locked ?? false);\n this.locked = element.locked;\n }\n\n /**\n * Update the bulk editing interface.\n *\n * @param {object} param\n * @param {Object} param.state the state data\n */\n _refreshBulk({state}) {\n const bulk = state.bulk;\n // For now, dragging elements in bulk is not possible.\n this.setDraggable(!bulk.enabled);\n // Convert the card into an active element in bulk mode.\n if (bulk.enabled) {\n this.element.dataset.action = 'toggleSelectionCm';\n this.element.dataset.preventDefault = 1;\n } else {\n this.element.removeAttribute('data-action');\n this.element.removeAttribute('data-preventDefault');\n }\n\n this.getElement(this.selectors.BULKSELECT)?.classList.toggle(this.classes.HIDE, !bulk.enabled);\n\n const disabled = !this._isCmBulkEnabled(bulk);\n const selected = this._isSelected(bulk);\n this._refreshActivityCard(bulk, selected);\n this._setCheckboxValue(selected, disabled);\n }\n\n /**\n * Update the activity card depending on the bulk selection.\n *\n * @param {Object} bulk the current bulk state data\n * @param {Boolean} selected if the activity is selected.\n */\n _refreshActivityCard(bulk, selected) {\n this.getElement(this.selectors.INPLACEEDITABLE)?.classList.toggle(this.classes.HIDE, bulk.enabled);\n this.getElement(this.selectors.CARD)?.classList.toggle(this.classes.SELECTED, selected);\n this.element.classList.toggle(this.classes.SELECTED, selected);\n }\n\n /**\n * Modify the checkbox element.\n * @param {Boolean} checked the new checked value\n * @param {Boolean} disabled the new disabled value\n */\n _setCheckboxValue(checked, disabled) {\n const checkbox = this.getElement(this.selectors.BULKCHECKBOX);\n if (!checkbox) {\n return;\n }\n checkbox.checked = checked;\n checkbox.disabled = disabled;\n // Is selectable is used to easily scan the page for bulk checkboxes.\n if (disabled) {\n checkbox.removeAttribute('data-is-selectable');\n } else {\n checkbox.dataset.isSelectable = 1;\n }\n }\n\n /**\n * Check if cm bulk selection is available.\n * @param {Object} bulk the current state bulk attribute\n * @returns {Boolean}\n */\n _isCmBulkEnabled(bulk) {\n if (!bulk.enabled) {\n return false;\n }\n return (bulk.selectedType === '' || bulk.selectedType === 'cm');\n }\n\n /**\n * Check if the cm id is part of the current bulk selection.\n * @param {Object} bulk the current state bulk attribute\n * @returns {Boolean}\n */\n _isSelected(bulk) {\n if (bulk.selectedType !== 'cm') {\n return false;\n }\n return bulk.selection.includes(this.id);\n }\n}\n"],"names":["DndCmItem","create","name","selectors","BULKSELECT","BULKCHECKBOX","CARD","DRAGICON","INPLACEEDITABLE","classes","LOCKED","HIDE","SELECTED","id","this","element","dataset","stateReady","state","configDragDrop","getElement","classList","add","_refreshBulk","getWatchers","watch","handler","unregister","_refreshCm","toggle","DRAGGING","dragging","locked","bulk","setDraggable","enabled","action","preventDefault","removeAttribute","disabled","_isCmBulkEnabled","selected","_isSelected","_refreshActivityCard","_setCheckboxValue","checked","checkbox","isSelectable","selectedType","selection","includes"],"mappings":";;;;;;;;;;0KA4B6BA,mBAKzBC,cAESC,KAAO,8BAEPC,UAAY,CACbC,uCACAC,mCACAC,sBACAC,yBACAC,mDAGCC,QAAU,CACXC,OAAQ,iBACRC,KAAM,SACNC,SAAU,iBAGTC,GAAKC,KAAKC,QAAQC,QAAQH,GAOnCI,WAAWC,iCACFC,eAAeL,KAAKD,kCACpBO,WAAWN,KAAKX,UAAUI,wDAAWc,UAAUC,IAAIR,KAAKL,QAAQF,eAChEgB,aAAa,CAACL,MAAAA,QAQvBM,oBACW,CACH,CAACC,mBAAaX,KAAKD,gBAAea,QAASZ,KAAKa,YAChD,CAACF,mBAAaX,KAAKD,gBAAea,QAASZ,KAAKc,YAChD,CAACH,qBAAuBC,QAASZ,KAAKS,eAU9CK,2DAAWb,QAACA,mBAEHA,QAAQM,UAAUQ,OAAOf,KAAKL,QAAQqB,mCAAUf,QAAQgB,+DACxDhB,QAAQM,UAAUQ,OAAOf,KAAKL,QAAQC,+BAAQK,QAAQiB,yDACtDA,OAASjB,QAAQiB,OAS1BT,8CAAaL,MAACA,mBACJe,KAAOf,MAAMe,UAEdC,cAAcD,KAAKE,SAEpBF,KAAKE,cACApB,QAAQC,QAAQoB,OAAS,yBACzBrB,QAAQC,QAAQqB,eAAiB,SAEjCtB,QAAQuB,gBAAgB,oBACxBvB,QAAQuB,gBAAgB,uDAG5BlB,WAAWN,KAAKX,UAAUC,4DAAaiB,UAAUQ,OAAOf,KAAKL,QAAQE,MAAOsB,KAAKE,eAEhFI,UAAYzB,KAAK0B,iBAAiBP,MAClCQ,SAAW3B,KAAK4B,YAAYT,WAC7BU,qBAAqBV,KAAMQ,eAC3BG,kBAAkBH,SAAUF,UASrCI,qBAAqBV,KAAMQ,iFAClBrB,WAAWN,KAAKX,UAAUK,iEAAkBa,UAAUQ,OAAOf,KAAKL,QAAQE,KAAMsB,KAAKE,wCACrFf,WAAWN,KAAKX,UAAUG,sDAAOe,UAAUQ,OAAOf,KAAKL,QAAQG,SAAU6B,eACzE1B,QAAQM,UAAUQ,OAAOf,KAAKL,QAAQG,SAAU6B,UAQzDG,kBAAkBC,QAASN,gBACjBO,SAAWhC,KAAKM,WAAWN,KAAKX,UAAUE,cAC3CyC,WAGLA,SAASD,QAAUA,QACnBC,SAASP,SAAWA,SAEhBA,SACAO,SAASR,gBAAgB,sBAEzBQ,SAAS9B,QAAQ+B,aAAe,GASxCP,iBAAiBP,cACRA,KAAKE,UAGoB,KAAtBF,KAAKe,cAA6C,OAAtBf,KAAKe,cAQ7CN,YAAYT,YACkB,OAAtBA,KAAKe,cAGFf,KAAKgB,UAAUC,SAASpC,KAAKD"} \ 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 3780a6e3e5f..5bbda6cd955 100644 --- a/course/format/amd/src/local/content/actions.js +++ b/course/format/amd/src/local/content/actions.js @@ -32,6 +32,7 @@ import Templates from 'core/templates'; import {prefetchStrings} from 'core/prefetch'; import {get_string as getString} from 'core/str'; import {getFirst} from 'core/normalise'; +import {toggleBulkSelectionAction} from 'core_courseformat/local/content/actions/bulkselection'; import * as CourseEvents from 'core_course/events'; import Pending from 'core/pending'; import ContentTree from 'core_courseformat/local/courseeditor/contenttree'; @@ -456,30 +457,20 @@ export default class extends BaseComponent { * Handle a toggle cm selection. * * @param {Element} target the dispatch action element + * @param {Event} event the triggered event */ - async _requestToggleSelectionCm(target) { - const cmId = target.dataset.id; - if (!cmId) { - return; - } - const value = target.checked ?? false; - const mutation = (value) ? 'cmSelect' : 'cmUnselect'; - this.reactive.dispatch(mutation, [cmId]); + async _requestToggleSelectionCm(target, event) { + toggleBulkSelectionAction(this.reactive, target, event, 'cm'); } /** * Handle a toggle section selection. * * @param {Element} target the dispatch action element + * @param {Event} event the triggered event */ - async _requestToggleSelectionSection(target) { - const sectionId = target.dataset.id; - if (!sectionId) { - return; - } - const value = target.checked ?? false; - const mutation = (value) ? 'sectionSelect' : 'sectionUnselect'; - this.reactive.dispatch(mutation, [sectionId]); + async _requestToggleSelectionSection(target, event) { + toggleBulkSelectionAction(this.reactive, target, event, 'section'); } /** diff --git a/course/format/amd/src/local/content/actions/bulkselection.js b/course/format/amd/src/local/content/actions/bulkselection.js new file mode 100644 index 00000000000..1027983fcb7 --- /dev/null +++ b/course/format/amd/src/local/content/actions/bulkselection.js @@ -0,0 +1,385 @@ +// This file is part of Moodle - http://moodle.org/ +// +// Moodle is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Moodle is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Moodle. If not, see . + +/** + * Bulk selection auxiliar methods. + * + * @module core_courseformat/local/content/actions/bulkselection + * @class core_courseformat/local/content/actions/bulkselection + * @copyright 2023 Ferran Recio + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class BulkSelector { + + /** + * The class constructor. + * @param {CourseEditor} courseEditor the original actions component. + */ + constructor(courseEditor) { + this.courseEditor = courseEditor; + this.selectors = { + BULKCMCHECKBOX: `[data-bulkcheckbox][data-action='toggleSelectionCm']`, + BULKSECTIONCHECKBOX: `[data-bulkcheckbox][data-action='toggleSelectionSection']`, + CONTENT: `#region-main`, + }; + } + + /** + * Process a new selection. + * @param {Number} id + * @param {String} elementType cm or section + * @param {Object} settings special selection settings + * @param {Boolean} settings.all if the action is over all elements of the same type + * @param {Boolean} settings.range if the action is over a range of elements + */ + processNewSelection(id, elementType, settings) { + const value = !this._isBulkSelected(id, elementType); + if (settings.all && settings.range) { + this.switchCurrentSelection(); + return; + } + if (!this._isSelectable(id, elementType)) { + return; + } + if (settings.all) { + if (elementType == 'cm') { + this._updateBulkCmSiblings(id, value); + } else { + this._updateBulkSelectionAll(elementType, value); + } + return; + } + if (settings.range) { + this._updateBulkSelectionRange(id, elementType, value); + return; + } + this._updateBulkSelection([id], elementType, value); + } + + /** + * Switch between section and cm selection. + */ + switchCurrentSelection() { + const bulk = this.courseEditor.get('bulk'); + if (bulk.selectedType === '' || bulk.selection.length == 0) { + return; + } + const newSelectedType = (bulk.selectedType === 'section') ? 'cm' : 'section'; + let newSelectedIds; + if (bulk.selectedType === 'section') { + newSelectedIds = this._getCmIdsFromSections(bulk.selection); + } else { + newSelectedIds = this._getSectionIdsFromCms(bulk.selection); + } + // Formats can display only a few activities of the section, + // We need to select on the activities present in the page. + const affectedIds = []; + newSelectedIds.forEach(newId => { + if (this._getSelector(newId, newSelectedType)) { + affectedIds.push(newId); + } + }); + this.courseEditor.dispatch('bulkEnable', true); + if (affectedIds.length != 0) { + this._updateBulkSelection(affectedIds, newSelectedType, true); + } + } + + /** + * Select all elements of the current type. + * @param {Boolean} value the wanted selected value + */ + selectAll(value) { + const bulk = this.courseEditor.get('bulk'); + if (bulk.selectedType == '') { + return; + } + if (!value) { + this.courseEditor.dispatch('bulkEnable', true); + return; + } + const elementType = bulk.selectedType; + this._updateBulkSelectionAll(elementType, value); + } + + /** + * Checks if all selectable elements are selected. + * @returns {Boolean} true if all are selected + */ + checkAllSelected() { + const bulk = this.courseEditor.get('bulk'); + if (bulk.selectedType == '') { + return false; + } + return this._getContentCheckboxes(bulk.selectedType).every(bulkSelect => { + if (bulkSelect.disabled) { + return false; + } + // Section zero is never selectale for bulk actions. + if (bulk.selectedType == 'section') { + const section = this.courseEditor.get('section', bulkSelect.dataset.id); + if (section.number == 0) { + return true; + } + } + return bulk.selection.includes(bulkSelect.dataset.id); + }); + } + + /** + * Check if the id is part of the current bulk selection. + * @private + * @param {Number} id + * @param {String} elementType + * @returns {Boolean} if the element is present in the current selection. + */ + _isBulkSelected(id, elementType) { + const bulk = this.courseEditor.get('bulk'); + if (bulk.selectedType !== elementType) { + return false; + } + return bulk.selection.includes(id); + } + + /** + * Update the current bulk selection removing or adding Ids. + * @private + * @param {Number[]} ids the user selected element id + * @param {String} elementType cm or section + * @param {Boolean} value the wanted selected value + */ + _updateBulkSelection(ids, elementType, value) { + let mutation = elementType; + mutation += (value) ? 'Select' : 'Unselect'; + this.courseEditor.dispatch(mutation, ids); + } + + /** + * Get all content bulk selector checkboxes of one type (section/cm). + * @private + * @param {String} elementType section or cm + * @returns {HTMLElement[]} an array with all checkboxes + */ + _getContentCheckboxes(elementType) { + const selector = (elementType == 'cm') ? this.selectors.BULKCMCHECKBOX : this.selectors.BULKSECTIONCHECKBOX; + const checkboxes = document.querySelectorAll(`${this.selectors.CONTENT} ${selector}`); + // Converting to array because NodeList has less iteration methods. + return [...checkboxes]; + } + + /** + * Validate if an element is selectable in the current page. + * @private + * @param {Number} id the user selected element id + * @param {String} elementType cm or section + * @return {Boolean} + */ + _isSelectable(id, elementType) { + const bulkSelect = this._getSelector(id, elementType); + if (!bulkSelect || bulkSelect.disabled) { + return false; + } + return true; + } + + /** + * Get as specific element checkbox. + * @private + * @param {Number} id + * @param {String} elementType cm or section + * @returns {HTMLElement|undefined} + */ + _getSelector(id, elementType) { + let selector = (elementType == 'cm') ? this.selectors.BULKCMCHECKBOX : this.selectors.BULKSECTIONCHECKBOX; + selector += `[data-id='${id}']`; + return document.querySelector(`${this.selectors.CONTENT} ${selector}`); + } + + /** + * Update the current bulk selection when a user uses shift to select a range. + * @private + * @param {Number} id the user selected element id + * @param {String} elementType cm or section + * @param {Boolean} value the wanted selected value + */ + _updateBulkSelectionRange(id, elementType, value) { + const bulk = this.courseEditor.get('bulk'); + let lastSelectedId = bulk.selection.at(-1); + if (bulk.selectedType !== elementType || lastSelectedId == id) { + this._updateBulkSelection([id], elementType, value); + return; + } + const affectedIds = []; + let found = 0; + this._getContentCheckboxes(elementType).every(bulkSelect => { + if (bulkSelect.disabled) { + return true; + } + if (bulkSelect.dataset.id == id || bulkSelect.dataset.id == lastSelectedId) { + found++; + } + if (found == 0) { + return true; + } + affectedIds.push(bulkSelect.dataset.id); + return found != 2; + }); + this._updateBulkSelection(affectedIds, elementType, value); + } + + /** + * Select or unselect all cm siblings. + * @private + * @param {Number} cmId the user selected element id + * @param {Boolean} value the wanted selected value + */ + _updateBulkCmSiblings(cmId, value) { + const bulk = this.courseEditor.get('bulk'); + if (bulk.selectedType === 'section') { + return; + } + const cm = this.courseEditor.get('cm', cmId); + const section = this.courseEditor.get('section', cm.sectionid); + // Formats can display only a few activities of the section, + // We need to select on the activities selectable in the page. + const affectedIds = []; + section.cmlist.forEach(sectionCmId => { + if (this._isSelectable(sectionCmId, 'cm')) { + affectedIds.push(sectionCmId); + } + }); + this._updateBulkSelection(affectedIds, 'cm', value); + } + + /** + * Select or unselects al elements of the same type. + * @private + * @param {String} elementType section or cm + * @param {Boolean} value if the elements must be selected or unselected. + */ + _updateBulkSelectionAll(elementType, value) { + const affectedIds = []; + this._getContentCheckboxes(elementType).forEach(bulkSelect => { + if (bulkSelect.disabled) { + return; + } + if (elementType == 'section') { + const section = this.courseEditor.get('section', bulkSelect.dataset.id); + if (section?.number == 0) { + return; + } + } + affectedIds.push(bulkSelect.dataset.id); + }); + this._updateBulkSelection(affectedIds, elementType, value); + } + + /** + * Get all cm ids from a specific section ids. + * @private + * @param {Number[]} sectionIds + * @returns {Number[]} the cm ids + */ + _getCmIdsFromSections(sectionIds) { + const result = []; + sectionIds.forEach(sectionId => { + const section = this.courseEditor.get('section', sectionId); + result.push(...section.cmlist); + }); + return result; + } + + /** + * Get all section ids containing a specific cm ids. + * @private + * @param {Number[]} cmIds + * @returns {Number[]} the section ids + */ + _getSectionIdsFromCms(cmIds) { + const result = new Set(); + cmIds.forEach(cmId => { + const cm = this.courseEditor.get('cm', cmId); + if (cm.sectionnumber == 0) { + return; + } + result.add(cm.sectionid); + }); + return [...result]; + } +} + +/** + * Process a bulk selection toggle action. + * @method + * @param {CourseEditor} courseEditor + * @param {HTMLElement} target the action element + * @param {Event} event + * @param {String} elementType cm or section + */ +export const toggleBulkSelectionAction = function(courseEditor, target, event, elementType) { + const id = target.dataset.id; + if (!id) { + return; + } + // When the action cames from a form element (checkbox) we should not preventDefault. + // If we do it the changechecker module will execute the state change twice. + if (target.dataset.preventDefault) { + event.preventDefault(); + } + // Using shift or alt key can produce text selection. + document.getSelection().removeAllRanges(); + + const bulkSelector = new BulkSelector(courseEditor); + bulkSelector.processNewSelection( + id, + elementType, + { + range: event.shiftKey, + all: event.altKey, + } + ); +}; + +/** + * Switch the current bulk selection. + * @method + * @param {CourseEditor} courseEditor + */ +export const switchBulkSelection = function(courseEditor) { + const bulkSelector = new BulkSelector(courseEditor); + bulkSelector.switchCurrentSelection(); +}; + +/** + * Select/unselect all element of the selected type. + * @method + * @param {CourseEditor} courseEditor + * @param {Boolean} value if the elements must be selected or unselected. + */ +export const selectAllBulk = function(courseEditor, value) { + const bulkSelector = new BulkSelector(courseEditor); + bulkSelector.selectAll(value); +}; + +/** + * Check if all possible elements are selected. + * @method + * @param {CourseEditor} courseEditor + * @return {Boolean} if all elements of the current type are selected. + */ +export const checkAllBulkSelected = function(courseEditor) { + const bulkSelector = new BulkSelector(courseEditor); + return bulkSelector.checkAllSelected(); +}; diff --git a/course/format/amd/src/local/content/bulkedittools.js b/course/format/amd/src/local/content/bulkedittools.js index 2301639f8a5..b9e3dcc7065 100644 --- a/course/format/amd/src/local/content/bulkedittools.js +++ b/course/format/amd/src/local/content/bulkedittools.js @@ -28,6 +28,11 @@ import {getCurrentCourseEditor} from 'core_courseformat/courseeditor'; import {get_string as getString} from 'core/str'; import Pending from 'core/pending'; import {prefetchStrings} from 'core/prefetch'; +import { + selectAllBulk, + switchBulkSelection, + checkAllBulkSelected +} from 'core_courseformat/local/content/actions/bulkselection'; // Load global strings. prefetchStrings( @@ -85,7 +90,7 @@ export default class Component extends BaseComponent { } const selectAll = this.getElement(this.selectors.SELECTALL); if (selectAll) { - this.addEventListener(selectAll, 'change', this._selectAllClick); + this.addEventListener(selectAll, 'click', this._selectAllClick); } } @@ -154,15 +159,17 @@ export default class Component extends BaseComponent { if (!selectall) { return; } - if (bulk.selectedType === '') { - selectall.checked = false; - selectall.disabled = true; - return; - } - - selectall.disabled = false; - const maxSelection = document.querySelectorAll(this.selectors.SELECTABLE).length; - selectall.checked = (bulk.selection.length == maxSelection); + selectall.disabled = (bulk.selectedType === ''); + // The changechecker module can prevent the checkbox form changing it's value. + // To avoid that we leave the sniffer to act before changing the value. + const pending = new Pending(`courseformat/bulktools:refreshSelectAll`); + setTimeout( + () => { + selectall.checked = checkAllBulkSelected(this.reactive); + pending.resolve(); + }, + 100 + ); } /** @@ -199,20 +206,20 @@ export default class Component extends BaseComponent { } /** - * Select all elements click handler. + * Handle special select all cases. * @param {Event} event */ _selectAllClick(event) { - const target = event.target; - const bulk = this.reactive.get('bulk'); - if (bulk.selectedType === '') { + event.preventDefault(); + if (event.altKey) { + switchBulkSelection(this.reactive); return; } - if (!target.checked) { + if (checkAllBulkSelected(this.reactive)) { this._handleUnselectAll(); return; } - this._handleSelectAll(bulk); + selectAllBulk(this.reactive, true); } /** @@ -220,30 +227,11 @@ export default class Component extends BaseComponent { */ _handleUnselectAll() { const pending = new Pending(`courseformat/content:bulktUnselectAll`); - // Re-enable bulk will clean the selection and the selection type. - this.reactive.dispatch('bulkEnable', true); + selectAllBulk(this.reactive, false); // Wait for a while and focus on the first checkbox. setTimeout(() => { document.querySelector(this.selectors.SELECTABLE)?.focus(); pending.resolve(); }, 150); } - - /** - * Process a select all selectable elements. - * @param {Object} bulk the state bulk data - * @param {String} bulk.selectedType the current selected type (section/cm) - */ - _handleSelectAll(bulk) { - const selectableIds = []; - const selectables = document.querySelectorAll(this.selectors.SELECTABLE); - if (selectables.length == 0) { - return; - } - selectables.forEach(selectable => { - selectableIds.push(selectable.dataset.id); - }); - const mutation = (bulk.selectedType === 'cm') ? 'cmSelect' : 'sectionSelect'; - this.reactive.dispatch(mutation, selectableIds); - } } diff --git a/course/format/amd/src/local/content/section/cmitem.js b/course/format/amd/src/local/content/section/cmitem.js index 5efcdb2c318..e8e8b909ae5 100644 --- a/course/format/amd/src/local/content/section/cmitem.js +++ b/course/format/amd/src/local/content/section/cmitem.js @@ -60,7 +60,6 @@ export default class extends DndCmItem { this.configDragDrop(this.id); this.getElement(this.selectors.DRAGICON)?.classList.add(this.classes.DRAGICON); this._refreshBulk({state}); - this.addEventListener(this.element, 'click', this._handleBulkModeClick); } /** @@ -99,6 +98,14 @@ export default class extends DndCmItem { const bulk = state.bulk; // For now, dragging elements in bulk is not possible. this.setDraggable(!bulk.enabled); + // Convert the card into an active element in bulk mode. + if (bulk.enabled) { + this.element.dataset.action = 'toggleSelectionCm'; + this.element.dataset.preventDefault = 1; + } else { + this.element.removeAttribute('data-action'); + this.element.removeAttribute('data-preventDefault'); + } this.getElement(this.selectors.BULKSELECT)?.classList.toggle(this.classes.HIDE, !bulk.enabled); @@ -140,28 +147,6 @@ export default class extends DndCmItem { } } - /** - * Handle the activity card click in bulk mode. - * @param {Event} event the click event - */ - _handleBulkModeClick(event) { - const selectElement = event.target.closest(this.selectors.BULKSELECT); - if (selectElement) { - // The select element checkbox execute a normal content action as - // any regular action button. This is because the chengechecker module - // is sniffing any form element and will with the checked value - // changing it twice. - return; - } - const bulk = this.reactive.get('bulk'); - if (!this._isCmBulkEnabled(bulk)) { - return; - } - event.preventDefault(); - const mutation = (this._isSelected(bulk)) ? 'cmUnselect' : 'cmSelect'; - this.reactive.dispatch(mutation, [this.id]); - } - /** * Check if cm bulk selection is available. * @param {Object} bulk the current state bulk attribute diff --git a/course/format/tests/behat/bulk_select.feature b/course/format/tests/behat/bulk_select.feature index 0399c49b47a..12d38ee0c5b 100644 --- a/course/format/tests/behat/bulk_select.feature +++ b/course/format/tests/behat/bulk_select.feature @@ -9,7 +9,7 @@ Feature: Bulk activity and section selection. | fullname | Course 1 | | shortname | C1 | | category | 0 | - | numsections | 2 | + | numsections | 4 | And the following "activities" exist: | activity | name | intro | course | idnumber | section | | assign | Activity sample 1 | Test assignment description | C1 | sample1 | 1 | @@ -84,13 +84,15 @@ Feature: Bulk activity and section selection. And I should see "1 selected" in the "sticky-footer" "region" And the "Select all" "checkbox" should be enabled When I click on "Select all" "checkbox" in the "sticky-footer" "region" - Then I should see "2 selected" in the "sticky-footer" "region" + Then I should see "4 selected" in the "sticky-footer" "region" Scenario: Click on a select all with all sections selected unselects all sections Given I click on "Bulk edit" "button" And 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 "Select topic Topic 3" "checkbox" + And I click on "Select topic Topic 4" "checkbox" + And I should see "4 selected" in the "sticky-footer" "region" And the "Select all" "checkbox" should be enabled When I click on "Select all" "checkbox" in the "sticky-footer" "region" Then I should see "0 selected" in the "sticky-footer" "region" @@ -116,3 +118,29 @@ Feature: Bulk activity and section selection. And I should see "2 selected" in the "sticky-footer" "region" Then I click on "Activity sample 1" "link" in the "Topic 1" "section" And I should see "1 selected" in the "sticky-footer" "region" + + Scenario: Select a range of activities using shift + Given I click on "Bulk edit" "button" + And I should see "0 selected" in the "sticky-footer" "region" + When I click on "Activity sample 1" "link" in the "Topic 1" "section" + And I shift click on "Activity sample 3" "link" in the "Topic 2" "section" + Then I should see "3 selected" in the "sticky-footer" "region" + + Scenario: Select all activities in a section using alt + Given I click on "Bulk edit" "button" + And I should see "0 selected" in the "sticky-footer" "region" + When I alt click on "Activity sample 3" "link" in the "Topic 2" "section" + Then I should see "2 selected" in the "sticky-footer" "region" + + Scenario: Select a range of sections using shift + Given I click on "Bulk edit" "button" + And I should see "0 selected" in the "sticky-footer" "region" + When I click on "Select topic Topic 1" "checkbox" + And I shift click on "Select topic Topic 3" "checkbox" in the "page" "region" + Then I should see "3 selected" in the "sticky-footer" "region" + + Scenario: Select all section with alt click + Given I click on "Bulk edit" "button" + And I should see "0 selected" in the "sticky-footer" "region" + When I alt click on "Select topic Topic 3" "checkbox" in the "page" "region" + And I should see "4 selected" in the "sticky-footer" "region" diff --git a/lib/tests/behat/behat_general.php b/lib/tests/behat/behat_general.php index b19ca32fc77..a64786c167a 100644 --- a/lib/tests/behat/behat_general.php +++ b/lib/tests/behat/behat_general.php @@ -464,6 +464,55 @@ class behat_general extends behat_base { $node->click(); } + /** + * Click on the element with some modifier key pressed (alt, shift, meta or control). + * + * It is important to note that not all HTML elements are compatible with this step because + * the webdriver limitations. For example, alt click on checkboxes with a visible label will + * produce a normal checkbox click without the modifier. + * + * @When I :modifier click on :element :selectortype in the :nodeelement :nodeselectortype + * @param string $modifier the extra modifier to press (for example, alt+shift or shift) + * @param string $element Element we look for + * @param string $selectortype The type of what we look for + * @param string $nodeelement Element we look in + * @param string $nodeselectortype The type of selector where we look in + */ + public function i_key_click_on_in_the($modifier, $element, $selectortype, $nodeelement, $nodeselectortype) { + behat_base::require_javascript_in_session($this->getSession()); + + $key = null; + switch (strtoupper(trim($modifier))) { + case '': + break; + case 'SHIFT': + $key = behat_keys::SHIFT; + break; + case 'CTRL': + $key = behat_keys::CONTROL; + break; + case 'ALT': + $key = behat_keys::ALT; + break; + case 'META': + $key = behat_keys::META; + break; + default: + throw new \coding_exception("Unknown modifier key '$modifier'}"); + } + + $node = $this->get_node_in_container($selectortype, $element, $nodeselectortype, $nodeelement); + $this->ensure_node_is_visible($node); + + // KeyUP and KeyDown require the element to be displayed in the current window. + $this->execute_js_on_node($node, '{{ELEMENT}}.scrollIntoView();'); + $node->keyDown($key); + $node->click(); + // Any click action can move the scroll. Ensure the element is still displayed. + $this->execute_js_on_node($node, '{{ELEMENT}}.scrollIntoView();'); + $node->keyUp($key); + } + /** * Drags and drops the specified element to the specified container. This step does not work in all the browsers, consider it experimental. *