From ac77de112276ad9ee6ea8c79cfb17d7f5df90730 Mon Sep 17 00:00:00 2001 From: Ferran Recio Date: Fri, 26 Nov 2021 17:26:53 +0100 Subject: [PATCH] MDL-72897 core_courseformat: add scroll control to course index --- course/format/amd/build/local/content.min.js | 2 +- .../format/amd/build/local/content.min.js.map | 2 +- .../build/local/courseeditor/exporter.min.js | 2 +- .../local/courseeditor/exporter.min.js.map | 2 +- .../build/local/courseeditor/mutations.min.js | 2 +- .../local/courseeditor/mutations.min.js.map | 2 +- .../amd/build/local/courseindex/cm.min.js | 2 +- .../amd/build/local/courseindex/cm.min.js.map | 2 +- .../local/courseindex/courseindex.min.js | 2 +- .../local/courseindex/courseindex.min.js.map | 2 +- .../build/local/courseindex/section.min.js | 2 +- .../local/courseindex/section.min.js.map | 2 +- course/format/amd/src/local/content.js | 35 ++++++++++++++ .../amd/src/local/courseeditor/exporter.js | 23 +++++++++ .../amd/src/local/courseeditor/mutations.js | 44 +++++++++++++++++ course/format/amd/src/local/courseindex/cm.js | 36 ++++++++++++-- .../amd/src/local/courseindex/courseindex.js | 31 +++++++++++- .../amd/src/local/courseindex/section.js | 47 +++++++++++++++++-- .../local/courseindex/section.mustache | 2 +- theme/boost/scss/moodle/courseindex.scss | 16 +++++-- theme/boost/style/moodle.css | 23 ++++++--- theme/classic/style/moodle.css | 23 ++++++--- 22 files changed, 267 insertions(+), 37 deletions(-) diff --git a/course/format/amd/build/local/content.min.js b/course/format/amd/build/local/content.min.js index 83bfdaaec58..25195d224b2 100644 --- a/course/format/amd/build/local/content.min.js +++ b/course/format/amd/build/local/content.min.js @@ -1,2 +1,2 @@ -define ("core_courseformat/local/content",["exports","core/reactive","core_courseformat/courseeditor","core/inplace_editable","core_courseformat/local/content/section","core_courseformat/local/content/section/cmitem","core_course/actions","core_courseformat/local/content/actions","core_course/events"],function(a,b,c,d,e,f,g,h,i){"use strict";Object.defineProperty(a,"__esModule",{value:!0});a.default=void 0;d=l(d);e=l(e);f=l(f);g=l(g);h=l(h);i=k(i);function j(){if("function"!=typeof WeakMap)return null;var a=new WeakMap;j=function(){return a};return a}function k(a){if(a&&a.__esModule){return a}if(null===a||"object"!==m(a)&&"function"!=typeof a){return{default:a}}var b=j();if(b&&b.has(a)){return b.get(a)}var c={},d=Object.defineProperty&&Object.getOwnPropertyDescriptor;for(var e in a){if(Object.prototype.hasOwnProperty.call(a,e)){var f=d?Object.getOwnPropertyDescriptor(a,e):null;if(f&&(f.get||f.set)){Object.defineProperty(c,e,f)}else{c[e]=a[e]}}}c.default=a;if(b){b.set(a,c)}return c}function l(a){return a&&a.__esModule?a:{default:a}}function m(a){"@babel/helpers - typeof";if("function"==typeof Symbol&&"symbol"==typeof Symbol.iterator){m=function(a){return typeof a}}else{m=function(a){return a&&"function"==typeof Symbol&&a.constructor===Symbol&&a!==Symbol.prototype?"symbol":typeof a}}return m(a)}function n(a,b,c,d,e,f,g){try{var h=a[f](g),i=h.value}catch(a){c(a);return}if(h.done){b(i)}else{Promise.resolve(i).then(d,e)}}function o(a){return function(){var b=this,c=arguments;return new Promise(function(d,e){var h=a.apply(b,c);function f(a){n(h,d,e,f,g,"next",a)}function g(a){n(h,d,e,f,g,"throw",a)}f(void 0)})}}function p(a,b){var c=Object.keys(a);if(Object.getOwnPropertySymbols){var d=Object.getOwnPropertySymbols(a);if(b)d=d.filter(function(b){return Object.getOwnPropertyDescriptor(a,b).enumerable});c.push.apply(c,d)}return c}function q(a){for(var b=1,c;bc.length){j=b.lastChild;if(null===j||void 0===j?void 0:null===(i=j.classList)||void 0===i?void 0:i.contains("dndupload-preview")){h=j}else{e[null!==(k=null===j||void 0===j?void 0:null===(l=j.dataset)||void 0===l?void 0:l.id)&&void 0!==k?k:0]=j}b.removeChild(j)}if(h){b.append(h)}case 10:case"end":return a.stop();}}},a)}));return function _fixOrder(){return a.apply(this,arguments)}}()}],[{key:"init",value:function init(a,d,e){return new b({element:document.getElementById(a),reactive:(0,c.getCurrentCourseEditor)(),selectors:d,sectionReturn:e})}}]);return b}(b.BaseComponent);a.default=C;return a.default}); +define ("core_courseformat/local/content",["exports","core/reactive","core_courseformat/courseeditor","core/inplace_editable","core_courseformat/local/content/section","core_courseformat/local/content/section/cmitem","core_course/actions","core_courseformat/local/content/actions","core_course/events"],function(a,b,c,d,e,f,g,h,i){"use strict";Object.defineProperty(a,"__esModule",{value:!0});a.default=void 0;d=l(d);e=l(e);f=l(f);g=l(g);h=l(h);i=k(i);function j(){if("function"!=typeof WeakMap)return null;var a=new WeakMap;j=function(){return a};return a}function k(a){if(a&&a.__esModule){return a}if(null===a||"object"!==m(a)&&"function"!=typeof a){return{default:a}}var b=j();if(b&&b.has(a)){return b.get(a)}var c={},d=Object.defineProperty&&Object.getOwnPropertyDescriptor;for(var e in a){if(Object.prototype.hasOwnProperty.call(a,e)){var f=d?Object.getOwnPropertyDescriptor(a,e):null;if(f&&(f.get||f.set)){Object.defineProperty(c,e,f)}else{c[e]=a[e]}}}c.default=a;if(b){b.set(a,c)}return c}function l(a){return a&&a.__esModule?a:{default:a}}function m(a){"@babel/helpers - typeof";if("function"==typeof Symbol&&"symbol"==typeof Symbol.iterator){m=function(a){return typeof a}}else{m=function(a){return a&&"function"==typeof Symbol&&a.constructor===Symbol&&a!==Symbol.prototype?"symbol":typeof a}}return m(a)}function n(a,b,c,d,e,f,g){try{var h=a[f](g),i=h.value}catch(a){c(a);return}if(h.done){b(i)}else{Promise.resolve(i).then(d,e)}}function o(a){return function(){var b=this,c=arguments;return new Promise(function(d,e){var h=a.apply(b,c);function f(a){n(h,d,e,f,g,"next",a)}function g(a){n(h,d,e,f,g,"throw",a)}f(void 0)})}}function p(a,b){var c=Object.keys(a);if(Object.getOwnPropertySymbols){var d=Object.getOwnPropertySymbols(a);if(b)d=d.filter(function(b){return Object.getOwnPropertyDescriptor(a,b).enumerable});c.push.apply(c,d)}return c}function q(a){for(var b=1,c;b=f.offsetTop}d=c;return b>=f.offsetTop});if(d){this.reactive.dispatch("setPageItem",d.type,d.id)}}},{key:"_refreshSectionNumber",value:function _refreshSectionNumber(a){var b=a.element,c=this.getElement(this.selectors.SECTION,b.id);if(!c){return}c.id="section-".concat(b.number);c.dataset.sectionid=b.number;c.dataset.number=b.number;var e=d.default.getInplaceEditable(c.querySelector(this.selectors.SECTION_ITEM));if(e){var f=e.getValue(),g=e.getItemId();if(""===e.getValue()){if(g==b.id&&(f!=b.rawtitle||""==b.rawtitle)){e.setValue(b.rawtitle)}}}}},{key:"_refreshSectionCmlist",value:function _refreshSectionCmlist(a){var b,c=a.element,d=null!==(b=c.cmlist)&&void 0!==b?b:[],e=this.getElement(this.selectors.SECTION,c.id),f=null===e||void 0===e?void 0:e.querySelector(this.selectors.SECTION_CMLIST),g=this._createCmItem.bind(this);if(f){this._fixOrder(f,d,this.selectors.CM,this.dettachedCms,g)}}},{key:"_refreshCourseSectionlist",value:function _refreshCourseSectionlist(a){var b,c=a.element;if(0!=this.reactive.sectionReturn){return}var d=null!==(b=c.sectionlist)&&void 0!==b?b:[],e=this.getElement(this.selectors.COURSE_SECTIONLIST),f=this._createSectionItem.bind(this);if(e){this._fixOrder(e,d,this.selectors.SECTION,this.dettachedSections,f)}}},{key:"_indexContents",value:function _indexContents(){this._scanIndex(this.selectors.SECTION,this.sections,function(a){return new e.default(a)});this._scanIndex(this.selectors.CM,this.cms,function(a){return new f.default(a)})}},{key:"_scanIndex",value:function _scanIndex(a,b,c){var d=this,e=this.getElements("".concat(a,":not([data-indexed])"));e.forEach(function(a){var e;if(!(null===a||void 0===a?void 0:null===(e=a.dataset)||void 0===e?void 0:e.id)){return}if(b[a.dataset.id]!==void 0){b[a.dataset.id].unregister()}b[a.dataset.id]=c(q({},d,{element:a}));a.dataset.indexed=!0})}},{key:"_reloadCm",value:function _reloadCm(a){var b=this,c=a.element,d=this.getElement(this.selectors.CM,c.id);if(d){var e=g.default.refreshModule(d,c.id);e.then(function(){b._indexContents()}).catch()}}},{key:"_reloadSection",value:function _reloadSection(a){var b=this,c=a.element,d=this.getElement(this.selectors.SECTION,c.id);if(d){var e=g.default.refreshSection(d,c.id);e.then(function(){b._indexContents()}).catch()}}},{key:"_createCmItem",value:function _createCmItem(a,b){var c=document.createElement(this.selectors.ACTIVITYTAG);c.dataset.for="cmitem";c.dataset.id=b;c.id="module-".concat(b);c.classList.add(this.classes.ACTIVITY);a.append(c);this._reloadCm({element:this.reactive.get("cm",b)});return c}},{key:"_createSectionItem",value:function _createSectionItem(a,b){var c=this.reactive.get("section",b),d=document.createElement(this.selectors.SECTIONTAG);d.dataset.for="section";d.dataset.id=b;d.dataset.number=c.number;d.id="section-".concat(b);d.classList.add(this.classes.SECTION);a.append(d);this._reloadSection({element:c});return d}},{key:"_fixOrder",value:function(){var a=o(regeneratorRuntime.mark(function a(b,c,d,e,f){var g=this,h,i,j,k,l;return regeneratorRuntime.wrap(function(a){while(1){switch(a.prev=a.next){case 0:if(!(b===void 0)){a.next=2;break}return a.abrupt("return");case 2:if(c.length){a.next=6;break}b.classList.add("hidden");b.innerHTML="";return a.abrupt("return");case 6:b.classList.remove("hidden");c.forEach(function(a,c){var h,i,j=null!==(h=null!==(i=g.getElement(d,a))&&void 0!==i?i:e[a])&&void 0!==h?h:f(b,a);if(j===void 0){return}var k=b.children[c];if(k===void 0){b.append(j);return}if(k!==j){b.insertBefore(j,k)}});while(b.children.length>c.length){j=b.lastChild;if(null===j||void 0===j?void 0:null===(i=j.classList)||void 0===i?void 0:i.contains("dndupload-preview")){h=j}else{e[null!==(k=null===j||void 0===j?void 0:null===(l=j.dataset)||void 0===l?void 0:l.id)&&void 0!==k?k:0]=j}b.removeChild(j)}if(h){b.append(h)}case 10:case"end":return a.stop();}}},a)}));return function _fixOrder(){return a.apply(this,arguments)}}()}],[{key:"init",value:function init(a,d,e){return new b({element:document.getElementById(a),reactive:(0,c.getCurrentCourseEditor)(),selectors:d,sectionReturn:e})}}]);return b}(b.BaseComponent);a.default=C;return a.default}); //# sourceMappingURL=content.min.js.map diff --git a/course/format/amd/build/local/content.min.js.map b/course/format/amd/build/local/content.min.js.map index 20d825da02e..782d1da57ad 100644 --- a/course/format/amd/build/local/content.min.js.map +++ b/course/format/amd/build/local/content.min.js.map @@ -1 +1 @@ -{"version":3,"sources":["../../src/local/content.js"],"names":["Component","descriptor","name","selectors","SECTION","SECTION_ITEM","SECTION_CMLIST","COURSE_SECTIONLIST","CM","TOGGLER","COLLAPSE","TOGGLEALL","ACTIVITYTAG","SECTIONTAG","classes","COLLAPSED","ACTIVITY","STATEDREADY","dettachedCms","dettachedSections","sections","cms","sectionReturn","state","_indexContents","addEventListener","element","_sectionTogglers","toogleAll","getElement","_allSectionToggler","_refreshAllSectionsToggler","reactive","supportComponents","isEditing","DispatchActions","classList","add","CourseEvents","manualCompletionToggled","_completionHandler","event","sectionlink","target","closest","isChevron","section","toggler","querySelector","isCollapsed","contains","sectionId","getAttribute","dispatch","contentcollapsed","preventDefault","isAllCollapsed","course","get","sectionlist","watch","handler","_reloadCm","_refreshSectionNumber","_refreshSectionCollapsed","_startProcessing","_refreshCourseSectionlist","_refreshSectionCmlist","id","Error","click","allcollapsed","allexpanded","forEach","remove","detail","cmid","completed","number","dataset","sectionid","inplace","inplaceeditable","getInplaceEditable","currentvalue","getValue","currentitemid","getItemId","rawtitle","setValue","cmlist","listparent","createCm","_createCmItem","bind","_fixOrder","createSection","_createSectionItem","_scanIndex","item","Section","CmItem","selector","index","creationhandler","items","getElements","unregister","indexed","cmitem","promise","courseActions","refreshModule","then","catch","sectionitem","refreshSection","container","newItem","document","createElement","for","append","_reloadSection","neworder","dettachedelements","createMethod","length","innerHTML","itemid","currentitem","children","insertBefore","lastchild","lastChild","dndFakeActivity","removeChild","getElementById","BaseComponent"],"mappings":"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","sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * Course index main component.\n *\n * @module core_courseformat/local/content\n * @class core_courseformat/local/content\n * @copyright 2020 Ferran Recio \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport {BaseComponent} from 'core/reactive';\nimport {getCurrentCourseEditor} from 'core_courseformat/courseeditor';\nimport inplaceeditable from 'core/inplace_editable';\nimport Section from 'core_courseformat/local/content/section';\nimport CmItem from 'core_courseformat/local/content/section/cmitem';\n// Course actions is needed for actions that are not migrated to components.\nimport courseActions from 'core_course/actions';\nimport DispatchActions from 'core_courseformat/local/content/actions';\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 // Optional component name for debugging.\n this.name = 'course_format';\n // Default query selectors.\n this.selectors = {\n SECTION: `[data-for='section']`,\n SECTION_ITEM: `[data-for='section_title']`,\n SECTION_CMLIST: `[data-for='cmlist']`,\n COURSE_SECTIONLIST: `[data-for='course_sectionlist']`,\n CM: `[data-for='cmitem']`,\n TOGGLER: `[data-action=\"togglecoursecontentsection\"]`,\n COLLAPSE: `[data-toggle=\"collapse\"]`,\n TOGGLEALL: `[data-toggle=\"toggleall\"]`,\n // Formats can override the activity tag but a default one is needed to create new elements.\n ACTIVITYTAG: 'li',\n SECTIONTAG: 'li',\n };\n // Default classes to toggle on refresh.\n this.classes = {\n COLLAPSED: `collapsed`,\n // Course content classes.\n ACTIVITY: `activity`,\n STATEDREADY: `stateready`,\n SECTION: `section`,\n };\n // Array to save dettached elements during element resorting.\n this.dettachedCms = {};\n this.dettachedSections = {};\n // Index of sections and cms components.\n this.sections = {};\n this.cms = {};\n // The page section return.\n this.sectionReturn = descriptor.sectionReturn ?? 0;\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 * @return {Component}\n */\n static init(target, selectors, sectionReturn) {\n return new Component({\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 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 this.addEventListener(toogleAll, 'click', this._allSectionToggler);\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);\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\n /**\n * Setup sections toggler.\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 _sectionTogglers(event) {\n const sectionlink = event.target.closest(this.selectors.TOGGLER);\n const isChevron = event.target.closest(this.selectors.COLLAPSE);\n\n if (sectionlink || isChevron) {\n\n const section = event.target.closest(this.selectors.SECTION);\n const toggler = section.querySelector(this.selectors.COLLAPSE);\n const isCollapsed = toggler?.classList.contains(this.classes.COLLAPSED) ?? false;\n\n if (isChevron || isCollapsed) {\n // Update the state.\n const sectionId = section.getAttribute('data-id');\n this.reactive.dispatch(\n 'sectionPreferences',\n [sectionId],\n {\n contentcollapsed: !isCollapsed,\n },\n );\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 const course = this.reactive.get('course');\n this.reactive.dispatch(\n 'sectionPreferences',\n course.sectionlist ?? [],\n {\n contentcollapsed: !isAllCollapsed,\n }\n );\n }\n\n /**\n * Return the component watchers.\n *\n * @returns {Array} of watchers\n */\n getWatchers() {\n // Section return is a global page variable but most formats define it just before start printing\n // the course content. This is the reason why we define this page setting here.\n this.reactive.sectionReturn = this.sectionReturn;\n\n // Check if the course format is compatible with reactive components.\n if (!this.reactive.supportComponents) {\n return [];\n }\n return [\n // State changes that require to reload some course modules.\n {watch: `cm.visible:updated`, handler: this._reloadCm},\n // Update section number and title.\n {watch: `section.number:updated`, handler: this._refreshSectionNumber},\n // Collapse and expand sections.\n {watch: `section.contentcollapsed:updated`, handler: this._refreshSectionCollapsed},\n // Sections and cm sorting.\n {watch: `transaction:start`, handler: this._startProcessing},\n {watch: `course.sectionlist:updated`, handler: this._refreshCourseSectionlist},\n {watch: `section.cmlist:updated`, handler: this._refreshSectionCmlist},\n // Reindex sections and cms.\n {watch: `state:updated`, handler: this._indexContents},\n // State changes thaty require to reload course modules.\n {watch: `cm.visible:updated`, handler: this._reloadCm},\n {watch: `cm.sectionid:updated`, handler: this._reloadCm},\n ];\n }\n\n /**\n * Update section collapsed.\n *\n * @param {object} args\n * @param {Object} args.state The state data\n * @param {Object} args.element The element to update\n */\n _refreshSectionCollapsed({state, element}) {\n const target = this.getElement(this.selectors.SECTION, element.id);\n if (!target) {\n throw new Error(`Unknown section with ID ${element.id}`);\n }\n // Check if it is already done.\n const toggler = target.querySelector(this.selectors.COLLAPSE);\n const isCollapsed = toggler?.classList.contains(this.classes.COLLAPSED) ?? false;\n\n if (element.contentcollapsed !== isCollapsed) {\n toggler.click();\n }\n\n this._refreshAllSectionsToggler(state);\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 state.section.forEach(\n section => {\n allcollapsed = allcollapsed && section.contentcollapsed;\n allexpanded = allexpanded && !section.contentcollapsed;\n }\n );\n if (allcollapsed) {\n target.classList.add(this.classes.COLLAPSED);\n }\n if (allexpanded) {\n target.classList.remove(this.classes.COLLAPSED);\n }\n }\n\n /**\n * Setup the component to start a transaction.\n *\n * Some of the course actions replaces the current DOM element with a new one before updating the\n * course state. This means the component cannot preload any index properly until the transaction starts.\n *\n */\n _startProcessing() {\n // During a section or cm sorting, some elements could be dettached from the DOM and we\n // need to store somewhare in case they are needed later.\n this.dettachedCms = {};\n this.dettachedSections = {};\n }\n\n /**\n * Activity manual completion listener.\n *\n * @param {Event} event the custom ecent\n */\n _completionHandler({detail}) {\n if (detail === undefined) {\n return;\n }\n this.reactive.dispatch('cmCompletion', [detail.cmid], detail.completed);\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() === '') {\n // The value to send can be an empty value if it is a default name.\n if (currentitemid == element.id && (currentvalue != element.rawtitle || element.rawtitle == '')) {\n inplace.setValue(element.rawtitle);\n }\n }\n }\n }\n\n /**\n * Refresh a section cm list.\n *\n * @param {Object} param\n * @param {Object} param.element details the update details.\n */\n _refreshSectionCmlist({element}) {\n const cmlist = element.cmlist ?? [];\n const section = this.getElement(this.selectors.SECTION, element.id);\n const listparent = section?.querySelector(this.selectors.SECTION_CMLIST);\n // A method to create a fake element to be replaced when the item is ready.\n const createCm = this._createCmItem.bind(this);\n if (listparent) {\n this._fixOrder(listparent, cmlist, this.selectors.CM, this.dettachedCms, createCm);\n }\n }\n\n /**\n * Refresh the section list.\n *\n * @param {Object} param\n * @param {Object} param.element details the update details.\n */\n _refreshCourseSectionlist({element}) {\n // If we have a section return means we only show a single section so no need to fix order.\n if (this.reactive.sectionReturn != 0) {\n return;\n }\n const sectionlist = element.sectionlist ?? [];\n const listparent = this.getElement(this.selectors.COURSE_SECTIONLIST);\n // For now section cannot be created at a frontend level.\n const createSection = this._createSectionItem.bind(this);\n if (listparent) {\n this._fixOrder(listparent, sectionlist, this.selectors.SECTION, this.dettachedSections, createSection);\n }\n }\n\n /**\n * Regenerate content indexes.\n *\n * This method is used when a legacy action refresh some content element.\n */\n _indexContents() {\n // Find unindexed sections.\n this._scanIndex(\n this.selectors.SECTION,\n this.sections,\n (item) => {\n return new Section(item);\n }\n );\n\n // Find unindexed cms.\n this._scanIndex(\n this.selectors.CM,\n this.cms,\n (item) => {\n return new CmItem(item);\n }\n );\n }\n\n /**\n * Reindex a content (section or cm) of the course content.\n *\n * This method is used internally by _indexContents.\n *\n * @param {string} selector the DOM selector to scan\n * @param {*} index the index attribute to update\n * @param {*} creationhandler method to create a new indexed element\n */\n _scanIndex(selector, index, creationhandler) {\n const items = this.getElements(`${selector}:not([data-indexed])`);\n items.forEach((item) => {\n if (!item?.dataset?.id) {\n return;\n }\n // Delete previous item component.\n if (index[item.dataset.id] !== undefined) {\n index[item.dataset.id].unregister();\n }\n // Create the new component.\n index[item.dataset.id] = creationhandler({\n ...this,\n element: item,\n });\n // Mark as indexed.\n item.dataset.indexed = true;\n });\n }\n\n /**\n * Reload a course module contents.\n *\n * Most course module HTML is still strongly backend dependant.\n * Some changes require to get a new version of the module.\n *\n * @param {object} param0 the watcher details\n * @param {object} param0.element the state object\n */\n _reloadCm({element}) {\n const cmitem = this.getElement(this.selectors.CM, element.id);\n if (cmitem) {\n const promise = courseActions.refreshModule(cmitem, element.id);\n promise.then(() => {\n this._indexContents();\n return;\n }).catch();\n }\n }\n\n /**\n * Reload a course section contents.\n *\n * Section HTML is still strongly backend dependant.\n * Some changes require to get a new version of the section.\n *\n * @param {details} param0 the watcher details\n * @param {object} param0.element the state object\n */\n _reloadSection({element}) {\n const sectionitem = this.getElement(this.selectors.SECTION, element.id);\n if (sectionitem) {\n const promise = courseActions.refreshSection(sectionitem, element.id);\n promise.then(() => {\n this._indexContents();\n return;\n }).catch();\n }\n }\n\n /**\n * Create a new course module item in a section.\n *\n * Thos method will append a fake item in the container and trigger an ajax request to\n * replace the fake element by the real content.\n *\n * @param {Element} container the container element (section)\n * @param {Number} cmid the course-module ID\n * @returns {Element} the created element\n */\n _createCmItem(container, cmid) {\n const newItem = document.createElement(this.selectors.ACTIVITYTAG);\n newItem.dataset.for = 'cmitem';\n newItem.dataset.id = cmid;\n // The legacy actions.js requires a specific ID and class to refresh the CM.\n newItem.id = `module-${cmid}`;\n newItem.classList.add(this.classes.ACTIVITY);\n container.append(newItem);\n this._reloadCm({\n element: this.reactive.get('cm', cmid),\n });\n return newItem;\n }\n\n /**\n * Create a new section item.\n *\n * This method will append a fake item in the container and trigger an ajax request to\n * replace the fake element by the real content.\n *\n * @param {Element} container the container element (section)\n * @param {Number} sectionid the course-module ID\n * @returns {Element} the created element\n */\n _createSectionItem(container, sectionid) {\n const section = this.reactive.get('section', sectionid);\n const newItem = document.createElement(this.selectors.SECTIONTAG);\n newItem.dataset.for = 'section';\n newItem.dataset.id = sectionid;\n newItem.dataset.number = section.number;\n // The legacy actions.js requires a specific ID and class to refresh the section.\n newItem.id = `section-${sectionid}`;\n newItem.classList.add(this.classes.SECTION);\n container.append(newItem);\n this._reloadSection({\n element: section,\n });\n return newItem;\n }\n\n /**\n * Fix/reorder the section or cms order.\n *\n * @param {Element} container the HTML element to reorder.\n * @param {Array} neworder an array with the ids order\n * @param {string} selector the element selector\n * @param {Object} dettachedelements a list of dettached elements\n * @param {function} createMethod method to create missing elements\n */\n async _fixOrder(container, neworder, selector, dettachedelements, createMethod) {\n if (container === undefined) {\n return;\n }\n\n // Empty lists should not be visible.\n if (!neworder.length) {\n container.classList.add('hidden');\n container.innerHTML = '';\n return;\n }\n\n // Grant the list is visible (in case it was empty).\n container.classList.remove('hidden');\n\n // Move the elements in order at the beginning of the list.\n neworder.forEach((itemid, index) => {\n let item = this.getElement(selector, itemid) ?? dettachedelements[itemid] ?? createMethod(container, itemid);\n if (item === undefined) {\n // Missing elements cannot be sorted.\n return;\n }\n // Get the current elemnt at that position.\n const currentitem = container.children[index];\n if (currentitem === undefined) {\n container.append(item);\n return;\n }\n if (currentitem !== item) {\n container.insertBefore(item, currentitem);\n }\n });\n\n // Dndupload add a fake element we need to keep.\n let dndFakeActivity;\n\n // Remove the remaining elements.\n while (container.children.length > neworder.length) {\n const lastchild = container.lastChild;\n if (lastchild?.classList?.contains('dndupload-preview')) {\n dndFakeActivity = lastchild;\n } else {\n dettachedelements[lastchild?.dataset?.id ?? 0] = lastchild;\n }\n container.removeChild(lastchild);\n }\n // Restore dndupload fake element.\n if (dndFakeActivity) {\n container.append(dndFakeActivity);\n }\n }\n}\n"],"file":"content.min.js"} \ No newline at end of file +{"version":3,"sources":["../../src/local/content.js"],"names":["Component","descriptor","name","selectors","SECTION","SECTION_ITEM","SECTION_CMLIST","COURSE_SECTIONLIST","CM","PAGE","TOGGLER","COLLAPSE","TOGGLEALL","ACTIVITYTAG","SECTIONTAG","classes","COLLAPSED","ACTIVITY","STATEDREADY","dettachedCms","dettachedSections","sections","cms","sectionReturn","state","_indexContents","addEventListener","element","_sectionTogglers","toogleAll","getElement","_allSectionToggler","_refreshAllSectionsToggler","reactive","supportComponents","isEditing","DispatchActions","classList","add","CourseEvents","manualCompletionToggled","_completionHandler","document","querySelector","_scrollHandler","event","sectionlink","target","closest","isChevron","section","toggler","isCollapsed","contains","sectionId","getAttribute","dispatch","contentcollapsed","preventDefault","isAllCollapsed","course","get","sectionlist","watch","handler","_reloadCm","_refreshSectionNumber","_refreshSectionCollapsed","_startProcessing","_refreshCourseSectionlist","_refreshSectionCmlist","id","Error","click","allcollapsed","allexpanded","forEach","remove","detail","cmid","completed","pageOffset","scrollTop","items","getExporter","allItemsArray","pageItem","every","item","index","type","url","offsetTop","number","dataset","sectionid","inplace","inplaceeditable","getInplaceEditable","currentvalue","getValue","currentitemid","getItemId","rawtitle","setValue","cmlist","listparent","createCm","_createCmItem","bind","_fixOrder","createSection","_createSectionItem","_scanIndex","Section","CmItem","selector","creationhandler","getElements","unregister","indexed","cmitem","promise","courseActions","refreshModule","then","catch","sectionitem","refreshSection","container","newItem","createElement","for","append","_reloadSection","neworder","dettachedelements","createMethod","length","innerHTML","itemid","currentitem","children","insertBefore","lastchild","lastChild","dndFakeActivity","removeChild","getElementById","BaseComponent"],"mappings":"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","sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * Course index main component.\n *\n * @module core_courseformat/local/content\n * @class core_courseformat/local/content\n * @copyright 2020 Ferran Recio \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport {BaseComponent} from 'core/reactive';\nimport {getCurrentCourseEditor} from 'core_courseformat/courseeditor';\nimport inplaceeditable from 'core/inplace_editable';\nimport Section from 'core_courseformat/local/content/section';\nimport CmItem from 'core_courseformat/local/content/section/cmitem';\n// Course actions is needed for actions that are not migrated to components.\nimport courseActions from 'core_course/actions';\nimport DispatchActions from 'core_courseformat/local/content/actions';\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 // Optional component name for debugging.\n this.name = 'course_format';\n // Default query selectors.\n this.selectors = {\n SECTION: `[data-for='section']`,\n SECTION_ITEM: `[data-for='section_title']`,\n SECTION_CMLIST: `[data-for='cmlist']`,\n COURSE_SECTIONLIST: `[data-for='course_sectionlist']`,\n CM: `[data-for='cmitem']`,\n PAGE: `#page`,\n TOGGLER: `[data-action=\"togglecoursecontentsection\"]`,\n COLLAPSE: `[data-toggle=\"collapse\"]`,\n TOGGLEALL: `[data-toggle=\"toggleall\"]`,\n // Formats can override the activity tag but a default one is needed to create new elements.\n ACTIVITYTAG: 'li',\n SECTIONTAG: 'li',\n };\n // Default classes to toggle on refresh.\n this.classes = {\n COLLAPSED: `collapsed`,\n // Course content classes.\n ACTIVITY: `activity`,\n STATEDREADY: `stateready`,\n SECTION: `section`,\n };\n // Array to save dettached elements during element resorting.\n this.dettachedCms = {};\n this.dettachedSections = {};\n // Index of sections and cms components.\n this.sections = {};\n this.cms = {};\n // The page section return.\n this.sectionReturn = descriptor.sectionReturn ?? 0;\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 * @return {Component}\n */\n static init(target, selectors, sectionReturn) {\n return new Component({\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 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 this.addEventListener(toogleAll, 'click', this._allSectionToggler);\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);\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 document.querySelector(this.selectors.PAGE),\n \"scroll\",\n this._scrollHandler\n );\n }\n\n /**\n * Setup sections toggler.\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 _sectionTogglers(event) {\n const sectionlink = event.target.closest(this.selectors.TOGGLER);\n const isChevron = event.target.closest(this.selectors.COLLAPSE);\n\n if (sectionlink || isChevron) {\n\n const section = event.target.closest(this.selectors.SECTION);\n const toggler = section.querySelector(this.selectors.COLLAPSE);\n const isCollapsed = toggler?.classList.contains(this.classes.COLLAPSED) ?? false;\n\n if (isChevron || isCollapsed) {\n // Update the state.\n const sectionId = section.getAttribute('data-id');\n this.reactive.dispatch(\n 'sectionPreferences',\n [sectionId],\n {\n contentcollapsed: !isCollapsed,\n },\n );\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 const course = this.reactive.get('course');\n this.reactive.dispatch(\n 'sectionPreferences',\n course.sectionlist ?? [],\n {\n contentcollapsed: !isAllCollapsed,\n }\n );\n }\n\n /**\n * Return the component watchers.\n *\n * @returns {Array} of watchers\n */\n getWatchers() {\n // Section return is a global page variable but most formats define it just before start printing\n // the course content. This is the reason why we define this page setting here.\n this.reactive.sectionReturn = this.sectionReturn;\n\n // Check if the course format is compatible with reactive components.\n if (!this.reactive.supportComponents) {\n return [];\n }\n return [\n // State changes that require to reload some course modules.\n {watch: `cm.visible:updated`, handler: this._reloadCm},\n // Update section number and title.\n {watch: `section.number:updated`, handler: this._refreshSectionNumber},\n // Collapse and expand sections.\n {watch: `section.contentcollapsed:updated`, handler: this._refreshSectionCollapsed},\n // Sections and cm sorting.\n {watch: `transaction:start`, handler: this._startProcessing},\n {watch: `course.sectionlist:updated`, handler: this._refreshCourseSectionlist},\n {watch: `section.cmlist:updated`, handler: this._refreshSectionCmlist},\n // Reindex sections and cms.\n {watch: `state:updated`, handler: this._indexContents},\n // State changes thaty require to reload course modules.\n {watch: `cm.visible:updated`, handler: this._reloadCm},\n {watch: `cm.sectionid:updated`, handler: this._reloadCm},\n ];\n }\n\n /**\n * Update section collapsed.\n *\n * @param {object} args\n * @param {Object} args.state The state data\n * @param {Object} args.element The element to update\n */\n _refreshSectionCollapsed({state, element}) {\n const target = this.getElement(this.selectors.SECTION, element.id);\n if (!target) {\n throw new Error(`Unknown section with ID ${element.id}`);\n }\n // Check if it is already done.\n const toggler = target.querySelector(this.selectors.COLLAPSE);\n const isCollapsed = toggler?.classList.contains(this.classes.COLLAPSED) ?? false;\n\n if (element.contentcollapsed !== isCollapsed) {\n toggler.click();\n }\n\n this._refreshAllSectionsToggler(state);\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 state.section.forEach(\n section => {\n allcollapsed = allcollapsed && section.contentcollapsed;\n allexpanded = allexpanded && !section.contentcollapsed;\n }\n );\n if (allcollapsed) {\n target.classList.add(this.classes.COLLAPSED);\n }\n if (allexpanded) {\n target.classList.remove(this.classes.COLLAPSED);\n }\n }\n\n /**\n * Setup the component to start a transaction.\n *\n * Some of the course actions replaces the current DOM element with a new one before updating the\n * course state. This means the component cannot preload any index properly until the transaction starts.\n *\n */\n _startProcessing() {\n // During a section or cm sorting, some elements could be dettached from the DOM and we\n // need to store somewhare in case they are needed later.\n this.dettachedCms = {};\n this.dettachedSections = {};\n }\n\n /**\n * Activity manual completion listener.\n *\n * @param {Event} event the custom ecent\n */\n _completionHandler({detail}) {\n if (detail === undefined) {\n return;\n }\n this.reactive.dispatch('cmCompletion', [detail.cmid], detail.completed);\n }\n\n /**\n * Check the current page scroll and update the active element if necessary.\n */\n _scrollHandler() {\n const pageOffset = document.querySelector(this.selectors.PAGE).scrollTop;\n const items = this.reactive.getExporter().allItemsArray(this.reactive.state);\n // Check what is the active element now.\n let pageItem = null;\n items.every(item => {\n const index = (item.type === 'section') ? this.sections : this.cms;\n if (index[item.id] === undefined) {\n return true;\n }\n\n const element = index[item.id].element;\n // Activities without url can only be page items in edit mode.\n if (item.type === 'cm' && !item.url && !this.reactive.isEditing) {\n return pageOffset >= element.offsetTop;\n }\n pageItem = item;\n return pageOffset >= element.offsetTop;\n });\n if (pageItem) {\n this.reactive.dispatch('setPageItem', pageItem.type, pageItem.id);\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() === '') {\n // The value to send can be an empty value if it is a default name.\n if (currentitemid == element.id && (currentvalue != element.rawtitle || element.rawtitle == '')) {\n inplace.setValue(element.rawtitle);\n }\n }\n }\n }\n\n /**\n * Refresh a section cm list.\n *\n * @param {Object} param\n * @param {Object} param.element details the update details.\n */\n _refreshSectionCmlist({element}) {\n const cmlist = element.cmlist ?? [];\n const section = this.getElement(this.selectors.SECTION, element.id);\n const listparent = section?.querySelector(this.selectors.SECTION_CMLIST);\n // A method to create a fake element to be replaced when the item is ready.\n const createCm = this._createCmItem.bind(this);\n if (listparent) {\n this._fixOrder(listparent, cmlist, this.selectors.CM, this.dettachedCms, createCm);\n }\n }\n\n /**\n * Refresh the section list.\n *\n * @param {Object} param\n * @param {Object} param.element details the update details.\n */\n _refreshCourseSectionlist({element}) {\n // If we have a section return means we only show a single section so no need to fix order.\n if (this.reactive.sectionReturn != 0) {\n return;\n }\n const sectionlist = element.sectionlist ?? [];\n const listparent = this.getElement(this.selectors.COURSE_SECTIONLIST);\n // For now section cannot be created at a frontend level.\n const createSection = this._createSectionItem.bind(this);\n if (listparent) {\n this._fixOrder(listparent, sectionlist, this.selectors.SECTION, this.dettachedSections, createSection);\n }\n }\n\n /**\n * Regenerate content indexes.\n *\n * This method is used when a legacy action refresh some content element.\n */\n _indexContents() {\n // Find unindexed sections.\n this._scanIndex(\n this.selectors.SECTION,\n this.sections,\n (item) => {\n return new Section(item);\n }\n );\n\n // Find unindexed cms.\n this._scanIndex(\n this.selectors.CM,\n this.cms,\n (item) => {\n return new CmItem(item);\n }\n );\n }\n\n /**\n * Reindex a content (section or cm) of the course content.\n *\n * This method is used internally by _indexContents.\n *\n * @param {string} selector the DOM selector to scan\n * @param {*} index the index attribute to update\n * @param {*} creationhandler method to create a new indexed element\n */\n _scanIndex(selector, index, creationhandler) {\n const items = this.getElements(`${selector}:not([data-indexed])`);\n items.forEach((item) => {\n if (!item?.dataset?.id) {\n return;\n }\n // Delete previous item component.\n if (index[item.dataset.id] !== undefined) {\n index[item.dataset.id].unregister();\n }\n // Create the new component.\n index[item.dataset.id] = creationhandler({\n ...this,\n element: item,\n });\n // Mark as indexed.\n item.dataset.indexed = true;\n });\n }\n\n /**\n * Reload a course module contents.\n *\n * Most course module HTML is still strongly backend dependant.\n * Some changes require to get a new version of the module.\n *\n * @param {object} param0 the watcher details\n * @param {object} param0.element the state object\n */\n _reloadCm({element}) {\n const cmitem = this.getElement(this.selectors.CM, element.id);\n if (cmitem) {\n const promise = courseActions.refreshModule(cmitem, element.id);\n promise.then(() => {\n this._indexContents();\n return;\n }).catch();\n }\n }\n\n /**\n * Reload a course section contents.\n *\n * Section HTML is still strongly backend dependant.\n * Some changes require to get a new version of the section.\n *\n * @param {details} param0 the watcher details\n * @param {object} param0.element the state object\n */\n _reloadSection({element}) {\n const sectionitem = this.getElement(this.selectors.SECTION, element.id);\n if (sectionitem) {\n const promise = courseActions.refreshSection(sectionitem, element.id);\n promise.then(() => {\n this._indexContents();\n return;\n }).catch();\n }\n }\n\n /**\n * Create a new course module item in a section.\n *\n * Thos method will append a fake item in the container and trigger an ajax request to\n * replace the fake element by the real content.\n *\n * @param {Element} container the container element (section)\n * @param {Number} cmid the course-module ID\n * @returns {Element} the created element\n */\n _createCmItem(container, cmid) {\n const newItem = document.createElement(this.selectors.ACTIVITYTAG);\n newItem.dataset.for = 'cmitem';\n newItem.dataset.id = cmid;\n // The legacy actions.js requires a specific ID and class to refresh the CM.\n newItem.id = `module-${cmid}`;\n newItem.classList.add(this.classes.ACTIVITY);\n container.append(newItem);\n this._reloadCm({\n element: this.reactive.get('cm', cmid),\n });\n return newItem;\n }\n\n /**\n * Create a new section item.\n *\n * This method will append a fake item in the container and trigger an ajax request to\n * replace the fake element by the real content.\n *\n * @param {Element} container the container element (section)\n * @param {Number} sectionid the course-module ID\n * @returns {Element} the created element\n */\n _createSectionItem(container, sectionid) {\n const section = this.reactive.get('section', sectionid);\n const newItem = document.createElement(this.selectors.SECTIONTAG);\n newItem.dataset.for = 'section';\n newItem.dataset.id = sectionid;\n newItem.dataset.number = section.number;\n // The legacy actions.js requires a specific ID and class to refresh the section.\n newItem.id = `section-${sectionid}`;\n newItem.classList.add(this.classes.SECTION);\n container.append(newItem);\n this._reloadSection({\n element: section,\n });\n return newItem;\n }\n\n /**\n * Fix/reorder the section or cms order.\n *\n * @param {Element} container the HTML element to reorder.\n * @param {Array} neworder an array with the ids order\n * @param {string} selector the element selector\n * @param {Object} dettachedelements a list of dettached elements\n * @param {function} createMethod method to create missing elements\n */\n async _fixOrder(container, neworder, selector, dettachedelements, createMethod) {\n if (container === undefined) {\n return;\n }\n\n // Empty lists should not be visible.\n if (!neworder.length) {\n container.classList.add('hidden');\n container.innerHTML = '';\n return;\n }\n\n // Grant the list is visible (in case it was empty).\n container.classList.remove('hidden');\n\n // Move the elements in order at the beginning of the list.\n neworder.forEach((itemid, index) => {\n let item = this.getElement(selector, itemid) ?? dettachedelements[itemid] ?? createMethod(container, itemid);\n if (item === undefined) {\n // Missing elements cannot be sorted.\n return;\n }\n // Get the current elemnt at that position.\n const currentitem = container.children[index];\n if (currentitem === undefined) {\n container.append(item);\n return;\n }\n if (currentitem !== item) {\n container.insertBefore(item, currentitem);\n }\n });\n\n // Dndupload add a fake element we need to keep.\n let dndFakeActivity;\n\n // Remove the remaining elements.\n while (container.children.length > neworder.length) {\n const lastchild = container.lastChild;\n if (lastchild?.classList?.contains('dndupload-preview')) {\n dndFakeActivity = lastchild;\n } else {\n dettachedelements[lastchild?.dataset?.id ?? 0] = lastchild;\n }\n container.removeChild(lastchild);\n }\n // Restore dndupload fake element.\n if (dndFakeActivity) {\n container.append(dndFakeActivity);\n }\n }\n}\n"],"file":"content.min.js"} \ No newline at end of file diff --git a/course/format/amd/build/local/courseeditor/exporter.min.js b/course/format/amd/build/local/courseeditor/exporter.min.js index fde0509105b..0c475a957c0 100644 --- a/course/format/amd/build/local/courseeditor/exporter.min.js +++ b/course/format/amd/build/local/courseeditor/exporter.min.js @@ -1,2 +1,2 @@ -define ("core_courseformat/local/courseeditor/exporter",["exports"],function(a){"use strict";Object.defineProperty(a,"__esModule",{value:!0});a.default=void 0;function b(a,b){var c=Object.keys(a);if(Object.getOwnPropertySymbols){var d=Object.getOwnPropertySymbols(a);if(b)d=d.filter(function(b){return Object.getOwnPropertyDescriptor(a,b).enumerable});c.push.apply(c,d)}return c}function c(a){for(var c=1,e;c.\n\n/**\n * Module to export parts of the state and transform them to be used in templates\n * and as draggable data.\n *\n * @module core_courseformat/local/courseeditor/exporter\n * @class core_courseformat/local/courseeditor/exporter\n * @copyright 2021 Ferran Recio \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\nexport default class {\n\n /**\n * Class constructor.\n *\n * @param {CourseEditor} reactive the course editor object\n */\n constructor(reactive) {\n this.reactive = reactive;\n\n // Completions states are defined in lib/completionlib.php. There are 4 different completion\n // state values, however, the course index uses the same state for complete and complete_pass.\n // This is the reason why completed appears twice in the array.\n this.COMPLETIONS = ['incomplete', 'complete', 'complete', 'fail'];\n }\n\n /**\n * Generate the course export data from the state.\n *\n * @param {Object} state the current state.\n * @returns {Object}\n */\n course(state) {\n // Collect section information from the state.\n const data = {\n sections: [],\n editmode: this.reactive.isEditing,\n highlighted: state.course.highlighted ?? '',\n };\n const sectionlist = state.course.sectionlist ?? [];\n sectionlist.forEach(sectionid => {\n const sectioninfo = state.section.get(sectionid) ?? {};\n const section = this.section(state, sectioninfo);\n data.sections.push(section);\n });\n data.hassections = (data.sections.length != 0);\n\n return data;\n }\n\n /**\n * Generate a section export data from the state.\n *\n * @param {Object} state the current state.\n * @param {Object} sectioninfo the section state data.\n * @returns {Object}\n */\n section(state, sectioninfo) {\n const section = {\n ...sectioninfo,\n highlighted: state.course.highlighted ?? '',\n cms: [],\n };\n const cmlist = sectioninfo.cmlist ?? [];\n cmlist.forEach(cmid => {\n const cminfo = state.cm.get(cmid);\n const cm = this.cm(state, cminfo);\n section.cms.push(cm);\n });\n section.hascms = (section.cms.length != 0);\n\n return section;\n }\n\n /**\n * Generate a cm export data from the state.\n *\n * @param {Object} state the current state.\n * @param {Object} cminfo the course module state data.\n * @returns {Object}\n */\n cm(state, cminfo) {\n const cm = {\n ...cminfo,\n isactive: false,\n };\n return cm;\n }\n\n /**\n * Generate a dragable cm data structure.\n *\n * This method is used by any draggable course module element to generate drop data\n * for its reactive/dragdrop instance.\n *\n * @param {*} state the state object\n * @param {*} cmid the cours emodule id\n * @returns {Object|null}\n */\n cmDraggableData(state, cmid) {\n const cminfo = state.cm.get(cmid);\n if (!cminfo) {\n return null;\n }\n\n // Drop an activity over the next activity is the same as doing anything.\n let nextcmid;\n const section = state.section.get(cminfo.sectionid);\n const currentindex = section?.cmlist.indexOf(cminfo.id);\n if (currentindex !== undefined) {\n nextcmid = section?.cmlist[currentindex + 1];\n }\n\n return {\n type: 'cm',\n id: cminfo.id,\n name: cminfo.name,\n sectionid: cminfo.sectionid,\n nextcmid,\n };\n }\n\n /**\n * Generate a dragable cm data structure.\n *\n * This method is used by any draggable section element to generate drop data\n * for its reactive/dragdrop instance.\n *\n * @param {*} state the state object\n * @param {*} sectionid the cours section id\n * @returns {Object|null}\n */\n sectionDraggableData(state, sectionid) {\n const sectioninfo = state.section.get(sectionid);\n if (!sectioninfo) {\n return null;\n }\n return {\n type: 'section',\n id: sectioninfo.id,\n name: sectioninfo.name,\n number: sectioninfo.number,\n };\n }\n\n /**\n * Generate a compoetion export data from the cm element.\n *\n * @param {Object} state the current state.\n * @param {Object} cminfo the course module state data.\n * @returns {Object}\n */\n cmCompletion(state, cminfo) {\n const data = {\n statename: '',\n state: 'NaN',\n };\n if (cminfo.completionstate !== undefined) {\n data.state = cminfo.completionstate;\n data.hasstate = true;\n const statename = this.COMPLETIONS[cminfo.completionstate] ?? 'NaN';\n data[`is${statename}`] = true;\n }\n return data;\n }\n}\n"],"file":"exporter.min.js"} \ No newline at end of file +{"version":3,"sources":["../../../src/local/courseeditor/exporter.js"],"names":["reactive","COMPLETIONS","state","data","sections","editmode","isEditing","highlighted","course","sectionlist","forEach","sectionid","sectioninfo","section","get","push","hassections","length","cms","cmlist","cmid","cminfo","cm","hascms","isactive","nextcmid","currentindex","indexOf","id","type","name","number","statename","completionstate","hasstate","items","url","sectionurl"],"mappings":"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","sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * Module to export parts of the state and transform them to be used in templates\n * and as draggable data.\n *\n * @module core_courseformat/local/courseeditor/exporter\n * @class core_courseformat/local/courseeditor/exporter\n * @copyright 2021 Ferran Recio \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\nexport default class {\n\n /**\n * Class constructor.\n *\n * @param {CourseEditor} reactive the course editor object\n */\n constructor(reactive) {\n this.reactive = reactive;\n\n // Completions states are defined in lib/completionlib.php. There are 4 different completion\n // state values, however, the course index uses the same state for complete and complete_pass.\n // This is the reason why completed appears twice in the array.\n this.COMPLETIONS = ['incomplete', 'complete', 'complete', 'fail'];\n }\n\n /**\n * Generate the course export data from the state.\n *\n * @param {Object} state the current state.\n * @returns {Object}\n */\n course(state) {\n // Collect section information from the state.\n const data = {\n sections: [],\n editmode: this.reactive.isEditing,\n highlighted: state.course.highlighted ?? '',\n };\n const sectionlist = state.course.sectionlist ?? [];\n sectionlist.forEach(sectionid => {\n const sectioninfo = state.section.get(sectionid) ?? {};\n const section = this.section(state, sectioninfo);\n data.sections.push(section);\n });\n data.hassections = (data.sections.length != 0);\n\n return data;\n }\n\n /**\n * Generate a section export data from the state.\n *\n * @param {Object} state the current state.\n * @param {Object} sectioninfo the section state data.\n * @returns {Object}\n */\n section(state, sectioninfo) {\n const section = {\n ...sectioninfo,\n highlighted: state.course.highlighted ?? '',\n cms: [],\n };\n const cmlist = sectioninfo.cmlist ?? [];\n cmlist.forEach(cmid => {\n const cminfo = state.cm.get(cmid);\n const cm = this.cm(state, cminfo);\n section.cms.push(cm);\n });\n section.hascms = (section.cms.length != 0);\n\n return section;\n }\n\n /**\n * Generate a cm export data from the state.\n *\n * @param {Object} state the current state.\n * @param {Object} cminfo the course module state data.\n * @returns {Object}\n */\n cm(state, cminfo) {\n const cm = {\n ...cminfo,\n isactive: false,\n };\n return cm;\n }\n\n /**\n * Generate a dragable cm data structure.\n *\n * This method is used by any draggable course module element to generate drop data\n * for its reactive/dragdrop instance.\n *\n * @param {*} state the state object\n * @param {*} cmid the cours emodule id\n * @returns {Object|null}\n */\n cmDraggableData(state, cmid) {\n const cminfo = state.cm.get(cmid);\n if (!cminfo) {\n return null;\n }\n\n // Drop an activity over the next activity is the same as doing anything.\n let nextcmid;\n const section = state.section.get(cminfo.sectionid);\n const currentindex = section?.cmlist.indexOf(cminfo.id);\n if (currentindex !== undefined) {\n nextcmid = section?.cmlist[currentindex + 1];\n }\n\n return {\n type: 'cm',\n id: cminfo.id,\n name: cminfo.name,\n sectionid: cminfo.sectionid,\n nextcmid,\n };\n }\n\n /**\n * Generate a dragable cm data structure.\n *\n * This method is used by any draggable section element to generate drop data\n * for its reactive/dragdrop instance.\n *\n * @param {*} state the state object\n * @param {*} sectionid the cours section id\n * @returns {Object|null}\n */\n sectionDraggableData(state, sectionid) {\n const sectioninfo = state.section.get(sectionid);\n if (!sectioninfo) {\n return null;\n }\n return {\n type: 'section',\n id: sectioninfo.id,\n name: sectioninfo.name,\n number: sectioninfo.number,\n };\n }\n\n /**\n * Generate a compoetion export data from the cm element.\n *\n * @param {Object} state the current state.\n * @param {Object} cminfo the course module state data.\n * @returns {Object}\n */\n cmCompletion(state, cminfo) {\n const data = {\n statename: '',\n state: 'NaN',\n };\n if (cminfo.completionstate !== undefined) {\n data.state = cminfo.completionstate;\n data.hasstate = true;\n const statename = this.COMPLETIONS[cminfo.completionstate] ?? 'NaN';\n data[`is${statename}`] = true;\n }\n return data;\n }\n\n /**\n * Return a sorted list of all sections and cms items in the state.\n *\n * @param {Object} state the current state.\n * @returns {Array} all sections and cms items in the state.\n */\n allItemsArray(state) {\n const items = [];\n const sectionlist = state.course.sectionlist ?? [];\n // Add sections.\n sectionlist.forEach(sectionid => {\n const sectioninfo = state.section.get(sectionid);\n items.push({type: 'section', id: sectioninfo.id, url: sectioninfo.sectionurl});\n // Add cms.\n const cmlist = sectioninfo.cmlist ?? [];\n cmlist.forEach(cmid => {\n const cminfo = state.cm.get(cmid);\n items.push({type: 'cm', id: cminfo.id, url: cminfo.url});\n });\n });\n return items;\n }\n}\n"],"file":"exporter.min.js"} \ No newline at end of file diff --git a/course/format/amd/build/local/courseeditor/mutations.min.js b/course/format/amd/build/local/courseeditor/mutations.min.js index 2ec176fe895..ddb2fb47a1c 100644 --- a/course/format/amd/build/local/courseeditor/mutations.min.js +++ b/course/format/amd/build/local/courseeditor/mutations.min.js @@ -1,2 +1,2 @@ -define ("core_courseformat/local/courseeditor/mutations",["exports","core/ajax"],function(a,b){"use strict";Object.defineProperty(a,"__esModule",{value:!0});a.default=void 0;b=function(a){return a&&a.__esModule?a:{default:a}}(b);function c(a,b,c,d,e,f,g){try{var h=a[f](g),i=h.value}catch(a){c(a);return}if(h.done){b(i)}else{Promise.resolve(i).then(d,e)}}function d(a){return function(){var b=this,d=arguments;return new Promise(function(e,f){var i=a.apply(b,d);function g(a){c(i,e,f,g,h,"next",a)}function h(a){c(i,e,f,g,h,"throw",a)}g(void 0)})}}function e(a,b){if(!(a instanceof b)){throw new TypeError("Cannot call a class as a function")}}function f(a,b){for(var c=0,d;c.\n\nimport ajax from 'core/ajax';\n\n/**\n * Default mutation manager\n *\n * @module core_courseformat/local/courseeditor/mutations\n * @class core_courseformat/local/courseeditor/mutations\n * @copyright 2021 Ferran Recio \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\nexport default class {\n\n // All course editor mutations for Moodle 4.0 will be located in this file.\n\n /**\n * Private method to call core_courseformat_update_course webservice.\n *\n * @method _callEditWebservice\n * @param {string} action\n * @param {number} courseId\n * @param {array} ids\n * @param {number} targetSectionId optional target section id (for moving actions)\n * @param {number} targetCmId optional target cm id (for moving actions)\n */\n async _callEditWebservice(action, courseId, ids, targetSectionId, targetCmId) {\n const args = {\n action,\n courseid: courseId,\n ids,\n };\n if (targetSectionId) {\n args.targetsectionid = targetSectionId;\n }\n if (targetCmId) {\n args.targetcmid = targetCmId;\n }\n let ajaxresult = await ajax.call([{\n methodname: 'core_courseformat_update_course',\n args,\n }])[0];\n return JSON.parse(ajaxresult);\n }\n\n\n /**\n * Mutation module initialize.\n *\n * The reactive instance will execute this method when addMutations or setMutation is invoked.\n *\n * @param {StateManager} stateManager the state manager\n */\n init(stateManager) {\n // Add a method to prepare the fields when some update is comming from the server.\n stateManager.addUpdateTypes({\n prepareFields: this._prepareFields,\n });\n }\n\n /**\n * Add default values to state elements.\n *\n * This method is called every time a webservice returns a update state message.\n *\n * @param {Object} stateManager the state manager\n * @param {String} updateName the state element to update\n * @param {Object} fields the new data\n * @returns {Object} final fields data\n */\n _prepareFields(stateManager, updateName, fields) {\n // Any update should unlock the element.\n fields.locked = false;\n return fields;\n }\n\n /**\n * Move course modules to specific course location.\n *\n * Note that one of targetSectionId or targetCmId should be provided in order to identify the\n * new location:\n * - targetCmId: the activities will be located avobe the target cm. The targetSectionId\n * value will be ignored in this case.\n * - targetSectionId: the activities will be appended to the section. In this case\n * targetSectionId should not be present.\n *\n * @param {StateManager} stateManager the current state manager\n * @param {array} cmids the list of cm ids to move\n * @param {number} targetSectionId the target section id\n * @param {number} targetCmId the target course module id\n */\n async cmMove(stateManager, cmids, targetSectionId, targetCmId) {\n if (!targetSectionId && !targetCmId) {\n throw new Error(`Mutation cmMove requires targetSectionId or targetCmId`);\n }\n const course = stateManager.get('course');\n this.cmLock(stateManager, cmids, true);\n const updates = await this._callEditWebservice('cm_move', course.id, cmids, targetSectionId, targetCmId);\n stateManager.processUpdates(updates);\n this.cmLock(stateManager, cmids, false);\n }\n\n /**\n * Move course modules 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 async sectionMove(stateManager, sectionIds, targetSectionId) {\n if (!targetSectionId) {\n throw new Error(`Mutation sectionMove requires targetSectionId`);\n }\n const course = stateManager.get('course');\n this.sectionLock(stateManager, sectionIds, true);\n const updates = await this._callEditWebservice('section_move', course.id, sectionIds, targetSectionId);\n stateManager.processUpdates(updates);\n this.sectionLock(stateManager, sectionIds, 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 optional the target section id\n */\n async addSection(stateManager, targetSectionId) {\n if (!targetSectionId) {\n targetSectionId = 0;\n }\n const course = stateManager.get('course');\n const updates = await this._callEditWebservice('section_add', course.id, [], targetSectionId);\n stateManager.processUpdates(updates);\n }\n\n /**\n * Delete sections.\n *\n * @param {StateManager} stateManager the current state manager\n * @param {array} sectionIds the list of course modules ids\n */\n async sectionDelete(stateManager, sectionIds) {\n const course = stateManager.get('course');\n const updates = await this._callEditWebservice('section_delete', course.id, sectionIds);\n stateManager.processUpdates(updates);\n }\n\n /**\n * Mark or unmark course modules as dragging.\n *\n * @param {StateManager} stateManager the current state manager\n * @param {array} cmIds the list of course modules ids\n * @param {bool} dragValue the new dragging value\n */\n cmDrag(stateManager, cmIds, dragValue) {\n this._setElementsValue(stateManager, 'cm', cmIds, 'dragging', dragValue);\n }\n\n /**\n * Mark or unmark course sections as dragging.\n *\n * @param {StateManager} stateManager the current state manager\n * @param {array} sectionIds the list of section ids\n * @param {bool} dragValue the new dragging value\n */\n sectionDrag(stateManager, sectionIds, dragValue) {\n this._setElementsValue(stateManager, 'section', sectionIds, 'dragging', dragValue);\n }\n\n /**\n * Mark or unmark course modules as complete.\n *\n * @param {StateManager} stateManager the current state manager\n * @param {array} cmIds the list of course modules ids\n * @param {bool} complete the new completion value\n */\n cmCompletion(stateManager, cmIds, complete) {\n const newValue = (complete) ? 1 : 0;\n this._setElementsValue(stateManager, 'cm', cmIds, 'completionstate', newValue);\n }\n\n /**\n * Lock or unlock course modules.\n *\n * @param {StateManager} stateManager the current state manager\n * @param {array} cmIds the list of course modules ids\n * @param {bool} lockValue the new locked value\n */\n cmLock(stateManager, cmIds, lockValue) {\n this._setElementsValue(stateManager, 'cm', cmIds, 'locked', lockValue);\n }\n\n /**\n * Lock or unlock course sections.\n *\n * @param {StateManager} stateManager the current state manager\n * @param {array} sectionIds the list of section ids\n * @param {bool} lockValue the new locked value\n */\n sectionLock(stateManager, sectionIds, lockValue) {\n this._setElementsValue(stateManager, 'section', sectionIds, 'locked', lockValue);\n }\n\n _setElementsValue(stateManager, name, ids, fieldName, newValue) {\n stateManager.setReadOnly(false);\n ids.forEach((id) => {\n const element = stateManager.get(name, id);\n if (element) {\n element[fieldName] = newValue;\n }\n });\n stateManager.setReadOnly(true);\n }\n\n /**\n * Unlock all course elements.\n *\n * @param {StateManager} stateManager the current state manager\n */\n unlockAll(stateManager) {\n const state = stateManager.state;\n stateManager.setReadOnly(false);\n state.section.forEach((section) => {\n section.locked = false;\n });\n state.cm.forEach((cm) => {\n cm.locked = false;\n });\n stateManager.setReadOnly(true);\n }\n\n /*\n * Get updated user preferences and state data related to some section ids.\n *\n * @param {StateManager} stateManager the current state\n * @param {array} sectionIds the list of section ids to update\n * @param {Object} preferences the new preferences values\n */\n async sectionPreferences(stateManager, sectionIds, preferences) {\n stateManager.setReadOnly(false);\n // Check if we need to update preferences.\n let updatePreferences = false;\n sectionIds.forEach(sectionId => {\n const section = stateManager.get('section', sectionId);\n if (section === undefined) {\n return;\n }\n let newValue = preferences.contentcollapsed ?? section.contentcollapsed;\n if (section.contentcollapsed != newValue) {\n section.contentcollapsed = newValue;\n updatePreferences = true;\n }\n newValue = preferences.indexcollapsed ?? section.indexcollapsed;\n if (section.indexcollapsed != newValue) {\n section.indexcollapsed = newValue;\n updatePreferences = true;\n }\n });\n stateManager.setReadOnly(true);\n\n if (updatePreferences) {\n // Build the preference structures.\n const course = stateManager.get('course');\n const state = stateManager.state;\n const prefKey = `coursesectionspreferences_${course.id}`;\n const preferences = {\n contentcollapsed: [],\n indexcollapsed: [],\n };\n state.section.forEach(section => {\n if (section.contentcollapsed) {\n preferences.contentcollapsed.push(section.id);\n }\n if (section.indexcollapsed) {\n preferences.indexcollapsed.push(section.id);\n }\n });\n const jsonString = JSON.stringify(preferences);\n M.util.set_user_preference(prefKey, jsonString);\n }\n }\n\n /**\n * Get updated state data related to some cm ids.\n *\n * @method cmState\n * @param {StateManager} stateManager the current state\n * @param {array} cmids the list of cm ids to update\n */\n async cmState(stateManager, cmids) {\n this.cmLock(stateManager, cmids, true);\n const course = stateManager.get('course');\n const updates = await this._callEditWebservice('cm_state', course.id, cmids);\n stateManager.processUpdates(updates);\n this.cmLock(stateManager, cmids, false);\n }\n\n /**\n * Get updated state data related to some section ids.\n *\n * @method sectionState\n * @param {StateManager} stateManager the current state\n * @param {array} sectionIds the list of section ids to update\n */\n async sectionState(stateManager, sectionIds) {\n this.sectionLock(stateManager, sectionIds, true);\n const course = stateManager.get('course');\n const updates = await this._callEditWebservice('section_state', course.id, sectionIds);\n stateManager.processUpdates(updates);\n this.sectionLock(stateManager, sectionIds, false);\n }\n\n /**\n * Get the full updated state data of the course.\n *\n * @param {StateManager} stateManager the current state\n */\n async courseState(stateManager) {\n const course = stateManager.get('course');\n const updates = await this._callEditWebservice('course_state', course.id);\n stateManager.processUpdates(updates);\n }\n\n}\n"],"file":"mutations.min.js"} \ No newline at end of file +{"version":3,"sources":["../../../src/local/courseeditor/mutations.js"],"names":["action","courseId","ids","targetSectionId","targetCmId","args","courseid","targetsectionid","targetcmid","ajax","call","methodname","ajaxresult","JSON","parse","stateManager","addUpdateTypes","prepareFields","_prepareFields","updateName","fields","locked","cmids","Error","course","get","cmLock","_callEditWebservice","id","updates","processUpdates","sectionIds","sectionLock","cmIds","dragValue","setPageItem","_setElementsValue","complete","newValue","lockValue","name","fieldName","setReadOnly","forEach","element","type","isStatic","newPageItem","pageItem","sectionId","sectionid","state","section","cm","preferences","updatePreferences","contentcollapsed","indexcollapsed","prefKey","push","jsonString","stringify","M","util","set_user_preference"],"mappings":"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","sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\nimport ajax from 'core/ajax';\n\n/**\n * Default mutation manager\n *\n * @module core_courseformat/local/courseeditor/mutations\n * @class core_courseformat/local/courseeditor/mutations\n * @copyright 2021 Ferran Recio \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\nexport default class {\n\n // All course editor mutations for Moodle 4.0 will be located in this file.\n\n /**\n * Private method to call core_courseformat_update_course webservice.\n *\n * @method _callEditWebservice\n * @param {string} action\n * @param {number} courseId\n * @param {array} ids\n * @param {number} targetSectionId optional target section id (for moving actions)\n * @param {number} targetCmId optional target cm id (for moving actions)\n */\n async _callEditWebservice(action, courseId, ids, targetSectionId, targetCmId) {\n const args = {\n action,\n courseid: courseId,\n ids,\n };\n if (targetSectionId) {\n args.targetsectionid = targetSectionId;\n }\n if (targetCmId) {\n args.targetcmid = targetCmId;\n }\n let ajaxresult = await ajax.call([{\n methodname: 'core_courseformat_update_course',\n args,\n }])[0];\n return JSON.parse(ajaxresult);\n }\n\n\n /**\n * Mutation module initialize.\n *\n * The reactive instance will execute this method when addMutations or setMutation is invoked.\n *\n * @param {StateManager} stateManager the state manager\n */\n init(stateManager) {\n // Add a method to prepare the fields when some update is comming from the server.\n stateManager.addUpdateTypes({\n prepareFields: this._prepareFields,\n });\n }\n\n /**\n * Add default values to state elements.\n *\n * This method is called every time a webservice returns a update state message.\n *\n * @param {Object} stateManager the state manager\n * @param {String} updateName the state element to update\n * @param {Object} fields the new data\n * @returns {Object} final fields data\n */\n _prepareFields(stateManager, updateName, fields) {\n // Any update should unlock the element.\n fields.locked = false;\n return fields;\n }\n\n /**\n * Move course modules to specific course location.\n *\n * Note that one of targetSectionId or targetCmId should be provided in order to identify the\n * new location:\n * - targetCmId: the activities will be located avobe the target cm. The targetSectionId\n * value will be ignored in this case.\n * - targetSectionId: the activities will be appended to the section. In this case\n * targetSectionId should not be present.\n *\n * @param {StateManager} stateManager the current state manager\n * @param {array} cmids the list of cm ids to move\n * @param {number} targetSectionId the target section id\n * @param {number} targetCmId the target course module id\n */\n async cmMove(stateManager, cmids, targetSectionId, targetCmId) {\n if (!targetSectionId && !targetCmId) {\n throw new Error(`Mutation cmMove requires targetSectionId or targetCmId`);\n }\n const course = stateManager.get('course');\n this.cmLock(stateManager, cmids, true);\n const updates = await this._callEditWebservice('cm_move', course.id, cmids, targetSectionId, targetCmId);\n stateManager.processUpdates(updates);\n this.cmLock(stateManager, cmids, false);\n }\n\n /**\n * Move course modules 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 async sectionMove(stateManager, sectionIds, targetSectionId) {\n if (!targetSectionId) {\n throw new Error(`Mutation sectionMove requires targetSectionId`);\n }\n const course = stateManager.get('course');\n this.sectionLock(stateManager, sectionIds, true);\n const updates = await this._callEditWebservice('section_move', course.id, sectionIds, targetSectionId);\n stateManager.processUpdates(updates);\n this.sectionLock(stateManager, sectionIds, 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 optional the target section id\n */\n async addSection(stateManager, targetSectionId) {\n if (!targetSectionId) {\n targetSectionId = 0;\n }\n const course = stateManager.get('course');\n const updates = await this._callEditWebservice('section_add', course.id, [], targetSectionId);\n stateManager.processUpdates(updates);\n }\n\n /**\n * Delete sections.\n *\n * @param {StateManager} stateManager the current state manager\n * @param {array} sectionIds the list of course modules ids\n */\n async sectionDelete(stateManager, sectionIds) {\n const course = stateManager.get('course');\n const updates = await this._callEditWebservice('section_delete', course.id, sectionIds);\n stateManager.processUpdates(updates);\n }\n\n /**\n * Mark or unmark course modules as dragging.\n *\n * @param {StateManager} stateManager the current state manager\n * @param {array} cmIds the list of course modules ids\n * @param {bool} dragValue the new dragging value\n */\n cmDrag(stateManager, cmIds, dragValue) {\n this.setPageItem(stateManager);\n this._setElementsValue(stateManager, 'cm', cmIds, 'dragging', dragValue);\n }\n\n /**\n * Mark or unmark course sections as dragging.\n *\n * @param {StateManager} stateManager the current state manager\n * @param {array} sectionIds the list of section ids\n * @param {bool} dragValue the new dragging value\n */\n sectionDrag(stateManager, sectionIds, dragValue) {\n this.setPageItem(stateManager);\n this._setElementsValue(stateManager, 'section', sectionIds, 'dragging', dragValue);\n }\n\n /**\n * Mark or unmark course modules as complete.\n *\n * @param {StateManager} stateManager the current state manager\n * @param {array} cmIds the list of course modules ids\n * @param {bool} complete the new completion value\n */\n cmCompletion(stateManager, cmIds, complete) {\n const newValue = (complete) ? 1 : 0;\n this._setElementsValue(stateManager, 'cm', cmIds, 'completionstate', newValue);\n }\n\n /**\n * Lock or unlock course modules.\n *\n * @param {StateManager} stateManager the current state manager\n * @param {array} cmIds the list of course modules ids\n * @param {bool} lockValue the new locked value\n */\n cmLock(stateManager, cmIds, lockValue) {\n this._setElementsValue(stateManager, 'cm', cmIds, 'locked', lockValue);\n }\n\n /**\n * Lock or unlock course sections.\n *\n * @param {StateManager} stateManager the current state manager\n * @param {array} sectionIds the list of section ids\n * @param {bool} lockValue the new locked value\n */\n sectionLock(stateManager, sectionIds, lockValue) {\n this._setElementsValue(stateManager, 'section', sectionIds, 'locked', lockValue);\n }\n\n _setElementsValue(stateManager, name, ids, fieldName, newValue) {\n stateManager.setReadOnly(false);\n ids.forEach((id) => {\n const element = stateManager.get(name, id);\n if (element) {\n element[fieldName] = newValue;\n }\n });\n stateManager.setReadOnly(true);\n }\n\n /**\n * Set the page current item.\n *\n * Only one element of the course state can be the page item at a time.\n *\n * There are several actions that can alter the page current item. For example, when the user is in an activity\n * page, the page item is always the activity one. However, in a course page, when the user scrolls to an element,\n * this element get the page item.\n *\n * If the page item is static means that it is not meant to change. This is important because\n * static page items has some special logic. For example, if a cm is the static page item\n * and it is inside a collapsed section, the course index will expand the section to make it visible.\n *\n * @param {StateManager} stateManager the current state manager\n * @param {String|undefined} type the element type (section or cm). Undefined will remove the current page item.\n * @param {Number|undefined} id the element id\n * @param {boolean|undefined} isStatic if the page item is static\n */\n setPageItem(stateManager, type, id, isStatic) {\n let newPageItem;\n if (type !== undefined) {\n newPageItem = stateManager.get(type, id);\n if (!newPageItem) {\n return;\n }\n }\n stateManager.setReadOnly(false);\n // Remove the current page item.\n const course = stateManager.get('course');\n course.pageItem = null;\n // Save the new page item.\n if (newPageItem) {\n course.pageItem = {\n id,\n type,\n sectionId: (type == 'section') ? newPageItem.id : newPageItem.sectionid,\n isStatic,\n };\n }\n stateManager.setReadOnly(true);\n }\n\n /**\n * Unlock all course elements.\n *\n * @param {StateManager} stateManager the current state manager\n */\n unlockAll(stateManager) {\n const state = stateManager.state;\n stateManager.setReadOnly(false);\n state.section.forEach((section) => {\n section.locked = false;\n });\n state.cm.forEach((cm) => {\n cm.locked = false;\n });\n stateManager.setReadOnly(true);\n }\n\n /*\n * Get updated user preferences and state data related to some section ids.\n *\n * @param {StateManager} stateManager the current state\n * @param {array} sectionIds the list of section ids to update\n * @param {Object} preferences the new preferences values\n */\n async sectionPreferences(stateManager, sectionIds, preferences) {\n stateManager.setReadOnly(false);\n // Check if we need to update preferences.\n let updatePreferences = false;\n sectionIds.forEach(sectionId => {\n const section = stateManager.get('section', sectionId);\n if (section === undefined) {\n return;\n }\n let newValue = preferences.contentcollapsed ?? section.contentcollapsed;\n if (section.contentcollapsed != newValue) {\n section.contentcollapsed = newValue;\n updatePreferences = true;\n }\n newValue = preferences.indexcollapsed ?? section.indexcollapsed;\n if (section.indexcollapsed != newValue) {\n section.indexcollapsed = newValue;\n updatePreferences = true;\n }\n });\n stateManager.setReadOnly(true);\n\n if (updatePreferences) {\n // Build the preference structures.\n const course = stateManager.get('course');\n const state = stateManager.state;\n const prefKey = `coursesectionspreferences_${course.id}`;\n const preferences = {\n contentcollapsed: [],\n indexcollapsed: [],\n };\n state.section.forEach(section => {\n if (section.contentcollapsed) {\n preferences.contentcollapsed.push(section.id);\n }\n if (section.indexcollapsed) {\n preferences.indexcollapsed.push(section.id);\n }\n });\n const jsonString = JSON.stringify(preferences);\n M.util.set_user_preference(prefKey, jsonString);\n }\n }\n\n /**\n * Get updated state data related to some cm ids.\n *\n * @method cmState\n * @param {StateManager} stateManager the current state\n * @param {array} cmids the list of cm ids to update\n */\n async cmState(stateManager, cmids) {\n this.cmLock(stateManager, cmids, true);\n const course = stateManager.get('course');\n const updates = await this._callEditWebservice('cm_state', course.id, cmids);\n stateManager.processUpdates(updates);\n this.cmLock(stateManager, cmids, false);\n }\n\n /**\n * Get updated state data related to some section ids.\n *\n * @method sectionState\n * @param {StateManager} stateManager the current state\n * @param {array} sectionIds the list of section ids to update\n */\n async sectionState(stateManager, sectionIds) {\n this.sectionLock(stateManager, sectionIds, true);\n const course = stateManager.get('course');\n const updates = await this._callEditWebservice('section_state', course.id, sectionIds);\n stateManager.processUpdates(updates);\n this.sectionLock(stateManager, sectionIds, false);\n }\n\n /**\n * Get the full updated state data of the course.\n *\n * @param {StateManager} stateManager the current state\n */\n async courseState(stateManager) {\n const course = stateManager.get('course');\n const updates = await this._callEditWebservice('course_state', course.id);\n stateManager.processUpdates(updates);\n }\n\n}\n"],"file":"mutations.min.js"} \ No newline at end of file diff --git a/course/format/amd/build/local/courseindex/cm.min.js b/course/format/amd/build/local/courseindex/cm.min.js index 840b401c6aa..3977fd99485 100644 --- a/course/format/amd/build/local/courseindex/cm.min.js +++ b/course/format/amd/build/local/courseindex/cm.min.js @@ -1,2 +1,2 @@ -define ("core_courseformat/local/courseindex/cm",["exports","core_courseformat/local/courseeditor/dndcmitem","core/templates","core/prefetch","core/config"],function(a,b,c,d,e){"use strict";Object.defineProperty(a,"__esModule",{value:!0});a.default=void 0;b=f(b);c=f(c);d=f(d);e=f(e);function f(a){return a&&a.__esModule?a:{default:a}}function g(a){"@babel/helpers - typeof";if("function"==typeof Symbol&&"symbol"==typeof Symbol.iterator){g=function(a){return typeof a}}else{g=function(a){return a&&"function"==typeof Symbol&&a.constructor===Symbol&&a!==Symbol.prototype?"symbol":typeof a}}return g(a)}function h(a,b,c,d,e,f,g){try{var h=a[f](g),i=h.value}catch(a){c(a);return}if(h.done){b(i)}else{Promise.resolve(i).then(d,e)}}function i(a){return function(){var b=this,c=arguments;return new Promise(function(d,e){var i=a.apply(b,c);function f(a){h(i,d,e,f,g,"next",a)}function g(a){h(i,d,e,f,g,"throw",a)}f(void 0)})}}function j(a,b){if(!(a instanceof b)){throw new TypeError("Cannot call a class as a function")}}function k(a,b){for(var c=0,d;c.\n\n/**\n * Course index cm component.\n *\n * This component is used to control specific course modules interactions like drag and drop.\n *\n * @module core_courseformat/local/courseindex/cm\n * @class core_courseformat/local/courseindex/cm\n * @copyright 2021 Ferran Recio \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport DndCmItem from 'core_courseformat/local/courseeditor/dndcmitem';\nimport Templates from 'core/templates';\nimport Prefetch from 'core/prefetch';\nimport Config from 'core/config';\n\n// Prefetch the completion icons template.\nconst completionTemplate = 'core_courseformat/local/courseindex/cmcompletion';\nPrefetch.prefetchTemplate(completionTemplate);\n\nexport default class Component extends DndCmItem {\n\n /**\n * Constructor hook.\n */\n create() {\n // Optional component name for debugging.\n this.name = 'courseindex_cm';\n // Default query selectors.\n this.selectors = {\n CM_NAME: `[data-for='cm_name']`,\n CM_COMPLETION: `[data-for='cm_completion']`,\n };\n // Default classes to toggle on refresh.\n this.classes = {\n CMHIDDEN: 'dimmed',\n LOCKED: 'editinprogress',\n RESTRICTIONS: 'rectrictions',\n PAGEITEM: 'pageitem',\n };\n // We need our id to watch specific events.\n this.id = this.element.dataset.id;\n }\n\n /**\n * Static method to create a component instance form the mustache template.\n *\n * @param {element|string} target the DOM main element or its ID\n * @param {object} selectors optional css selector overrides\n * @return {Component}\n */\n static init(target, selectors) {\n return new Component({\n element: document.getElementById(target),\n selectors,\n });\n }\n\n /**\n * Initial state ready method.\n *\n * @param {Object} state the course state.\n */\n stateReady(state) {\n this.configDragDrop(this.id);\n const cm = state.cm.get(this.id);\n // Refresh completion icon.\n this._refreshCompletion({\n state,\n element: cm,\n });\n // Check if this we are displaying this activity id page.\n if (Config.contextid != Config.courseContextId && Config.contextInstanceId == this.id) {\n this.element.classList.add(this.classes.PAGEITEM);\n }\n // Add anchor logic if the element is not user visible.\n if (!cm.uservisible) {\n this.addEventListener(\n this.getElement(this.selectors.CM_NAME),\n 'click',\n this._activityAnchor,\n );\n }\n }\n\n /**\n * Component watchers.\n *\n * @returns {Array} of watchers\n */\n getWatchers() {\n return [\n {watch: `cm[${this.id}]:deleted`, handler: this.remove},\n {watch: `cm[${this.id}]:updated`, handler: this._refreshCm},\n {watch: `cm[${this.id}].completionstate:updated`, handler: this._refreshCompletion},\n ];\n }\n\n /**\n * Update a course index cm using the state information.\n *\n * @param {object} param\n * @param {Object} param.element details the update details.\n */\n _refreshCm({element}) {\n // Update classes.\n this.element.classList.toggle(this.classes.CMHIDDEN, !element.visible);\n this.getElement(this.selectors.CM_NAME).innerHTML = element.name;\n this.element.classList.toggle(this.classes.DRAGGING, element.dragging ?? false);\n this.element.classList.toggle(this.classes.LOCKED, element.locked ?? false);\n this.element.classList.toggle(this.classes.RESTRICTIONS, element.hascmrectrictions ?? false);\n this.locked = element.locked;\n }\n\n /**\n * Update the activity completion icon.\n *\n * @param {Object} details the update details\n * @param {Object} details.state the state data\n * @param {Object} details.element the element data\n */\n async _refreshCompletion({state, element}) {\n // No completion icons are displayed in edit mode.\n if (this.reactive.isEditing || !element.istrackeduser) {\n return;\n }\n // Check if the completion value has changed.\n const completionElement = this.getElement(this.selectors.CM_COMPLETION);\n if (completionElement.dataset.value == element.completionstate) {\n return;\n }\n\n // Collect section information from the state.\n const exporter = this.reactive.getExporter();\n const data = exporter.cmCompletion(state, element);\n\n try {\n const {html, js} = await Templates.renderForPromise(completionTemplate, data);\n Templates.replaceNode(completionElement, html, js);\n } catch (error) {\n throw error;\n }\n }\n\n /**\n * The activity anchor event.\n *\n * @param {Event} event\n */\n _activityAnchor(event) {\n const cm = this.reactive.get('cm', this.id);\n // If the user cannot access the element but the element is present in the page\n // the new url should be an anchor link.\n if (document.getElementById(cm.anchor)) {\n return;\n }\n // If the element is not present in the page we need to go to the specific section.\n const course = this.reactive.get('course');\n const section = this.reactive.get('section', cm.sectionid);\n if (!section) {\n return;\n }\n const url = `${course.baseurl}§ion=${section.number}#${cm.anchor}`;\n event.preventDefault();\n window.location = url;\n }\n}\n"],"file":"cm.min.js"} \ No newline at end of file +{"version":3,"sources":["../../../src/local/courseindex/cm.js"],"names":["Prefetch","prefetchTemplate","Component","name","selectors","CM_NAME","CM_COMPLETION","classes","CMHIDDEN","LOCKED","RESTRICTIONS","PAGEITEM","id","element","dataset","state","configDragDrop","cm","get","course","_refreshCompletion","window","location","href","url","baseurl","anchor","reactive","dispatch","scrollIntoView","block","Config","contextid","courseContextId","contextInstanceId","uservisible","addEventListener","getElement","_activityAnchor","watch","handler","remove","_refreshCm","_refreshPageItem","classList","toggle","visible","innerHTML","DRAGGING","dragging","locked","hascmrectrictions","pageItem","isPageId","type","isEditing","istrackeduser","completionElement","value","completionstate","exporter","getExporter","data","cmCompletion","Templates","renderForPromise","html","js","replaceNode","event","document","getElementById","setTimeout","section","sectionid","number","preventDefault","target","DndCmItem"],"mappings":"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","sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * Course index cm component.\n *\n * This component is used to control specific course modules interactions like drag and drop.\n *\n * @module core_courseformat/local/courseindex/cm\n * @class core_courseformat/local/courseindex/cm\n * @copyright 2021 Ferran Recio \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport DndCmItem from 'core_courseformat/local/courseeditor/dndcmitem';\nimport Templates from 'core/templates';\nimport Prefetch from 'core/prefetch';\nimport Config from 'core/config';\n\n// Prefetch the completion icons template.\nconst completionTemplate = 'core_courseformat/local/courseindex/cmcompletion';\nPrefetch.prefetchTemplate(completionTemplate);\n\nexport default class Component extends DndCmItem {\n\n /**\n * Constructor hook.\n */\n create() {\n // Optional component name for debugging.\n this.name = 'courseindex_cm';\n // Default query selectors.\n this.selectors = {\n CM_NAME: `[data-for='cm_name']`,\n CM_COMPLETION: `[data-for='cm_completion']`,\n };\n // Default classes to toggle on refresh.\n this.classes = {\n CMHIDDEN: 'dimmed',\n LOCKED: 'editinprogress',\n RESTRICTIONS: 'rectrictions',\n PAGEITEM: 'pageitem',\n };\n // We need our id to watch specific events.\n this.id = this.element.dataset.id;\n }\n\n /**\n * Static method to create a component instance form the mustache template.\n *\n * @param {element|string} target the DOM main element or its ID\n * @param {object} selectors optional css selector overrides\n * @return {Component}\n */\n static init(target, selectors) {\n return new Component({\n element: document.getElementById(target),\n selectors,\n });\n }\n\n /**\n * Initial state ready method.\n *\n * @param {Object} state the course state.\n */\n stateReady(state) {\n this.configDragDrop(this.id);\n const cm = state.cm.get(this.id);\n const course = state.course;\n // Refresh completion icon.\n this._refreshCompletion({\n state,\n element: cm,\n });\n // Check if the current url is the cm url.\n if (window.location.href == cm.url || window.location.href == `${course.baseurl}#${cm.anchor}`) {\n this.reactive.dispatch('setPageItem', 'cm', this.id);\n this.element.scrollIntoView({block: \"center\"});\n }\n // Check if this we are displaying this activity page.\n if (Config.contextid != Config.courseContextId && Config.contextInstanceId == this.id) {\n this.reactive.dispatch('setPageItem', 'cm', this.id, true);\n this.element.scrollIntoView({block: \"center\"});\n }\n // Add anchor logic if the element is not user visible.\n if (!cm.uservisible) {\n this.addEventListener(\n this.getElement(this.selectors.CM_NAME),\n 'click',\n this._activityAnchor,\n );\n }\n }\n\n /**\n * Component watchers.\n *\n * @returns {Array} of watchers\n */\n getWatchers() {\n return [\n {watch: `cm[${this.id}]:deleted`, handler: this.remove},\n {watch: `cm[${this.id}]:updated`, handler: this._refreshCm},\n {watch: `cm[${this.id}].completionstate:updated`, handler: this._refreshCompletion},\n {watch: `course.pageItem:updated`, handler: this._refreshPageItem},\n ];\n }\n\n /**\n * Update a course index cm using the state information.\n *\n * @param {object} param\n * @param {Object} param.element details the update details.\n */\n _refreshCm({element}) {\n // Update classes.\n this.element.classList.toggle(this.classes.CMHIDDEN, !element.visible);\n this.getElement(this.selectors.CM_NAME).innerHTML = element.name;\n this.element.classList.toggle(this.classes.DRAGGING, element.dragging ?? false);\n this.element.classList.toggle(this.classes.LOCKED, element.locked ?? false);\n this.element.classList.toggle(this.classes.RESTRICTIONS, element.hascmrectrictions ?? false);\n this.locked = element.locked;\n }\n\n /**\n * Handle a page item update.\n *\n * @param {Object} details the update details\n * @param {Object} details.element the course state data.\n */\n _refreshPageItem({element}) {\n if (!element.pageItem) {\n return;\n }\n const isPageId = (element.pageItem.type == 'cm' && element.pageItem.id == this.id);\n this.element.classList.toggle(this.classes.PAGEITEM, isPageId);\n if (isPageId && !this.reactive.isEditing) {\n this.element.scrollIntoView({block: \"nearest\"});\n }\n }\n\n /**\n * Update the activity completion icon.\n *\n * @param {Object} details the update details\n * @param {Object} details.state the state data\n * @param {Object} details.element the element data\n */\n async _refreshCompletion({state, element}) {\n // No completion icons are displayed in edit mode.\n if (this.reactive.isEditing || !element.istrackeduser) {\n return;\n }\n // Check if the completion value has changed.\n const completionElement = this.getElement(this.selectors.CM_COMPLETION);\n if (completionElement.dataset.value == element.completionstate) {\n return;\n }\n\n // Collect section information from the state.\n const exporter = this.reactive.getExporter();\n const data = exporter.cmCompletion(state, element);\n\n try {\n const {html, js} = await Templates.renderForPromise(completionTemplate, data);\n Templates.replaceNode(completionElement, html, js);\n } catch (error) {\n throw error;\n }\n }\n\n /**\n * The activity anchor event.\n *\n * @param {Event} event\n */\n _activityAnchor(event) {\n const cm = this.reactive.get('cm', this.id);\n // If the user cannot access the element but the element is present in the page\n // the new url should be an anchor link.\n const element = document.getElementById(cm.anchor);\n if (element) {\n // Marc the element as page item once the event is handled.\n setTimeout(() => {\n this.reactive.dispatch('setPageItem', 'cm', cm.id);\n }, 50);\n return;\n }\n // If the element is not present in the page we need to go to the specific section.\n const course = this.reactive.get('course');\n const section = this.reactive.get('section', cm.sectionid);\n if (!section) {\n return;\n }\n const url = `${course.baseurl}§ion=${section.number}#${cm.anchor}`;\n event.preventDefault();\n window.location = url;\n }\n}\n"],"file":"cm.min.js"} \ No newline at end of file diff --git a/course/format/amd/build/local/courseindex/courseindex.min.js b/course/format/amd/build/local/courseindex/courseindex.min.js index 26cb7412422..86aad9e2d37 100644 --- a/course/format/amd/build/local/courseindex/courseindex.min.js +++ b/course/format/amd/build/local/courseindex/courseindex.min.js @@ -1,2 +1,2 @@ -define ("core_courseformat/local/courseindex/courseindex",["exports","core/reactive","core_courseformat/courseeditor","jquery","core_courseformat/local/courseeditor/contenttree"],function(a,b,c,d,e){"use strict";Object.defineProperty(a,"__esModule",{value:!0});a.default=void 0;d=f(d);e=f(e);function f(a){return a&&a.__esModule?a:{default:a}}function g(a){"@babel/helpers - typeof";if("function"==typeof Symbol&&"symbol"==typeof Symbol.iterator){g=function(a){return typeof a}}else{g=function(a){return a&&"function"==typeof Symbol&&a.constructor===Symbol&&a!==Symbol.prototype?"symbol":typeof a}}return g(a)}function h(a,b,c,d,e,f,g){try{var h=a[f](g),i=h.value}catch(a){c(a);return}if(h.done){b(i)}else{Promise.resolve(i).then(d,e)}}function i(a){return function(){var b=this,c=arguments;return new Promise(function(d,e){var i=a.apply(b,c);function f(a){h(i,d,e,f,g,"next",a)}function g(a){h(i,d,e,f,g,"throw",a)}f(void 0)})}}function j(a,b){if(!(a instanceof b)){throw new TypeError("Cannot call a class as a function")}}function k(a,b){for(var c=0,d;cb.length){a.removeChild(a.lastChild)}}},{key:"_deleteCm",value:function _deleteCm(a){var b=a.element;delete this.cms[b.id]}},{key:"_deleteSection",value:function _deleteSection(a){var b=a.element;delete this.sections[b.id]}}],[{key:"init",value:function init(a,d){return new b({element:document.getElementById(a),reactive:(0,c.getCurrentCourseEditor)(),selectors:d})}}]);return b}(b.BaseComponent);a.default=t;return a.default}); +define ("core_courseformat/local/courseindex/courseindex",["exports","core/reactive","core_courseformat/courseeditor","jquery","core_courseformat/local/courseeditor/contenttree"],function(a,b,c,d,e){"use strict";Object.defineProperty(a,"__esModule",{value:!0});a.default=void 0;d=f(d);e=f(e);function f(a){return a&&a.__esModule?a:{default:a}}function g(a){"@babel/helpers - typeof";if("function"==typeof Symbol&&"symbol"==typeof Symbol.iterator){g=function(a){return typeof a}}else{g=function(a){return a&&"function"==typeof Symbol&&a.constructor===Symbol&&a!==Symbol.prototype?"symbol":typeof a}}return g(a)}function h(a,b,c,d,e,f,g){try{var h=a[f](g),i=h.value}catch(a){c(a);return}if(h.done){b(i)}else{Promise.resolve(i).then(d,e)}}function i(a){return function(){var b=this,c=arguments;return new Promise(function(d,e){var i=a.apply(b,c);function f(a){h(i,d,e,f,g,"next",a)}function g(a){h(i,d,e,f,g,"throw",a)}f(void 0)})}}function j(a,b){if(!(a instanceof b)){throw new TypeError("Cannot call a class as a function")}}function k(a,b){for(var c=0,d;cb.length){a.removeChild(a.lastChild)}}},{key:"_deleteCm",value:function _deleteCm(a){var b=a.element;delete this.cms[b.id]}},{key:"_deleteSection",value:function _deleteSection(a){var b=a.element;delete this.sections[b.id]}}],[{key:"init",value:function init(a,d){return new b({element:document.getElementById(a),reactive:(0,c.getCurrentCourseEditor)(),selectors:d})}}]);return b}(b.BaseComponent);a.default=t;return a.default}); //# sourceMappingURL=courseindex.min.js.map diff --git a/course/format/amd/build/local/courseindex/courseindex.min.js.map b/course/format/amd/build/local/courseindex/courseindex.min.js.map index 65f2db9c0e6..309f48ee5fc 100644 --- a/course/format/amd/build/local/courseindex/courseindex.min.js.map +++ b/course/format/amd/build/local/courseindex/courseindex.min.js.map @@ -1 +1 @@ -{"version":3,"sources":["../../../src/local/courseindex/courseindex.js"],"names":["Component","name","selectors","SECTION","SECTION_CMLIST","CM","TOGGLER","COLLAPSE","DRAWER","classes","SECTIONHIDDEN","CMHIDDEN","SECTIONCURRENT","COLLAPSED","SHOW","sections","cms","addEventListener","element","_sectionTogglers","getElements","forEach","section","dataset","id","cm","contentTree","ContentTree","reactive","isEditing","watch","handler","_refreshSectionCollapsed","_createCm","_deleteCm","_createSection","_deleteSection","_refreshCourseSectionlist","_refreshSectionCmlist","event","sectionlink","target","closest","isChevron","toggler","querySelector","isCollapsed","classList","contains","sectionId","getAttribute","dispatch","indexcollapsed","getElement","Error","_expandSectionNode","forceValue","collapsibleId","replace","collapsible","document","getElementById","togglerValue","collapse","state","fakeelement","createElement","add","innerHTML","get","sectionid","exporter","getExporter","data","renderComponent","newcomponent","newelement","parentNode","replaceChild","course","cmlist","listparent","_fixOrder","sectionlist","container","neworder","allitems","length","remove","itemid","index","item","currentitem","children","append","insertBefore","removeChild","lastChild","BaseComponent"],"mappings":"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","sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * Course index main component.\n *\n * @module core_courseformat/local/courseindex/courseindex\n * @class core_courseformat/local/courseindex/courseindex\n * @copyright 2021 Ferran Recio \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport {BaseComponent} from 'core/reactive';\nimport {getCurrentCourseEditor} from 'core_courseformat/courseeditor';\nimport jQuery from 'jquery';\nimport ContentTree from 'core_courseformat/local/courseeditor/contenttree';\n\nexport default class Component extends BaseComponent {\n\n /**\n * Constructor hook.\n */\n create() {\n // Optional component name for debugging.\n this.name = 'courseindex';\n // Default query selectors.\n this.selectors = {\n SECTION: `[data-for='section']`,\n SECTION_CMLIST: `[data-for='cmlist']`,\n CM: `[data-for='cm']`,\n TOGGLER: `[data-action=\"togglecourseindexsection\"]`,\n COLLAPSE: `[data-toggle=\"collapse\"]`,\n DRAWER: `.drawer`,\n };\n // Default classes to toggle on refresh.\n this.classes = {\n SECTIONHIDDEN: 'dimmed',\n CMHIDDEN: 'dimmed',\n SECTIONCURRENT: 'current',\n COLLAPSED: `collapsed`,\n SHOW: `show`,\n };\n // Arrays to keep cms and sections elements.\n this.sections = {};\n this.cms = {};\n }\n\n /**\n * Static method to create a component instance form the mustache template.\n *\n * @param {element|string} target the DOM main element or its ID\n * @param {object} selectors optional css selector overrides\n * @return {Component}\n */\n static init(target, selectors) {\n return new Component({\n element: document.getElementById(target),\n reactive: getCurrentCourseEditor(),\n selectors,\n });\n }\n\n /**\n * Initial state ready method.\n */\n stateReady() {\n // Activate section togglers.\n this.addEventListener(this.element, 'click', this._sectionTogglers);\n\n // Get cms and sections elements.\n const sections = this.getElements(this.selectors.SECTION);\n sections.forEach((section) => {\n this.sections[section.dataset.id] = section;\n });\n const cms = this.getElements(this.selectors.CM);\n cms.forEach((cm) => {\n this.cms[cm.dataset.id] = cm;\n });\n\n // Configure Aria Tree.\n this.contentTree = new ContentTree(this.element, this.selectors, this.reactive.isEditing);\n }\n\n getWatchers() {\n return [\n {watch: `section.indexcollapsed:updated`, handler: this._refreshSectionCollapsed},\n {watch: `cm:created`, handler: this._createCm},\n {watch: `cm:deleted`, handler: this._deleteCm},\n {watch: `section:created`, handler: this._createSection},\n {watch: `section:deleted`, handler: this._deleteSection},\n // Sections and cm sorting.\n {watch: `course.sectionlist:updated`, handler: this._refreshCourseSectionlist},\n {watch: `section.cmlist:updated`, handler: this._refreshSectionCmlist},\n ];\n }\n\n /**\n * Setup sections toggler.\n *\n * Toggler click is delegated to the main course index 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 _sectionTogglers(event) {\n const sectionlink = event.target.closest(this.selectors.TOGGLER);\n const isChevron = event.target.closest(this.selectors.COLLAPSE);\n\n if (sectionlink || isChevron) {\n\n const section = event.target.closest(this.selectors.SECTION);\n const toggler = section.querySelector(this.selectors.COLLAPSE);\n const isCollapsed = toggler?.classList.contains(this.classes.COLLAPSED) ?? false;\n\n if (isChevron || isCollapsed) {\n // Update the state.\n const sectionId = section.getAttribute('data-id');\n this.reactive.dispatch(\n 'sectionPreferences',\n [sectionId],\n {\n indexcollapsed: !isCollapsed,\n },\n );\n }\n }\n }\n\n /**\n * Update section collapsed.\n *\n * @param {object} args\n * @param {object} args.element The leement to be expanded\n */\n _refreshSectionCollapsed({element}) {\n const target = this.getElement(this.selectors.SECTION, element.id);\n if (!target) {\n throw new Error(`Unkown section with ID ${element.id}`);\n }\n // Check if it is already done.\n const toggler = target.querySelector(this.selectors.COLLAPSE);\n const isCollapsed = toggler?.classList.contains(this.classes.COLLAPSED) ?? false;\n\n if (element.indexcollapsed !== isCollapsed) {\n this._expandSectionNode(element);\n }\n }\n\n /**\n * Expand a section node.\n *\n * By default the method will use element.indexcollapsed to decide if the\n * section is opened or closed. However, using forceValue it is possible\n * to open or close a section independant from the indexcollapsed attribute.\n *\n * @param {Object} element the course module state element\n * @param {boolean} forceValue optional forced expanded value\n */\n _expandSectionNode(element, forceValue) {\n const target = this.getElement(this.selectors.SECTION, element.id);\n const toggler = target.querySelector(this.selectors.COLLAPSE);\n let collapsibleId = toggler.dataset.target ?? toggler.getAttribute(\"href\");\n if (!collapsibleId) {\n return;\n }\n collapsibleId = collapsibleId.replace('#', '');\n const collapsible = document.getElementById(collapsibleId);\n if (!collapsible) {\n return;\n }\n\n if (forceValue === undefined) {\n forceValue = (element.indexcollapsed) ? false : true;\n }\n\n // Course index is based on Bootstrap 4 collapsibles. To collapse them we need jQuery to\n // interact with collapsibles methods. Hopefully, this will change in Bootstrap 5 because\n // it does not require jQuery anymore (when MDL-79179 is integrated).\n const togglerValue = (forceValue) ? 'show' : 'hide';\n jQuery(collapsible).collapse(togglerValue);\n }\n\n /**\n * Create a newcm instance.\n *\n * @param {object} param\n * @param {Object} param.state\n * @param {Object} param.element\n */\n async _createCm({state, element}) {\n // Create a fake node while the component is loading.\n const fakeelement = document.createElement('li');\n fakeelement.classList.add('bg-pulse-grey', 'w-100');\n fakeelement.innerHTML = ' ';\n this.cms[element.id] = fakeelement;\n // Place the fake node on the correct position.\n this._refreshSectionCmlist({\n state,\n element: state.section.get(element.sectionid),\n });\n // Collect render data.\n const exporter = this.reactive.getExporter();\n const data = exporter.cm(state, element);\n // Create the new content.\n const newcomponent = await this.renderComponent(fakeelement, 'core_courseformat/local/courseindex/cm', data);\n // Replace the fake node with the real content.\n const newelement = newcomponent.getElement();\n this.cms[element.id] = newelement;\n fakeelement.parentNode.replaceChild(newelement, fakeelement);\n }\n\n /**\n * Create a new section instance.\n *\n * @param {Object} details the update details.\n * @param {Object} details.state the state data.\n * @param {Object} details.element the element data.\n */\n async _createSection({state, element}) {\n // Create a fake node while the component is loading.\n const fakeelement = document.createElement('div');\n fakeelement.classList.add('bg-pulse-grey', 'w-100');\n fakeelement.innerHTML = ' ';\n this.sections[element.id] = fakeelement;\n // Place the fake node on the correct position.\n this._refreshCourseSectionlist({\n state,\n element: state.course,\n });\n // Collect render data.\n const exporter = this.reactive.getExporter();\n const data = exporter.section(state, element);\n // Create the new content.\n const newcomponent = await this.renderComponent(fakeelement, 'core_courseformat/local/courseindex/section', data);\n // Replace the fake node with the real content.\n const newelement = newcomponent.getElement();\n this.sections[element.id] = newelement;\n fakeelement.parentNode.replaceChild(newelement, fakeelement);\n }\n\n /**\n * Refresh a section cm list.\n *\n * @param {object} param\n * @param {Object} param.element\n */\n _refreshSectionCmlist({element}) {\n const cmlist = element.cmlist ?? [];\n const listparent = this.getElement(this.selectors.SECTION_CMLIST, element.id);\n this._fixOrder(listparent, cmlist, this.cms);\n }\n\n /**\n * Refresh the section list.\n *\n * @param {object} param\n * @param {Object} param.element\n */\n _refreshCourseSectionlist({element}) {\n const sectionlist = element.sectionlist ?? [];\n this._fixOrder(this.element, sectionlist, this.sections);\n }\n\n /**\n * Fix/reorder the section or cms order.\n *\n * @param {Element} container the HTML element to reorder.\n * @param {Array} neworder an array with the ids order\n * @param {Array} allitems the list of html elements that can be placed in the container\n */\n _fixOrder(container, neworder, allitems) {\n\n // Empty lists should not be visible.\n if (!neworder.length) {\n container.classList.add('hidden');\n container.innerHTML = '';\n return;\n }\n\n // Grant the list is visible (in case it was empty).\n container.classList.remove('hidden');\n\n // Move the elements in order at the beginning of the list.\n neworder.forEach((itemid, index) => {\n const item = allitems[itemid];\n // Get the current element at that position.\n const currentitem = container.children[index];\n if (currentitem === undefined) {\n container.append(item);\n return;\n }\n if (currentitem !== item) {\n container.insertBefore(item, currentitem);\n }\n });\n // Remove the remaining elements.\n while (container.children.length > neworder.length) {\n container.removeChild(container.lastChild);\n }\n }\n\n /**\n * Remove a cm from the list.\n *\n * The actual DOM element removal is delegated to the cm component.\n *\n * @param {object} param\n * @param {Object} param.element\n */\n _deleteCm({element}) {\n delete this.cms[element.id];\n }\n\n /**\n * Remove a section from the list.\n *\n * The actual DOM element removal is delegated to the section component.\n *\n * @param {Object} details the update details.\n * @param {Object} details.element the element data.\n */\n _deleteSection({element}) {\n delete this.sections[element.id];\n }\n}\n"],"file":"courseindex.min.js"} \ No newline at end of file +{"version":3,"sources":["../../../src/local/courseindex/courseindex.js"],"names":["Component","name","selectors","SECTION","SECTION_CMLIST","CM","TOGGLER","COLLAPSE","DRAWER","classes","SECTIONHIDDEN","CMHIDDEN","SECTIONCURRENT","COLLAPSED","SHOW","sections","cms","state","addEventListener","element","_sectionTogglers","getElements","forEach","section","dataset","id","cm","_refreshPageItem","course","contentTree","ContentTree","reactive","isEditing","watch","handler","_refreshSectionCollapsed","_createCm","_deleteCm","_createSection","_deleteSection","_refreshCourseSectionlist","_refreshSectionCmlist","event","sectionlink","target","closest","isChevron","toggler","querySelector","isCollapsed","classList","contains","sectionId","getAttribute","dispatch","indexcollapsed","getElement","Error","_expandSectionNode","forceValue","collapsibleId","replace","collapsible","document","getElementById","togglerValue","collapse","pageItem","isStatic","type","get","setTimeout","scrollIntoView","block","fakeelement","createElement","add","innerHTML","sectionid","exporter","getExporter","data","renderComponent","newcomponent","newelement","parentNode","replaceChild","cmlist","listparent","_fixOrder","sectionlist","container","neworder","allitems","length","remove","itemid","index","item","currentitem","children","append","insertBefore","removeChild","lastChild","BaseComponent"],"mappings":"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","sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * Course index main component.\n *\n * @module core_courseformat/local/courseindex/courseindex\n * @class core_courseformat/local/courseindex/courseindex\n * @copyright 2021 Ferran Recio \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport {BaseComponent} from 'core/reactive';\nimport {getCurrentCourseEditor} from 'core_courseformat/courseeditor';\nimport jQuery from 'jquery';\nimport ContentTree from 'core_courseformat/local/courseeditor/contenttree';\n\nexport default class Component extends BaseComponent {\n\n /**\n * Constructor hook.\n */\n create() {\n // Optional component name for debugging.\n this.name = 'courseindex';\n // Default query selectors.\n this.selectors = {\n SECTION: `[data-for='section']`,\n SECTION_CMLIST: `[data-for='cmlist']`,\n CM: `[data-for='cm']`,\n TOGGLER: `[data-action=\"togglecourseindexsection\"]`,\n COLLAPSE: `[data-toggle=\"collapse\"]`,\n DRAWER: `.drawer`,\n };\n // Default classes to toggle on refresh.\n this.classes = {\n SECTIONHIDDEN: 'dimmed',\n CMHIDDEN: 'dimmed',\n SECTIONCURRENT: 'current',\n COLLAPSED: `collapsed`,\n SHOW: `show`,\n };\n // Arrays to keep cms and sections elements.\n this.sections = {};\n this.cms = {};\n }\n\n /**\n * Static method to create a component instance form the mustache template.\n *\n * @param {element|string} target the DOM main element or its ID\n * @param {object} selectors optional css selector overrides\n * @return {Component}\n */\n static init(target, selectors) {\n return new Component({\n element: document.getElementById(target),\n reactive: getCurrentCourseEditor(),\n selectors,\n });\n }\n\n /**\n * Initial state ready method.\n *\n * @param {Object} state the state data\n */\n stateReady(state) {\n // Activate section togglers.\n this.addEventListener(this.element, 'click', this._sectionTogglers);\n\n // Get cms and sections elements.\n const sections = this.getElements(this.selectors.SECTION);\n sections.forEach((section) => {\n this.sections[section.dataset.id] = section;\n });\n const cms = this.getElements(this.selectors.CM);\n cms.forEach((cm) => {\n this.cms[cm.dataset.id] = cm;\n });\n\n // Set the page item if any.\n this._refreshPageItem({element: state.course, state});\n\n // Configure Aria Tree.\n this.contentTree = new ContentTree(this.element, this.selectors, this.reactive.isEditing);\n }\n\n getWatchers() {\n return [\n {watch: `section.indexcollapsed:updated`, handler: this._refreshSectionCollapsed},\n {watch: `cm:created`, handler: this._createCm},\n {watch: `cm:deleted`, handler: this._deleteCm},\n {watch: `section:created`, handler: this._createSection},\n {watch: `section:deleted`, handler: this._deleteSection},\n {watch: `course.pageItem:created`, handler: this._refreshPageItem},\n {watch: `course.pageItem:updated`, handler: this._refreshPageItem},\n // Sections and cm sorting.\n {watch: `course.sectionlist:updated`, handler: this._refreshCourseSectionlist},\n {watch: `section.cmlist:updated`, handler: this._refreshSectionCmlist},\n ];\n }\n\n /**\n * Setup sections toggler.\n *\n * Toggler click is delegated to the main course index 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 _sectionTogglers(event) {\n const sectionlink = event.target.closest(this.selectors.TOGGLER);\n const isChevron = event.target.closest(this.selectors.COLLAPSE);\n\n if (sectionlink || isChevron) {\n\n const section = event.target.closest(this.selectors.SECTION);\n const toggler = section.querySelector(this.selectors.COLLAPSE);\n const isCollapsed = toggler?.classList.contains(this.classes.COLLAPSED) ?? false;\n\n if (isChevron || isCollapsed) {\n // Update the state.\n const sectionId = section.getAttribute('data-id');\n this.reactive.dispatch(\n 'sectionPreferences',\n [sectionId],\n {\n indexcollapsed: !isCollapsed,\n },\n );\n }\n }\n }\n\n /**\n * Update section collapsed.\n *\n * @param {object} args\n * @param {object} args.element The leement to be expanded\n */\n _refreshSectionCollapsed({element}) {\n const target = this.getElement(this.selectors.SECTION, element.id);\n if (!target) {\n throw new Error(`Unkown section with ID ${element.id}`);\n }\n // Check if it is already done.\n const toggler = target.querySelector(this.selectors.COLLAPSE);\n const isCollapsed = toggler?.classList.contains(this.classes.COLLAPSED) ?? false;\n\n if (element.indexcollapsed !== isCollapsed) {\n this._expandSectionNode(element);\n }\n }\n\n /**\n * Expand a section node.\n *\n * By default the method will use element.indexcollapsed to decide if the\n * section is opened or closed. However, using forceValue it is possible\n * to open or close a section independant from the indexcollapsed attribute.\n *\n * @param {Object} element the course module state element\n * @param {boolean} forceValue optional forced expanded value\n */\n _expandSectionNode(element, forceValue) {\n const target = this.getElement(this.selectors.SECTION, element.id);\n const toggler = target.querySelector(this.selectors.COLLAPSE);\n let collapsibleId = toggler.dataset.target ?? toggler.getAttribute(\"href\");\n if (!collapsibleId) {\n return;\n }\n collapsibleId = collapsibleId.replace('#', '');\n const collapsible = document.getElementById(collapsibleId);\n if (!collapsible) {\n return;\n }\n\n if (forceValue === undefined) {\n forceValue = (element.indexcollapsed) ? false : true;\n }\n\n // Course index is based on Bootstrap 4 collapsibles. To collapse them we need jQuery to\n // interact with collapsibles methods. Hopefully, this will change in Bootstrap 5 because\n // it does not require jQuery anymore (when MDL-79179 is integrated).\n const togglerValue = (forceValue) ? 'show' : 'hide';\n jQuery(collapsible).collapse(togglerValue);\n }\n\n /**\n * Handle a page item update.\n *\n * @param {Object} details the update details\n * @param {Object} details.state the state data.\n * @param {Object} details.element the course state data.\n */\n _refreshPageItem({element, state}) {\n if (!element?.pageItem?.isStatic || element.pageItem.type != 'cm') {\n return;\n }\n // Check if we need to uncollapse the section and scroll to the element.\n const section = state.section.get(element.pageItem.sectionId);\n if (section.indexcollapsed) {\n this._expandSectionNode(section, true);\n setTimeout(\n () => this.cms[element.pageItem.id]?.scrollIntoView({block: \"nearest\"}),\n 250\n );\n }\n }\n\n /**\n * Create a newcm instance.\n *\n * @param {object} param\n * @param {Object} param.state\n * @param {Object} param.element\n */\n async _createCm({state, element}) {\n // Create a fake node while the component is loading.\n const fakeelement = document.createElement('li');\n fakeelement.classList.add('bg-pulse-grey', 'w-100');\n fakeelement.innerHTML = ' ';\n this.cms[element.id] = fakeelement;\n // Place the fake node on the correct position.\n this._refreshSectionCmlist({\n state,\n element: state.section.get(element.sectionid),\n });\n // Collect render data.\n const exporter = this.reactive.getExporter();\n const data = exporter.cm(state, element);\n // Create the new content.\n const newcomponent = await this.renderComponent(fakeelement, 'core_courseformat/local/courseindex/cm', data);\n // Replace the fake node with the real content.\n const newelement = newcomponent.getElement();\n this.cms[element.id] = newelement;\n fakeelement.parentNode.replaceChild(newelement, fakeelement);\n }\n\n /**\n * Create a new section instance.\n *\n * @param {Object} details the update details.\n * @param {Object} details.state the state data.\n * @param {Object} details.element the element data.\n */\n async _createSection({state, element}) {\n // Create a fake node while the component is loading.\n const fakeelement = document.createElement('div');\n fakeelement.classList.add('bg-pulse-grey', 'w-100');\n fakeelement.innerHTML = ' ';\n this.sections[element.id] = fakeelement;\n // Place the fake node on the correct position.\n this._refreshCourseSectionlist({\n state,\n element: state.course,\n });\n // Collect render data.\n const exporter = this.reactive.getExporter();\n const data = exporter.section(state, element);\n // Create the new content.\n const newcomponent = await this.renderComponent(fakeelement, 'core_courseformat/local/courseindex/section', data);\n // Replace the fake node with the real content.\n const newelement = newcomponent.getElement();\n this.sections[element.id] = newelement;\n fakeelement.parentNode.replaceChild(newelement, fakeelement);\n }\n\n /**\n * Refresh a section cm list.\n *\n * @param {object} param\n * @param {Object} param.element\n */\n _refreshSectionCmlist({element}) {\n const cmlist = element.cmlist ?? [];\n const listparent = this.getElement(this.selectors.SECTION_CMLIST, element.id);\n this._fixOrder(listparent, cmlist, this.cms);\n }\n\n /**\n * Refresh the section list.\n *\n * @param {object} param\n * @param {Object} param.element\n */\n _refreshCourseSectionlist({element}) {\n const sectionlist = element.sectionlist ?? [];\n this._fixOrder(this.element, sectionlist, this.sections);\n }\n\n /**\n * Fix/reorder the section or cms order.\n *\n * @param {Element} container the HTML element to reorder.\n * @param {Array} neworder an array with the ids order\n * @param {Array} allitems the list of html elements that can be placed in the container\n */\n _fixOrder(container, neworder, allitems) {\n\n // Empty lists should not be visible.\n if (!neworder.length) {\n container.classList.add('hidden');\n container.innerHTML = '';\n return;\n }\n\n // Grant the list is visible (in case it was empty).\n container.classList.remove('hidden');\n\n // Move the elements in order at the beginning of the list.\n neworder.forEach((itemid, index) => {\n const item = allitems[itemid];\n // Get the current element at that position.\n const currentitem = container.children[index];\n if (currentitem === undefined) {\n container.append(item);\n return;\n }\n if (currentitem !== item) {\n container.insertBefore(item, currentitem);\n }\n });\n // Remove the remaining elements.\n while (container.children.length > neworder.length) {\n container.removeChild(container.lastChild);\n }\n }\n\n /**\n * Remove a cm from the list.\n *\n * The actual DOM element removal is delegated to the cm component.\n *\n * @param {object} param\n * @param {Object} param.element\n */\n _deleteCm({element}) {\n delete this.cms[element.id];\n }\n\n /**\n * Remove a section from the list.\n *\n * The actual DOM element removal is delegated to the section component.\n *\n * @param {Object} details the update details.\n * @param {Object} details.element the element data.\n */\n _deleteSection({element}) {\n delete this.sections[element.id];\n }\n}\n"],"file":"courseindex.min.js"} \ No newline at end of file diff --git a/course/format/amd/build/local/courseindex/section.min.js b/course/format/amd/build/local/courseindex/section.min.js index 00b3580a867..a84ffe3f236 100644 --- a/course/format/amd/build/local/courseindex/section.min.js +++ b/course/format/amd/build/local/courseindex/section.min.js @@ -1,2 +1,2 @@ -define ("core_courseformat/local/courseindex/section",["exports","core_courseformat/local/courseindex/sectiontitle","core_courseformat/local/courseeditor/dndsection"],function(a,b,c){"use strict";Object.defineProperty(a,"__esModule",{value:!0});a.default=void 0;b=d(b);c=d(c);function d(a){return a&&a.__esModule?a:{default:a}}function e(a){"@babel/helpers - typeof";if("function"==typeof Symbol&&"symbol"==typeof Symbol.iterator){e=function(a){return typeof a}}else{e=function(a){return a&&"function"==typeof Symbol&&a.constructor===Symbol&&a!==Symbol.prototype?"symbol":typeof a}}return e(a)}function f(a,b){var c=Object.keys(a);if(Object.getOwnPropertySymbols){var d=Object.getOwnPropertySymbols(a);if(b)d=d.filter(function(b){return Object.getOwnPropertyDescriptor(a,b).enumerable});c.push.apply(c,d)}return c}function g(a){for(var b=1,c;b.\n\n/**\n * Course index section component.\n *\n * This component is used to control specific course section interactions like drag and drop.\n *\n * @module core_courseformat/local/courseindex/section\n * @class core_courseformat/local/courseindex/section\n * @copyright 2021 Ferran Recio \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport SectionTitle from 'core_courseformat/local/courseindex/sectiontitle';\nimport DndSection from 'core_courseformat/local/courseeditor/dndsection';\n\nexport default class Component extends DndSection {\n\n /**\n * Constructor hook.\n */\n create() {\n // Optional component name for debugging.\n this.name = 'courseindex_section';\n // Default query selectors.\n this.selectors = {\n SECTION_ITEM: `[data-for='section_item']`,\n SECTION_TITLE: `[data-for='section_title']`,\n CM_LAST: `[data-for=\"cm\"]:last-child`,\n };\n // Default classes to toggle on refresh.\n this.classes = {\n SECTIONHIDDEN: 'dimmed',\n SECTIONCURRENT: 'current',\n LOCKED: 'editinprogress',\n RESTRICTIONS: 'rectrictions',\n };\n\n // We need our id to watch specific events.\n this.id = this.element.dataset.id;\n }\n\n /**\n * Static method to create a component instance form the mustahce template.\n *\n * @param {string} target the DOM main element or its ID\n * @param {object} selectors optional css selector overrides\n * @return {Component}\n */\n static init(target, selectors) {\n return new Component({\n element: document.getElementById(target),\n selectors,\n });\n }\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 // Init the inner dragable element passing the full section as affected region.\n const titleitem = new SectionTitle({\n ...this,\n element: this.getElement(this.selectors.SECTION_ITEM),\n fullregion: this.element,\n });\n this.configDragDrop(titleitem);\n }\n }\n\n /**\n * Component watchers.\n *\n * @returns {Array} of watchers\n */\n getWatchers() {\n return [\n {watch: `section[${this.id}]:deleted`, handler: this.remove},\n {watch: `section[${this.id}]:updated`, handler: this._refreshSection},\n ];\n }\n\n /**\n * Get the last CM element of that section.\n *\n * @returns {element|null}\n */\n getLastCm() {\n return this.getElement(this.selectors.CM_LAST);\n }\n\n /**\n * Update a course index section using the state information.\n *\n * @param {Object} param details the update details.\n * @param {Object} param.element\n */\n _refreshSection({element}) {\n // Update classes.\n const sectionitem = this.getElement(this.selectors.SECTION_ITEM);\n sectionitem.classList.toggle(this.classes.SECTIONHIDDEN, !element.visible);\n this.element.classList.toggle(this.classes.SECTIONCURRENT, element.current);\n this.element.classList.toggle(this.classes.DRAGGING, element.dragging ?? false);\n this.element.classList.toggle(this.classes.LOCKED, element.locked ?? false);\n this.element.classList.toggle(this.classes.RESTRICTIONS, element.hasrestrictions ?? false);\n this.locked = element.locked;\n // Update title.\n this.getElement(this.selectors.SECTION_TITLE).innerHTML = element.title;\n }\n}\n"],"file":"section.min.js"} \ No newline at end of file +{"version":3,"sources":["../../../src/local/courseindex/section.js"],"names":["Component","name","selectors","SECTION_ITEM","SECTION_TITLE","CM_LAST","classes","SECTIONHIDDEN","SECTIONCURRENT","LOCKED","RESTRICTIONS","PAGEITEM","id","element","dataset","isPageItem","state","configState","sectionItem","getElement","reactive","isEditing","supportComponents","titleitem","SectionTitle","fullregion","configDragDrop","section","get","window","location","href","sectionurl","replace","dispatch","scrollIntoView","watch","handler","remove","_refreshSection","_refreshPageItem","classList","toggle","visible","current","DRAGGING","dragging","locked","hasrestrictions","innerHTML","title","pageItem","sectionId","indexcollapsed","isStatic","type","block","target","document","getElementById","DndSection"],"mappings":"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","sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * Course index section component.\n *\n * This component is used to control specific course section interactions like drag and drop.\n *\n * @module core_courseformat/local/courseindex/section\n * @class core_courseformat/local/courseindex/section\n * @copyright 2021 Ferran Recio \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport SectionTitle from 'core_courseformat/local/courseindex/sectiontitle';\nimport DndSection from 'core_courseformat/local/courseeditor/dndsection';\n\nexport default class Component extends DndSection {\n\n /**\n * Constructor hook.\n */\n create() {\n // Optional component name for debugging.\n this.name = 'courseindex_section';\n // Default query selectors.\n this.selectors = {\n SECTION_ITEM: `[data-for='section_item']`,\n SECTION_TITLE: `[data-for='section_title']`,\n CM_LAST: `[data-for=\"cm\"]:last-child`,\n };\n // Default classes to toggle on refresh.\n this.classes = {\n SECTIONHIDDEN: 'dimmed',\n SECTIONCURRENT: 'current',\n LOCKED: 'editinprogress',\n RESTRICTIONS: 'rectrictions',\n PAGEITEM: 'pageitem',\n };\n\n // We need our id to watch specific events.\n this.id = this.element.dataset.id;\n this.isPageItem = false;\n }\n\n /**\n * Static method to create a component instance form the mustahce template.\n *\n * @param {string} target the DOM main element or its ID\n * @param {object} selectors optional css selector overrides\n * @return {Component}\n */\n static init(target, selectors) {\n return new Component({\n element: document.getElementById(target),\n selectors,\n });\n }\n\n /**\n * Initial state ready method.\n *\n * @param {Object} state the initial state\n */\n stateReady(state) {\n this.configState(state);\n const sectionItem = this.getElement(this.selectors.SECTION_ITEM);\n // Drag and drop is only available for components compatible course formats.\n if (this.reactive.isEditing && this.reactive.supportComponents) {\n // Init the inner dragable element passing the full section as affected region.\n const titleitem = new SectionTitle({\n ...this,\n element: sectionItem,\n fullregion: this.element,\n });\n this.configDragDrop(titleitem);\n }\n // Check if the current url is the section url.\n const section = state.section.get(this.id);\n if (window.location.href == section.sectionurl.replace(/&/g, \"&\")) {\n this.reactive.dispatch('setPageItem', 'section', this.id);\n sectionItem.scrollIntoView();\n }\n }\n\n /**\n * Component watchers.\n *\n * @returns {Array} of watchers\n */\n getWatchers() {\n return [\n {watch: `section[${this.id}]:deleted`, handler: this.remove},\n {watch: `section[${this.id}]:updated`, handler: this._refreshSection},\n {watch: `course.pageItem:updated`, handler: this._refreshPageItem},\n ];\n }\n\n /**\n * Get the last CM element of that section.\n *\n * @returns {element|null}\n */\n getLastCm() {\n return this.getElement(this.selectors.CM_LAST);\n }\n\n /**\n * Update a course index section using the state information.\n *\n * @param {Object} param details the update details.\n * @param {Object} param.element the section element\n */\n _refreshSection({element}) {\n // Update classes.\n const sectionItem = this.getElement(this.selectors.SECTION_ITEM);\n sectionItem.classList.toggle(this.classes.SECTIONHIDDEN, !element.visible);\n this.element.classList.toggle(this.classes.SECTIONCURRENT, element.current);\n this.element.classList.toggle(this.classes.DRAGGING, element.dragging ?? false);\n this.element.classList.toggle(this.classes.LOCKED, element.locked ?? false);\n this.element.classList.toggle(this.classes.RESTRICTIONS, element.hasrestrictions ?? false);\n this.locked = element.locked;\n // Update title.\n this.getElement(this.selectors.SECTION_TITLE).innerHTML = element.title;\n }\n\n /**\n * Handle a page item update.\n *\n * @param {Object} details the update details\n * @param {Object} details.state the state data.\n * @param {Object} details.element the course state data.\n */\n _refreshPageItem({element, state}) {\n if (!element.pageItem) {\n return;\n }\n if (element.pageItem.sectionId !== this.id && this.isPageItem) {\n this.pageItem = false;\n this.getElement(this.selectors.SECTION_ITEM).classList.remove(this.classes.PAGEITEM);\n return;\n }\n const section = state.section.get(this.id);\n if (section.indexcollapsed && !element.pageItem?.isStatic) {\n this.pageItem = (element.pageItem?.sectionId == this.id);\n } else {\n this.pageItem = (element.pageItem.type == 'section' && element.pageItem.id == this.id);\n }\n const sectionItem = this.getElement(this.selectors.SECTION_ITEM);\n sectionItem.classList.toggle(this.classes.PAGEITEM, this.pageItem ?? false);\n if (this.pageItem && !this.reactive.isEditing) {\n this.element.scrollIntoView({block: \"nearest\"});\n }\n }\n}\n"],"file":"section.min.js"} \ No newline at end of file diff --git a/course/format/amd/src/local/content.js b/course/format/amd/src/local/content.js index f6d055cc656..223f9bed63b 100644 --- a/course/format/amd/src/local/content.js +++ b/course/format/amd/src/local/content.js @@ -49,6 +49,7 @@ export default class Component extends BaseComponent { SECTION_CMLIST: `[data-for='cmlist']`, COURSE_SECTIONLIST: `[data-for='course_sectionlist']`, CM: `[data-for='cmitem']`, + PAGE: `#page`, TOGGLER: `[data-action="togglecoursecontentsection"]`, COLLAPSE: `[data-toggle="collapse"]`, TOGGLEALL: `[data-toggle="toggleall"]`, @@ -124,6 +125,13 @@ export default class Component extends BaseComponent { CourseEvents.manualCompletionToggled, this._completionHandler ); + + // Capture page scroll to update page item. + this.addEventListener( + document.querySelector(this.selectors.PAGE), + "scroll", + this._scrollHandler + ); } /** @@ -291,6 +299,33 @@ export default class Component extends BaseComponent { this.reactive.dispatch('cmCompletion', [detail.cmid], detail.completed); } + /** + * Check the current page scroll and update the active element if necessary. + */ + _scrollHandler() { + const pageOffset = document.querySelector(this.selectors.PAGE).scrollTop; + const items = this.reactive.getExporter().allItemsArray(this.reactive.state); + // Check what is the active element now. + let pageItem = null; + items.every(item => { + const index = (item.type === 'section') ? this.sections : this.cms; + if (index[item.id] === undefined) { + return true; + } + + const element = index[item.id].element; + // Activities without url can only be page items in edit mode. + if (item.type === 'cm' && !item.url && !this.reactive.isEditing) { + return pageOffset >= element.offsetTop; + } + pageItem = item; + return pageOffset >= element.offsetTop; + }); + if (pageItem) { + this.reactive.dispatch('setPageItem', pageItem.type, pageItem.id); + } + } + /** * Update a course section when the section number changes. * diff --git a/course/format/amd/src/local/courseeditor/exporter.js b/course/format/amd/src/local/courseeditor/exporter.js index c68a6cd3737..9a23fe50046 100644 --- a/course/format/amd/src/local/courseeditor/exporter.js +++ b/course/format/amd/src/local/courseeditor/exporter.js @@ -177,4 +177,27 @@ export default class { } return data; } + + /** + * Return a sorted list of all sections and cms items in the state. + * + * @param {Object} state the current state. + * @returns {Array} all sections and cms items in the state. + */ + allItemsArray(state) { + const items = []; + const sectionlist = state.course.sectionlist ?? []; + // Add sections. + sectionlist.forEach(sectionid => { + const sectioninfo = state.section.get(sectionid); + items.push({type: 'section', id: sectioninfo.id, url: sectioninfo.sectionurl}); + // Add cms. + const cmlist = sectioninfo.cmlist ?? []; + cmlist.forEach(cmid => { + const cminfo = state.cm.get(cmid); + items.push({type: 'cm', id: cminfo.id, url: cminfo.url}); + }); + }); + return items; + } } diff --git a/course/format/amd/src/local/courseeditor/mutations.js b/course/format/amd/src/local/courseeditor/mutations.js index 988d4088986..7f791c79689 100644 --- a/course/format/amd/src/local/courseeditor/mutations.js +++ b/course/format/amd/src/local/courseeditor/mutations.js @@ -166,6 +166,7 @@ export default class { * @param {bool} dragValue the new dragging value */ cmDrag(stateManager, cmIds, dragValue) { + this.setPageItem(stateManager); this._setElementsValue(stateManager, 'cm', cmIds, 'dragging', dragValue); } @@ -177,6 +178,7 @@ export default class { * @param {bool} dragValue the new dragging value */ sectionDrag(stateManager, sectionIds, dragValue) { + this.setPageItem(stateManager); this._setElementsValue(stateManager, 'section', sectionIds, 'dragging', dragValue); } @@ -225,6 +227,48 @@ export default class { stateManager.setReadOnly(true); } + /** + * Set the page current item. + * + * Only one element of the course state can be the page item at a time. + * + * There are several actions that can alter the page current item. For example, when the user is in an activity + * page, the page item is always the activity one. However, in a course page, when the user scrolls to an element, + * this element get the page item. + * + * If the page item is static means that it is not meant to change. This is important because + * static page items has some special logic. For example, if a cm is the static page item + * and it is inside a collapsed section, the course index will expand the section to make it visible. + * + * @param {StateManager} stateManager the current state manager + * @param {String|undefined} type the element type (section or cm). Undefined will remove the current page item. + * @param {Number|undefined} id the element id + * @param {boolean|undefined} isStatic if the page item is static + */ + setPageItem(stateManager, type, id, isStatic) { + let newPageItem; + if (type !== undefined) { + newPageItem = stateManager.get(type, id); + if (!newPageItem) { + return; + } + } + stateManager.setReadOnly(false); + // Remove the current page item. + const course = stateManager.get('course'); + course.pageItem = null; + // Save the new page item. + if (newPageItem) { + course.pageItem = { + id, + type, + sectionId: (type == 'section') ? newPageItem.id : newPageItem.sectionid, + isStatic, + }; + } + stateManager.setReadOnly(true); + } + /** * Unlock all course elements. * diff --git a/course/format/amd/src/local/courseindex/cm.js b/course/format/amd/src/local/courseindex/cm.js index 280ed1e0aed..ee39aa66ae2 100644 --- a/course/format/amd/src/local/courseindex/cm.js +++ b/course/format/amd/src/local/courseindex/cm.js @@ -79,14 +79,21 @@ export default class Component extends DndCmItem { stateReady(state) { this.configDragDrop(this.id); const cm = state.cm.get(this.id); + const course = state.course; // Refresh completion icon. this._refreshCompletion({ state, element: cm, }); - // Check if this we are displaying this activity id page. + // Check if the current url is the cm url. + if (window.location.href == cm.url || window.location.href == `${course.baseurl}#${cm.anchor}`) { + this.reactive.dispatch('setPageItem', 'cm', this.id); + this.element.scrollIntoView({block: "center"}); + } + // Check if this we are displaying this activity page. if (Config.contextid != Config.courseContextId && Config.contextInstanceId == this.id) { - this.element.classList.add(this.classes.PAGEITEM); + this.reactive.dispatch('setPageItem', 'cm', this.id, true); + this.element.scrollIntoView({block: "center"}); } // Add anchor logic if the element is not user visible. if (!cm.uservisible) { @@ -108,6 +115,7 @@ export default class Component extends DndCmItem { {watch: `cm[${this.id}]:deleted`, handler: this.remove}, {watch: `cm[${this.id}]:updated`, handler: this._refreshCm}, {watch: `cm[${this.id}].completionstate:updated`, handler: this._refreshCompletion}, + {watch: `course.pageItem:updated`, handler: this._refreshPageItem}, ]; } @@ -127,6 +135,23 @@ export default class Component extends DndCmItem { this.locked = element.locked; } + /** + * Handle a page item update. + * + * @param {Object} details the update details + * @param {Object} details.element the course state data. + */ + _refreshPageItem({element}) { + if (!element.pageItem) { + return; + } + const isPageId = (element.pageItem.type == 'cm' && element.pageItem.id == this.id); + this.element.classList.toggle(this.classes.PAGEITEM, isPageId); + if (isPageId && !this.reactive.isEditing) { + this.element.scrollIntoView({block: "nearest"}); + } + } + /** * Update the activity completion icon. * @@ -166,7 +191,12 @@ export default class Component extends DndCmItem { const cm = this.reactive.get('cm', this.id); // If the user cannot access the element but the element is present in the page // the new url should be an anchor link. - if (document.getElementById(cm.anchor)) { + const element = document.getElementById(cm.anchor); + if (element) { + // Marc the element as page item once the event is handled. + setTimeout(() => { + this.reactive.dispatch('setPageItem', 'cm', cm.id); + }, 50); return; } // If the element is not present in the page we need to go to the specific section. diff --git a/course/format/amd/src/local/courseindex/courseindex.js b/course/format/amd/src/local/courseindex/courseindex.js index 54f181af73f..9cc270c0e65 100644 --- a/course/format/amd/src/local/courseindex/courseindex.js +++ b/course/format/amd/src/local/courseindex/courseindex.js @@ -74,8 +74,10 @@ export default class Component extends BaseComponent { /** * Initial state ready method. + * + * @param {Object} state the state data */ - stateReady() { + stateReady(state) { // Activate section togglers. this.addEventListener(this.element, 'click', this._sectionTogglers); @@ -89,6 +91,9 @@ export default class Component extends BaseComponent { this.cms[cm.dataset.id] = cm; }); + // Set the page item if any. + this._refreshPageItem({element: state.course, state}); + // Configure Aria Tree. this.contentTree = new ContentTree(this.element, this.selectors, this.reactive.isEditing); } @@ -100,6 +105,8 @@ export default class Component extends BaseComponent { {watch: `cm:deleted`, handler: this._deleteCm}, {watch: `section:created`, handler: this._createSection}, {watch: `section:deleted`, handler: this._deleteSection}, + {watch: `course.pageItem:created`, handler: this._refreshPageItem}, + {watch: `course.pageItem:updated`, handler: this._refreshPageItem}, // Sections and cm sorting. {watch: `course.sectionlist:updated`, handler: this._refreshCourseSectionlist}, {watch: `section.cmlist:updated`, handler: this._refreshSectionCmlist}, @@ -192,6 +199,28 @@ export default class Component extends BaseComponent { jQuery(collapsible).collapse(togglerValue); } + /** + * Handle a page item update. + * + * @param {Object} details the update details + * @param {Object} details.state the state data. + * @param {Object} details.element the course state data. + */ + _refreshPageItem({element, state}) { + if (!element?.pageItem?.isStatic || element.pageItem.type != 'cm') { + return; + } + // Check if we need to uncollapse the section and scroll to the element. + const section = state.section.get(element.pageItem.sectionId); + if (section.indexcollapsed) { + this._expandSectionNode(section, true); + setTimeout( + () => this.cms[element.pageItem.id]?.scrollIntoView({block: "nearest"}), + 250 + ); + } + } + /** * Create a newcm instance. * diff --git a/course/format/amd/src/local/courseindex/section.js b/course/format/amd/src/local/courseindex/section.js index 6cf3092d66e..6dca16386fb 100644 --- a/course/format/amd/src/local/courseindex/section.js +++ b/course/format/amd/src/local/courseindex/section.js @@ -47,10 +47,12 @@ export default class Component extends DndSection { SECTIONCURRENT: 'current', LOCKED: 'editinprogress', RESTRICTIONS: 'rectrictions', + PAGEITEM: 'pageitem', }; // We need our id to watch specific events. this.id = this.element.dataset.id; + this.isPageItem = false; } /** @@ -74,16 +76,23 @@ export default class Component extends DndSection { */ stateReady(state) { this.configState(state); + const sectionItem = this.getElement(this.selectors.SECTION_ITEM); // Drag and drop is only available for components compatible course formats. if (this.reactive.isEditing && this.reactive.supportComponents) { // Init the inner dragable element passing the full section as affected region. const titleitem = new SectionTitle({ ...this, - element: this.getElement(this.selectors.SECTION_ITEM), + element: sectionItem, fullregion: this.element, }); this.configDragDrop(titleitem); } + // Check if the current url is the section url. + const section = state.section.get(this.id); + if (window.location.href == section.sectionurl.replace(/&/g, "&")) { + this.reactive.dispatch('setPageItem', 'section', this.id); + sectionItem.scrollIntoView(); + } } /** @@ -95,6 +104,7 @@ export default class Component extends DndSection { return [ {watch: `section[${this.id}]:deleted`, handler: this.remove}, {watch: `section[${this.id}]:updated`, handler: this._refreshSection}, + {watch: `course.pageItem:updated`, handler: this._refreshPageItem}, ]; } @@ -111,12 +121,12 @@ export default class Component extends DndSection { * Update a course index section using the state information. * * @param {Object} param details the update details. - * @param {Object} param.element + * @param {Object} param.element the section element */ _refreshSection({element}) { // Update classes. - const sectionitem = this.getElement(this.selectors.SECTION_ITEM); - sectionitem.classList.toggle(this.classes.SECTIONHIDDEN, !element.visible); + const sectionItem = this.getElement(this.selectors.SECTION_ITEM); + sectionItem.classList.toggle(this.classes.SECTIONHIDDEN, !element.visible); this.element.classList.toggle(this.classes.SECTIONCURRENT, element.current); this.element.classList.toggle(this.classes.DRAGGING, element.dragging ?? false); this.element.classList.toggle(this.classes.LOCKED, element.locked ?? false); @@ -125,4 +135,33 @@ export default class Component extends DndSection { // Update title. this.getElement(this.selectors.SECTION_TITLE).innerHTML = element.title; } + + /** + * Handle a page item update. + * + * @param {Object} details the update details + * @param {Object} details.state the state data. + * @param {Object} details.element the course state data. + */ + _refreshPageItem({element, state}) { + if (!element.pageItem) { + return; + } + if (element.pageItem.sectionId !== this.id && this.isPageItem) { + this.pageItem = false; + this.getElement(this.selectors.SECTION_ITEM).classList.remove(this.classes.PAGEITEM); + return; + } + const section = state.section.get(this.id); + if (section.indexcollapsed && !element.pageItem?.isStatic) { + this.pageItem = (element.pageItem?.sectionId == this.id); + } else { + this.pageItem = (element.pageItem.type == 'section' && element.pageItem.id == this.id); + } + const sectionItem = this.getElement(this.selectors.SECTION_ITEM); + sectionItem.classList.toggle(this.classes.PAGEITEM, this.pageItem ?? false); + if (this.pageItem && !this.reactive.isEditing) { + this.element.scrollIntoView({block: "nearest"}); + } + } } diff --git a/course/format/templates/local/courseindex/section.mustache b/course/format/templates/local/courseindex/section.mustache index 1b6e02e2b18..6411529ebcc 100644 --- a/course/format/templates/local/courseindex/section.mustache +++ b/course/format/templates/local/courseindex/section.mustache @@ -73,7 +73,7 @@ >