From 5389772830ebf89bd14ee216f71cbe6f825fbae5 Mon Sep 17 00:00:00 2001 From: James C <5689414+james-cnz@users.noreply.github.com> Date: Wed, 7 Aug 2024 15:23:39 +1200 Subject: [PATCH] Moodle 4.5 reactive add topic WIP 1 --- amd/build/courseformat/content.min.js | 4 +- amd/build/courseformat/content.min.js.map | 2 +- amd/build/courseformat/content/actions.min.js | 15 +++++ .../courseformat/content/actions.min.js.map | 1 + amd/build/courseformat/content/section.min.js | 2 +- .../courseformat/content/section.min.js.map | 2 +- .../courseeditor/mutations.min.js | 2 +- .../courseeditor/mutations.min.js.map | 2 +- amd/src/courseformat/content.js | 63 +++++++++++++++++- amd/src/courseformat/content/actions.js | 64 +++++++++++++++++++ amd/src/courseformat/content/section.js | 7 ++ .../courseformat/courseeditor/mutations.js | 13 ++++ classes/courseformat/stateactions.php | 49 ++++++++++++++ classes/output/courseformat/content.php | 22 +++++-- .../courseformat/content/addsection.php | 2 +- .../output/courseformat/content/section.php | 2 +- format.js | 47 +------------- format.php | 4 -- templates/courseformat/content.mustache | 2 +- .../courseformat/content/addsection.mustache | 29 ++++++--- .../courseformat/content/section.mustache | 2 +- .../content/section/header.mustache | 6 +- .../courseformat/contenttabs/tab.mustache | 6 +- 23 files changed, 265 insertions(+), 83 deletions(-) create mode 100644 amd/build/courseformat/content/actions.min.js create mode 100644 amd/build/courseformat/content/actions.min.js.map create mode 100644 amd/src/courseformat/content/actions.js diff --git a/amd/build/courseformat/content.min.js b/amd/build/courseformat/content.min.js index b949dc8..02e244f 100644 --- a/amd/build/courseformat/content.min.js +++ b/amd/build/courseformat/content.min.js @@ -1,4 +1,4 @@ -define("format_multitopic/courseformat/content",["exports","core_courseformat/local/content","core_courseformat/courseeditor","core/inplace_editable","format_multitopic/courseformat/content/section","format_multitopic/courseformat/content/section/cmitem","core/templates"],(function(_exports,_content,_courseeditor,_inplace_editable,_section,_cmitem,_templates){function _interopRequireDefault(obj){return obj&&obj.__esModule?obj:{default:obj}} +define("format_multitopic/courseformat/content",["exports","core_courseformat/local/content","core_courseformat/courseeditor","core/inplace_editable","format_multitopic/courseformat/content/section","format_multitopic/courseformat/content/section/cmitem","core/templates","format_multitopic/courseformat/content/actions","core_course/events"],(function(_exports,_content,_courseeditor,_inplace_editable,_section,_cmitem,_templates,_actions,CourseEvents){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 index main component. * @@ -7,6 +7,6 @@ define("format_multitopic/courseformat/content",["exports","core_courseformat/lo * @copyright 2022 James Calder and Otago Polytechnic * @copyright based on work by 2020 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,_content=_interopRequireDefault(_content),_inplace_editable=_interopRequireDefault(_inplace_editable),_section=_interopRequireDefault(_section),_cmitem=_interopRequireDefault(_cmitem),_templates=_interopRequireDefault(_templates);class Component extends _content.default{static init(target,selectors,sectionReturn){return new this({element:document.getElementById(target),reactive:(0,_courseeditor.getCurrentCourseEditor)(),selectors:selectors,sectionReturn:sectionReturn})}stateReady(state){super.stateReady(state),this.fmtCollapseOnHashChange(),window.addEventListener("hashchange",this.fmtCollapseOnHashChange.bind(this))}fmtCollapseOnHashChange(event){let anchor=window.location.hash;if(!anchor.match(/^#sectionid-\d+(?:-title)?$/))return;let oldStyle=!1;anchor.match(/^#sectionid-\d+$/)&&(anchor+="-title",oldStyle=!0,history.replaceState(history.state,"",anchor));const selSectionHeaderDom=document.querySelector(".course-content ul.sections li.section.section-topic .sectionname"+anchor);if(!selSectionHeaderDom)return;const selSectionDom=selSectionHeaderDom.closest("li.section.section-topic"),sectionId=selSectionDom.getAttribute("data-id"),section=this.reactive.get("section",sectionId);selSectionDom.matches(".section-topic-collapsible")&&(selSectionDom.querySelector(".course-section-header .icons-collapse-expand.collapsed")||section.contentcollapsed)&&this.reactive.dispatch("sectionContentCollapsed",[sectionId],!1),oldStyle&&selSectionDom.scrollIntoView()}_allSectionToggler(event){event.preventDefault();const isAllCollapsed=event.target.closest(this.selectors.TOGGLEALL).classList.contains(this.classes.COLLAPSED);let sectionlist=[];const sectionlistDom=this.element.querySelectorAll(".course-section.section-topic-collapsible[data-fmtonpage='1']");for(let sectionCount=0;sectionCount{sectionCollapsible[section.id]&&(allcollapsed=allcollapsed&§ion.contentcollapsed,allexpanded=allexpanded&&!section.contentcollapsed)})),target.style.display=allexpanded&&allcollapsed?"none":"block",allcollapsed&&(target.classList.add(this.classes.COLLAPSED),target.setAttribute("aria-expanded",!1)),allexpanded&&(target.classList.remove(this.classes.COLLAPSED),target.setAttribute("aria-expanded",!0))}_refreshSectionNumber(_ref){let{element:element}=_ref;const target=this.getElement(this.selectors.SECTION,element.id);if(!target)return;target.id="section-".concat(element.number),target.dataset.sectionid=element.number,target.dataset.number=element.number;const inplace=_inplace_editable.default.getInplaceEditable(target.querySelector(this.selectors.SECTION_ITEM));if(inplace){const currentvalue=inplace.getValue(),currentitemid=inplace.getItemId();(""===inplace.getValue()||element.timed)&&(currentitemid!=element.id||currentvalue==element.rawtitle&&""!=element.rawtitle&&!element.timed||inplace.setValue(element.rawtitle))}}_refreshCourseSectionlist(param){super._refreshCourseSectionlist(param);const sectionsDom=this.element.querySelectorAll(this.selectors.SECTION);for(let sdi=0;sdinew _section.default(item))),this._scanIndex(this.selectors.CM,this.cms,(item=>new _cmitem.default(item)))}}return _exports.default=Component,_exports.default})); + */Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.default=void 0,_content=_interopRequireDefault(_content),_inplace_editable=_interopRequireDefault(_inplace_editable),_section=_interopRequireDefault(_section),_cmitem=_interopRequireDefault(_cmitem),_templates=_interopRequireDefault(_templates),_actions=_interopRequireDefault(_actions),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);class Component extends _content.default{create(descriptor){super.create(descriptor),this.version=descriptor.version}static init(target,selectors,sectionReturn,version){return new this({element:document.getElementById(target),reactive:(0,_courseeditor.getCurrentCourseEditor)(),selectors:selectors,sectionReturn:sectionReturn,version:version})}stateReady(state){this._indexContents(),this.addEventListener(this.element,"click",this._sectionTogglers);const toogleAll=this.getElement(this.selectors.TOGGLEALL);if(toogleAll){const collapseElementIds=[...this.getElements(this.selectors.COLLAPSE)].map((element=>element.id));toogleAll.setAttribute("aria-controls",collapseElementIds.join(" ")),this.addEventListener(toogleAll,"click",this._allSectionToggler),this.addEventListener(toogleAll,"keydown",(e=>{" "===e.key&&this._allSectionToggler(e)})),this._refreshAllSectionsToggler(state)}this.reactive.supportComponents&&(this.reactive.isEditing&&new _actions.default(this),this.element.classList.add(this.classes.STATEDREADY)),this.addEventListener(this.element,CourseEvents.manualCompletionToggled,this._completionHandler),this.addEventListener(this.version>=2023081800?document:document.querySelector(this.selectors.PAGE),"scroll",this._scrollHandler),this.fmtCollapseOnHashChange(),window.addEventListener("hashchange",this.fmtCollapseOnHashChange.bind(this))}fmtCollapseOnHashChange(event){let anchor=window.location.hash;if(!anchor.match(/^#sectionid-\d+(?:-title)?$/))return;let oldStyle=!1;anchor.match(/^#sectionid-\d+$/)&&(anchor+="-title",oldStyle=!0,history.replaceState(history.state,"",anchor));const selSectionHeaderDom=document.querySelector(".course-content ul.sections li.section.section-topic .sectionname"+anchor);if(!selSectionHeaderDom)return;const selSectionDom=selSectionHeaderDom.closest("li.section.section-topic"),sectionId=selSectionDom.getAttribute("data-id"),section=this.reactive.get("section",sectionId);selSectionDom.matches(".section-topic-collapsible")&&(selSectionDom.querySelector(".course-section-header .icons-collapse-expand.collapsed")||section.contentcollapsed)&&this.reactive.dispatch("sectionContentCollapsed",[sectionId],!1),oldStyle&&selSectionDom.scrollIntoView()}_allSectionToggler(event){event.preventDefault();const isAllCollapsed=event.target.closest(this.selectors.TOGGLEALL).classList.contains(this.classes.COLLAPSED);let sectionlist=[];const sectionlistDom=this.element.querySelectorAll(".course-section.section-topic-collapsible[data-fmtonpage='1']");for(let sectionCount=0;sectionCount{sectionCollapsible[section.id]&&(allcollapsed=allcollapsed&§ion.contentcollapsed,allexpanded=allexpanded&&!section.contentcollapsed)})),target.style.display=allexpanded&&allcollapsed?"none":"block",allcollapsed&&(target.classList.add(this.classes.COLLAPSED),target.setAttribute("aria-expanded",!1)),allexpanded&&(target.classList.remove(this.classes.COLLAPSED),target.setAttribute("aria-expanded",!0))}_refreshSectionNumber(_ref){let{element:element}=_ref;const target=this.getElement(this.selectors.SECTION,element.id);if(!target)return;target.id="section-".concat(element.number),target.dataset.sectionid=element.number,target.dataset.number=element.number;const inplace=_inplace_editable.default.getInplaceEditable(target.querySelector(this.selectors.SECTION_ITEM));if(inplace){const currentvalue=inplace.getValue(),currentitemid=inplace.getItemId();(""===inplace.getValue()||element.timed)&&(currentitemid!=element.id||currentvalue==element.rawtitle&&""!=element.rawtitle&&!element.timed||inplace.setValue(element.rawtitle))}}_refreshCourseSectionlist(param){super._refreshCourseSectionlist(param);const sectionsDom=this.element.querySelectorAll(this.selectors.SECTION);for(let sdi=0;sdinew _section.default(item))),this._scanIndex(this.selectors.CM,this.cms,(item=>new _cmitem.default(item)))}}return _exports.default=Component,_exports.default})); //# sourceMappingURL=content.min.js.map \ No newline at end of file diff --git a/amd/build/courseformat/content.min.js.map b/amd/build/courseformat/content.min.js.map index 7a031ce..2ac37f1 100644 --- a/amd/build/courseformat/content.min.js.map +++ b/amd/build/courseformat/content.min.js.map @@ -1 +1 @@ -{"version":3,"file":"content.min.js","sources":["../../src/courseformat/content.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 index main component.\n *\n * @module format_multitopic/courseformat/content\n * @class format_multitopic/courseformat/content\n * @copyright 2022 James Calder and Otago Polytechnic\n * @copyright based on work by 2020 Ferran Recio \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport BaseComponent from 'core_courseformat/local/content';\nimport {getCurrentCourseEditor} from 'core_courseformat/courseeditor';\nimport inplaceeditable from 'core/inplace_editable';\nimport Section from 'format_multitopic/courseformat/content/section';\nimport CmItem from 'format_multitopic/courseformat/content/section/cmitem';\nimport Templates from 'core/templates';\n\nexport default class Component extends BaseComponent {\n\n /**\n * Static method to create a component instance form the mustahce template.\n *\n * @param {string} target the DOM main element or its ID\n * @param {object} selectors optional css selector overrides\n * @param {number} sectionReturn the content section return\n * @return {Component}\n */\n static init(target, selectors, sectionReturn) {\n return new this({ // CHANGED.\n element: document.getElementById(target),\n reactive: getCurrentCourseEditor(),\n selectors,\n sectionReturn,\n });\n }\n\n /**\n * Initial state ready method.\n *\n * @param {Object} state the state data\n */\n stateReady(state) {\n super.stateReady(state);\n\n // Set the initial state of collapsible sections.\n this.fmtCollapseOnHashChange();\n\n // Capture clicks on course section links.\n window.addEventListener(\"hashchange\", this.fmtCollapseOnHashChange.bind(this));\n\n }\n\n /**\n * Expand, and scroll to, the section specified in the URL bar.\n *\n * @param {HashChangeEvent?} event The triggering event, if any\n */\n /* eslint-disable no-unused-vars */\n fmtCollapseOnHashChange(event) {\n /* eslint-enable no-unused-vars */\n\n // Find the specified section.\n let anchor = window.location.hash;\n if (!anchor.match(/^#sectionid-\\d+(?:-title)?$/)) {\n return;\n }\n let oldStyle = false;\n if (anchor.match(/^#sectionid-\\d+$/)) {\n anchor = anchor + \"-title\";\n oldStyle = true;\n history.replaceState(history.state, \"\", anchor);\n }\n const selSectionHeaderDom =\n document.querySelector(\".course-content ul.sections li.section.section-topic .sectionname\" + anchor);\n\n // Exit if there is no recognised section.\n if (!selSectionHeaderDom) {\n return;\n }\n\n const selSectionDom = selSectionHeaderDom.closest(\"li.section.section-topic\");\n const sectionId = selSectionDom.getAttribute('data-id');\n const section = this.reactive.get('section', sectionId);\n\n // Expand, if appropriate.\n if (selSectionDom.matches(\".section-topic-collapsible\")\n && (selSectionDom.querySelector(\".course-section-header .icons-collapse-expand.collapsed\")\n || section.contentcollapsed)) {\n this.reactive.dispatch(\n 'sectionContentCollapsed',\n [sectionId],\n false\n );\n }\n\n // Scroll to the specified section.\n if (oldStyle) {\n selSectionDom.scrollIntoView();\n }\n\n }\n\n /**\n * Handle the collapse/expand all sections button.\n *\n * Toggler click is delegated to the main course content element because new sections can\n * appear at any moment and this way we prevent accidental double bindings.\n *\n * @param {Event} event the triggered event\n */\n _allSectionToggler(event) {\n event.preventDefault();\n\n const target = event.target.closest(this.selectors.TOGGLEALL);\n const isAllCollapsed = target.classList.contains(this.classes.COLLAPSED);\n\n // CHANGED.\n let sectionlist = [];\n const sectionlistDom = this.element.querySelectorAll(\".course-section.section-topic-collapsible[data-fmtonpage='1']\");\n for (let sectionCount = 0; sectionCount < sectionlistDom.length; sectionCount++) {\n sectionlist.push(sectionlistDom[sectionCount].dataset.id);\n }\n // END CHANGED.\n\n this.reactive.dispatch(\n 'sectionContentCollapsed',\n sectionlist, // CHANGED.\n !isAllCollapsed\n );\n }\n\n /**\n * Refresh the collapse/expand all sections element.\n *\n * @param {Object} state The state data\n */\n _refreshAllSectionsToggler(state) {\n const target = this.getElement(this.selectors.TOGGLEALL);\n if (!target) {\n return;\n }\n // Check if we have all sections collapsed/expanded.\n let allcollapsed = true;\n let allexpanded = true;\n // ADDED.\n let sectionCollapsible = {};\n const sectionlistDom = this.element.querySelectorAll(\".course-section.section-topic-collapsible[data-fmtonpage='1']\");\n for (let sectionCount = 0; sectionCount < sectionlistDom.length; sectionCount++) {\n sectionCollapsible[sectionlistDom[sectionCount].dataset.id] = true;\n }\n // END ADDED.\n state.section.forEach(\n section => {\n if (sectionCollapsible[section.id]) { // ADDED.\n allcollapsed = allcollapsed && section.contentcollapsed;\n allexpanded = allexpanded && !section.contentcollapsed;\n }\n }\n );\n target.style.display = (allexpanded && allcollapsed) ? \"none\" : \"block\"; // ADDED.\n if (allcollapsed) {\n target.classList.add(this.classes.COLLAPSED);\n target.setAttribute('aria-expanded', false);\n }\n if (allexpanded) {\n target.classList.remove(this.classes.COLLAPSED);\n target.setAttribute('aria-expanded', true);\n }\n }\n\n /**\n * Update a course section when the section number changes.\n *\n * The courseActions module used for most course section tools still depends on css classes and\n * section numbers (not id). To prevent inconsistencies when a section is moved, we need to refresh\n * the\n *\n * Course formats can override the section title rendering so the frontend depends heavily on backend\n * rendering. Luckily in edit mode we can trigger a title update using the inplace_editable module.\n *\n * @param {Object} param\n * @param {Object} param.element details the update details.\n */\n _refreshSectionNumber({element}) {\n // Find the element.\n const target = this.getElement(this.selectors.SECTION, element.id);\n if (!target) {\n // Job done. Nothing to refresh.\n return;\n }\n // Update section numbers in all data, css and YUI attributes.\n target.id = `section-${element.number}`;\n // YUI uses section number as section id in data-sectionid, in principle if a format use components\n // don't need this sectionid attribute anymore, but we keep the compatibility in case some plugin\n // use it for legacy purposes.\n target.dataset.sectionid = element.number;\n // The data-number is the attribute used by components to store the section number.\n target.dataset.number = element.number;\n\n // Update title and title inplace editable, if any.\n const inplace = inplaceeditable.getInplaceEditable(target.querySelector(this.selectors.SECTION_ITEM));\n if (inplace) {\n // The course content HTML can be modified at any moment, so the function need to do some checkings\n // to make sure the inplace editable still represents the same itemid.\n const currentvalue = inplace.getValue();\n const currentitemid = inplace.getItemId();\n // Unnamed sections must be recalculated.\n if (inplace.getValue() === '' || element.timed) { // CHANGED.\n // The value to send can be an empty value if it is a default name.\n if (currentitemid == element.id\n && (currentvalue != element.rawtitle || element.rawtitle == '' || element.timed)) { // CHANGED.\n inplace.setValue(element.rawtitle);\n }\n }\n }\n }\n\n /**\n * Refresh the section list.\n *\n * @param {Object} param\n * @param {Object} param.element details the update details (Moodle <4.4).\n * @param {Object} param.state the full state object (Moodle >=4.4).\n */\n _refreshCourseSectionlist(param) {\n super._refreshCourseSectionlist(param);\n const sectionsDom = this.element.querySelectorAll(this.selectors.SECTION);\n for (let sdi = 0; sdi < sectionsDom.length; sdi++) {\n const sectionDom = sectionsDom[sdi];\n const section = this.reactive.get(\"section\", sectionDom.dataset.id);\n if (!section) {\n continue;\n }\n let refreshCms = false;\n const pageSectionDom = this.element.querySelector(\".course-section[data-id='\" + section.pageid + \"']\");\n const pageSectionDisplay = pageSectionDom.dataset.fmtonpage;\n if (sectionDom.dataset.fmtonpage != pageSectionDisplay) {\n sectionDom.dataset.fmtonpage = pageSectionDisplay;\n sectionDom.style.display = (pageSectionDisplay == \"1\") ? \"block\" : \"none\";\n if (pageSectionDisplay == \"1\") {\n refreshCms = true;\n }\n }\n if (section.visible == sectionDom.classList.contains(\"hidden\")) {\n const badgeDom = sectionDom.querySelector(\"span.badge[data-type='hiddenfromstudents']\");\n if (section.visible) {\n sectionDom.classList.remove(\"hidden\");\n badgeDom.classList.add(\"d-none\");\n } else {\n sectionDom.classList.add(\"hidden\");\n badgeDom.classList.remove(\"d-none\");\n }\n if (sectionDom.dataset.fmtonpage == \"1\") {\n refreshCms = true;\n }\n }\n if (refreshCms) {\n // Note: Visibility state doesn't get updated for CMs already rendered.\n this._refreshSectionCmlist({element: section});\n }\n const menuDom = sectionDom.querySelector(\".course-section-header .section_action_menu\");\n Templates.render(\"core_courseformat/local/content/section/controlmenu\", section.controlmenu).done(function(html) {\n Templates.replaceNode(menuDom, html, \"\");\n });\n }\n this._refreshAllSectionsToggler(this.reactive.stateManager.state);\n }\n\n /**\n * Regenerate content indexes.\n *\n * This method is used when a legacy action refresh some content element.\n */\n _indexContents() {\n // Find unindexed sections.\n this._scanIndex(\n this.selectors.SECTION,\n this.sections,\n (item) => {\n return new Section(item); // CHANGED.\n }\n );\n\n // Find unindexed cms.\n this._scanIndex(\n this.selectors.CM,\n this.cms,\n (item) => {\n return new CmItem(item); // CHANGED.\n }\n );\n }\n\n}"],"names":["Component","BaseComponent","target","selectors","sectionReturn","this","element","document","getElementById","reactive","stateReady","state","fmtCollapseOnHashChange","window","addEventListener","bind","event","anchor","location","hash","match","oldStyle","history","replaceState","selSectionHeaderDom","querySelector","selSectionDom","closest","sectionId","getAttribute","section","get","matches","contentcollapsed","dispatch","scrollIntoView","_allSectionToggler","preventDefault","isAllCollapsed","TOGGLEALL","classList","contains","classes","COLLAPSED","sectionlist","sectionlistDom","querySelectorAll","sectionCount","length","push","dataset","id","_refreshAllSectionsToggler","getElement","allcollapsed","allexpanded","sectionCollapsible","forEach","style","display","add","setAttribute","remove","_refreshSectionNumber","SECTION","number","sectionid","inplace","inplaceeditable","getInplaceEditable","SECTION_ITEM","currentvalue","getValue","currentitemid","getItemId","timed","rawtitle","setValue","_refreshCourseSectionlist","param","sectionsDom","sdi","sectionDom","refreshCms","pageSectionDisplay","pageid","fmtonpage","visible","badgeDom","_refreshSectionCmlist","menuDom","render","controlmenu","done","html","replaceNode","stateManager","_indexContents","_scanIndex","sections","item","Section","CM","cms","CmItem"],"mappings":";;;;;;;;;iUAgCqBA,kBAAkBC,6BAUvBC,OAAQC,UAAWC,sBACpB,IAAIC,KAAK,CACZC,QAASC,SAASC,eAAeN,QACjCO,UAAU,0CACVN,UAAAA,UACAC,cAAAA,gBASRM,WAAWC,aACDD,WAAWC,YAGZC,0BAGLC,OAAOC,iBAAiB,aAAcT,KAAKO,wBAAwBG,KAAKV,OAU5EO,wBAAwBI,WAIhBC,OAASJ,OAAOK,SAASC,SACxBF,OAAOG,MAAM,0CAGdC,UAAW,EACXJ,OAAOG,MAAM,sBACbH,QAAkB,SAClBI,UAAW,EACXC,QAAQC,aAAaD,QAAQX,MAAO,GAAIM,eAEtCO,oBACFjB,SAASkB,cAAc,oEAAsER,YAG5FO,iCAICE,cAAgBF,oBAAoBG,QAAQ,4BAC5CC,UAAYF,cAAcG,aAAa,WACvCC,QAAUzB,KAAKI,SAASsB,IAAI,UAAWH,WAGzCF,cAAcM,QAAQ,gCACdN,cAAcD,cAAc,4DACzBK,QAAQG,wBACdxB,SAASyB,SACV,0BACA,CAACN,YACD,GAKJP,UACAK,cAAcS,iBAatBC,mBAAmBpB,OACfA,MAAMqB,uBAGAC,eADStB,MAAMd,OAAOyB,QAAQtB,KAAKF,UAAUoC,WACrBC,UAAUC,SAASpC,KAAKqC,QAAQC,eAG1DC,YAAc,SACZC,eAAiBxC,KAAKC,QAAQwC,iBAAiB,qEAChD,IAAIC,aAAe,EAAGA,aAAeF,eAAeG,OAAQD,eAC7DH,YAAYK,KAAKJ,eAAeE,cAAcG,QAAQC,SAIrD1C,SAASyB,SACV,0BACAU,aACCN,gBASTc,2BAA2BzC,aACjBT,OAASG,KAAKgD,WAAWhD,KAAKF,UAAUoC,eACzCrC,kBAIDoD,cAAe,EACfC,aAAc,EAEdC,mBAAqB,SACnBX,eAAiBxC,KAAKC,QAAQwC,iBAAiB,qEAChD,IAAIC,aAAe,EAAGA,aAAeF,eAAeG,OAAQD,eAC7DS,mBAAmBX,eAAeE,cAAcG,QAAQC,KAAM,EAGlExC,MAAMmB,QAAQ2B,SACV3B,UACQ0B,mBAAmB1B,QAAQqB,MAC3BG,aAAeA,cAAgBxB,QAAQG,iBACvCsB,YAAcA,cAAgBzB,QAAQG,qBAIlD/B,OAAOwD,MAAMC,QAAWJ,aAAeD,aAAgB,OAAS,QAC5DA,eACApD,OAAOsC,UAAUoB,IAAIvD,KAAKqC,QAAQC,WAClCzC,OAAO2D,aAAa,iBAAiB,IAErCN,cACArD,OAAOsC,UAAUsB,OAAOzD,KAAKqC,QAAQC,WACrCzC,OAAO2D,aAAa,iBAAiB,IAiB7CE,gCAAsBzD,QAACA,oBAEbJ,OAASG,KAAKgD,WAAWhD,KAAKF,UAAU6D,QAAS1D,QAAQ6C,QAC1DjD,cAKLA,OAAOiD,qBAAgB7C,QAAQ2D,QAI/B/D,OAAOgD,QAAQgB,UAAY5D,QAAQ2D,OAEnC/D,OAAOgD,QAAQe,OAAS3D,QAAQ2D,aAG1BE,QAAUC,0BAAgBC,mBAAmBnE,OAAOuB,cAAcpB,KAAKF,UAAUmE,kBACnFH,QAAS,OAGHI,aAAeJ,QAAQK,WACvBC,cAAgBN,QAAQO,aAEH,KAAvBP,QAAQK,YAAqBlE,QAAQqE,SAEjCF,eAAiBnE,QAAQ6C,IACrBoB,cAAgBjE,QAAQsE,UAAgC,IAApBtE,QAAQsE,WAAkBtE,QAAQqE,OAC1ER,QAAQU,SAASvE,QAAQsE,YAazCE,0BAA0BC,aAChBD,0BAA0BC,aAC1BC,YAAc3E,KAAKC,QAAQwC,iBAAiBzC,KAAKF,UAAU6D,aAC5D,IAAIiB,IAAM,EAAGA,IAAMD,YAAYhC,OAAQiC,MAAO,OACzCC,WAAaF,YAAYC,KACzBnD,QAAUzB,KAAKI,SAASsB,IAAI,UAAWmD,WAAWhC,QAAQC,QAC3DrB,qBAGDqD,YAAa,QAEXC,mBADiB/E,KAAKC,QAAQmB,cAAc,4BAA8BK,QAAQuD,OAAS,MACvDnC,QAAQoC,aAC9CJ,WAAWhC,QAAQoC,WAAaF,qBAChCF,WAAWhC,QAAQoC,UAAYF,mBAC/BF,WAAWxB,MAAMC,QAAiC,KAAtByB,mBAA6B,QAAU,OACzC,KAAtBA,qBACAD,YAAa,IAGjBrD,QAAQyD,SAAWL,WAAW1C,UAAUC,SAAS,UAAW,OACtD+C,SAAWN,WAAWzD,cAAc,8CACtCK,QAAQyD,SACRL,WAAW1C,UAAUsB,OAAO,UAC5B0B,SAAShD,UAAUoB,IAAI,YAEvBsB,WAAW1C,UAAUoB,IAAI,UACzB4B,SAAShD,UAAUsB,OAAO,WAEM,KAAhCoB,WAAWhC,QAAQoC,YACnBH,YAAa,GAGjBA,iBAEKM,sBAAsB,CAACnF,QAASwB,gBAEnC4D,QAAUR,WAAWzD,cAAc,kEAC/BkE,OAAO,sDAAuD7D,QAAQ8D,aAAaC,MAAK,SAASC,yBAC7FC,YAAYL,QAASI,KAAM,YAGxC1C,2BAA2B/C,KAAKI,SAASuF,aAAarF,OAQ/DsF,sBAESC,WACD7F,KAAKF,UAAU6D,QACf3D,KAAK8F,UACJC,MACU,IAAIC,iBAAQD,aAKtBF,WACD7F,KAAKF,UAAUmG,GACfjG,KAAKkG,KACJH,MACU,IAAII,gBAAOJ"} \ No newline at end of file +{"version":3,"file":"content.min.js","sources":["../../src/courseformat/content.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 index main component.\n *\n * @module format_multitopic/courseformat/content\n * @class format_multitopic/courseformat/content\n * @copyright 2022 James Calder and Otago Polytechnic\n * @copyright based on work by 2020 Ferran Recio \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport BaseComponent from 'core_courseformat/local/content';\nimport {getCurrentCourseEditor} from 'core_courseformat/courseeditor';\nimport inplaceeditable from 'core/inplace_editable';\nimport Section from 'format_multitopic/courseformat/content/section';\nimport CmItem from 'format_multitopic/courseformat/content/section/cmitem';\nimport Templates from 'core/templates';\nimport DispatchActions from 'format_multitopic/courseformat/content/actions';\nimport * as CourseEvents from 'core_course/events';\n\nexport default class Component extends BaseComponent {\n\n /**\n * Constructor hook.\n *\n * @param {Object} descriptor the component descriptor\n */\n create(descriptor) {\n super.create(descriptor);\n this.version = descriptor.version;\n }\n\n /**\n * Static method to create a component instance form the mustahce template.\n *\n * @param {string} target the DOM main element or its ID\n * @param {object} selectors optional css selector overrides\n * @param {number} sectionReturn the content section return\n * @param {number} version Moodle version number\n * @return {Component}\n */\n static init(target, selectors, sectionReturn, version) {\n return new this({ // CHANGED.\n element: document.getElementById(target),\n reactive: getCurrentCourseEditor(),\n selectors,\n sectionReturn,\n version,\n });\n }\n\n /**\n * Initial state ready method.\n *\n * @param {Object} state the state data\n */\n stateReady(state) {\n this._indexContents();\n // Activate section togglers.\n this.addEventListener(this.element, 'click', this._sectionTogglers);\n\n // Collapse/Expand all sections button.\n const toogleAll = this.getElement(this.selectors.TOGGLEALL);\n if (toogleAll) {\n\n // Ensure collapse menu button adds aria-controls attribute referring to each collapsible element.\n const collapseElements = this.getElements(this.selectors.COLLAPSE);\n const collapseElementIds = [...collapseElements].map(element => element.id);\n toogleAll.setAttribute('aria-controls', collapseElementIds.join(' '));\n\n this.addEventListener(toogleAll, 'click', this._allSectionToggler);\n this.addEventListener(toogleAll, 'keydown', e => {\n // Collapse/expand all sections when Space key is pressed on the toggle button.\n if (e.key === ' ') {\n this._allSectionToggler(e);\n }\n });\n this._refreshAllSectionsToggler(state);\n }\n\n if (this.reactive.supportComponents) {\n // Actions are only available in edit mode.\n if (this.reactive.isEditing) {\n new DispatchActions(this); // CHANGED.\n }\n\n // Mark content as state ready.\n this.element.classList.add(this.classes.STATEDREADY);\n }\n\n // Capture completion events.\n this.addEventListener(\n this.element,\n CourseEvents.manualCompletionToggled,\n this._completionHandler\n );\n\n // Capture page scroll to update page item.\n this.addEventListener(\n (this.version >= 2023081800) ? document : document.querySelector(this.selectors.PAGE),\n \"scroll\",\n this._scrollHandler\n );\n\n // Set the initial state of collapsible sections.\n this.fmtCollapseOnHashChange();\n\n // Capture clicks on course section links.\n window.addEventListener(\"hashchange\", this.fmtCollapseOnHashChange.bind(this));\n\n }\n\n /**\n * Expand, and scroll to, the section specified in the URL bar.\n *\n * @param {HashChangeEvent?} event The triggering event, if any\n */\n /* eslint-disable no-unused-vars */\n fmtCollapseOnHashChange(event) {\n /* eslint-enable no-unused-vars */\n\n // Find the specified section.\n let anchor = window.location.hash;\n if (!anchor.match(/^#sectionid-\\d+(?:-title)?$/)) {\n return;\n }\n let oldStyle = false;\n if (anchor.match(/^#sectionid-\\d+$/)) {\n anchor = anchor + \"-title\";\n oldStyle = true;\n history.replaceState(history.state, \"\", anchor);\n }\n const selSectionHeaderDom =\n document.querySelector(\".course-content ul.sections li.section.section-topic .sectionname\" + anchor);\n\n // Exit if there is no recognised section.\n if (!selSectionHeaderDom) {\n return;\n }\n\n const selSectionDom = selSectionHeaderDom.closest(\"li.section.section-topic\");\n const sectionId = selSectionDom.getAttribute('data-id');\n const section = this.reactive.get('section', sectionId);\n\n // Expand, if appropriate.\n if (selSectionDom.matches(\".section-topic-collapsible\")\n && (selSectionDom.querySelector(\".course-section-header .icons-collapse-expand.collapsed\")\n || section.contentcollapsed)) {\n this.reactive.dispatch(\n 'sectionContentCollapsed',\n [sectionId],\n false\n );\n }\n\n // Scroll to the specified section.\n if (oldStyle) {\n selSectionDom.scrollIntoView();\n }\n\n }\n\n /**\n * Handle the collapse/expand all sections button.\n *\n * Toggler click is delegated to the main course content element because new sections can\n * appear at any moment and this way we prevent accidental double bindings.\n *\n * @param {Event} event the triggered event\n */\n _allSectionToggler(event) {\n event.preventDefault();\n\n const target = event.target.closest(this.selectors.TOGGLEALL);\n const isAllCollapsed = target.classList.contains(this.classes.COLLAPSED);\n\n // CHANGED.\n let sectionlist = [];\n const sectionlistDom = this.element.querySelectorAll(\".course-section.section-topic-collapsible[data-fmtonpage='1']\");\n for (let sectionCount = 0; sectionCount < sectionlistDom.length; sectionCount++) {\n sectionlist.push(sectionlistDom[sectionCount].dataset.id);\n }\n // END CHANGED.\n\n this.reactive.dispatch(\n 'sectionContentCollapsed',\n sectionlist, // CHANGED.\n !isAllCollapsed\n );\n }\n\n /**\n * Refresh the collapse/expand all sections element.\n *\n * @param {Object} state The state data\n */\n _refreshAllSectionsToggler(state) {\n const target = this.getElement(this.selectors.TOGGLEALL);\n if (!target) {\n return;\n }\n // Check if we have all sections collapsed/expanded.\n let allcollapsed = true;\n let allexpanded = true;\n // ADDED.\n let sectionCollapsible = {};\n const sectionlistDom = this.element.querySelectorAll(\".course-section.section-topic-collapsible[data-fmtonpage='1']\");\n for (let sectionCount = 0; sectionCount < sectionlistDom.length; sectionCount++) {\n sectionCollapsible[sectionlistDom[sectionCount].dataset.id] = true;\n }\n // END ADDED.\n state.section.forEach(\n section => {\n if (sectionCollapsible[section.id]) { // ADDED.\n allcollapsed = allcollapsed && section.contentcollapsed;\n allexpanded = allexpanded && !section.contentcollapsed;\n }\n }\n );\n target.style.display = (allexpanded && allcollapsed) ? \"none\" : \"block\"; // ADDED.\n if (allcollapsed) {\n target.classList.add(this.classes.COLLAPSED);\n target.setAttribute('aria-expanded', false);\n }\n if (allexpanded) {\n target.classList.remove(this.classes.COLLAPSED);\n target.setAttribute('aria-expanded', true);\n }\n }\n\n /**\n * Update a course section when the section number changes.\n *\n * The courseActions module used for most course section tools still depends on css classes and\n * section numbers (not id). To prevent inconsistencies when a section is moved, we need to refresh\n * the\n *\n * Course formats can override the section title rendering so the frontend depends heavily on backend\n * rendering. Luckily in edit mode we can trigger a title update using the inplace_editable module.\n *\n * @param {Object} param\n * @param {Object} param.element details the update details.\n */\n _refreshSectionNumber({element}) {\n // Find the element.\n const target = this.getElement(this.selectors.SECTION, element.id);\n if (!target) {\n // Job done. Nothing to refresh.\n return;\n }\n // Update section numbers in all data, css and YUI attributes.\n target.id = `section-${element.number}`;\n // YUI uses section number as section id in data-sectionid, in principle if a format use components\n // don't need this sectionid attribute anymore, but we keep the compatibility in case some plugin\n // use it for legacy purposes.\n target.dataset.sectionid = element.number;\n // The data-number is the attribute used by components to store the section number.\n target.dataset.number = element.number;\n\n // Update title and title inplace editable, if any.\n const inplace = inplaceeditable.getInplaceEditable(target.querySelector(this.selectors.SECTION_ITEM));\n if (inplace) {\n // The course content HTML can be modified at any moment, so the function need to do some checkings\n // to make sure the inplace editable still represents the same itemid.\n const currentvalue = inplace.getValue();\n const currentitemid = inplace.getItemId();\n // Unnamed sections must be recalculated.\n if (inplace.getValue() === '' || element.timed) { // CHANGED.\n // The value to send can be an empty value if it is a default name.\n if (currentitemid == element.id\n && (currentvalue != element.rawtitle || element.rawtitle == '' || element.timed)) { // CHANGED.\n inplace.setValue(element.rawtitle);\n }\n }\n }\n }\n\n /**\n * Refresh the section list.\n *\n * @param {Object} param\n * @param {Object} param.element details the update details (Moodle <4.4).\n * @param {Object} param.state the full state object (Moodle >=4.4).\n */\n _refreshCourseSectionlist(param) {\n super._refreshCourseSectionlist(param);\n const sectionsDom = this.element.querySelectorAll(this.selectors.SECTION);\n for (let sdi = 0; sdi < sectionsDom.length; sdi++) {\n const sectionDom = sectionsDom[sdi];\n const section = this.reactive.get(\"section\", sectionDom.dataset.id);\n if (!section) {\n continue;\n }\n let refreshCms = false;\n const pageSectionDom = this.element.querySelector(\".course-section[data-id='\" + section.pageid + \"']\");\n const pageSectionDisplay = pageSectionDom.dataset.fmtonpage;\n if (sectionDom.dataset.fmtonpage != pageSectionDisplay) {\n sectionDom.dataset.fmtonpage = pageSectionDisplay;\n sectionDom.style.display = (pageSectionDisplay == \"1\") ? \"block\" : \"none\";\n if (pageSectionDisplay == \"1\") {\n refreshCms = true;\n }\n }\n if (section.visible == sectionDom.classList.contains(\"hidden\")) {\n const badgeDom = sectionDom.querySelector(\"span.badge[data-type='hiddenfromstudents']\");\n if (section.visible) {\n sectionDom.classList.remove(\"hidden\");\n badgeDom.classList.add(\"d-none\");\n } else {\n sectionDom.classList.add(\"hidden\");\n badgeDom.classList.remove(\"d-none\");\n }\n if (sectionDom.dataset.fmtonpage == \"1\") {\n refreshCms = true;\n }\n }\n if (refreshCms) {\n // Note: Visibility state doesn't get updated for CMs already rendered.\n this._refreshSectionCmlist({element: section});\n }\n const menuDom = sectionDom.querySelector(\".course-section-header .section_action_menu\");\n Templates.render(\"core_courseformat/local/content/section/controlmenu\", section.controlmenu).done(function(html) {\n Templates.replaceNode(menuDom, html, \"\");\n });\n }\n this._refreshAllSectionsToggler(this.reactive.stateManager.state);\n }\n\n /**\n * Regenerate content indexes.\n *\n * This method is used when a legacy action refresh some content element.\n */\n _indexContents() {\n // Find unindexed sections.\n this._scanIndex(\n this.selectors.SECTION,\n this.sections,\n (item) => {\n return new Section(item); // CHANGED.\n }\n );\n\n // Find unindexed cms.\n this._scanIndex(\n this.selectors.CM,\n this.cms,\n (item) => {\n return new CmItem(item); // CHANGED.\n }\n );\n }\n\n}"],"names":["Component","BaseComponent","create","descriptor","version","target","selectors","sectionReturn","this","element","document","getElementById","reactive","stateReady","state","_indexContents","addEventListener","_sectionTogglers","toogleAll","getElement","TOGGLEALL","collapseElementIds","getElements","COLLAPSE","map","id","setAttribute","join","_allSectionToggler","e","key","_refreshAllSectionsToggler","supportComponents","isEditing","DispatchActions","classList","add","classes","STATEDREADY","CourseEvents","manualCompletionToggled","_completionHandler","querySelector","PAGE","_scrollHandler","fmtCollapseOnHashChange","window","bind","event","anchor","location","hash","match","oldStyle","history","replaceState","selSectionHeaderDom","selSectionDom","closest","sectionId","getAttribute","section","get","matches","contentcollapsed","dispatch","scrollIntoView","preventDefault","isAllCollapsed","contains","COLLAPSED","sectionlist","sectionlistDom","querySelectorAll","sectionCount","length","push","dataset","allcollapsed","allexpanded","sectionCollapsible","forEach","style","display","remove","_refreshSectionNumber","SECTION","number","sectionid","inplace","inplaceeditable","getInplaceEditable","SECTION_ITEM","currentvalue","getValue","currentitemid","getItemId","timed","rawtitle","setValue","_refreshCourseSectionlist","param","sectionsDom","sdi","sectionDom","refreshCms","pageSectionDisplay","pageid","fmtonpage","visible","badgeDom","_refreshSectionCmlist","menuDom","render","controlmenu","done","html","replaceNode","stateManager","_scanIndex","sections","item","Section","CM","cms","CmItem"],"mappings":"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"} \ No newline at end of file diff --git a/amd/build/courseformat/content/actions.min.js b/amd/build/courseformat/content/actions.min.js new file mode 100644 index 0000000..2c6988f --- /dev/null +++ b/amd/build/courseformat/content/actions.min.js @@ -0,0 +1,15 @@ +define("format_multitopic/courseformat/content/actions",["exports","core_courseformat/local/content/actions"],(function(_exports,_actions){var obj; +/** + * Course state actions dispatcher. + * + * This module captures all data-dispatch links in the course content and dispatch the proper + * state mutation, including any confirmation and modal required. + * + * @module format_multitopic/courseformat/content/actions + * @class format_multitopic/courseformat/content/actions + * @copyright 2024 James Calder and Otago Polytechnic + * @copyright based on work by 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,_actions=(obj=_actions)&&obj.__esModule?obj:{default:obj};class _default extends _actions.default{async _requestAddSection(target,event){var _target$dataset$intoI,_target$dataset$level,_target$dataset$id;(event.preventDefault(),target.dataset.intoId)?this.reactive.dispatch("fmtAddSectionInto",null!==(_target$dataset$intoI=target.dataset.intoId)&&void 0!==_target$dataset$intoI?_target$dataset$intoI:0,null!==(_target$dataset$level=target.dataset.level)&&void 0!==_target$dataset$level?_target$dataset$level:2):this.reactive.dispatch("addSection",null!==(_target$dataset$id=target.dataset.id)&&void 0!==_target$dataset$id?_target$dataset$id:0)}_setAddSectionLocked(locked){super._setAddSectionLocked(locked);this.getElements("[data-region='tab-addsection']").forEach((element=>{element.classList.toggle(this.classes.DISABLED,locked);const addSectionElement=element.querySelector("a.nav-link");addSectionElement.classList.toggle(this.classes.DISABLED,locked),this.setElementLocked(addSectionElement,locked)}))}}return _exports.default=_default,_exports.default})); + +//# sourceMappingURL=actions.min.js.map \ No newline at end of file diff --git a/amd/build/courseformat/content/actions.min.js.map b/amd/build/courseformat/content/actions.min.js.map new file mode 100644 index 0000000..2cd5e65 --- /dev/null +++ b/amd/build/courseformat/content/actions.min.js.map @@ -0,0 +1 @@ +{"version":3,"file":"actions.min.js","sources":["../../../src/courseformat/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 format_multitopic/courseformat/content/actions\n * @class format_multitopic/courseformat/content/actions\n * @copyright 2024 James Calder and Otago Polytechnic\n * @copyright based on work by 2021 Ferran Recio \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport BaseComponent from 'core_courseformat/local/content/actions';\n\nexport default class extends BaseComponent {\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 if (target.dataset.intoId) {\n this.reactive.dispatch('fmtAddSectionInto', target.dataset.intoId ?? 0, target.dataset.level ?? 2);\n } else {\n this.reactive.dispatch('addSection', target.dataset.id ?? 0);\n }\n }\n\n /**\n * Disable all add sections actions.\n *\n * @param {boolean} locked the new locked value.\n */\n _setAddSectionLocked(locked) {\n super._setAddSectionLocked(locked);\n const targets = this.getElements(`[data-region='tab-addsection']`);\n targets.forEach(element => {\n element.classList.toggle(this.classes.DISABLED, locked);\n const addSectionElement = element.querySelector(`a.nav-link`);\n addSectionElement.classList.toggle(this.classes.DISABLED, locked);\n this.setElementLocked(addSectionElement, locked);\n });\n }\n\n}\n"],"names":["BaseComponent","target","event","preventDefault","dataset","intoId","reactive","dispatch","level","id","_setAddSectionLocked","locked","this","getElements","forEach","element","classList","toggle","classes","DISABLED","addSectionElement","querySelector","setElementLocked"],"mappings":";;;;;;;;;;;;sKA8B6BA,0CAQAC,OAAQC,2EAC7BA,MAAMC,iBACFF,OAAOG,QAAQC,aACVC,SAASC,SAAS,kDAAqBN,OAAOG,QAAQC,8DAAU,gCAAGJ,OAAOG,QAAQI,6DAAS,QAE3FF,SAASC,SAAS,wCAAcN,OAAOG,QAAQK,oDAAM,GASlEC,qBAAqBC,cACXD,qBAAqBC,QACXC,KAAKC,8CACbC,SAAQC,UACZA,QAAQC,UAAUC,OAAOL,KAAKM,QAAQC,SAAUR,cAC1CS,kBAAoBL,QAAQM,4BAClCD,kBAAkBJ,UAAUC,OAAOL,KAAKM,QAAQC,SAAUR,aACrDW,iBAAiBF,kBAAmBT"} \ No newline at end of file diff --git a/amd/build/courseformat/content/section.min.js b/amd/build/courseformat/content/section.min.js index 2794353..3fadf94 100644 --- a/amd/build/courseformat/content/section.min.js +++ b/amd/build/courseformat/content/section.min.js @@ -7,6 +7,6 @@ define("format_multitopic/courseformat/content/section",["exports","format_multi * @copyright 2022 James Calder and Otago Polytechnic * @copyright based on work by 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,_header=_interopRequireDefault(_header),_section=_interopRequireDefault(_section);class _default extends _section.default{stateReady(state){if(this.configState(state),this.reactive.isEditing&&this.reactive.supportComponents){const sectionItem=this.getElement(this.selectors.SECTION_ITEM);if(sectionItem){const headerComponent=new _header.default({...this,element:sectionItem,fullregion:this.element});this.configDragDrop(headerComponent)}}}validateDropData(dropdata){if("section"===(null==dropdata?void 0:dropdata.type)){const origin=this.reactive.get("section",dropdata.id);return origin.id!=this.section.id&&origin.levelsan>=2&&(this.section.levelsan>=2||this.section.section>origin.section||this.course.draganddropsectionmoveafter)}return super.validateDropData(dropdata)}}return _exports.default=_default,_exports.default})); + */Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.default=void 0,_header=_interopRequireDefault(_header),_section=_interopRequireDefault(_section);class _default extends _section.default{stateReady(state){if(this.configState(state),this.reactive.isEditing&&this.reactive.supportComponents){const sectionItem=this.getElement(this.selectors.SECTION_ITEM);if(sectionItem){const headerComponent=new _header.default({...this,element:sectionItem,fullregion:this.element});this.configDragDrop(headerComponent)}}const pageSectionDisplay=document.querySelector(".course-section[data-id='"+this.section.pageid+"']").dataset.fmtonpage;this.element.dataset.fmtonpage!=pageSectionDisplay&&(this.element.dataset.fmtonpage=pageSectionDisplay,this.element.style.display="1"==pageSectionDisplay?"block":"none")}validateDropData(dropdata){if("section"===(null==dropdata?void 0:dropdata.type)){const origin=this.reactive.get("section",dropdata.id);return origin.id!=this.section.id&&origin.levelsan>=2&&(this.section.levelsan>=2||this.section.section>origin.section||this.course.draganddropsectionmoveafter)}return super.validateDropData(dropdata)}}return _exports.default=_default,_exports.default})); //# sourceMappingURL=section.min.js.map \ No newline at end of file diff --git a/amd/build/courseformat/content/section.min.js.map b/amd/build/courseformat/content/section.min.js.map index 170039a..3a5e2e8 100644 --- a/amd/build/courseformat/content/section.min.js.map +++ b/amd/build/courseformat/content/section.min.js.map @@ -1 +1 @@ -{"version":3,"file":"section.min.js","sources":["../../../src/courseformat/content/section.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 section format component.\n *\n * @module format_multitopic/courseformat/content/section\n * @class format_multitopic/courseformat/content/section\n * @copyright 2022 James Calder and Otago Polytechnic\n * @copyright based on work by 2021 Ferran Recio \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport Header from 'format_multitopic/courseformat/content/section/header';\nimport SectionBase from 'core_courseformat/local/content/section';\n\nexport default class extends SectionBase {\n\n /**\n * Initial state ready method.\n *\n * @param {Object} state the initial state\n */\n stateReady(state) {\n this.configState(state);\n // Drag and drop is only available for components compatible course formats.\n if (this.reactive.isEditing && this.reactive.supportComponents) {\n // Section zero and other formats sections may not have a title to drag.\n const sectionItem = this.getElement(this.selectors.SECTION_ITEM);\n if (sectionItem) {\n // Init the inner dragable element.\n const headerComponent = new Header({ // CHANGED.\n ...this,\n element: sectionItem,\n fullregion: this.element,\n });\n this.configDragDrop(headerComponent);\n }\n }\n }\n\n /**\n * Validate if the drop data can be dropped over the component.\n *\n * @param {Object} dropdata the exported drop data.\n * @returns {boolean}\n */\n validateDropData(dropdata) {\n if (dropdata?.type === 'section') {\n const origin = this.reactive.get(\"section\", dropdata.id);\n return origin.id != this.section.id && origin.levelsan >= 2\n && (this.section.levelsan >= 2 || this.section.section > origin.section\n || this.course.draganddropsectionmoveafter);\n }\n return super.validateDropData(dropdata);\n }\n\n}"],"names":["SectionBase","stateReady","state","configState","this","reactive","isEditing","supportComponents","sectionItem","getElement","selectors","SECTION_ITEM","headerComponent","Header","element","fullregion","configDragDrop","validateDropData","dropdata","type","origin","get","id","section","levelsan","course","draganddropsectionmoveafter","super"],"mappings":";;;;;;;;;8LA4B6BA,iBAOzBC,WAAWC,eACFC,YAAYD,OAEbE,KAAKC,SAASC,WAAaF,KAAKC,SAASE,kBAAmB,OAEtDC,YAAcJ,KAAKK,WAAWL,KAAKM,UAAUC,iBAC/CH,YAAa,OAEPI,gBAAkB,IAAIC,gBAAO,IAC5BT,KACHU,QAASN,YACTO,WAAYX,KAAKU,eAEhBE,eAAeJ,mBAWhCK,iBAAiBC,aACU,aAAnBA,MAAAA,gBAAAA,SAAUC,MAAoB,OACxBC,OAAShB,KAAKC,SAASgB,IAAI,UAAWH,SAASI,WAC9CF,OAAOE,IAAMlB,KAAKmB,QAAQD,IAAMF,OAAOI,UAAY,IAC9CpB,KAAKmB,QAAQC,UAAY,GAAKpB,KAAKmB,QAAQA,QAAUH,OAAOG,SACzDnB,KAAKqB,OAAOC,oCAExBC,MAAMV,iBAAiBC"} \ No newline at end of file +{"version":3,"file":"section.min.js","sources":["../../../src/courseformat/content/section.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 section format component.\n *\n * @module format_multitopic/courseformat/content/section\n * @class format_multitopic/courseformat/content/section\n * @copyright 2022 James Calder and Otago Polytechnic\n * @copyright based on work by 2021 Ferran Recio \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport Header from 'format_multitopic/courseformat/content/section/header';\nimport SectionBase from 'core_courseformat/local/content/section';\n\nexport default class extends SectionBase {\n\n /**\n * Initial state ready method.\n *\n * @param {Object} state the initial state\n */\n stateReady(state) {\n this.configState(state);\n // Drag and drop is only available for components compatible course formats.\n if (this.reactive.isEditing && this.reactive.supportComponents) {\n // Section zero and other formats sections may not have a title to drag.\n const sectionItem = this.getElement(this.selectors.SECTION_ITEM);\n if (sectionItem) {\n // Init the inner dragable element.\n const headerComponent = new Header({ // CHANGED.\n ...this,\n element: sectionItem,\n fullregion: this.element,\n });\n this.configDragDrop(headerComponent);\n }\n }\n\n const pageSectionDom = document.querySelector(\".course-section[data-id='\" + this.section.pageid + \"']\");\n const pageSectionDisplay = pageSectionDom.dataset.fmtonpage;\n if (this.element.dataset.fmtonpage != pageSectionDisplay) {\n this.element.dataset.fmtonpage = pageSectionDisplay;\n this.element.style.display = (pageSectionDisplay == \"1\") ? \"block\" : \"none\";\n }\n }\n\n /**\n * Validate if the drop data can be dropped over the component.\n *\n * @param {Object} dropdata the exported drop data.\n * @returns {boolean}\n */\n validateDropData(dropdata) {\n if (dropdata?.type === 'section') {\n const origin = this.reactive.get(\"section\", dropdata.id);\n return origin.id != this.section.id && origin.levelsan >= 2\n && (this.section.levelsan >= 2 || this.section.section > origin.section\n || this.course.draganddropsectionmoveafter);\n }\n return super.validateDropData(dropdata);\n }\n\n}"],"names":["SectionBase","stateReady","state","configState","this","reactive","isEditing","supportComponents","sectionItem","getElement","selectors","SECTION_ITEM","headerComponent","Header","element","fullregion","configDragDrop","pageSectionDisplay","document","querySelector","section","pageid","dataset","fmtonpage","style","display","validateDropData","dropdata","type","origin","get","id","levelsan","course","draganddropsectionmoveafter","super"],"mappings":";;;;;;;;;8LA4B6BA,iBAOzBC,WAAWC,eACFC,YAAYD,OAEbE,KAAKC,SAASC,WAAaF,KAAKC,SAASE,kBAAmB,OAEtDC,YAAcJ,KAAKK,WAAWL,KAAKM,UAAUC,iBAC/CH,YAAa,OAEPI,gBAAkB,IAAIC,gBAAO,IAC5BT,KACHU,QAASN,YACTO,WAAYX,KAAKU,eAEhBE,eAAeJ,wBAKtBK,mBADiBC,SAASC,cAAc,4BAA8Bf,KAAKgB,QAAQC,OAAS,MACxDC,QAAQC,UAC9CnB,KAAKU,QAAQQ,QAAQC,WAAaN,0BAC7BH,QAAQQ,QAAQC,UAAYN,wBAC5BH,QAAQU,MAAMC,QAAiC,KAAtBR,mBAA6B,QAAU,QAU7ES,iBAAiBC,aACU,aAAnBA,MAAAA,gBAAAA,SAAUC,MAAoB,OACxBC,OAASzB,KAAKC,SAASyB,IAAI,UAAWH,SAASI,WAC9CF,OAAOE,IAAM3B,KAAKgB,QAAQW,IAAMF,OAAOG,UAAY,IAC9C5B,KAAKgB,QAAQY,UAAY,GAAK5B,KAAKgB,QAAQA,QAAUS,OAAOT,SACzDhB,KAAK6B,OAAOC,oCAExBC,MAAMT,iBAAiBC"} \ No newline at end of file diff --git a/amd/build/courseformat/courseeditor/mutations.min.js b/amd/build/courseformat/courseeditor/mutations.min.js index 8bb826b..d4c0c97 100644 --- a/amd/build/courseformat/courseeditor/mutations.min.js +++ b/amd/build/courseformat/courseeditor/mutations.min.js @@ -1,3 +1,3 @@ -define("format_multitopic/courseformat/courseeditor/mutations",["exports","core_courseformat/courseeditor","core_courseformat/local/courseeditor/mutations"],(function(_exports,_courseeditor,_mutations){var obj;function _defineProperty(obj,key,value){return key in obj?Object.defineProperty(obj,key,{value:value,enumerable:!0,configurable:!0,writable:!0}):obj[key]=value,obj}Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.init=void 0,_mutations=(obj=_mutations)&&obj.__esModule?obj:{default:obj};class MultitopicMutations extends _mutations.default{constructor(){super(...arguments),_defineProperty(this,"sectionMove",(async function(stateManager,sectionIds,targetSectionId){if(!targetSectionId)throw new Error("Mutation sectionMove requires targetSectionId");const course=stateManager.get("course");let subsectionIds=[];for(const sectionId of sectionIds){const section=stateManager.get("section",sectionId);for(let subsection=section;subsection&&(subsection.id==section.id||subsection.levelsan>section.levelsan);subsection=course.sectionlist.length>subsection.number+1?stateManager.get("section",course.sectionlist[subsection.number+1]):null)subsectionIds.push(subsection.id)}this.sectionLock(stateManager,subsectionIds,!0);const updates=await this._callEditWebservice("section_move",course.id,sectionIds,targetSectionId);"function"==typeof this.bulkReset&&this.bulkReset(stateManager),stateManager.processUpdates(updates),this.sectionLock(stateManager,subsectionIds,!1)})),_defineProperty(this,"sectionMoveAfter",(async function(stateManager,sectionIds,targetSectionId){if(!targetSectionId)throw new Error("Mutation sectionMoveAfter requires targetSectionId");const course=stateManager.get("course");let subsectionIds=[];for(const sectionId of sectionIds){const section=stateManager.get("section",sectionId);for(let subsection=section;subsection&&(subsection.id==section.id||subsection.levelsan>section.levelsan);subsection=course.sectionlist.length>subsection.number+1?stateManager.get("section",course.sectionlist[subsection.number+1]):null)subsectionIds.push(subsection.id)}this.sectionLock(stateManager,subsectionIds,!0);const updates=await this._callEditWebservice("section_move_after",course.id,sectionIds,targetSectionId);"function"==typeof this.bulkReset&&this.bulkReset(stateManager),stateManager.processUpdates(updates),this.sectionLock(stateManager,subsectionIds,!1)})),_defineProperty(this,"fmtSectionMoveInto",(async function(stateManager,sectionIds,targetSectionId){if(!targetSectionId)throw new Error("Mutation sectionMoveInto requires targetSectionId");const course=stateManager.get("course");let subsectionIds=[];for(const sectionId of sectionIds){const section=stateManager.get("section",sectionId);for(let subsection=section;subsection&&(subsection.id==section.id||subsection.levelsan>section.levelsan);subsection=course.sectionlist.length>subsection.number+1?stateManager.get("section",course.sectionlist[subsection.number+1]):null)subsectionIds.push(subsection.id)}this.sectionLock(stateManager,subsectionIds,!0);const updates=await this._callEditWebservice("fmt_section_move_into",course.id,sectionIds,targetSectionId);"function"==typeof this.bulkReset&&this.bulkReset(stateManager),stateManager.processUpdates(updates),this.sectionLock(stateManager,subsectionIds,!1)}))}}_exports.init=()=>{(0,_courseeditor.getCurrentCourseEditor)().addMutations(new MultitopicMutations)}})); +define("format_multitopic/courseformat/courseeditor/mutations",["exports","core_courseformat/courseeditor","core_courseformat/local/courseeditor/mutations"],(function(_exports,_courseeditor,_mutations){var obj;function _defineProperty(obj,key,value){return key in obj?Object.defineProperty(obj,key,{value:value,enumerable:!0,configurable:!0,writable:!0}):obj[key]=value,obj}Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.init=void 0,_mutations=(obj=_mutations)&&obj.__esModule?obj:{default:obj};class MultitopicMutations extends _mutations.default{constructor(){super(...arguments),_defineProperty(this,"sectionMove",(async function(stateManager,sectionIds,targetSectionId){if(!targetSectionId)throw new Error("Mutation sectionMove requires targetSectionId");const course=stateManager.get("course");let subsectionIds=[];for(const sectionId of sectionIds){const section=stateManager.get("section",sectionId);for(let subsection=section;subsection&&(subsection.id==section.id||subsection.levelsan>section.levelsan);subsection=course.sectionlist.length>subsection.number+1?stateManager.get("section",course.sectionlist[subsection.number+1]):null)subsectionIds.push(subsection.id)}this.sectionLock(stateManager,subsectionIds,!0);const updates=await this._callEditWebservice("section_move",course.id,sectionIds,targetSectionId);"function"==typeof this.bulkReset&&this.bulkReset(stateManager),stateManager.processUpdates(updates),this.sectionLock(stateManager,subsectionIds,!1)})),_defineProperty(this,"sectionMoveAfter",(async function(stateManager,sectionIds,targetSectionId){if(!targetSectionId)throw new Error("Mutation sectionMoveAfter requires targetSectionId");const course=stateManager.get("course");let subsectionIds=[];for(const sectionId of sectionIds){const section=stateManager.get("section",sectionId);for(let subsection=section;subsection&&(subsection.id==section.id||subsection.levelsan>section.levelsan);subsection=course.sectionlist.length>subsection.number+1?stateManager.get("section",course.sectionlist[subsection.number+1]):null)subsectionIds.push(subsection.id)}this.sectionLock(stateManager,subsectionIds,!0);const updates=await this._callEditWebservice("section_move_after",course.id,sectionIds,targetSectionId);"function"==typeof this.bulkReset&&this.bulkReset(stateManager),stateManager.processUpdates(updates),this.sectionLock(stateManager,subsectionIds,!1)})),_defineProperty(this,"fmtSectionMoveInto",(async function(stateManager,sectionIds,targetSectionId){if(!targetSectionId)throw new Error("Mutation sectionMoveInto requires targetSectionId");const course=stateManager.get("course");let subsectionIds=[];for(const sectionId of sectionIds){const section=stateManager.get("section",sectionId);for(let subsection=section;subsection&&(subsection.id==section.id||subsection.levelsan>section.levelsan);subsection=course.sectionlist.length>subsection.number+1?stateManager.get("section",course.sectionlist[subsection.number+1]):null)subsectionIds.push(subsection.id)}this.sectionLock(stateManager,subsectionIds,!0);const updates=await this._callEditWebservice("fmt_section_move_into",course.id,sectionIds,targetSectionId);"function"==typeof this.bulkReset&&this.bulkReset(stateManager),stateManager.processUpdates(updates),this.sectionLock(stateManager,subsectionIds,!1)})),_defineProperty(this,"fmtAddSectionInto",(async function(stateManager,targetSectionId,newLevel){const course=stateManager.get("course"),updates=await this._callEditWebservice("fmt_section_add_into",course.id,[],targetSectionId,newLevel);stateManager.processUpdates(updates)}))}}_exports.init=()=>{(0,_courseeditor.getCurrentCourseEditor)().addMutations(new MultitopicMutations)}})); //# sourceMappingURL=mutations.min.js.map \ No newline at end of file diff --git a/amd/build/courseformat/courseeditor/mutations.min.js.map b/amd/build/courseformat/courseeditor/mutations.min.js.map index af917a6..e6f6ce1 100644 --- a/amd/build/courseformat/courseeditor/mutations.min.js.map +++ b/amd/build/courseformat/courseeditor/mutations.min.js.map @@ -1 +1 @@ -{"version":3,"file":"mutations.min.js","sources":["../../../src/courseformat/courseeditor/mutations.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 * Format Multitopic mutations.\n *\n * An instance of this class will be used to add custom mutations to the course editor.\n * To make sure the addMutations method find the proper functions, all functions must\n * be declared as class attributes, not a simple methods. The reason is because many\n * plugins can add extra mutations to the course editor.\n *\n * @module format_multitopic/courseformat/courseeditor/mutations\n * @copyright 2022 onwards James Calder and Otago Polytechnic\n * @copyright based on work by 2021 onwards Ferran Recio \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport {getCurrentCourseEditor} from 'core_courseformat/courseeditor';\nimport DefaultMutations from 'core_courseformat/local/courseeditor/mutations';\n\nclass MultitopicMutations extends DefaultMutations {\n\n /**\n * Move course sections to specific course location.\n *\n * @param {StateManager} stateManager the current state manager\n * @param {array} sectionIds the list of section ids to move\n * @param {number} targetSectionId the target section id\n */\n sectionMove = async function(stateManager, sectionIds, targetSectionId) {\n if (!targetSectionId) {\n throw new Error(`Mutation sectionMove requires targetSectionId`);\n }\n const course = stateManager.get('course');\n // ADDED.\n let subsectionIds = [];\n for (const sectionId of sectionIds) {\n const section = stateManager.get(\"section\", sectionId);\n for (let subsection = section;\n subsection && (subsection.id == section.id || subsection.levelsan > section.levelsan);\n subsection = (course.sectionlist.length > subsection.number + 1) ?\n stateManager.get(\"section\", course.sectionlist[subsection.number + 1]) : null) {\n subsectionIds.push(subsection.id);\n }\n }\n // END ADDED.\n this.sectionLock(stateManager, subsectionIds, true);\n const updates = await this._callEditWebservice('section_move', course.id, sectionIds, targetSectionId);\n if (typeof this.bulkReset === \"function\") {\n this.bulkReset(stateManager);\n }\n stateManager.processUpdates(updates);\n this.sectionLock(stateManager, subsectionIds, false);\n };\n\n /**\n * Move course sections after a specific course location.\n *\n * @param {StateManager} stateManager the current state manager\n * @param {array} sectionIds the list of section ids to move\n * @param {number} targetSectionId the target section id\n */\n sectionMoveAfter = async function(stateManager, sectionIds, targetSectionId) {\n if (!targetSectionId) {\n throw new Error(`Mutation sectionMoveAfter requires targetSectionId`);\n }\n const course = stateManager.get('course');\n // ADDED.\n let subsectionIds = [];\n for (const sectionId of sectionIds) {\n const section = stateManager.get(\"section\", sectionId);\n for (let subsection = section;\n subsection && (subsection.id == section.id || subsection.levelsan > section.levelsan);\n subsection = (course.sectionlist.length > subsection.number + 1) ?\n stateManager.get(\"section\", course.sectionlist[subsection.number + 1]) : null) {\n subsectionIds.push(subsection.id);\n }\n }\n // END ADDED.\n this.sectionLock(stateManager, subsectionIds, true);\n const updates = await this._callEditWebservice('section_move_after', course.id, sectionIds, targetSectionId);\n if (typeof this.bulkReset === \"function\") {\n this.bulkReset(stateManager);\n }\n stateManager.processUpdates(updates);\n this.sectionLock(stateManager, subsectionIds, false);\n };\n\n /**\n * Move course sections into a specific course location.\n *\n * @param {StateManager} stateManager the current state manager\n * @param {array} sectionIds the list of section ids to move\n * @param {number} targetSectionId the target section id\n */\n fmtSectionMoveInto = async function(stateManager, sectionIds, targetSectionId) {\n if (!targetSectionId) {\n throw new Error(`Mutation sectionMoveInto requires targetSectionId`);\n }\n const course = stateManager.get('course');\n // ADDED.\n let subsectionIds = [];\n for (const sectionId of sectionIds) {\n const section = stateManager.get(\"section\", sectionId);\n for (let subsection = section;\n subsection && (subsection.id == section.id || subsection.levelsan > section.levelsan);\n subsection = (course.sectionlist.length > subsection.number + 1) ?\n stateManager.get(\"section\", course.sectionlist[subsection.number + 1]) : null) {\n subsectionIds.push(subsection.id);\n }\n }\n // END ADDED.\n this.sectionLock(stateManager, subsectionIds, true);\n const updates = await this._callEditWebservice('fmt_section_move_into', course.id, sectionIds, targetSectionId);\n if (typeof this.bulkReset === \"function\") {\n this.bulkReset(stateManager);\n }\n stateManager.processUpdates(updates);\n this.sectionLock(stateManager, subsectionIds, false);\n };\n\n}\n\nexport const init = () => {\n const courseEditor = getCurrentCourseEditor();\n // Some plugin (activity or block) may have their own mutations already registered.\n // This is why we use addMutations instead of setMutations here.\n courseEditor.addMutations(new MultitopicMutations());\n};\n"],"names":["MultitopicMutations","DefaultMutations","async","stateManager","sectionIds","targetSectionId","Error","course","get","subsectionIds","sectionId","section","subsection","id","levelsan","sectionlist","length","number","push","sectionLock","updates","this","_callEditWebservice","bulkReset","processUpdates","addMutations"],"mappings":"ugBAgCMA,4BAA4BC,yFAShBC,eAAeC,aAAcC,WAAYC,qBAC9CA,sBACK,IAAIC,6DAERC,OAASJ,aAAaK,IAAI,cAE5BC,cAAgB,OACf,MAAMC,aAAaN,WAAY,OAC1BO,QAAUR,aAAaK,IAAI,UAAWE,eACvC,IAAIE,WAAaD,QACdC,aAAeA,WAAWC,IAAMF,QAAQE,IAAMD,WAAWE,SAAWH,QAAQG,UAC5EF,WAAcL,OAAOQ,YAAYC,OAASJ,WAAWK,OAAS,EAC1Dd,aAAaK,IAAI,UAAWD,OAAOQ,YAAYH,WAAWK,OAAS,IAAM,KACjFR,cAAcS,KAAKN,WAAWC,SAIjCM,YAAYhB,aAAcM,eAAe,SACxCW,cAAgBC,KAAKC,oBAAoB,eAAgBf,OAAOM,GAAIT,WAAYC,iBACxD,mBAAnBgB,KAAKE,gBACPA,UAAUpB,cAEnBA,aAAaqB,eAAeJ,cACvBD,YAAYhB,aAAcM,eAAe,+CAU/BP,eAAeC,aAAcC,WAAYC,qBACnDA,sBACK,IAAIC,kEAERC,OAASJ,aAAaK,IAAI,cAE5BC,cAAgB,OACf,MAAMC,aAAaN,WAAY,OAC1BO,QAAUR,aAAaK,IAAI,UAAWE,eACvC,IAAIE,WAAaD,QACdC,aAAeA,WAAWC,IAAMF,QAAQE,IAAMD,WAAWE,SAAWH,QAAQG,UAC5EF,WAAcL,OAAOQ,YAAYC,OAASJ,WAAWK,OAAS,EAC1Dd,aAAaK,IAAI,UAAWD,OAAOQ,YAAYH,WAAWK,OAAS,IAAM,KACjFR,cAAcS,KAAKN,WAAWC,SAIjCM,YAAYhB,aAAcM,eAAe,SACxCW,cAAgBC,KAAKC,oBAAoB,qBAAsBf,OAAOM,GAAIT,WAAYC,iBAC9D,mBAAnBgB,KAAKE,gBACPA,UAAUpB,cAEnBA,aAAaqB,eAAeJ,cACvBD,YAAYhB,aAAcM,eAAe,iDAU7BP,eAAeC,aAAcC,WAAYC,qBACrDA,sBACK,IAAIC,iEAERC,OAASJ,aAAaK,IAAI,cAE5BC,cAAgB,OACf,MAAMC,aAAaN,WAAY,OAC1BO,QAAUR,aAAaK,IAAI,UAAWE,eACvC,IAAIE,WAAaD,QACdC,aAAeA,WAAWC,IAAMF,QAAQE,IAAMD,WAAWE,SAAWH,QAAQG,UAC5EF,WAAcL,OAAOQ,YAAYC,OAASJ,WAAWK,OAAS,EAC1Dd,aAAaK,IAAI,UAAWD,OAAOQ,YAAYH,WAAWK,OAAS,IAAM,KACjFR,cAAcS,KAAKN,WAAWC,SAIjCM,YAAYhB,aAAcM,eAAe,SACxCW,cAAgBC,KAAKC,oBAAoB,wBAAyBf,OAAOM,GAAIT,WAAYC,iBACjE,mBAAnBgB,KAAKE,gBACPA,UAAUpB,cAEnBA,aAAaqB,eAAeJ,cACvBD,YAAYhB,aAAcM,eAAe,qBAKlC,MACK,0CAGRgB,aAAa,IAAIzB"} \ No newline at end of file +{"version":3,"file":"mutations.min.js","sources":["../../../src/courseformat/courseeditor/mutations.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 * Format Multitopic mutations.\n *\n * An instance of this class will be used to add custom mutations to the course editor.\n * To make sure the addMutations method find the proper functions, all functions must\n * be declared as class attributes, not a simple methods. The reason is because many\n * plugins can add extra mutations to the course editor.\n *\n * @module format_multitopic/courseformat/courseeditor/mutations\n * @copyright 2022 onwards James Calder and Otago Polytechnic\n * @copyright based on work by 2021 onwards Ferran Recio \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport {getCurrentCourseEditor} from 'core_courseformat/courseeditor';\nimport DefaultMutations from 'core_courseformat/local/courseeditor/mutations';\n\nclass MultitopicMutations extends DefaultMutations {\n\n /**\n * Move course sections to specific course location.\n *\n * @param {StateManager} stateManager the current state manager\n * @param {array} sectionIds the list of section ids to move\n * @param {number} targetSectionId the target section id\n */\n sectionMove = async function(stateManager, sectionIds, targetSectionId) {\n if (!targetSectionId) {\n throw new Error(`Mutation sectionMove requires targetSectionId`);\n }\n const course = stateManager.get('course');\n // ADDED.\n let subsectionIds = [];\n for (const sectionId of sectionIds) {\n const section = stateManager.get(\"section\", sectionId);\n for (let subsection = section;\n subsection && (subsection.id == section.id || subsection.levelsan > section.levelsan);\n subsection = (course.sectionlist.length > subsection.number + 1) ?\n stateManager.get(\"section\", course.sectionlist[subsection.number + 1]) : null) {\n subsectionIds.push(subsection.id);\n }\n }\n // END ADDED.\n this.sectionLock(stateManager, subsectionIds, true);\n const updates = await this._callEditWebservice('section_move', course.id, sectionIds, targetSectionId);\n if (typeof this.bulkReset === \"function\") {\n this.bulkReset(stateManager);\n }\n stateManager.processUpdates(updates);\n this.sectionLock(stateManager, subsectionIds, false);\n };\n\n /**\n * Move course sections after a specific course location.\n *\n * @param {StateManager} stateManager the current state manager\n * @param {array} sectionIds the list of section ids to move\n * @param {number} targetSectionId the target section id\n */\n sectionMoveAfter = async function(stateManager, sectionIds, targetSectionId) {\n if (!targetSectionId) {\n throw new Error(`Mutation sectionMoveAfter requires targetSectionId`);\n }\n const course = stateManager.get('course');\n // ADDED.\n let subsectionIds = [];\n for (const sectionId of sectionIds) {\n const section = stateManager.get(\"section\", sectionId);\n for (let subsection = section;\n subsection && (subsection.id == section.id || subsection.levelsan > section.levelsan);\n subsection = (course.sectionlist.length > subsection.number + 1) ?\n stateManager.get(\"section\", course.sectionlist[subsection.number + 1]) : null) {\n subsectionIds.push(subsection.id);\n }\n }\n // END ADDED.\n this.sectionLock(stateManager, subsectionIds, true);\n const updates = await this._callEditWebservice('section_move_after', course.id, sectionIds, targetSectionId);\n if (typeof this.bulkReset === \"function\") {\n this.bulkReset(stateManager);\n }\n stateManager.processUpdates(updates);\n this.sectionLock(stateManager, subsectionIds, false);\n };\n\n /**\n * Move course sections into a specific course location.\n *\n * @param {StateManager} stateManager the current state manager\n * @param {array} sectionIds the list of section ids to move\n * @param {number} targetSectionId the target section id\n */\n fmtSectionMoveInto = async function(stateManager, sectionIds, targetSectionId) {\n if (!targetSectionId) {\n throw new Error(`Mutation sectionMoveInto requires targetSectionId`);\n }\n const course = stateManager.get('course');\n // ADDED.\n let subsectionIds = [];\n for (const sectionId of sectionIds) {\n const section = stateManager.get(\"section\", sectionId);\n for (let subsection = section;\n subsection && (subsection.id == section.id || subsection.levelsan > section.levelsan);\n subsection = (course.sectionlist.length > subsection.number + 1) ?\n stateManager.get(\"section\", course.sectionlist[subsection.number + 1]) : null) {\n subsectionIds.push(subsection.id);\n }\n }\n // END ADDED.\n this.sectionLock(stateManager, subsectionIds, true);\n const updates = await this._callEditWebservice('fmt_section_move_into', course.id, sectionIds, targetSectionId);\n if (typeof this.bulkReset === \"function\") {\n this.bulkReset(stateManager);\n }\n stateManager.processUpdates(updates);\n this.sectionLock(stateManager, subsectionIds, false);\n };\n\n /**\n * Add a new section to a specific course location.\n *\n * @param {StateManager} stateManager the current state manager\n * @param {number} targetSectionId the target section id\n * @param {number} newLevel the new section level\n */\n fmtAddSectionInto = async function(stateManager, targetSectionId, newLevel) {\n const course = stateManager.get('course');\n const updates = await this._callEditWebservice('fmt_section_add_into', course.id, [], targetSectionId, newLevel);\n stateManager.processUpdates(updates);\n };\n\n}\n\nexport const init = () => {\n const courseEditor = getCurrentCourseEditor();\n // Some plugin (activity or block) may have their own mutations already registered.\n // This is why we use addMutations instead of setMutations here.\n courseEditor.addMutations(new MultitopicMutations());\n};\n"],"names":["MultitopicMutations","DefaultMutations","async","stateManager","sectionIds","targetSectionId","Error","course","get","subsectionIds","sectionId","section","subsection","id","levelsan","sectionlist","length","number","push","sectionLock","updates","this","_callEditWebservice","bulkReset","processUpdates","newLevel","addMutations"],"mappings":"ugBAgCMA,4BAA4BC,yFAShBC,eAAeC,aAAcC,WAAYC,qBAC9CA,sBACK,IAAIC,6DAERC,OAASJ,aAAaK,IAAI,cAE5BC,cAAgB,OACf,MAAMC,aAAaN,WAAY,OAC1BO,QAAUR,aAAaK,IAAI,UAAWE,eACvC,IAAIE,WAAaD,QACdC,aAAeA,WAAWC,IAAMF,QAAQE,IAAMD,WAAWE,SAAWH,QAAQG,UAC5EF,WAAcL,OAAOQ,YAAYC,OAASJ,WAAWK,OAAS,EAC1Dd,aAAaK,IAAI,UAAWD,OAAOQ,YAAYH,WAAWK,OAAS,IAAM,KACjFR,cAAcS,KAAKN,WAAWC,SAIjCM,YAAYhB,aAAcM,eAAe,SACxCW,cAAgBC,KAAKC,oBAAoB,eAAgBf,OAAOM,GAAIT,WAAYC,iBACxD,mBAAnBgB,KAAKE,gBACPA,UAAUpB,cAEnBA,aAAaqB,eAAeJ,cACvBD,YAAYhB,aAAcM,eAAe,+CAU/BP,eAAeC,aAAcC,WAAYC,qBACnDA,sBACK,IAAIC,kEAERC,OAASJ,aAAaK,IAAI,cAE5BC,cAAgB,OACf,MAAMC,aAAaN,WAAY,OAC1BO,QAAUR,aAAaK,IAAI,UAAWE,eACvC,IAAIE,WAAaD,QACdC,aAAeA,WAAWC,IAAMF,QAAQE,IAAMD,WAAWE,SAAWH,QAAQG,UAC5EF,WAAcL,OAAOQ,YAAYC,OAASJ,WAAWK,OAAS,EAC1Dd,aAAaK,IAAI,UAAWD,OAAOQ,YAAYH,WAAWK,OAAS,IAAM,KACjFR,cAAcS,KAAKN,WAAWC,SAIjCM,YAAYhB,aAAcM,eAAe,SACxCW,cAAgBC,KAAKC,oBAAoB,qBAAsBf,OAAOM,GAAIT,WAAYC,iBAC9D,mBAAnBgB,KAAKE,gBACPA,UAAUpB,cAEnBA,aAAaqB,eAAeJ,cACvBD,YAAYhB,aAAcM,eAAe,iDAU7BP,eAAeC,aAAcC,WAAYC,qBACrDA,sBACK,IAAIC,iEAERC,OAASJ,aAAaK,IAAI,cAE5BC,cAAgB,OACf,MAAMC,aAAaN,WAAY,OAC1BO,QAAUR,aAAaK,IAAI,UAAWE,eACvC,IAAIE,WAAaD,QACdC,aAAeA,WAAWC,IAAMF,QAAQE,IAAMD,WAAWE,SAAWH,QAAQG,UAC5EF,WAAcL,OAAOQ,YAAYC,OAASJ,WAAWK,OAAS,EAC1Dd,aAAaK,IAAI,UAAWD,OAAOQ,YAAYH,WAAWK,OAAS,IAAM,KACjFR,cAAcS,KAAKN,WAAWC,SAIjCM,YAAYhB,aAAcM,eAAe,SACxCW,cAAgBC,KAAKC,oBAAoB,wBAAyBf,OAAOM,GAAIT,WAAYC,iBACjE,mBAAnBgB,KAAKE,gBACPA,UAAUpB,cAEnBA,aAAaqB,eAAeJ,cACvBD,YAAYhB,aAAcM,eAAe,gDAU9BP,eAAeC,aAAcE,gBAAiBoB,gBACxDlB,OAASJ,aAAaK,IAAI,UAC1BY,cAAgBC,KAAKC,oBAAoB,uBAAwBf,OAAOM,GAAI,GAAIR,gBAAiBoB,UACvGtB,aAAaqB,eAAeJ,2BAKhB,MACK,0CAGRM,aAAa,IAAI1B"} \ No newline at end of file diff --git a/amd/src/courseformat/content.js b/amd/src/courseformat/content.js index c30e09e..1882c52 100644 --- a/amd/src/courseformat/content.js +++ b/amd/src/courseformat/content.js @@ -29,23 +29,37 @@ import inplaceeditable from 'core/inplace_editable'; import Section from 'format_multitopic/courseformat/content/section'; import CmItem from 'format_multitopic/courseformat/content/section/cmitem'; import Templates from 'core/templates'; +import DispatchActions from 'format_multitopic/courseformat/content/actions'; +import * as CourseEvents from 'core_course/events'; export default class Component extends BaseComponent { + /** + * Constructor hook. + * + * @param {Object} descriptor the component descriptor + */ + create(descriptor) { + super.create(descriptor); + this.version = descriptor.version; + } + /** * Static method to create a component instance form the mustahce template. * * @param {string} target the DOM main element or its ID * @param {object} selectors optional css selector overrides * @param {number} sectionReturn the content section return + * @param {number} version Moodle version number * @return {Component} */ - static init(target, selectors, sectionReturn) { + static init(target, selectors, sectionReturn, version) { return new this({ // CHANGED. element: document.getElementById(target), reactive: getCurrentCourseEditor(), selectors, sectionReturn, + version, }); } @@ -55,7 +69,52 @@ export default class Component extends BaseComponent { * @param {Object} state the state data */ stateReady(state) { - super.stateReady(state); + this._indexContents(); + // Activate section togglers. + this.addEventListener(this.element, 'click', this._sectionTogglers); + + // Collapse/Expand all sections button. + const toogleAll = this.getElement(this.selectors.TOGGLEALL); + if (toogleAll) { + + // Ensure collapse menu button adds aria-controls attribute referring to each collapsible element. + const collapseElements = this.getElements(this.selectors.COLLAPSE); + const collapseElementIds = [...collapseElements].map(element => element.id); + toogleAll.setAttribute('aria-controls', collapseElementIds.join(' ')); + + this.addEventListener(toogleAll, 'click', this._allSectionToggler); + this.addEventListener(toogleAll, 'keydown', e => { + // Collapse/expand all sections when Space key is pressed on the toggle button. + if (e.key === ' ') { + this._allSectionToggler(e); + } + }); + this._refreshAllSectionsToggler(state); + } + + if (this.reactive.supportComponents) { + // Actions are only available in edit mode. + if (this.reactive.isEditing) { + new DispatchActions(this); // CHANGED. + } + + // Mark content as state ready. + this.element.classList.add(this.classes.STATEDREADY); + } + + // Capture completion events. + this.addEventListener( + this.element, + CourseEvents.manualCompletionToggled, + this._completionHandler + ); + + // Capture page scroll to update page item. + this.addEventListener( + (this.version >= 2023081800) ? document : document.querySelector(this.selectors.PAGE), + "scroll", + this._scrollHandler + ); // Set the initial state of collapsible sections. this.fmtCollapseOnHashChange(); diff --git a/amd/src/courseformat/content/actions.js b/amd/src/courseformat/content/actions.js new file mode 100644 index 0000000..6e31d5c --- /dev/null +++ b/amd/src/courseformat/content/actions.js @@ -0,0 +1,64 @@ +// This file is part of Moodle - http://moodle.org/ +// +// Moodle is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Moodle is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Moodle. If not, see . + +/** + * Course state actions dispatcher. + * + * This module captures all data-dispatch links in the course content and dispatch the proper + * state mutation, including any confirmation and modal required. + * + * @module format_multitopic/courseformat/content/actions + * @class format_multitopic/courseformat/content/actions + * @copyright 2024 James Calder and Otago Polytechnic + * @copyright based on work by 2021 Ferran Recio + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +import BaseComponent from 'core_courseformat/local/content/actions'; + +export default class extends BaseComponent { + + /** + * Handle a create section request. + * + * @param {Element} target the dispatch action element + * @param {Event} event the triggered event + */ + async _requestAddSection(target, event) { + event.preventDefault(); + if (target.dataset.intoId) { + this.reactive.dispatch('fmtAddSectionInto', target.dataset.intoId ?? 0, target.dataset.level ?? 2); + } else { + this.reactive.dispatch('addSection', target.dataset.id ?? 0); + } + } + + /** + * Disable all add sections actions. + * + * @param {boolean} locked the new locked value. + */ + _setAddSectionLocked(locked) { + super._setAddSectionLocked(locked); + const targets = this.getElements(`[data-region='tab-addsection']`); + targets.forEach(element => { + element.classList.toggle(this.classes.DISABLED, locked); + const addSectionElement = element.querySelector(`a.nav-link`); + addSectionElement.classList.toggle(this.classes.DISABLED, locked); + this.setElementLocked(addSectionElement, locked); + }); + } + +} diff --git a/amd/src/courseformat/content/section.js b/amd/src/courseformat/content/section.js index c420d1e..cbe143e 100644 --- a/amd/src/courseformat/content/section.js +++ b/amd/src/courseformat/content/section.js @@ -49,6 +49,13 @@ export default class extends SectionBase { this.configDragDrop(headerComponent); } } + + const pageSectionDom = document.querySelector(".course-section[data-id='" + this.section.pageid + "']"); + const pageSectionDisplay = pageSectionDom.dataset.fmtonpage; + if (this.element.dataset.fmtonpage != pageSectionDisplay) { + this.element.dataset.fmtonpage = pageSectionDisplay; + this.element.style.display = (pageSectionDisplay == "1") ? "block" : "none"; + } } /** diff --git a/amd/src/courseformat/courseeditor/mutations.js b/amd/src/courseformat/courseeditor/mutations.js index ea7a881..f0ffcf5 100644 --- a/amd/src/courseformat/courseeditor/mutations.js +++ b/amd/src/courseformat/courseeditor/mutations.js @@ -131,6 +131,19 @@ class MultitopicMutations extends DefaultMutations { this.sectionLock(stateManager, subsectionIds, false); }; + /** + * Add a new section to a specific course location. + * + * @param {StateManager} stateManager the current state manager + * @param {number} targetSectionId the target section id + * @param {number} newLevel the new section level + */ + fmtAddSectionInto = async function(stateManager, targetSectionId, newLevel) { + const course = stateManager.get('course'); + const updates = await this._callEditWebservice('fmt_section_add_into', course.id, [], targetSectionId, newLevel); + stateManager.processUpdates(updates); + }; + } export const init = () => { diff --git a/classes/courseformat/stateactions.php b/classes/courseformat/stateactions.php index 96db9a5..fbfcd9b 100644 --- a/classes/courseformat/stateactions.php +++ b/classes/courseformat/stateactions.php @@ -263,6 +263,55 @@ public function fmt_section_move_into( $updates->add_course_put(); } + /** + * Create a course section. + * + * This method follows the same logic as changenumsections.php. + * + * @param \core_courseformat\stateupdates $updates the affected course elements track + * @param \stdClass $course the course object + * @param int[] $ids not used + * @param int $targetsectionid target section id + * @param int $newlevel the new section level + */ + public function fmt_section_add_into( + \core_courseformat\stateupdates $updates, + \stdClass $course, + array $ids = [], + ?int $targetsectionid = null, + ?int $newlevel = null + ): void { + + $coursecontext = \context_course::instance($course->id); + require_capability('moodle/course:update', $coursecontext); + + // Get course format settings. + $format = course_get_format($course->id); + $lastsectionnumber = $format->get_last_section_number(); + $maxsections = $format->get_max_sections(); + + if ($lastsectionnumber >= $maxsections) { + throw new \moodle_exception('maxsectionslimit', 'moodle', '', $maxsections); + } + + $modinfo = get_fast_modinfo($course); + + // Get target section. + if ($targetsectionid) { + $this->validate_sections($course, [$targetsectionid], __FUNCTION__); + // Inserting sections at any position except in the very end requires capability to move sections. + require_capability('moodle/course:movesections', $coursecontext); + $insertposition = (object)[ 'parentid' => $targetsectionid, 'level' => $newlevel ]; + } else { + throw new \moodle_exception('sectionnotexist'); + } + + format_multitopic_course_create_section($course, $insertposition); + + // Adding a section affects the full course structure. + $this->course_state($updates, $course); + } + /** * Show/hide course sections. * diff --git a/classes/output/courseformat/content.php b/classes/output/courseformat/content.php index 18d7252..ef458d7 100644 --- a/classes/output/courseformat/content.php +++ b/classes/output/courseformat/content.php @@ -58,8 +58,6 @@ public function export_for_template(\renderer_base $output) { // ADDED. $course = $format->get_course(); $sectionsextra = $this->format->fmt_get_sections_extra(); - $maxsections = $format->get_max_sections(); - $canaddmore = $maxsections > $format->get_last_section_number(); $displaysectionextra = $sectionsextra[$this->format->get_sectionid()]; $activesectionids = []; for ($activesectionextra = $displaysectionextra; /* ... */ @@ -108,6 +106,8 @@ public function export_for_template(\renderer_base $output) { // and /course/format/onetopic/renderer.php function print_single_section_page tabs parts CHANGED. // Init custom tabs. + $straddsection = get_string_manager()->string_exists('addsectionpage', 'format_' . $course->format) ? + get_string('addsectionpage', 'format_' . $course->format) : get_string('addsections'); $tabs = []; $inactivetabs = []; @@ -193,8 +193,6 @@ public function export_for_template(\renderer_base $output) { $level--) { // Make "add" tab. - $straddsection = get_string_manager()->string_exists('addsectionpage', 'format_' . $course->format) ? - get_string('addsectionpage', 'format_' . $course->format) : get_string('addsections'); $params = [ 'courseid' => $course->id, 'increase' => true, @@ -206,9 +204,8 @@ public function export_for_template(\renderer_base $output) { "§ionid={$format->get_sectionid()}" : "")), ]; $url = new \moodle_url('/course/format/multitopic/_course_changenumsections.php', $params); - $attrs = !$canaddmore ? ['class' => 'dimmed_text cantadd'] : null; - $icon = $output->pix_icon('t/switch_plus', $straddsection, 'moodle', $attrs); - $newtab = new \tabobject("tab_id_{$sectionextraatlevel[$level - 1]->id}_l{($level - 1)}_add", + $icon = $output->pix_icon('t/switch_plus', $straddsection, 'moodle'); + $newtab = new \tabobject("tab_id_{$sectionextraatlevel[$level - 1]->id}_l{$level}_add", $url, $icon, s($straddsection)); @@ -235,6 +232,10 @@ public function export_for_template(\renderer_base $output) { if (preg_match('/^tab_id_(\d+)_l(\d+)$/', $tabeft->id, $matches)) { $tabeft->sectionid = $matches[1]; $tabeft->level = $matches[2]; + } elseif (preg_match('/^tab_id_(\d+)_l(\d+)_add$/', $tabeft->id, $matches)) { + $tabeft->dataaddsections = $straddsection; + $tabeft->intoid = $matches[1]; + $tabeft->level = $matches[2]; } } if ($tabseft->secondrow) { @@ -242,6 +243,10 @@ public function export_for_template(\renderer_base $output) { if (preg_match('/^tab_id_(\d+)_l(\d+)$/', $tabeft->id, $matches)) { $tabeft->sectionid = $matches[1]; $tabeft->level = $matches[2]; + } elseif (preg_match('/^tab_id_(\d+)_l(\d+)_add$/', $tabeft->id, $matches)) { + $tabeft->dataaddsections = $straddsection; + $tabeft->intoid = $matches[1]; + $tabeft->level = $matches[2]; } } } @@ -261,6 +266,9 @@ public function export_for_template(\renderer_base $output) { 'initialsection' => $initialsection, 'sections' => $sectionseft, 'format' => $format->get_format(), + 'fmtinsertinto' => true, + 'fmtpageid' => $displaysectionextra->id, + 'version' => $CFG->version, ]; // INCLUDED from course/format/classes/output/local/content/section/cmlist.php export_for_template() . diff --git a/classes/output/courseformat/content/addsection.php b/classes/output/courseformat/content/addsection.php index fecf1ae..e1fa96d 100644 --- a/classes/output/courseformat/content/addsection.php +++ b/classes/output/courseformat/content/addsection.php @@ -112,7 +112,7 @@ protected function get_add_section_data(\renderer_base $output, int $lastsection 'url' => new \moodle_url('/course/format/multitopic/_course_changenumsections.php', $params), 'title' => $addstring, 'newsection' => $maxsections - $lastsection, - 'canaddmore' => $maxsections > $lastsection, + 'canaddsection' => $lastsection < $maxsections, ]; return $data; } diff --git a/classes/output/courseformat/content/section.php b/classes/output/courseformat/content/section.php index 833a904..cde4716 100644 --- a/classes/output/courseformat/content/section.php +++ b/classes/output/courseformat/content/section.php @@ -221,7 +221,7 @@ protected function add_format_data(\stdClass &$data, array $haspartials, \render $course = $this->format->get_course(); $pageid = ($sectionextra->levelsan < FORMAT_MULTITOPIC_SECTION_LEVEL_TOPIC) ? $section->id : $sectionextra->parentid; - $onpage = ($pageid == $format->get_sectionid()) || ($format->get_sectionid() === null); + $onpage = ($pageid == $format->get_sectionid()); $sectionstyle = " sectionid-{$section->id}"; $iscollapsible = false; // Determine the section type. diff --git a/format.js b/format.js index 112d055..aba829d 100644 --- a/format.js +++ b/format.js @@ -15,10 +15,8 @@ /** * @file Javascript functions for Multitopic course format. - * @copyright 2019 James Calder and Otago Polytechnic - * @author James Calder - * @author Jeremy FitzPatrick - * @author Kuslan Kabalin and others. + * @copyright 2019 onwards James Calder and Otago Polytechnic + * @copyright based on work by Ruslan Kabalin and others * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ @@ -129,44 +127,3 @@ M.course.format.process_sections = function(Y, sectionlist, response, sectionfro // END INCLUDED. } }; - -// REMAINDER ADDED. - -/** - * Show notice dialog when trying to add sections and maximum has been reached. - * @param {event} e - * @return {boolean} - */ -M.course.format.fmtWarnMaxsections = function(e) { - var cantaddlink = e.target.matches('.cantadd'); - - if (cantaddlink === false && e.target.firstElementChild !== null) { - // Maybe we clicked on the parent . - cantaddlink = e.target.firstElementChild.matches('.cantadd'); - } - if (cantaddlink) { - e.preventDefault(); - require(['core/notification'], function(notification) { - notification.addNotification({ - message: M.course.format.fmtMaxsections, - type: 'warning' - }); - }); - document.querySelector("#user-notifications").scrollIntoView({behavior: "smooth", block: "center"}); - } - - return true; -}; - -/** - * Initialise: Watch for user input. - * @param {YUI} Y - * @param {string} max - */ -M.course.format.fmtInit = function(Y, max) { - M.course.format.fmtMaxsections = max; - - // Capture clicks on add section links. - document.querySelector(".course-content") - .addEventListener('click', M.course.format.fmtWarnMaxsections); -}; diff --git a/format.php b/format.php index e634df3..cec70f8 100644 --- a/format.php +++ b/format.php @@ -61,8 +61,4 @@ echo $renderer->render($widget); // Include course format js module. -$courseformat = course_get_format($course); -$maxsections = $courseformat->get_max_sections(); -$maxsections = get_string('maxsectionslimit', 'core', $maxsections); $PAGE->requires->js('/course/format/multitopic/format.js'); -$PAGE->requires->js_init_call('M.course.format.fmtInit', ['max' => $maxsections], true); diff --git a/templates/courseformat/content.mustache b/templates/courseformat/content.mustache index 46053e7..87aac81 100644 --- a/templates/courseformat/content.mustache +++ b/templates/courseformat/content.mustache @@ -205,6 +205,6 @@ {{#js}} require(['format_multitopic/courseformat/content'], function(component) { - component.init('{{uniqid}}-course-format'); + component.init('{{uniqid}}-course-format', undefined, undefined, {{version}}); }); {{/js}} diff --git a/templates/courseformat/content/addsection.mustache b/templates/courseformat/content/addsection.mustache index 8f01c03..c47f887 100644 --- a/templates/courseformat/content/addsection.mustache +++ b/templates/courseformat/content/addsection.mustache @@ -30,16 +30,27 @@ } }} {{#showaddsection}} -
+
{{#addsections}} - - {{#pix}} t/add, moodle {{/pix}} {{title}} - + + {{#pix}} t/add, core {{/pix}} + {{title}} + +
+
+ {{#pix}}t/block, moodle{{/pix}} +
+
+ {{#str}}maxsectionaddmessage, core_courseformat{{/str}} +
+
{{/addsections}}
{{/showaddsection}} diff --git a/templates/courseformat/content/section.mustache b/templates/courseformat/content/section.mustache index 4defbbb..40132e8 100644 --- a/templates/courseformat/content/section.mustache +++ b/templates/courseformat/content/section.mustache @@ -139,7 +139,7 @@ {{/ core_courseformat/local/content/section/controlmenu }} {{/controlmenu}}
-
diff --git a/templates/courseformat/content/section/header.mustache b/templates/courseformat/content/section/header.mustache index 9b90c7f..366cbf1 100644 --- a/templates/courseformat/content/section/header.mustache +++ b/templates/courseformat/content/section/header.mustache @@ -43,10 +43,10 @@
{{#iscollapsible}} {{/testingtab}}