{"version":3,"file":"content.min.js","sources":["https:\/\/dl1.cuni.cz\/course\/format\/flexsections\/amd\/src\/local\/content.js"],"sourcesContent":["\/\/ This file is part of Moodle - http:\/\/moodle.org\/\n\/\/\n\/\/ Moodle is free software: you can redistribute it and\/or modify\n\/\/ it under the terms of the GNU General Public License as published by\n\/\/ the Free Software Foundation, either version 3 of the License, or\n\/\/ (at your option) any later version.\n\/\/\n\/\/ Moodle is distributed in the hope that it will be useful,\n\/\/ but WITHOUT ANY WARRANTY; without even the implied warranty of\n\/\/ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n\/\/ GNU General Public License for more details.\n\/\/\n\/\/ You should have received a copy of the GNU General Public License\n\/\/ along with Moodle. If not, see .\n\nimport Component from 'core_courseformat\/local\/content';\nimport {getCurrentCourseEditor} from 'core_courseformat\/courseeditor';\nimport Section from 'format_flexsections\/local\/content\/section';\nimport CmItem from 'core_courseformat\/local\/content\/section\/cmitem';\nimport Mutations from \"format_flexsections\/local\/courseeditor\/mutations\";\nimport FlexsectionsActions from 'format_flexsections\/local\/content\/actions';\nimport Exporter from \"format_flexsections\/local\/courseeditor\/exporter\";\n\n\/**\n * Course format component\n *\n * @module format_flexsections\/local\/content\n * @copyright 2022 Marina Glancy\n * @license http:\/\/www.gnu.org\/copyleft\/gpl.html GNU GPL v3 or later\n *\/\nexport default class FlexsectionComponent extends Component {\n \/\/ Extends course\/format\/amd\/src\/local\/content.js\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\n const courseEditor = getCurrentCourseEditor();\n courseEditor.getExporter = () => new Exporter(courseEditor);\n\n \/\/ Hack to preserve legacy mutations (added in core_course\/actions) after we set own plugin mutations.\n let legacyActivityAction = courseEditor.mutations.legacyActivityAction ?? null;\n let legacySectionAction = courseEditor.mutations.legacySectionAction ?? null;\n courseEditor.setMutations(new Mutations());\n courseEditor.addMutations({\n ...(legacyActivityAction ? {legacyActivityAction} : {}),\n ...(legacySectionAction ? {legacySectionAction} : {})});\n\n return new FlexsectionComponent({\n element: document.getElementById(target),\n reactive: courseEditor,\n selectors,\n sectionReturn,\n });\n }\n\n \/**\n * Constructor hook.\n *\n * @param {Object} descriptor the component descriptor\n *\/\n create(descriptor) {\n super.create(descriptor);\n \/\/ Optional component name for debugging.\n this.name = 'course_format_flexsections';\n this.selectors.COURSE_SUBSECTIONLIST = `[data-for='course_subsectionlist']`;\n }\n\n \/**\n * Initial state ready method.\n *\n * @param {Object} state the state data\n *\/\n stateReady(state) {\n super.stateReady(state);\n if (this.reactive.supportComponents) {\n \/\/ Actions are only available in edit mode.\n if (this.reactive.isEditing) {\n new FlexsectionsActions(this);\n }\n }\n if (state.course.accordion) {\n this._ensureOnlyOneSectionIsExpanded(state);\n \/\/ Monitor hash change so that we can expand the section from the hash.\n window.addEventListener(\n \"hashchange\",\n this._hashHandler.bind(this),\n );\n }\n }\n\n _ensureOnlyOneSectionIsExpanded(state) {\n const isExpanded = (sectionInfo) => !sectionInfo.showaslink && !sectionInfo.contentcollapsed;\n const hasExpandedChildren = (sectionInfo) =>\n (sectionInfo.children ?? []).some(s => isExpanded(s));\n\n let firstExpandedSection = null;\n for (let sectionInfo of this._getSectionsWithCollapse(state)) {\n if (!firstExpandedSection && isExpanded(sectionInfo) && !hasExpandedChildren(sectionInfo)) {\n firstExpandedSection = sectionInfo;\n }\n }\n\n if (firstExpandedSection) {\n const sectionitem = this.getElement(this.selectors.SECTION, firstExpandedSection.id);\n this._collapseAllSectionsExceptFor(sectionitem, false);\n }\n }\n\n \/**\n * Return the component watchers.\n *\n * @returns {Array} of watchers\n *\/\n getWatchers() {\n let res = super.getWatchers();\n res.push({watch: `course.hierarchy:updated`, handler: this._refreshCourseHierarchy});\n res.push({watch: `section.showaslink:updated`, handler: this._reloadSection});\n return res;\n }\n\n \/**\n * Refresh the section list.\n *\n * @param {Object} param\n * @param {Object} param.element details the update details.\n *\/\n _refreshCourseHierarchy({element}) {\n const hierarchy = element.hierarchy ?? [];\n const createSection = this._createSectionItem.bind(this);\n for (let i = 0; i < hierarchy.length; i++) {\n const sectionlist = hierarchy[i].children;\n const listparent = this.getElement(this.selectors.COURSE_SUBSECTIONLIST + `[data-parent='${hierarchy[i].id}']`);\n if (listparent) {\n this._fixOrder(listparent, sectionlist, this.selectors.SECTION, this.dettachedSections, createSection);\n }\n }\n }\n\n \/**\n * Regenerate content indexes.\n *\n * This method is used when a legacy action refresh some content element.\n *\n * Override parent method and replace with our Section and CmItem classes.\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 * 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 \/\/ Overrides parent method to add more functionality.\n const sectionlink = event.target.closest(this.selectors.TOGGLER);\n const closestCollapse = event.target.closest(this.selectors.COLLAPSE);\n const isChevron = closestCollapse?.closest(this.selectors.SECTION_ITEM);\n\n if (sectionlink || isChevron) {\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\n if (isChevron || isCollapsed) {\n const sectionId = parseInt(section.getAttribute('data-id'));\n \/\/ Update the state.\n this.reactive.dispatch(\n 'sectionContentCollapsed',\n [sectionId],\n !isCollapsed\n );\n }\n \/\/ If we expanded a section, collapse all other expanded sections\n \/\/ except for this section parents.\n if (isCollapsed && this.reactive.stateManager.state.course.accordion) {\n this._collapseAllSectionsExceptFor(section);\n }\n }\n }\n\n \/**\n * Collapse all sections except for the given one and its parents.\n *\n * @param {HTMLElement} section\n * @param {Boolean} scrollToSection\n *\/\n _collapseAllSectionsExceptFor(section, scrollToSection = true) {\n const sectionNumber = parseInt(section.getAttribute('data-sectionid'));\n const leaveOpen = [...this._findAllParents(sectionNumber), sectionNumber];\n if (sectionNumber > 0 && leaveOpen.includes(0)) {\n leaveOpen.splice(leaveOpen.indexOf(0), 1);\n }\n const sectionIds =\n this._getSectionsWithCollapse(this.reactive.stateManager.state)\n .filter(s => !leaveOpen.includes(parseInt(s.section)))\n .map(s => s.id);\n this.reactive.dispatch(\n 'sectionContentCollapsed',\n sectionIds,\n true\n );\n if (scrollToSection) {\n const toggler = section.querySelector(this.selectors.COLLAPSE);\n setTimeout(() => {\n toggler.scrollIntoView({behavior: \"smooth\", block: \"nearest\"});\n }, 500);\n }\n }\n\n \/**\n * Refresh the collapse\/expand all sections element.\n *\n * @param {Object} state The state data\n *\/\n _refreshAllSectionsToggler(state) {\n const target = this.getElement(this.selectors.TOGGLEALL);\n if (!target) {\n return;\n }\n \/\/ Check if we have all sections collapsed\/expanded.\n let allcollapsed = true;\n const mainSection = this._mainSection();\n const sections = this._getSectionsWithCollapse(state);\n for (let i in sections) {\n if (parseInt(sections[i].parent) === mainSection) {\n allcollapsed = allcollapsed && sections[i].contentcollapsed;\n }\n }\n \/\/ Update control.\n if (allcollapsed) {\n target.classList.add(this.classes.COLLAPSED);\n target.setAttribute('aria-expanded', false);\n } else {\n target.classList.remove(this.classes.COLLAPSED);\n target.setAttribute('aria-expanded', true);\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 sections = this._getSectionsWithCollapse();\n let ids = [];\n for (let i in sections) {\n ids.push(sections[i].id);\n }\n this.reactive.dispatch(\n 'sectionContentCollapsed',\n ids,\n !isAllCollapsed\n );\n }\n\n \/**\n * Find main section\n *\n * @returns {Number}\n *\/\n _mainSection() {\n return parseInt(this.element.getAttribute('data-flexsections-mainsection'));\n }\n\n \/**\n * Get all sections that can be collapsed or expanded\n *\n * @param {Object} state The state data\n * @returns {Array}\n *\/\n _getSectionsWithCollapse(state) {\n if (state === undefined) {\n state = this.reactive.stateManager.state;\n }\n const mainSection = this._mainSection();\n let parents = {};\n parents[`${mainSection}`] = `${mainSection}`;\n let displayedSections = [];\n state.section.forEach(\n section => {\n const sectionNumber = parseInt(section.number);\n const toggler = this.getElement(this.selectors.SECTION, section.id)?.querySelector(this.selectors.COLLAPSE);\n\n if (!toggler || !(`${section.parent}` in parents) || section.showaslink) {\n return;\n }\n parents[`${sectionNumber}`] = `${sectionNumber}`;\n displayedSections.push(section);\n }\n );\n return displayedSections;\n }\n\n \/**\n * Find all parents of the current section (section numbers, not ids)\n *\n * @param {Number} thisSectionNumber\n * @returns {Array} Array of section numbers that are parents of this one\n *\/\n _findAllParents(thisSectionNumber) {\n \/\/ Section object has properties: number, id, parent, parentid.\n if (thisSectionNumber === this._mainSection()) {\n return [];\n }\n let section = this.reactive.stateManager.state.section\n .find(section => parseInt(section.number) === thisSectionNumber);\n if (section && section.parent !== undefined) {\n const parent = parseInt(section.parent);\n return [...this._findAllParents(parent), parent];\n }\n return [];\n }\n\n \/**\n * Handler for when the page hash was changed - if in accordion mode, expand the target section\n *\/\n _hashHandler() {\n if ((window.location.hash ?? '').length <= 1) {\n return;\n }\n const target = document.querySelector(`${window.location.hash}${this.selectors.SECTION}`);\n if (!target) {\n return;\n }\n const toggler = target.querySelector(this.selectors.COLLAPSE);\n if (toggler) {\n const sectionNumber = parseInt(target.getAttribute('data-sectionid'));\n const toExpand = [...this._findAllParents(sectionNumber), sectionNumber].filter(s => s > 0);\n const sectionIds =\n this._getSectionsWithCollapse(this.reactive.stateManager.state)\n .filter(s => toExpand.includes(parseInt(s.section)))\n .map(s => s.id);\n this.reactive.dispatch(\n 'sectionContentCollapsed',\n sectionIds,\n false\n );\n this._collapseAllSectionsExceptFor(target);\n }\n }\n}"],"names":["FlexsectionComponent","Component","target","selectors","sectionReturn","courseEditor","getExporter","Exporter","legacyActivityAction","mutations","legacySectionAction","setMutations","Mutations","addMutations","element","document","getElementById","reactive","create","descriptor","name","COURSE_SUBSECTIONLIST","stateReady","state","this","supportComponents","isEditing","FlexsectionsActions","course","accordion","_ensureOnlyOneSectionIsExpanded","window","addEventListener","_hashHandler","bind","isExpanded","sectionInfo","showaslink","contentcollapsed","hasExpandedChildren","children","some","s","firstExpandedSection","_getSectionsWithCollapse","sectionitem","getElement","SECTION","id","_collapseAllSectionsExceptFor","getWatchers","res","super","push","watch","handler","_refreshCourseHierarchy","_reloadSection","hierarchy","createSection","_createSectionItem","i","length","sectionlist","listparent","_fixOrder","dettachedSections","_indexContents","_scanIndex","sections","item","Section","CM","cms","CmItem","_sectionTogglers","event","sectionlink","closest","TOGGLER","closestCollapse","COLLAPSE","isChevron","SECTION_ITEM","section","toggler","querySelector","isCollapsed","classList","contains","classes","COLLAPSED","sectionId","parseInt","getAttribute","dispatch","stateManager","scrollToSection","sectionNumber","leaveOpen","_findAllParents","includes","splice","indexOf","sectionIds","filter","map","setTimeout","scrollIntoView","behavior","block","_refreshAllSectionsToggler","TOGGLEALL","allcollapsed","mainSection","_mainSection","parent","add","setAttribute","remove","_allSectionToggler","preventDefault","isAllCollapsed","ids","undefined","parents","displayedSections","forEach","number","_this$getElement","thisSectionNumber","find","location","hash","toExpand"],"mappings":"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\/C2E,cACA\/G,OAAOsF,UAAU6B,IAAI7F,KAAKkE,QAAQC,WAClCzF,OAAOoH,aAAa,iBAAiB,KAErCpH,OAAOsF,UAAU+B,OAAO\/F,KAAKkE,QAAQC,WACrCzF,OAAOoH,aAAa,iBAAiB,IAY7CE,mBAAmB5C,OACfA,MAAM6C,uBAGAC,eADS9C,MAAM1E,OAAO4E,QAAQtD,KAAKrB,UAAU6G,WACrBxB,UAAUC,SAASjE,KAAKkE,QAAQC,WAExDtB,SAAW7C,KAAKoB,+BAClB+E,IAAM,OACL,IAAI9D,KAAKQ,SACVsD,IAAItE,KAAKgB,SAASR,GAAGb,SAEpB\/B,SAAS8E,SACV,0BACA4B,KACCD,gBASTP,sBACWtB,SAASrE,KAAKV,QAAQgF,aAAa,kCAS9ClD,yBAAyBrB,YACPqG,IAAVrG,QACAA,MAAQC,KAAKP,SAAS+E,aAAazE,aAEjC2F,YAAc1F,KAAK2F,mBACrBU,QAAU,GACdA,kBAAWX,wBAAoBA,iBAC3BY,kBAAoB,UACxBvG,MAAM6D,QAAQ2C,SACV3C,qCACUc,cAAgBL,SAAST,QAAQ4C,kCACvBxG,KAAKsB,WAAWtB,KAAKrB,UAAU4C,QAASqC,QAAQpC,uCAAhDiF,iBAAqD3C,cAAc9D,KAAKrB,UAAU8E,YAEhF,UAAGG,QAAQgC,UAAYS,UAAYzC,QAAQ\/C,aAG7DwF,kBAAW3B,0BAAsBA,eACjC4B,kBAAkBzE,KAAK+B,aAGxB0C,kBASX1B,gBAAgB8B,sBAERA,oBAAsB1G,KAAK2F,qBACpB,OAEP\/B,QAAU5D,KAAKP,SAAS+E,aAAazE,MAAM6D,QAC1C+C,MAAK\/C,SAAWS,SAAST,QAAQ4C,UAAYE,uBAC9C9C,cAA8BwC,IAAnBxC,QAAQgC,OAAsB,OACnCA,OAASvB,SAAST,QAAQgC,cACzB,IAAI5F,KAAK4E,gBAAgBgB,QAASA,cAEtC,GAMXnF,2EACSF,OAAOqG,SAASC,4DAAQ,IAAIvE,QAAU,eAGrC5D,OAASa,SAASuE,wBAAiBvD,OAAOqG,SAASC,aAAO7G,KAAKrB,UAAU4C,cAC1E7C,iBAGWA,OAAOoF,cAAc9D,KAAKrB,UAAU8E,UACvC,OACHiB,cAAgBL,SAAS3F,OAAO4F,aAAa,mBAC7CwC,SAAW,IAAI9G,KAAK4E,gBAAgBF,eAAgBA,eAAeO,QAAO\/D,GAAKA,EAAI,IACnF8D,WACFhF,KAAKoB,yBAAyBpB,KAAKP,SAAS+E,aAAazE,OACpDkF,QAAO\/D,GAAK4F,SAASjC,SAASR,SAASnD,EAAE0C,YACzCsB,KAAIhE,GAAKA,EAAEM,UACf\/B,SAAS8E,SACV,0BACAS,YACA,QAECvD,8BAA8B\/C"}