diff --git a/course/format/amd/build/local/content/actions.min.js.map b/course/format/amd/build/local/content/actions.min.js.map index 318937a716ee1..79d412167d9a2 100644 --- a/course/format/amd/build/local/content/actions.min.js.map +++ b/course/format/amd/build/local/content/actions.min.js.map @@ -1 +1 @@ -{"version":3,"file":"actions.min.js","sources":["../../../src/local/content/actions.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * Course state actions dispatcher.\n *\n * This module captures all data-dispatch links in the course content and dispatch the proper\n * state mutation, including any confirmation and modal required.\n *\n * @module core_courseformat/local/content/actions\n * @class core_courseformat/local/content/actions\n * @copyright 2021 Ferran Recio \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport {BaseComponent} from 'core/reactive';\nimport ModalFactory from 'core/modal_factory';\nimport ModalEvents from 'core/modal_events';\nimport Templates from 'core/templates';\nimport {prefetchStrings} from 'core/prefetch';\nimport {get_string as getString} from 'core/str';\nimport {getList} from 'core/normalise';\nimport * as CourseEvents from 'core_course/events';\nimport Pending from 'core/pending';\nimport ContentTree from 'core_courseformat/local/courseeditor/contenttree';\n// The jQuery module is only used for interacting with Boostrap 4. It can we removed when MDL-79179 is integrated.\nimport jQuery from 'jquery';\n\n// Load global strings.\nprefetchStrings('core', ['movecoursesection', 'movecoursemodule', 'confirm', 'delete']);\n\nexport default class extends BaseComponent {\n\n /**\n * Constructor hook.\n */\n create() {\n // Optional component name for debugging.\n this.name = 'content_actions';\n // Default query selectors.\n this.selectors = {\n ACTIONLINK: `[data-action]`,\n // Move modal selectors.\n SECTIONLINK: `[data-for='section']`,\n CMLINK: `[data-for='cm']`,\n SECTIONNODE: `[data-for='sectionnode']`,\n MODALTOGGLER: `[data-toggle='collapse']`,\n ADDSECTION: `[data-action='addSection']`,\n CONTENTTREE: `#destination-selector`,\n ACTIONMENU: `.action-menu`,\n ACTIONMENUTOGGLER: `[data-toggle=\"dropdown\"]`,\n };\n // Component css classes.\n this.classes = {\n DISABLED: `disabled`,\n };\n }\n\n /**\n * Initial state ready method.\n *\n * @param {Object} state the state data.\n *\n */\n stateReady(state) {\n // Delegate dispatch clicks.\n this.addEventListener(\n this.element,\n 'click',\n this._dispatchClick\n );\n // Check section limit.\n this._checkSectionlist({state});\n // Add an Event listener to recalculate limits it if a section HTML is altered.\n this.addEventListener(\n this.element,\n CourseEvents.sectionRefreshed,\n () => this._checkSectionlist({state})\n );\n }\n\n /**\n * Return the component watchers.\n *\n * @returns {Array} of watchers\n */\n getWatchers() {\n return [\n // Check section limit.\n {watch: `course.sectionlist:updated`, handler: this._checkSectionlist},\n ];\n }\n\n _dispatchClick(event) {\n const target = event.target.closest(this.selectors.ACTIONLINK);\n if (!target) {\n return;\n }\n if (target.classList.contains(this.classes.DISABLED)) {\n event.preventDefault();\n return;\n }\n\n // Invoke proper method.\n const methodName = this._actionMethodName(target.dataset.action);\n\n if (this[methodName] !== undefined) {\n this[methodName](target, event);\n }\n }\n\n _actionMethodName(name) {\n const requestName = name.charAt(0).toUpperCase() + name.slice(1);\n return `_request${requestName}`;\n }\n\n /**\n * Check the section list and disable some options if needed.\n *\n * @param {Object} detail the update details.\n * @param {Object} detail.state the state object.\n */\n _checkSectionlist({state}) {\n // Disable \"add section\" actions if the course max sections has been exceeded.\n this._setAddSectionLocked(state.course.sectionlist.length > state.course.maxsections);\n }\n\n /**\n * Handle a move section request.\n *\n * @param {Element} target the dispatch action element\n * @param {Event} event the triggered event\n */\n async _requestMoveSection(target, event) {\n // Check we have an id.\n const sectionId = target.dataset.id;\n if (!sectionId) {\n return;\n }\n const sectionInfo = this.reactive.get('section', sectionId);\n\n event.preventDefault();\n\n // The section edit menu to refocus on end.\n const editTools = this._getClosestActionMenuToogler(target);\n\n // Collect section information from the state.\n const exporter = this.reactive.getExporter();\n const data = exporter.course(this.reactive.state);\n\n // Add the target section id and title.\n data.sectionid = sectionInfo.id;\n data.sectiontitle = sectionInfo.title;\n\n // Build the modal parameters from the event data.\n const modalParams = {\n title: getString('movecoursesection', 'core'),\n body: Templates.render('core_courseformat/local/content/movesection', data),\n };\n\n // Create the modal.\n const modal = await this._modalBodyRenderedPromise(modalParams);\n\n const modalBody = getList(modal.getBody())[0];\n\n // Disable current element and section zero.\n const currentElement = modalBody.querySelector(`${this.selectors.SECTIONLINK}[data-id='${sectionId}']`);\n this._disableLink(currentElement);\n const generalSection = modalBody.querySelector(`${this.selectors.SECTIONLINK}[data-number='0']`);\n this._disableLink(generalSection);\n\n // Setup keyboard navigation.\n new ContentTree(\n modalBody.querySelector(this.selectors.CONTENTTREE),\n {\n SECTION: this.selectors.SECTIONNODE,\n TOGGLER: this.selectors.MODALTOGGLER,\n COLLAPSE: this.selectors.MODALTOGGLER,\n },\n true\n );\n\n // Capture click.\n modalBody.addEventListener('click', (event) => {\n const target = event.target;\n if (!target.matches('a') || target.dataset.for != 'section' || target.dataset.id === undefined) {\n return;\n }\n if (target.getAttribute('aria-disabled')) {\n return;\n }\n event.preventDefault();\n this.reactive.dispatch('sectionMove', [sectionId], target.dataset.id);\n this._destroyModal(modal, editTools);\n });\n }\n\n /**\n * Handle a move cm request.\n *\n * @param {Element} target the dispatch action element\n * @param {Event} event the triggered event\n */\n async _requestMoveCm(target, event) {\n // Check we have an id.\n const cmId = target.dataset.id;\n if (!cmId) {\n return;\n }\n const cmInfo = this.reactive.get('cm', cmId);\n\n event.preventDefault();\n\n // The section edit menu to refocus on end.\n const editTools = this._getClosestActionMenuToogler(target);\n\n // Collect section information from the state.\n const exporter = this.reactive.getExporter();\n const data = exporter.course(this.reactive.state);\n\n // Add the target cm info.\n data.cmid = cmInfo.id;\n data.cmname = cmInfo.name;\n\n // Build the modal parameters from the event data.\n const modalParams = {\n title: getString('movecoursemodule', 'core'),\n body: Templates.render('core_courseformat/local/content/movecm', data),\n };\n\n // Create the modal.\n const modal = await this._modalBodyRenderedPromise(modalParams);\n\n const modalBody = getList(modal.getBody())[0];\n\n // Disable current element.\n let currentElement = modalBody.querySelector(`${this.selectors.CMLINK}[data-id='${cmId}']`);\n this._disableLink(currentElement);\n\n // Setup keyboard navigation.\n new ContentTree(\n modalBody.querySelector(this.selectors.CONTENTTREE),\n {\n SECTION: this.selectors.SECTIONNODE,\n TOGGLER: this.selectors.MODALTOGGLER,\n COLLAPSE: this.selectors.MODALTOGGLER,\n ENTER: this.selectors.SECTIONLINK,\n }\n );\n\n // Open the cm section node if possible (Bootstrap 4 uses jQuery to interact with collapsibles).\n // All jQuery int this code can be replaced when MDL-79179 is integrated.\n const sectionnode = currentElement.closest(this.selectors.SECTIONNODE);\n const toggler = jQuery(sectionnode).find(this.selectors.MODALTOGGLER);\n let collapsibleId = toggler.data('target') ?? toggler.attr('href');\n if (collapsibleId) {\n // We cannot be sure we have # in the id element name.\n collapsibleId = collapsibleId.replace('#', '');\n jQuery(`#${collapsibleId}`).collapse('toggle');\n }\n\n // Capture click.\n modalBody.addEventListener('click', (event) => {\n const target = event.target;\n if (!target.matches('a') || target.dataset.for === undefined || target.dataset.id === undefined) {\n return;\n }\n if (target.getAttribute('aria-disabled')) {\n return;\n }\n event.preventDefault();\n\n // Get draggable data from cm or section to dispatch.\n let targetSectionId;\n let targetCmId;\n if (target.dataset.for == 'cm') {\n const dropData = exporter.cmDraggableData(this.reactive.state, target.dataset.id);\n targetSectionId = dropData.sectionid;\n targetCmId = dropData.nextcmid;\n } else {\n const section = this.reactive.get('section', target.dataset.id);\n targetSectionId = target.dataset.id;\n targetCmId = section?.cmlist[0];\n }\n\n this.reactive.dispatch('cmMove', [cmId], targetSectionId, targetCmId);\n this._destroyModal(modal, editTools);\n });\n }\n\n /**\n * Handle a create section request.\n *\n * @param {Element} target the dispatch action element\n * @param {Event} event the triggered event\n */\n async _requestAddSection(target, event) {\n event.preventDefault();\n this.reactive.dispatch('addSection', target.dataset.id ?? 0);\n }\n\n /**\n * Handle a delete section request.\n *\n * @param {Element} target the dispatch action element\n * @param {Event} event the triggered event\n */\n async _requestDeleteSection(target, event) {\n // Check we have an id.\n const sectionId = target.dataset.id;\n\n if (!sectionId) {\n return;\n }\n const sectionInfo = this.reactive.get('section', sectionId);\n\n event.preventDefault();\n\n const cmList = sectionInfo.cmlist ?? [];\n if (cmList.length || sectionInfo.hassummary || sectionInfo.rawtitle) {\n // We need confirmation if the section has something.\n const modalParams = {\n title: getString('confirm', 'core'),\n body: getString('confirmdeletesection', 'moodle', sectionInfo.title),\n saveButtonText: getString('delete', 'core'),\n type: ModalFactory.types.SAVE_CANCEL,\n };\n\n const modal = await this._modalBodyRenderedPromise(modalParams);\n\n modal.getRoot().on(\n ModalEvents.save,\n e => {\n // Stop the default save button behaviour which is to close the modal.\n e.preventDefault();\n modal.destroy();\n this.reactive.dispatch('sectionDelete', [sectionId]);\n }\n );\n return;\n } else {\n // We don't need confirmation to delete empty sections.\n this.reactive.dispatch('sectionDelete', [sectionId]);\n }\n }\n\n /**\n * Disable all add sections actions.\n *\n * @param {boolean} locked the new locked value.\n */\n _setAddSectionLocked(locked) {\n const targets = this.getElements(this.selectors.ADDSECTION);\n targets.forEach(element => {\n element.classList.toggle(this.classes.DISABLED, locked);\n this.setElementLocked(element, locked);\n });\n }\n\n /**\n * Replace an element with a copy with a different tag name.\n *\n * @param {Element} element the original element\n */\n _disableLink(element) {\n if (element) {\n element.style.pointerEvents = 'none';\n element.style.userSelect = 'none';\n element.classList.add(this.classes.DISABLED);\n element.setAttribute('aria-disabled', true);\n element.addEventListener('click', event => event.preventDefault());\n }\n }\n\n /**\n * Render a modal and return a body ready promise.\n *\n * @param {object} modalParams the modal params\n * @return {Promise} the modal body ready promise\n */\n _modalBodyRenderedPromise(modalParams) {\n return new Promise((resolve, reject) => {\n ModalFactory.create(modalParams).then((modal) => {\n modal.setRemoveOnClose(true);\n // Handle body loading event.\n modal.getRoot().on(ModalEvents.bodyRendered, () => {\n resolve(modal);\n });\n // Configure some extra modal params.\n if (modalParams.saveButtonText !== undefined) {\n modal.setSaveButtonText(modalParams.saveButtonText);\n }\n modal.show();\n return;\n }).catch(() => {\n reject(`Cannot load modal content`);\n });\n });\n }\n\n /**\n * Hide and later destroy a modal.\n *\n * Behat will fail if we remove the modal while some boostrap collapse is executing.\n *\n * @param {Modal} modal\n * @param {HTMLElement} element the dom element to focus on.\n */\n _destroyModal(modal, element) {\n modal.hide();\n const pendingDestroy = new Pending(`courseformat/actions:destroyModal`);\n if (element) {\n element.focus();\n }\n setTimeout(() =>{\n modal.destroy();\n pendingDestroy.resolve();\n }, 500);\n }\n\n /**\n * Get the closest actions menu toggler to an action element.\n *\n * @param {HTMLElement} element the action link element\n * @returns {HTMLElement|undefined}\n */\n _getClosestActionMenuToogler(element) {\n const actionMenu = element.closest(this.selectors.ACTIONMENU);\n if (!actionMenu) {\n return undefined;\n }\n return actionMenu.querySelector(this.selectors.ACTIONMENUTOGGLER);\n }\n}\n"],"names":["BaseComponent","create","name","selectors","ACTIONLINK","SECTIONLINK","CMLINK","SECTIONNODE","MODALTOGGLER","ADDSECTION","CONTENTTREE","ACTIONMENU","ACTIONMENUTOGGLER","classes","DISABLED","stateReady","state","addEventListener","this","element","_dispatchClick","_checkSectionlist","CourseEvents","sectionRefreshed","getWatchers","watch","handler","event","target","closest","classList","contains","preventDefault","methodName","_actionMethodName","dataset","action","undefined","requestName","charAt","toUpperCase","slice","_setAddSectionLocked","course","sectionlist","length","maxsections","sectionId","id","sectionInfo","reactive","get","editTools","_getClosestActionMenuToogler","data","getExporter","sectionid","sectiontitle","title","modalParams","body","Templates","render","modal","_modalBodyRenderedPromise","modalBody","getBody","currentElement","querySelector","_disableLink","generalSection","ContentTree","SECTION","TOGGLER","COLLAPSE","matches","for","getAttribute","dispatch","_destroyModal","cmId","cmInfo","exporter","cmid","cmname","ENTER","sectionnode","toggler","find","collapsibleId","attr","replace","collapse","targetSectionId","targetCmId","dropData","cmDraggableData","nextcmid","section","cmlist","hassummary","rawtitle","saveButtonText","type","ModalFactory","types","SAVE_CANCEL","getRoot","on","ModalEvents","save","e","destroy","locked","getElements","forEach","toggle","setElementLocked","style","pointerEvents","userSelect","add","setAttribute","Promise","resolve","reject","then","setRemoveOnClose","bodyRendered","setSaveButtonText","show","catch","hide","pendingDestroy","Pending","focus","setTimeout","actionMenu"],"mappings":"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"} \ No newline at end of file +{"version":3,"file":"actions.min.js","sources":["../../../src/local/content/actions.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * Course state actions dispatcher.\n *\n * This module captures all data-dispatch links in the course content and dispatch the proper\n * state mutation, including any confirmation and modal required.\n *\n * @module core_courseformat/local/content/actions\n * @class core_courseformat/local/content/actions\n * @copyright 2021 Ferran Recio \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport {BaseComponent} from 'core/reactive';\nimport ModalFactory from 'core/modal_factory';\nimport ModalEvents from 'core/modal_events';\nimport Templates from 'core/templates';\nimport {prefetchStrings} from 'core/prefetch';\nimport {get_string as getString} from 'core/str';\nimport {getList} from 'core/normalise';\nimport * as CourseEvents from 'core_course/events';\nimport Pending from 'core/pending';\nimport ContentTree from 'core_courseformat/local/courseeditor/contenttree';\n// The jQuery module is only used for interacting with Boostrap 4. It can we removed when MDL-71979 is integrated.\nimport jQuery from 'jquery';\n\n// Load global strings.\nprefetchStrings('core', ['movecoursesection', 'movecoursemodule', 'confirm', 'delete']);\n\nexport default class extends BaseComponent {\n\n /**\n * Constructor hook.\n */\n create() {\n // Optional component name for debugging.\n this.name = 'content_actions';\n // Default query selectors.\n this.selectors = {\n ACTIONLINK: `[data-action]`,\n // Move modal selectors.\n SECTIONLINK: `[data-for='section']`,\n CMLINK: `[data-for='cm']`,\n SECTIONNODE: `[data-for='sectionnode']`,\n MODALTOGGLER: `[data-toggle='collapse']`,\n ADDSECTION: `[data-action='addSection']`,\n CONTENTTREE: `#destination-selector`,\n ACTIONMENU: `.action-menu`,\n ACTIONMENUTOGGLER: `[data-toggle=\"dropdown\"]`,\n };\n // Component css classes.\n this.classes = {\n DISABLED: `disabled`,\n };\n }\n\n /**\n * Initial state ready method.\n *\n * @param {Object} state the state data.\n *\n */\n stateReady(state) {\n // Delegate dispatch clicks.\n this.addEventListener(\n this.element,\n 'click',\n this._dispatchClick\n );\n // Check section limit.\n this._checkSectionlist({state});\n // Add an Event listener to recalculate limits it if a section HTML is altered.\n this.addEventListener(\n this.element,\n CourseEvents.sectionRefreshed,\n () => this._checkSectionlist({state})\n );\n }\n\n /**\n * Return the component watchers.\n *\n * @returns {Array} of watchers\n */\n getWatchers() {\n return [\n // Check section limit.\n {watch: `course.sectionlist:updated`, handler: this._checkSectionlist},\n ];\n }\n\n _dispatchClick(event) {\n const target = event.target.closest(this.selectors.ACTIONLINK);\n if (!target) {\n return;\n }\n if (target.classList.contains(this.classes.DISABLED)) {\n event.preventDefault();\n return;\n }\n\n // Invoke proper method.\n const methodName = this._actionMethodName(target.dataset.action);\n\n if (this[methodName] !== undefined) {\n this[methodName](target, event);\n }\n }\n\n _actionMethodName(name) {\n const requestName = name.charAt(0).toUpperCase() + name.slice(1);\n return `_request${requestName}`;\n }\n\n /**\n * Check the section list and disable some options if needed.\n *\n * @param {Object} detail the update details.\n * @param {Object} detail.state the state object.\n */\n _checkSectionlist({state}) {\n // Disable \"add section\" actions if the course max sections has been exceeded.\n this._setAddSectionLocked(state.course.sectionlist.length > state.course.maxsections);\n }\n\n /**\n * Handle a move section request.\n *\n * @param {Element} target the dispatch action element\n * @param {Event} event the triggered event\n */\n async _requestMoveSection(target, event) {\n // Check we have an id.\n const sectionId = target.dataset.id;\n if (!sectionId) {\n return;\n }\n const sectionInfo = this.reactive.get('section', sectionId);\n\n event.preventDefault();\n\n // The section edit menu to refocus on end.\n const editTools = this._getClosestActionMenuToogler(target);\n\n // Collect section information from the state.\n const exporter = this.reactive.getExporter();\n const data = exporter.course(this.reactive.state);\n\n // Add the target section id and title.\n data.sectionid = sectionInfo.id;\n data.sectiontitle = sectionInfo.title;\n\n // Build the modal parameters from the event data.\n const modalParams = {\n title: getString('movecoursesection', 'core'),\n body: Templates.render('core_courseformat/local/content/movesection', data),\n };\n\n // Create the modal.\n const modal = await this._modalBodyRenderedPromise(modalParams);\n\n const modalBody = getList(modal.getBody())[0];\n\n // Disable current element and section zero.\n const currentElement = modalBody.querySelector(`${this.selectors.SECTIONLINK}[data-id='${sectionId}']`);\n this._disableLink(currentElement);\n const generalSection = modalBody.querySelector(`${this.selectors.SECTIONLINK}[data-number='0']`);\n this._disableLink(generalSection);\n\n // Setup keyboard navigation.\n new ContentTree(\n modalBody.querySelector(this.selectors.CONTENTTREE),\n {\n SECTION: this.selectors.SECTIONNODE,\n TOGGLER: this.selectors.MODALTOGGLER,\n COLLAPSE: this.selectors.MODALTOGGLER,\n },\n true\n );\n\n // Capture click.\n modalBody.addEventListener('click', (event) => {\n const target = event.target;\n if (!target.matches('a') || target.dataset.for != 'section' || target.dataset.id === undefined) {\n return;\n }\n if (target.getAttribute('aria-disabled')) {\n return;\n }\n event.preventDefault();\n this.reactive.dispatch('sectionMove', [sectionId], target.dataset.id);\n this._destroyModal(modal, editTools);\n });\n }\n\n /**\n * Handle a move cm request.\n *\n * @param {Element} target the dispatch action element\n * @param {Event} event the triggered event\n */\n async _requestMoveCm(target, event) {\n // Check we have an id.\n const cmId = target.dataset.id;\n if (!cmId) {\n return;\n }\n const cmInfo = this.reactive.get('cm', cmId);\n\n event.preventDefault();\n\n // The section edit menu to refocus on end.\n const editTools = this._getClosestActionMenuToogler(target);\n\n // Collect section information from the state.\n const exporter = this.reactive.getExporter();\n const data = exporter.course(this.reactive.state);\n\n // Add the target cm info.\n data.cmid = cmInfo.id;\n data.cmname = cmInfo.name;\n\n // Build the modal parameters from the event data.\n const modalParams = {\n title: getString('movecoursemodule', 'core'),\n body: Templates.render('core_courseformat/local/content/movecm', data),\n };\n\n // Create the modal.\n const modal = await this._modalBodyRenderedPromise(modalParams);\n\n const modalBody = getList(modal.getBody())[0];\n\n // Disable current element.\n let currentElement = modalBody.querySelector(`${this.selectors.CMLINK}[data-id='${cmId}']`);\n this._disableLink(currentElement);\n\n // Setup keyboard navigation.\n new ContentTree(\n modalBody.querySelector(this.selectors.CONTENTTREE),\n {\n SECTION: this.selectors.SECTIONNODE,\n TOGGLER: this.selectors.MODALTOGGLER,\n COLLAPSE: this.selectors.MODALTOGGLER,\n ENTER: this.selectors.SECTIONLINK,\n }\n );\n\n // Open the cm section node if possible (Bootstrap 4 uses jQuery to interact with collapsibles).\n // All jQuery int this code can be replaced when MDL-71979 is integrated.\n const sectionnode = currentElement.closest(this.selectors.SECTIONNODE);\n const toggler = jQuery(sectionnode).find(this.selectors.MODALTOGGLER);\n let collapsibleId = toggler.data('target') ?? toggler.attr('href');\n if (collapsibleId) {\n // We cannot be sure we have # in the id element name.\n collapsibleId = collapsibleId.replace('#', '');\n jQuery(`#${collapsibleId}`).collapse('toggle');\n }\n\n // Capture click.\n modalBody.addEventListener('click', (event) => {\n const target = event.target;\n if (!target.matches('a') || target.dataset.for === undefined || target.dataset.id === undefined) {\n return;\n }\n if (target.getAttribute('aria-disabled')) {\n return;\n }\n event.preventDefault();\n\n // Get draggable data from cm or section to dispatch.\n let targetSectionId;\n let targetCmId;\n if (target.dataset.for == 'cm') {\n const dropData = exporter.cmDraggableData(this.reactive.state, target.dataset.id);\n targetSectionId = dropData.sectionid;\n targetCmId = dropData.nextcmid;\n } else {\n const section = this.reactive.get('section', target.dataset.id);\n targetSectionId = target.dataset.id;\n targetCmId = section?.cmlist[0];\n }\n\n this.reactive.dispatch('cmMove', [cmId], targetSectionId, targetCmId);\n this._destroyModal(modal, editTools);\n });\n }\n\n /**\n * Handle a create section request.\n *\n * @param {Element} target the dispatch action element\n * @param {Event} event the triggered event\n */\n async _requestAddSection(target, event) {\n event.preventDefault();\n this.reactive.dispatch('addSection', target.dataset.id ?? 0);\n }\n\n /**\n * Handle a delete section request.\n *\n * @param {Element} target the dispatch action element\n * @param {Event} event the triggered event\n */\n async _requestDeleteSection(target, event) {\n // Check we have an id.\n const sectionId = target.dataset.id;\n\n if (!sectionId) {\n return;\n }\n const sectionInfo = this.reactive.get('section', sectionId);\n\n event.preventDefault();\n\n const cmList = sectionInfo.cmlist ?? [];\n if (cmList.length || sectionInfo.hassummary || sectionInfo.rawtitle) {\n // We need confirmation if the section has something.\n const modalParams = {\n title: getString('confirm', 'core'),\n body: getString('confirmdeletesection', 'moodle', sectionInfo.title),\n saveButtonText: getString('delete', 'core'),\n type: ModalFactory.types.SAVE_CANCEL,\n };\n\n const modal = await this._modalBodyRenderedPromise(modalParams);\n\n modal.getRoot().on(\n ModalEvents.save,\n e => {\n // Stop the default save button behaviour which is to close the modal.\n e.preventDefault();\n modal.destroy();\n this.reactive.dispatch('sectionDelete', [sectionId]);\n }\n );\n return;\n } else {\n // We don't need confirmation to delete empty sections.\n this.reactive.dispatch('sectionDelete', [sectionId]);\n }\n }\n\n /**\n * Disable all add sections actions.\n *\n * @param {boolean} locked the new locked value.\n */\n _setAddSectionLocked(locked) {\n const targets = this.getElements(this.selectors.ADDSECTION);\n targets.forEach(element => {\n element.classList.toggle(this.classes.DISABLED, locked);\n this.setElementLocked(element, locked);\n });\n }\n\n /**\n * Replace an element with a copy with a different tag name.\n *\n * @param {Element} element the original element\n */\n _disableLink(element) {\n if (element) {\n element.style.pointerEvents = 'none';\n element.style.userSelect = 'none';\n element.classList.add(this.classes.DISABLED);\n element.setAttribute('aria-disabled', true);\n element.addEventListener('click', event => event.preventDefault());\n }\n }\n\n /**\n * Render a modal and return a body ready promise.\n *\n * @param {object} modalParams the modal params\n * @return {Promise} the modal body ready promise\n */\n _modalBodyRenderedPromise(modalParams) {\n return new Promise((resolve, reject) => {\n ModalFactory.create(modalParams).then((modal) => {\n modal.setRemoveOnClose(true);\n // Handle body loading event.\n modal.getRoot().on(ModalEvents.bodyRendered, () => {\n resolve(modal);\n });\n // Configure some extra modal params.\n if (modalParams.saveButtonText !== undefined) {\n modal.setSaveButtonText(modalParams.saveButtonText);\n }\n modal.show();\n return;\n }).catch(() => {\n reject(`Cannot load modal content`);\n });\n });\n }\n\n /**\n * Hide and later destroy a modal.\n *\n * Behat will fail if we remove the modal while some boostrap collapse is executing.\n *\n * @param {Modal} modal\n * @param {HTMLElement} element the dom element to focus on.\n */\n _destroyModal(modal, element) {\n modal.hide();\n const pendingDestroy = new Pending(`courseformat/actions:destroyModal`);\n if (element) {\n element.focus();\n }\n setTimeout(() =>{\n modal.destroy();\n pendingDestroy.resolve();\n }, 500);\n }\n\n /**\n * Get the closest actions menu toggler to an action element.\n *\n * @param {HTMLElement} element the action link element\n * @returns {HTMLElement|undefined}\n */\n _getClosestActionMenuToogler(element) {\n const actionMenu = element.closest(this.selectors.ACTIONMENU);\n if (!actionMenu) {\n return undefined;\n }\n return actionMenu.querySelector(this.selectors.ACTIONMENUTOGGLER);\n }\n}\n"],"names":["BaseComponent","create","name","selectors","ACTIONLINK","SECTIONLINK","CMLINK","SECTIONNODE","MODALTOGGLER","ADDSECTION","CONTENTTREE","ACTIONMENU","ACTIONMENUTOGGLER","classes","DISABLED","stateReady","state","addEventListener","this","element","_dispatchClick","_checkSectionlist","CourseEvents","sectionRefreshed","getWatchers","watch","handler","event","target","closest","classList","contains","preventDefault","methodName","_actionMethodName","dataset","action","undefined","requestName","charAt","toUpperCase","slice","_setAddSectionLocked","course","sectionlist","length","maxsections","sectionId","id","sectionInfo","reactive","get","editTools","_getClosestActionMenuToogler","data","getExporter","sectionid","sectiontitle","title","modalParams","body","Templates","render","modal","_modalBodyRenderedPromise","modalBody","getBody","currentElement","querySelector","_disableLink","generalSection","ContentTree","SECTION","TOGGLER","COLLAPSE","matches","for","getAttribute","dispatch","_destroyModal","cmId","cmInfo","exporter","cmid","cmname","ENTER","sectionnode","toggler","find","collapsibleId","attr","replace","collapse","targetSectionId","targetCmId","dropData","cmDraggableData","nextcmid","section","cmlist","hassummary","rawtitle","saveButtonText","type","ModalFactory","types","SAVE_CANCEL","getRoot","on","ModalEvents","save","e","destroy","locked","getElements","forEach","toggle","setElementLocked","style","pointerEvents","userSelect","add","setAttribute","Promise","resolve","reject","then","setRemoveOnClose","bodyRendered","setSaveButtonText","show","catch","hide","pendingDestroy","Pending","focus","setTimeout","actionMenu"],"mappings":"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"} \ No newline at end of file diff --git a/course/format/amd/build/local/courseeditor/contenttree.min.js.map b/course/format/amd/build/local/courseeditor/contenttree.min.js.map index 2351fce4ad4e2..d9d0ac8549393 100644 --- a/course/format/amd/build/local/courseeditor/contenttree.min.js.map +++ b/course/format/amd/build/local/courseeditor/contenttree.min.js.map @@ -1 +1 @@ -{"version":3,"file":"contenttree.min.js","sources":["../../../src/local/courseeditor/contenttree.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * Course index keyboard navigation and aria-tree compatibility.\n *\n * Node tree and bootstrap collapsibles don't use the same HTML structure. However,\n * all keybindings and logic is compatible. This class translate the primitive opetations\n * to a bootstrap collapsible structure.\n *\n * @module core_courseformat/local/courseindex/keyboardnav\n * @class core_courseformat/local/courseindex/keyboardnav\n * @copyright 2021 Ferran Recio \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\n// The core/tree uses jQuery to expand all nodes.\nimport jQuery from 'jquery';\nimport Tree from 'core/tree';\nimport {getList} from 'core/normalise';\n\nexport default class extends Tree {\n\n /**\n * Setup the core/tree keyboard navigation.\n *\n * @param {Element|undefined} mainElement an alternative main element in case it is not from the parent component\n * @param {Object|undefined} selectors alternative selectors\n * @param {boolean} preventcache if the elements cache must be disabled.\n */\n constructor(mainElement, selectors, preventcache) {\n // Init this value with the parent DOM element.\n super(mainElement);\n\n // Get selectors from parent.\n this.selectors = {\n SECTION: selectors.SECTION,\n TOGGLER: selectors.TOGGLER,\n COLLAPSE: selectors.COLLAPSE,\n ENTER: selectors.ENTER ?? selectors.TOGGLER,\n };\n\n // The core/tree library saves the visible elements cache inside the main tree node.\n // However, in edit mode content can change suddenly so we need to refresh caches when needed.\n if (preventcache) {\n this._getVisibleItems = this.getVisibleItems;\n this.getVisibleItems = () => {\n this.refreshVisibleItemsCache();\n return this._getVisibleItems();\n };\n }\n // All jQuery events can be replaced when MDL-79179 is integrated.\n this.treeRoot.on('hidden.bs.collapse shown.bs.collapse', () => {\n this.refreshVisibleItemsCache();\n });\n // Register a custom callback for pressing enter key.\n this.registerEnterCallback(this.enterCallback.bind(this));\n }\n\n /**\n * Return the current active node.\n *\n * @return {Element|undefined} the active item if any\n */\n getActiveItem() {\n const activeItem = this.treeRoot.data('activeItem');\n if (activeItem) {\n return getList(activeItem)[0];\n }\n return undefined;\n }\n\n /**\n * Handle enter key on a collpasible node.\n *\n * @param {JQuery} jQueryItem the jQuery object\n */\n enterCallback(jQueryItem) {\n const item = getList(jQueryItem)[0];\n if (this.isGroupItem(jQueryItem)) {\n // Group elements is like clicking a topic but without loosing the focus.\n const enter = item.querySelector(this.selectors.ENTER);\n if (enter.getAttribute('href') !== '#') {\n window.location.href = enter.getAttribute('href');\n }\n enter.click();\n } else {\n // Activity links just follow the link href.\n const link = item.querySelector('a');\n if (link.getAttribute('href') !== '#') {\n window.location.href = link.getAttribute('href');\n } else {\n link.click();\n }\n return;\n }\n }\n\n /**\n * Handle an item click.\n *\n * @param {Event} event the click event\n * @param {jQuery} jQueryItem the item clicked\n */\n handleItemClick(event, jQueryItem) {\n const isChevron = event.target.closest(this.selectors.COLLAPSE);\n // Only chevron clicks toogle the sections always.\n if (isChevron) {\n super.handleItemClick(event, jQueryItem);\n return;\n }\n // This is a title or activity name click.\n jQueryItem.focus();\n if (this.isGroupItem(jQueryItem)) {\n this.expandGroup(jQueryItem);\n }\n }\n\n /**\n * Check if a gorup item is collapsed.\n *\n * @param {JQuery} jQueryItem the jQuery object\n * @returns {boolean} if the element is collapsed\n */\n isGroupCollapsed(jQueryItem) {\n const item = getList(jQueryItem)[0];\n const toggler = item.querySelector(`[aria-expanded]`);\n return toggler.getAttribute('aria-expanded') === 'false';\n }\n\n /**\n * Toggle a group item.\n *\n * @param {JQuery} item the jQuery object\n */\n toggleGroup(item) {\n // All jQuery in this segment of code can be replaced when MDL-79179 is integrated.\n const toggler = item.find(this.selectors.COLLAPSE);\n let collapsibleId = toggler.data('target') ?? toggler.attr('href');\n if (!collapsibleId) {\n return;\n }\n collapsibleId = collapsibleId.replace('#', '');\n\n // Bootstrap 4 uses jQuery to interact with collapsibles.\n const collapsible = jQuery(`#${collapsibleId}`);\n if (collapsible.length) {\n jQuery(`#${collapsibleId}`).collapse('toggle');\n }\n }\n\n /**\n * Expand a group item.\n *\n * @param {JQuery} item the jQuery object\n */\n expandGroup(item) {\n if (this.isGroupCollapsed(item)) {\n this.toggleGroup(item);\n }\n }\n\n /**\n * Collpase a group item.\n *\n * @param {JQuery} item the jQuery object\n */\n collapseGroup(item) {\n if (!this.isGroupCollapsed(item)) {\n this.toggleGroup(item);\n }\n }\n\n /**\n * Expand all groups.\n */\n expandAllGroups() {\n const togglers = getList(this.treeRoot)[0].querySelectorAll(this.selectors.SECTION);\n togglers.forEach(item => {\n this.expandGroup(jQuery(item));\n });\n }\n}\n"],"names":["Tree","constructor","mainElement","selectors","preventcache","SECTION","TOGGLER","COLLAPSE","ENTER","_getVisibleItems","this","getVisibleItems","refreshVisibleItemsCache","treeRoot","on","registerEnterCallback","enterCallback","bind","getActiveItem","activeItem","data","jQueryItem","item","isGroupItem","enter","querySelector","getAttribute","window","location","href","click","link","handleItemClick","event","target","closest","focus","expandGroup","isGroupCollapsed","toggleGroup","toggler","find","collapsibleId","attr","replace","length","collapse","collapseGroup","expandAllGroups","querySelectorAll","forEach"],"mappings":";;;;;;;;;;;;wLAiC6BA,cASzBC,YAAYC,YAAaC,UAAWC,yCAE1BF,kBAGDC,UAAY,CACbE,QAASF,UAAUE,QACnBC,QAASH,UAAUG,QACnBC,SAAUJ,UAAUI,SACpBC,+BAAOL,UAAUK,mDAASL,UAAUG,SAKpCF,oBACKK,iBAAmBC,KAAKC,qBACxBA,gBAAkB,UACdC,2BACEF,KAAKD,0BAIfI,SAASC,GAAG,wCAAwC,UAChDF,mCAGJG,sBAAsBL,KAAKM,cAAcC,KAAKP,OAQvDQ,sBACUC,WAAaT,KAAKG,SAASO,KAAK,iBAClCD,kBACO,sBAAQA,YAAY,GAUnCH,cAAcK,kBACJC,MAAO,sBAAQD,YAAY,MAC7BX,KAAKa,YAAYF,YAAa,OAExBG,MAAQF,KAAKG,cAAcf,KAAKP,UAAUK,OACb,MAA/BgB,MAAME,aAAa,UACnBC,OAAOC,SAASC,KAAOL,MAAME,aAAa,SAE9CF,MAAMM,mBAGAC,KAAOT,KAAKG,cAAc,KACE,MAA9BM,KAAKL,aAAa,QAClBC,OAAOC,SAASC,KAAOE,KAAKL,aAAa,QAEzCK,KAAKD,SAYjBE,gBAAgBC,MAAOZ,YACDY,MAAMC,OAAOC,QAAQzB,KAAKP,UAAUI,gBAG5CyB,gBAAgBC,MAAOZ,aAIjCA,WAAWe,QACP1B,KAAKa,YAAYF,kBACZgB,YAAYhB,aAUzBiB,iBAAiBjB,kBAGoC,WAFpC,sBAAQA,YAAY,GACZI,iCACNC,aAAa,iBAQhCa,YAAYjB,8BAEFkB,QAAUlB,KAAKmB,KAAK/B,KAAKP,UAAUI,cACrCmC,oCAAgBF,QAAQpB,KAAK,iDAAaoB,QAAQG,KAAK,YACtDD,qBAGLA,cAAgBA,cAAcE,QAAQ,IAAK,KAGvB,8BAAWF,gBACfG,uCACDH,gBAAiBI,SAAS,UAS7CT,YAAYf,MACJZ,KAAK4B,iBAAiBhB,YACjBiB,YAAYjB,MASzByB,cAAczB,MACLZ,KAAK4B,iBAAiBhB,YAClBiB,YAAYjB,MAOzB0B,mBACqB,sBAAQtC,KAAKG,UAAU,GAAGoC,iBAAiBvC,KAAKP,UAAUE,SAClE6C,SAAQ5B,YACRe,aAAY,mBAAOf"} \ No newline at end of file +{"version":3,"file":"contenttree.min.js","sources":["../../../src/local/courseeditor/contenttree.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * Course index keyboard navigation and aria-tree compatibility.\n *\n * Node tree and bootstrap collapsibles don't use the same HTML structure. However,\n * all keybindings and logic is compatible. This class translate the primitive opetations\n * to a bootstrap collapsible structure.\n *\n * @module core_courseformat/local/courseindex/keyboardnav\n * @class core_courseformat/local/courseindex/keyboardnav\n * @copyright 2021 Ferran Recio \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\n// The core/tree uses jQuery to expand all nodes.\nimport jQuery from 'jquery';\nimport Tree from 'core/tree';\nimport {getList} from 'core/normalise';\n\nexport default class extends Tree {\n\n /**\n * Setup the core/tree keyboard navigation.\n *\n * @param {Element|undefined} mainElement an alternative main element in case it is not from the parent component\n * @param {Object|undefined} selectors alternative selectors\n * @param {boolean} preventcache if the elements cache must be disabled.\n */\n constructor(mainElement, selectors, preventcache) {\n // Init this value with the parent DOM element.\n super(mainElement);\n\n // Get selectors from parent.\n this.selectors = {\n SECTION: selectors.SECTION,\n TOGGLER: selectors.TOGGLER,\n COLLAPSE: selectors.COLLAPSE,\n ENTER: selectors.ENTER ?? selectors.TOGGLER,\n };\n\n // The core/tree library saves the visible elements cache inside the main tree node.\n // However, in edit mode content can change suddenly so we need to refresh caches when needed.\n if (preventcache) {\n this._getVisibleItems = this.getVisibleItems;\n this.getVisibleItems = () => {\n this.refreshVisibleItemsCache();\n return this._getVisibleItems();\n };\n }\n // All jQuery events can be replaced when MDL-71979 is integrated.\n this.treeRoot.on('hidden.bs.collapse shown.bs.collapse', () => {\n this.refreshVisibleItemsCache();\n });\n // Register a custom callback for pressing enter key.\n this.registerEnterCallback(this.enterCallback.bind(this));\n }\n\n /**\n * Return the current active node.\n *\n * @return {Element|undefined} the active item if any\n */\n getActiveItem() {\n const activeItem = this.treeRoot.data('activeItem');\n if (activeItem) {\n return getList(activeItem)[0];\n }\n return undefined;\n }\n\n /**\n * Handle enter key on a collpasible node.\n *\n * @param {JQuery} jQueryItem the jQuery object\n */\n enterCallback(jQueryItem) {\n const item = getList(jQueryItem)[0];\n if (this.isGroupItem(jQueryItem)) {\n // Group elements is like clicking a topic but without loosing the focus.\n const enter = item.querySelector(this.selectors.ENTER);\n if (enter.getAttribute('href') !== '#') {\n window.location.href = enter.getAttribute('href');\n }\n enter.click();\n } else {\n // Activity links just follow the link href.\n const link = item.querySelector('a');\n if (link.getAttribute('href') !== '#') {\n window.location.href = link.getAttribute('href');\n } else {\n link.click();\n }\n return;\n }\n }\n\n /**\n * Handle an item click.\n *\n * @param {Event} event the click event\n * @param {jQuery} jQueryItem the item clicked\n */\n handleItemClick(event, jQueryItem) {\n const isChevron = event.target.closest(this.selectors.COLLAPSE);\n // Only chevron clicks toogle the sections always.\n if (isChevron) {\n super.handleItemClick(event, jQueryItem);\n return;\n }\n // This is a title or activity name click.\n jQueryItem.focus();\n if (this.isGroupItem(jQueryItem)) {\n this.expandGroup(jQueryItem);\n }\n }\n\n /**\n * Check if a gorup item is collapsed.\n *\n * @param {JQuery} jQueryItem the jQuery object\n * @returns {boolean} if the element is collapsed\n */\n isGroupCollapsed(jQueryItem) {\n const item = getList(jQueryItem)[0];\n const toggler = item.querySelector(`[aria-expanded]`);\n return toggler.getAttribute('aria-expanded') === 'false';\n }\n\n /**\n * Toggle a group item.\n *\n * @param {JQuery} item the jQuery object\n */\n toggleGroup(item) {\n // All jQuery in this segment of code can be replaced when MDL-71979 is integrated.\n const toggler = item.find(this.selectors.COLLAPSE);\n let collapsibleId = toggler.data('target') ?? toggler.attr('href');\n if (!collapsibleId) {\n return;\n }\n collapsibleId = collapsibleId.replace('#', '');\n\n // Bootstrap 4 uses jQuery to interact with collapsibles.\n const collapsible = jQuery(`#${collapsibleId}`);\n if (collapsible.length) {\n jQuery(`#${collapsibleId}`).collapse('toggle');\n }\n }\n\n /**\n * Expand a group item.\n *\n * @param {JQuery} item the jQuery object\n */\n expandGroup(item) {\n if (this.isGroupCollapsed(item)) {\n this.toggleGroup(item);\n }\n }\n\n /**\n * Collpase a group item.\n *\n * @param {JQuery} item the jQuery object\n */\n collapseGroup(item) {\n if (!this.isGroupCollapsed(item)) {\n this.toggleGroup(item);\n }\n }\n\n /**\n * Expand all groups.\n */\n expandAllGroups() {\n const togglers = getList(this.treeRoot)[0].querySelectorAll(this.selectors.SECTION);\n togglers.forEach(item => {\n this.expandGroup(jQuery(item));\n });\n }\n}\n"],"names":["Tree","constructor","mainElement","selectors","preventcache","SECTION","TOGGLER","COLLAPSE","ENTER","_getVisibleItems","this","getVisibleItems","refreshVisibleItemsCache","treeRoot","on","registerEnterCallback","enterCallback","bind","getActiveItem","activeItem","data","jQueryItem","item","isGroupItem","enter","querySelector","getAttribute","window","location","href","click","link","handleItemClick","event","target","closest","focus","expandGroup","isGroupCollapsed","toggleGroup","toggler","find","collapsibleId","attr","replace","length","collapse","collapseGroup","expandAllGroups","querySelectorAll","forEach"],"mappings":";;;;;;;;;;;;wLAiC6BA,cASzBC,YAAYC,YAAaC,UAAWC,yCAE1BF,kBAGDC,UAAY,CACbE,QAASF,UAAUE,QACnBC,QAASH,UAAUG,QACnBC,SAAUJ,UAAUI,SACpBC,+BAAOL,UAAUK,mDAASL,UAAUG,SAKpCF,oBACKK,iBAAmBC,KAAKC,qBACxBA,gBAAkB,UACdC,2BACEF,KAAKD,0BAIfI,SAASC,GAAG,wCAAwC,UAChDF,mCAGJG,sBAAsBL,KAAKM,cAAcC,KAAKP,OAQvDQ,sBACUC,WAAaT,KAAKG,SAASO,KAAK,iBAClCD,kBACO,sBAAQA,YAAY,GAUnCH,cAAcK,kBACJC,MAAO,sBAAQD,YAAY,MAC7BX,KAAKa,YAAYF,YAAa,OAExBG,MAAQF,KAAKG,cAAcf,KAAKP,UAAUK,OACb,MAA/BgB,MAAME,aAAa,UACnBC,OAAOC,SAASC,KAAOL,MAAME,aAAa,SAE9CF,MAAMM,mBAGAC,KAAOT,KAAKG,cAAc,KACE,MAA9BM,KAAKL,aAAa,QAClBC,OAAOC,SAASC,KAAOE,KAAKL,aAAa,QAEzCK,KAAKD,SAYjBE,gBAAgBC,MAAOZ,YACDY,MAAMC,OAAOC,QAAQzB,KAAKP,UAAUI,gBAG5CyB,gBAAgBC,MAAOZ,aAIjCA,WAAWe,QACP1B,KAAKa,YAAYF,kBACZgB,YAAYhB,aAUzBiB,iBAAiBjB,kBAGoC,WAFpC,sBAAQA,YAAY,GACZI,iCACNC,aAAa,iBAQhCa,YAAYjB,8BAEFkB,QAAUlB,KAAKmB,KAAK/B,KAAKP,UAAUI,cACrCmC,oCAAgBF,QAAQpB,KAAK,iDAAaoB,QAAQG,KAAK,YACtDD,qBAGLA,cAAgBA,cAAcE,QAAQ,IAAK,KAGvB,8BAAWF,gBACfG,uCACDH,gBAAiBI,SAAS,UAS7CT,YAAYf,MACJZ,KAAK4B,iBAAiBhB,YACjBiB,YAAYjB,MASzByB,cAAczB,MACLZ,KAAK4B,iBAAiBhB,YAClBiB,YAAYjB,MAOzB0B,mBACqB,sBAAQtC,KAAKG,UAAU,GAAGoC,iBAAiBvC,KAAKP,UAAUE,SAClE6C,SAAQ5B,YACRe,aAAY,mBAAOf"} \ No newline at end of file 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 0b6b3f0cd6d34..e313f922faabd 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,"file":"courseindex.min.js","sources":["../../../src/local/courseindex/courseindex.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * Course index main component.\n *\n * @module 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 'sectionIndexCollapsed',\n [sectionId],\n !isCollapsed\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"],"names":["Component","BaseComponent","create","name","selectors","SECTION","SECTION_CMLIST","CM","TOGGLER","COLLAPSE","DRAWER","classes","SECTIONHIDDEN","CMHIDDEN","SECTIONCURRENT","COLLAPSED","SHOW","sections","cms","target","element","document","getElementById","reactive","stateReady","state","addEventListener","this","_sectionTogglers","getElements","forEach","section","dataset","id","cm","_refreshPageItem","course","contentTree","ContentTree","isEditing","getWatchers","watch","handler","_refreshSectionCollapsed","_createCm","_deleteCm","_createSection","_deleteSection","_refreshCourseSectionlist","_refreshSectionCmlist","event","sectionlink","closest","isChevron","toggler","querySelector","isCollapsed","classList","contains","sectionId","getAttribute","dispatch","getElement","Error","indexcollapsed","_expandSectionNode","forceValue","collapsibleId","replace","collapsible","undefined","togglerValue","collapse","pageItem","_element$pageItem","isStatic","type","get","setTimeout","_this$cms$element$pag","scrollIntoView","block","fakeelement","createElement","add","innerHTML","sectionid","data","getExporter","newelement","renderComponent","parentNode","replaceChild","cmlist","listparent","_fixOrder","sectionlist","container","neworder","allitems","length","remove","itemid","index","item","currentitem","children","insertBefore","append","removeChild","lastChild"],"mappings":"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"} \ No newline at end of file +{"version":3,"file":"courseindex.min.js","sources":["../../../src/local/courseindex/courseindex.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * Course index main component.\n *\n * @module 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 'sectionIndexCollapsed',\n [sectionId],\n !isCollapsed\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-71979 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"],"names":["Component","BaseComponent","create","name","selectors","SECTION","SECTION_CMLIST","CM","TOGGLER","COLLAPSE","DRAWER","classes","SECTIONHIDDEN","CMHIDDEN","SECTIONCURRENT","COLLAPSED","SHOW","sections","cms","target","element","document","getElementById","reactive","stateReady","state","addEventListener","this","_sectionTogglers","getElements","forEach","section","dataset","id","cm","_refreshPageItem","course","contentTree","ContentTree","isEditing","getWatchers","watch","handler","_refreshSectionCollapsed","_createCm","_deleteCm","_createSection","_deleteSection","_refreshCourseSectionlist","_refreshSectionCmlist","event","sectionlink","closest","isChevron","toggler","querySelector","isCollapsed","classList","contains","sectionId","getAttribute","dispatch","getElement","Error","indexcollapsed","_expandSectionNode","forceValue","collapsibleId","replace","collapsible","undefined","togglerValue","collapse","pageItem","_element$pageItem","isStatic","type","get","setTimeout","_this$cms$element$pag","scrollIntoView","block","fakeelement","createElement","add","innerHTML","sectionid","data","getExporter","newelement","renderComponent","parentNode","replaceChild","cmlist","listparent","_fixOrder","sectionlist","container","neworder","allitems","length","remove","itemid","index","item","currentitem","children","insertBefore","append","removeChild","lastChild"],"mappings":"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"} \ No newline at end of file diff --git a/course/format/amd/src/local/content/actions.js b/course/format/amd/src/local/content/actions.js index 9d930cacac232..53400fabb608e 100644 --- a/course/format/amd/src/local/content/actions.js +++ b/course/format/amd/src/local/content/actions.js @@ -35,7 +35,7 @@ import {getList} from 'core/normalise'; import * as CourseEvents from 'core_course/events'; import Pending from 'core/pending'; import ContentTree from 'core_courseformat/local/courseeditor/contenttree'; -// The jQuery module is only used for interacting with Boostrap 4. It can we removed when MDL-79179 is integrated. +// The jQuery module is only used for interacting with Boostrap 4. It can we removed when MDL-71979 is integrated. import jQuery from 'jquery'; // Load global strings. @@ -261,7 +261,7 @@ export default class extends BaseComponent { ); // Open the cm section node if possible (Bootstrap 4 uses jQuery to interact with collapsibles). - // All jQuery int this code can be replaced when MDL-79179 is integrated. + // All jQuery int this code can be replaced when MDL-71979 is integrated. const sectionnode = currentElement.closest(this.selectors.SECTIONNODE); const toggler = jQuery(sectionnode).find(this.selectors.MODALTOGGLER); let collapsibleId = toggler.data('target') ?? toggler.attr('href'); diff --git a/course/format/amd/src/local/courseeditor/contenttree.js b/course/format/amd/src/local/courseeditor/contenttree.js index bb29bec1da6f6..4157783f4aa42 100644 --- a/course/format/amd/src/local/courseeditor/contenttree.js +++ b/course/format/amd/src/local/courseeditor/contenttree.js @@ -61,7 +61,7 @@ export default class extends Tree { return this._getVisibleItems(); }; } - // All jQuery events can be replaced when MDL-79179 is integrated. + // All jQuery events can be replaced when MDL-71979 is integrated. this.treeRoot.on('hidden.bs.collapse shown.bs.collapse', () => { this.refreshVisibleItemsCache(); }); @@ -146,7 +146,7 @@ export default class extends Tree { * @param {JQuery} item the jQuery object */ toggleGroup(item) { - // All jQuery in this segment of code can be replaced when MDL-79179 is integrated. + // All jQuery in this segment of code can be replaced when MDL-71979 is integrated. const toggler = item.find(this.selectors.COLLAPSE); let collapsibleId = toggler.data('target') ?? toggler.attr('href'); if (!collapsibleId) { diff --git a/course/format/amd/src/local/courseindex/courseindex.js b/course/format/amd/src/local/courseindex/courseindex.js index 0556e30defba3..9590664c17e93 100644 --- a/course/format/amd/src/local/courseindex/courseindex.js +++ b/course/format/amd/src/local/courseindex/courseindex.js @@ -192,7 +192,7 @@ export default class Component extends BaseComponent { // Course index is based on Bootstrap 4 collapsibles. To collapse them we need jQuery to // interact with collapsibles methods. Hopefully, this will change in Bootstrap 5 because - // it does not require jQuery anymore (when MDL-79179 is integrated). + // it does not require jQuery anymore (when MDL-71979 is integrated). const togglerValue = (forceValue) ? 'show' : 'hide'; jQuery(collapsible).collapse(togglerValue); } diff --git a/theme/boost/amd/build/footer-popover.min.js.map b/theme/boost/amd/build/footer-popover.min.js.map index 3b3e9b0f5b7cb..c56b015fd7afc 100644 --- a/theme/boost/amd/build/footer-popover.min.js.map +++ b/theme/boost/amd/build/footer-popover.min.js.map @@ -1 +1 @@ -{"version":3,"file":"footer-popover.min.js","sources":["../src/footer-popover.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * Shows the footer content in a popover.\n *\n * @module theme_boost/footer-popover\n * @copyright 2021 Bas Brands\n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport $ from 'jquery';\nimport Popover from './popover';\n\nconst SELECTORS = {\n FOOTERCONTAINER: '[data-region=\"footer-container-popover\"]',\n FOOTERCONTENT: '[data-region=\"footer-content-popover\"]',\n FOOTERBUTTON: '[data-action=\"footer-popover\"]'\n};\n\nlet footerIsShown = false;\n\nexport const init = () => {\n const container = document.querySelector(SELECTORS.FOOTERCONTAINER);\n const footerButton = document.querySelector(SELECTORS.FOOTERBUTTON);\n\n // All jQuery in this code can be replaced when MDL-79179 is integrated.\n $(footerButton).popover({\n content: getFooterContent,\n container: container,\n html: true,\n placement: 'top',\n customClass: 'footer',\n trigger: 'click'\n });\n\n document.addEventListener('click', e => {\n if (footerIsShown && !e.target.closest(SELECTORS.FOOTERCONTAINER)) {\n $(footerButton).popover('hide');\n }\n },\n true);\n\n document.addEventListener('keydown', e => {\n if (footerIsShown && e.key === 'Escape') {\n $(footerButton).popover('hide');\n footerButton.focus();\n }\n });\n\n document.addEventListener('focus', e => {\n if (footerIsShown && !e.target.closest(SELECTORS.FOOTERCONTAINER)) {\n $(footerButton).popover('hide');\n }\n },\n true);\n\n $(footerButton).on('show.bs.popover', () => {\n footerIsShown = true;\n });\n\n $(footerButton).on('hide.bs.popover', () => {\n footerIsShown = false;\n });\n};\n\n/**\n * Get the footer content for popover.\n *\n * @returns {String} HTML string\n * @private\n */\nconst getFooterContent = () => {\n return document.querySelector(SELECTORS.FOOTERCONTENT).innerHTML;\n};\n\nexport {\n Popover\n};\n"],"names":["SELECTORS","footerIsShown","container","document","querySelector","footerButton","popover","content","getFooterContent","html","placement","customClass","trigger","addEventListener","e","target","closest","key","focus","on","innerHTML"],"mappings":";;;;;;;4QA0BMA,0BACe,2CADfA,wBAEa,yCAFbA,uBAGY,qCAGdC,eAAgB,gBAEA,WACVC,UAAYC,SAASC,cAAcJ,2BACnCK,aAAeF,SAASC,cAAcJ,4CAG1CK,cAAcC,QAAQ,CACpBC,QAASC,iBACTN,UAAWA,UACXO,MAAM,EACNC,UAAW,MACXC,YAAa,SACbC,QAAS,UAGbT,SAASU,iBAAiB,SAASC,IAC3Bb,gBAAkBa,EAAEC,OAAOC,QAAQhB,gDACjCK,cAAcC,QAAQ,WAGhC,GAEAH,SAASU,iBAAiB,WAAWC,IAC7Bb,eAA2B,WAAVa,EAAEG,0BACjBZ,cAAcC,QAAQ,QACxBD,aAAaa,YAIrBf,SAASU,iBAAiB,SAASC,IAC3Bb,gBAAkBa,EAAEC,OAAOC,QAAQhB,gDACjCK,cAAcC,QAAQ,WAGhC,uBAEED,cAAcc,GAAG,mBAAmB,KAClClB,eAAgB,yBAGlBI,cAAcc,GAAG,mBAAmB,KAClClB,eAAgB,YAUlBO,iBAAmB,IACdL,SAASC,cAAcJ,yBAAyBoB"} \ No newline at end of file +{"version":3,"file":"footer-popover.min.js","sources":["../src/footer-popover.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * Shows the footer content in a popover.\n *\n * @module theme_boost/footer-popover\n * @copyright 2021 Bas Brands\n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport $ from 'jquery';\nimport Popover from './popover';\n\nconst SELECTORS = {\n FOOTERCONTAINER: '[data-region=\"footer-container-popover\"]',\n FOOTERCONTENT: '[data-region=\"footer-content-popover\"]',\n FOOTERBUTTON: '[data-action=\"footer-popover\"]'\n};\n\nlet footerIsShown = false;\n\nexport const init = () => {\n const container = document.querySelector(SELECTORS.FOOTERCONTAINER);\n const footerButton = document.querySelector(SELECTORS.FOOTERBUTTON);\n\n // All jQuery in this code can be replaced when MDL-71979 is integrated.\n $(footerButton).popover({\n content: getFooterContent,\n container: container,\n html: true,\n placement: 'top',\n customClass: 'footer',\n trigger: 'click'\n });\n\n document.addEventListener('click', e => {\n if (footerIsShown && !e.target.closest(SELECTORS.FOOTERCONTAINER)) {\n $(footerButton).popover('hide');\n }\n },\n true);\n\n document.addEventListener('keydown', e => {\n if (footerIsShown && e.key === 'Escape') {\n $(footerButton).popover('hide');\n footerButton.focus();\n }\n });\n\n document.addEventListener('focus', e => {\n if (footerIsShown && !e.target.closest(SELECTORS.FOOTERCONTAINER)) {\n $(footerButton).popover('hide');\n }\n },\n true);\n\n $(footerButton).on('show.bs.popover', () => {\n footerIsShown = true;\n });\n\n $(footerButton).on('hide.bs.popover', () => {\n footerIsShown = false;\n });\n};\n\n/**\n * Get the footer content for popover.\n *\n * @returns {String} HTML string\n * @private\n */\nconst getFooterContent = () => {\n return document.querySelector(SELECTORS.FOOTERCONTENT).innerHTML;\n};\n\nexport {\n Popover\n};\n"],"names":["SELECTORS","footerIsShown","container","document","querySelector","footerButton","popover","content","getFooterContent","html","placement","customClass","trigger","addEventListener","e","target","closest","key","focus","on","innerHTML"],"mappings":";;;;;;;4QA0BMA,0BACe,2CADfA,wBAEa,yCAFbA,uBAGY,qCAGdC,eAAgB,gBAEA,WACVC,UAAYC,SAASC,cAAcJ,2BACnCK,aAAeF,SAASC,cAAcJ,4CAG1CK,cAAcC,QAAQ,CACpBC,QAASC,iBACTN,UAAWA,UACXO,MAAM,EACNC,UAAW,MACXC,YAAa,SACbC,QAAS,UAGbT,SAASU,iBAAiB,SAASC,IAC3Bb,gBAAkBa,EAAEC,OAAOC,QAAQhB,gDACjCK,cAAcC,QAAQ,WAGhC,GAEAH,SAASU,iBAAiB,WAAWC,IAC7Bb,eAA2B,WAAVa,EAAEG,0BACjBZ,cAAcC,QAAQ,QACxBD,aAAaa,YAIrBf,SAASU,iBAAiB,SAASC,IAC3Bb,gBAAkBa,EAAEC,OAAOC,QAAQhB,gDACjCK,cAAcC,QAAQ,WAGhC,uBAEED,cAAcc,GAAG,mBAAmB,KAClClB,eAAgB,yBAGlBI,cAAcc,GAAG,mBAAmB,KAClClB,eAAgB,YAUlBO,iBAAmB,IACdL,SAASC,cAAcJ,yBAAyBoB"} \ No newline at end of file diff --git a/theme/boost/amd/src/footer-popover.js b/theme/boost/amd/src/footer-popover.js index c768b8ce02270..1d2858a44df22 100644 --- a/theme/boost/amd/src/footer-popover.js +++ b/theme/boost/amd/src/footer-popover.js @@ -36,7 +36,7 @@ export const init = () => { const container = document.querySelector(SELECTORS.FOOTERCONTAINER); const footerButton = document.querySelector(SELECTORS.FOOTERBUTTON); - // All jQuery in this code can be replaced when MDL-79179 is integrated. + // All jQuery in this code can be replaced when MDL-71979 is integrated. $(footerButton).popover({ content: getFooterContent, container: container,