From bf06c4b9347144afc59d868a656b74a92dfcb968 Mon Sep 17 00:00:00 2001 From: james-cnz <5689414+james-cnz@users.noreply.github.com> Date: Tue, 12 Nov 2024 11:58:19 +1300 Subject: [PATCH] MDL-83690 course: Fix course index label links --- course/format/amd/build/local/courseindex/cm.min.js | 2 +- course/format/amd/build/local/courseindex/cm.min.js.map | 2 +- course/format/amd/src/local/courseindex/cm.js | 3 ++- course/format/classes/output/local/state/section.php | 2 +- 4 files changed, 5 insertions(+), 4 deletions(-) diff --git a/course/format/amd/build/local/courseindex/cm.min.js b/course/format/amd/build/local/courseindex/cm.min.js index 7fda10e23ad..32436b5fa5d 100644 --- a/course/format/amd/build/local/courseindex/cm.min.js +++ b/course/format/amd/build/local/courseindex/cm.min.js @@ -8,6 +8,6 @@ define("core_courseformat/local/courseindex/cm",["exports","core_courseformat/lo * @class core_courseformat/local/courseindex/cm * @copyright 2021 Ferran Recio * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.default=void 0,_dndcmitem=_interopRequireDefault(_dndcmitem),_templates=_interopRequireDefault(_templates),_prefetch=_interopRequireDefault(_prefetch),_config=_interopRequireDefault(_config),_pending=_interopRequireDefault(_pending),_log=_interopRequireDefault(_log);_prefetch.default.prefetchTemplate("core_courseformat/local/courseindex/cmcompletion");class Component extends _dndcmitem.default{create(){this.name="courseindex_cm",this.selectors={CM_NAME:"[data-for='cm_name']",CM_COMPLETION:"[data-for='cm_completion']"},this.classes={CMHIDDEN:"dimmed",LOCKED:"editinprogress",RESTRICTIONS:"restrictions",PAGEITEM:"pageitem",INDENTED:"indented"},this.id=this.element.dataset.id}static init(target,selectors){let element=document.querySelector(target);return element||(_log.default.debug("Init component with id is deprecated, use a query selector instead."),element=document.getElementById(target)),new this({element:element,selectors:selectors})}stateReady(state){this.configDragDrop(this.id);const cm=state.cm.get(this.id),course=state.course;this._refreshCompletion({state:state,element:cm});const anchor=new URL(window.location.href).hash.replace("#","");if((window.location.href==cm.url||window.location.href.includes(course.baseurl)&&anchor==cm.anchor)&&this.element.scrollIntoView({block:"center"}),_config.default.contextid!=_config.default.courseContextId&&_config.default.contextInstanceId==this.id&&(this.reactive.dispatch("setPageItem","cm",this.id,!0),this.element.scrollIntoView({block:"center"})),!cm.uservisible||!cm.url){const element=this.getElement(this.selectors.CM_NAME);this.addEventListener(element,"click",this._activityAnchor),document.getElementById(cm.anchor)||element.setAttribute("href",this._getActivitySectionURL(cm))}}getWatchers(){return[{watch:"cm[".concat(this.id,"]:deleted"),handler:this.remove},{watch:"cm[".concat(this.id,"]:updated"),handler:this._refreshCm},{watch:"cm[".concat(this.id,"].completionstate:updated"),handler:this._refreshCompletion},{watch:"course.pageItem:updated",handler:this._refreshPageItem}]}_refreshCm(_ref){var _element$dragging,_element$locked,_element$hascmrestric;let{element:element}=_ref;this.element.classList.toggle(this.classes.CMHIDDEN,!element.visible),this.getElement(this.selectors.CM_NAME).innerHTML=element.name,this.element.classList.toggle(this.classes.DRAGGING,null!==(_element$dragging=element.dragging)&&void 0!==_element$dragging&&_element$dragging),this.element.classList.toggle(this.classes.LOCKED,null!==(_element$locked=element.locked)&&void 0!==_element$locked&&_element$locked),this.element.classList.toggle(this.classes.RESTRICTIONS,null!==(_element$hascmrestric=element.hascmrestrictions)&&void 0!==_element$hascmrestric&&_element$hascmrestric),this.element.classList.toggle(this.classes.INDENTED,element.indent),this.locked=element.locked}_refreshPageItem(_ref2){let{element:element}=_ref2;if(!element.pageItem)return;const isPageId="cm"==element.pageItem.type&&element.pageItem.id==this.id;this.element.classList.toggle(this.classes.PAGEITEM,isPageId),isPageId&&!this.reactive.isEditing&&this.element.scrollIntoView({block:"nearest"})}async _refreshCompletion(_ref3){let{state:state,element:element}=_ref3;if(this.reactive.isEditing||!element.istrackeduser)return;const completionElement=this.getElement(this.selectors.CM_COMPLETION);if(!completionElement||completionElement.dataset.value==element.completionstate)return;const data=this.reactive.getExporter().cmCompletion(state,element),{html:html,js:js}=await _templates.default.renderForPromise("core_courseformat/local/courseindex/cmcompletion",data);_templates.default.replaceNode(completionElement,html,js)}_activityAnchor(event){const cm=this.reactive.get("cm",this.id);if(document.getElementById(cm.anchor)){this.reactive.dispatch("sectionContentCollapsed",[cm.sectionid],!1);const pendingAnchor=new _pending.default("courseformat/activity:openAnchor");setTimeout((()=>{this.reactive.dispatch("setPageItem","cm",cm.id),pendingAnchor.resolve()}),50)}else event.preventDefault(),window.location=this._getActivitySectionURL(cm)}_getActivitySectionURL(cm){let section=this.reactive.get("section",cm.sectionid);return section.component&§ion.parentsectionid&&(section=this.reactive.get("section",section.parentsectionid)),section?"".concat(section.sectionurl,"#").concat(cm.anchor):"#"}}return _exports.default=Component,_exports.default})); + */Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.default=void 0,_dndcmitem=_interopRequireDefault(_dndcmitem),_templates=_interopRequireDefault(_templates),_prefetch=_interopRequireDefault(_prefetch),_config=_interopRequireDefault(_config),_pending=_interopRequireDefault(_pending),_log=_interopRequireDefault(_log);_prefetch.default.prefetchTemplate("core_courseformat/local/courseindex/cmcompletion");class Component extends _dndcmitem.default{create(){this.name="courseindex_cm",this.selectors={CM_NAME:"[data-for='cm_name']",CM_COMPLETION:"[data-for='cm_completion']"},this.classes={CMHIDDEN:"dimmed",LOCKED:"editinprogress",RESTRICTIONS:"restrictions",PAGEITEM:"pageitem",INDENTED:"indented"},this.id=this.element.dataset.id}static init(target,selectors){let element=document.querySelector(target);return element||(_log.default.debug("Init component with id is deprecated, use a query selector instead."),element=document.getElementById(target)),new this({element:element,selectors:selectors})}stateReady(state){this.configDragDrop(this.id);const cm=state.cm.get(this.id),course=state.course;this._refreshCompletion({state:state,element:cm});const anchor=new URL(window.location.href).hash.replace("#","");if((window.location.href==cm.url||window.location.href.includes(course.baseurl)&&anchor==cm.anchor)&&this.element.scrollIntoView({block:"center"}),_config.default.contextid!=_config.default.courseContextId&&_config.default.contextInstanceId==this.id&&(this.reactive.dispatch("setPageItem","cm",this.id,!0),this.element.scrollIntoView({block:"center"})),!cm.uservisible||!cm.url){const element=this.getElement(this.selectors.CM_NAME);this.addEventListener(element,"click",this._activityAnchor),document.getElementById(cm.anchor)||element.setAttribute("href",this._getActivitySectionURL(cm))}}getWatchers(){return[{watch:"cm[".concat(this.id,"]:deleted"),handler:this.remove},{watch:"cm[".concat(this.id,"]:updated"),handler:this._refreshCm},{watch:"cm[".concat(this.id,"].completionstate:updated"),handler:this._refreshCompletion},{watch:"course.pageItem:updated",handler:this._refreshPageItem}]}_refreshCm(_ref){var _element$dragging,_element$locked,_element$hascmrestric;let{element:element}=_ref;this.element.classList.toggle(this.classes.CMHIDDEN,!element.visible),this.getElement(this.selectors.CM_NAME).innerHTML=element.name,this.element.classList.toggle(this.classes.DRAGGING,null!==(_element$dragging=element.dragging)&&void 0!==_element$dragging&&_element$dragging),this.element.classList.toggle(this.classes.LOCKED,null!==(_element$locked=element.locked)&&void 0!==_element$locked&&_element$locked),this.element.classList.toggle(this.classes.RESTRICTIONS,null!==(_element$hascmrestric=element.hascmrestrictions)&&void 0!==_element$hascmrestric&&_element$hascmrestric),this.element.classList.toggle(this.classes.INDENTED,element.indent),this.locked=element.locked}_refreshPageItem(_ref2){let{element:element}=_ref2;if(!element.pageItem)return;const isPageId="cm"==element.pageItem.type&&element.pageItem.id==this.id;this.element.classList.toggle(this.classes.PAGEITEM,isPageId),isPageId&&!this.reactive.isEditing&&this.element.scrollIntoView({block:"nearest"})}async _refreshCompletion(_ref3){let{state:state,element:element}=_ref3;if(this.reactive.isEditing||!element.istrackeduser)return;const completionElement=this.getElement(this.selectors.CM_COMPLETION);if(!completionElement||completionElement.dataset.value==element.completionstate)return;const data=this.reactive.getExporter().cmCompletion(state,element),{html:html,js:js}=await _templates.default.renderForPromise("core_courseformat/local/courseindex/cmcompletion",data);_templates.default.replaceNode(completionElement,html,js)}_activityAnchor(event){const cm=this.reactive.get("cm",this.id);if(document.getElementById(cm.anchor)){this.reactive.dispatch("sectionContentCollapsed",[cm.sectionid],!1);const pendingAnchor=new _pending.default("courseformat/activity:openAnchor");setTimeout((()=>{this.reactive.dispatch("setPageItem","cm",cm.id),pendingAnchor.resolve()}),50)}else event.preventDefault(),window.location=this._getActivitySectionURL(cm)}_getActivitySectionURL(cm){let section=this.reactive.get("section",cm.sectionid);if(section.component&§ion.parentsectionid&&(section=this.reactive.get("section",section.parentsectionid)),!section)return"#";const sectionurl=section.sectionurl.split("#")[0];return"".concat(sectionurl,"#").concat(cm.anchor)}}return _exports.default=Component,_exports.default})); //# sourceMappingURL=cm.min.js.map \ No newline at end of file diff --git a/course/format/amd/build/local/courseindex/cm.min.js.map b/course/format/amd/build/local/courseindex/cm.min.js.map index 32453155a15..f028f2d74de 100644 --- a/course/format/amd/build/local/courseindex/cm.min.js.map +++ b/course/format/amd/build/local/courseindex/cm.min.js.map @@ -1 +1 @@ -{"version":3,"file":"cm.min.js","sources":["../../../src/local/courseindex/cm.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 cm component.\n *\n * This component is used to control specific course modules interactions like drag and drop.\n *\n * @module core_courseformat/local/courseindex/cm\n * @class core_courseformat/local/courseindex/cm\n * @copyright 2021 Ferran Recio \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport DndCmItem from 'core_courseformat/local/courseeditor/dndcmitem';\nimport Templates from 'core/templates';\nimport Prefetch from 'core/prefetch';\nimport Config from 'core/config';\nimport Pending from \"core/pending\";\nimport log from \"core/log\";\n\n// Prefetch the completion icons template.\nconst completionTemplate = 'core_courseformat/local/courseindex/cmcompletion';\nPrefetch.prefetchTemplate(completionTemplate);\n\nexport default class Component extends DndCmItem {\n\n /**\n * Constructor hook.\n */\n create() {\n // Optional component name for debugging.\n this.name = 'courseindex_cm';\n // Default query selectors.\n this.selectors = {\n CM_NAME: `[data-for='cm_name']`,\n CM_COMPLETION: `[data-for='cm_completion']`,\n };\n // Default classes to toggle on refresh.\n this.classes = {\n CMHIDDEN: 'dimmed',\n LOCKED: 'editinprogress',\n RESTRICTIONS: 'restrictions',\n PAGEITEM: 'pageitem',\n INDENTED: 'indented',\n };\n // We need our id to watch specific events.\n this.id = this.element.dataset.id;\n }\n\n /**\n * Static method to create a component instance form the mustache template.\n *\n * @param {element|string} target the DOM main element or its ID\n * @param {object} selectors optional css selector overrides\n * @return {Component}\n */\n static init(target, selectors) {\n let element = document.querySelector(target);\n // TODO Remove this if condition as part of MDL-83851.\n if (!element) {\n log.debug('Init component with id is deprecated, use a query selector instead.');\n element = document.getElementById(target);\n }\n return new this({\n element,\n selectors,\n });\n }\n\n /**\n * Initial state ready method.\n *\n * @param {Object} state the course state.\n */\n stateReady(state) {\n this.configDragDrop(this.id);\n const cm = state.cm.get(this.id);\n const course = state.course;\n // Refresh completion icon.\n this._refreshCompletion({\n state,\n element: cm,\n });\n const url = new URL(window.location.href);\n const anchor = url.hash.replace('#', '');\n // Check if the current url is the cm url.\n if (window.location.href == cm.url\n || (window.location.href.includes(course.baseurl) && anchor == cm.anchor)\n ) {\n this.element.scrollIntoView({block: \"center\"});\n }\n // Check if this we are displaying this activity page.\n if (Config.contextid != Config.courseContextId && Config.contextInstanceId == this.id) {\n this.reactive.dispatch('setPageItem', 'cm', this.id, true);\n this.element.scrollIntoView({block: \"center\"});\n }\n // Add anchor logic if the element is not user visible or the element hasn't URL.\n if (!cm.uservisible || !cm.url) {\n const element = this.getElement(this.selectors.CM_NAME);\n this.addEventListener(\n element,\n 'click',\n this._activityAnchor,\n );\n // If the element is not user visible we also need to update the anchor link including the section page.\n if (!document.getElementById(cm.anchor)) {\n element.setAttribute('href', this._getActivitySectionURL(cm));\n }\n }\n }\n\n /**\n * Component watchers.\n *\n * @returns {Array} of watchers\n */\n getWatchers() {\n return [\n {watch: `cm[${this.id}]:deleted`, handler: this.remove},\n {watch: `cm[${this.id}]:updated`, handler: this._refreshCm},\n {watch: `cm[${this.id}].completionstate:updated`, handler: this._refreshCompletion},\n {watch: `course.pageItem:updated`, handler: this._refreshPageItem},\n ];\n }\n\n /**\n * Update a course index cm using the state information.\n *\n * @param {object} param\n * @param {Object} param.element details the update details.\n */\n _refreshCm({element}) {\n // Update classes.\n this.element.classList.toggle(this.classes.CMHIDDEN, !element.visible);\n this.getElement(this.selectors.CM_NAME).innerHTML = element.name;\n this.element.classList.toggle(this.classes.DRAGGING, element.dragging ?? false);\n this.element.classList.toggle(this.classes.LOCKED, element.locked ?? false);\n this.element.classList.toggle(this.classes.RESTRICTIONS, element.hascmrestrictions ?? false);\n this.element.classList.toggle(this.classes.INDENTED, element.indent);\n this.locked = element.locked;\n }\n\n /**\n * Handle a page item update.\n *\n * @param {Object} details the update details\n * @param {Object} details.element the course state data.\n */\n _refreshPageItem({element}) {\n if (!element.pageItem) {\n return;\n }\n const isPageId = (element.pageItem.type == 'cm' && element.pageItem.id == this.id);\n this.element.classList.toggle(this.classes.PAGEITEM, isPageId);\n if (isPageId && !this.reactive.isEditing) {\n this.element.scrollIntoView({block: \"nearest\"});\n }\n }\n\n /**\n * Update the activity completion icon.\n *\n * @param {Object} details the update details\n * @param {Object} details.state the state data\n * @param {Object} details.element the element data\n */\n async _refreshCompletion({state, element}) {\n // No completion icons are displayed in edit mode.\n if (this.reactive.isEditing || !element.istrackeduser) {\n return;\n }\n // Check if the completion value has changed.\n const completionElement = this.getElement(this.selectors.CM_COMPLETION);\n if (!completionElement || completionElement.dataset.value == element.completionstate) {\n return;\n }\n\n // Collect section information from the state.\n const exporter = this.reactive.getExporter();\n const data = exporter.cmCompletion(state, element);\n\n const {html, js} = await Templates.renderForPromise(completionTemplate, data);\n Templates.replaceNode(completionElement, html, js);\n }\n\n /**\n * The activity anchor event.\n *\n * @param {Event} event\n */\n _activityAnchor(event) {\n const cm = this.reactive.get('cm', this.id);\n // If the user cannot access the element but the element is present in the page\n // the new url should be an anchor link.\n const element = document.getElementById(cm.anchor);\n if (element) {\n // Make sure the section is expanded.\n this.reactive.dispatch('sectionContentCollapsed', [cm.sectionid], false);\n // Marc the element as page item once the event is handled.\n const pendingAnchor = new Pending(`courseformat/activity:openAnchor`);\n setTimeout(() => {\n this.reactive.dispatch('setPageItem', 'cm', cm.id);\n pendingAnchor.resolve();\n }, 50);\n return;\n }\n // If the element is not present in the page we need to go to the specific section.\n event.preventDefault();\n window.location = this._getActivitySectionURL(cm);\n }\n\n /**\n * Get the anchor link in section page for the cm.\n *\n * @param {Object} cm the course module data.\n * @return {String} the anchor link.\n */\n _getActivitySectionURL(cm) {\n let section = this.reactive.get('section', cm.sectionid);\n\n // If the section is delegated get its parent section if it has one.\n if (section.component && section.parentsectionid) {\n section = this.reactive.get('section', section.parentsectionid);\n }\n\n if (!section) {\n return '#';\n }\n\n return `${section.sectionurl}#${cm.anchor}`;\n }\n}\n"],"names":["prefetchTemplate","Component","DndCmItem","create","name","selectors","CM_NAME","CM_COMPLETION","classes","CMHIDDEN","LOCKED","RESTRICTIONS","PAGEITEM","INDENTED","id","this","element","dataset","target","document","querySelector","debug","getElementById","stateReady","state","configDragDrop","cm","get","course","_refreshCompletion","anchor","URL","window","location","href","hash","replace","url","includes","baseurl","scrollIntoView","block","Config","contextid","courseContextId","contextInstanceId","reactive","dispatch","uservisible","getElement","addEventListener","_activityAnchor","setAttribute","_getActivitySectionURL","getWatchers","watch","handler","remove","_refreshCm","_refreshPageItem","classList","toggle","visible","innerHTML","DRAGGING","dragging","locked","hascmrestrictions","indent","pageItem","isPageId","type","isEditing","istrackeduser","completionElement","value","completionstate","data","getExporter","cmCompletion","html","js","Templates","renderForPromise","replaceNode","event","sectionid","pendingAnchor","Pending","setTimeout","resolve","preventDefault","section","component","parentsectionid","sectionurl"],"mappings":"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"} \ No newline at end of file +{"version":3,"file":"cm.min.js","sources":["../../../src/local/courseindex/cm.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 cm component.\n *\n * This component is used to control specific course modules interactions like drag and drop.\n *\n * @module core_courseformat/local/courseindex/cm\n * @class core_courseformat/local/courseindex/cm\n * @copyright 2021 Ferran Recio \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport DndCmItem from 'core_courseformat/local/courseeditor/dndcmitem';\nimport Templates from 'core/templates';\nimport Prefetch from 'core/prefetch';\nimport Config from 'core/config';\nimport Pending from \"core/pending\";\nimport log from \"core/log\";\n\n// Prefetch the completion icons template.\nconst completionTemplate = 'core_courseformat/local/courseindex/cmcompletion';\nPrefetch.prefetchTemplate(completionTemplate);\n\nexport default class Component extends DndCmItem {\n\n /**\n * Constructor hook.\n */\n create() {\n // Optional component name for debugging.\n this.name = 'courseindex_cm';\n // Default query selectors.\n this.selectors = {\n CM_NAME: `[data-for='cm_name']`,\n CM_COMPLETION: `[data-for='cm_completion']`,\n };\n // Default classes to toggle on refresh.\n this.classes = {\n CMHIDDEN: 'dimmed',\n LOCKED: 'editinprogress',\n RESTRICTIONS: 'restrictions',\n PAGEITEM: 'pageitem',\n INDENTED: 'indented',\n };\n // We need our id to watch specific events.\n this.id = this.element.dataset.id;\n }\n\n /**\n * Static method to create a component instance form the mustache template.\n *\n * @param {element|string} target the DOM main element or its ID\n * @param {object} selectors optional css selector overrides\n * @return {Component}\n */\n static init(target, selectors) {\n let element = document.querySelector(target);\n // TODO Remove this if condition as part of MDL-83851.\n if (!element) {\n log.debug('Init component with id is deprecated, use a query selector instead.');\n element = document.getElementById(target);\n }\n return new this({\n element,\n selectors,\n });\n }\n\n /**\n * Initial state ready method.\n *\n * @param {Object} state the course state.\n */\n stateReady(state) {\n this.configDragDrop(this.id);\n const cm = state.cm.get(this.id);\n const course = state.course;\n // Refresh completion icon.\n this._refreshCompletion({\n state,\n element: cm,\n });\n const url = new URL(window.location.href);\n const anchor = url.hash.replace('#', '');\n // Check if the current url is the cm url.\n if (window.location.href == cm.url\n || (window.location.href.includes(course.baseurl) && anchor == cm.anchor)\n ) {\n this.element.scrollIntoView({block: \"center\"});\n }\n // Check if this we are displaying this activity page.\n if (Config.contextid != Config.courseContextId && Config.contextInstanceId == this.id) {\n this.reactive.dispatch('setPageItem', 'cm', this.id, true);\n this.element.scrollIntoView({block: \"center\"});\n }\n // Add anchor logic if the element is not user visible or the element hasn't URL.\n if (!cm.uservisible || !cm.url) {\n const element = this.getElement(this.selectors.CM_NAME);\n this.addEventListener(\n element,\n 'click',\n this._activityAnchor,\n );\n // If the element is not user visible we also need to update the anchor link including the section page.\n if (!document.getElementById(cm.anchor)) {\n element.setAttribute('href', this._getActivitySectionURL(cm));\n }\n }\n }\n\n /**\n * Component watchers.\n *\n * @returns {Array} of watchers\n */\n getWatchers() {\n return [\n {watch: `cm[${this.id}]:deleted`, handler: this.remove},\n {watch: `cm[${this.id}]:updated`, handler: this._refreshCm},\n {watch: `cm[${this.id}].completionstate:updated`, handler: this._refreshCompletion},\n {watch: `course.pageItem:updated`, handler: this._refreshPageItem},\n ];\n }\n\n /**\n * Update a course index cm using the state information.\n *\n * @param {object} param\n * @param {Object} param.element details the update details.\n */\n _refreshCm({element}) {\n // Update classes.\n this.element.classList.toggle(this.classes.CMHIDDEN, !element.visible);\n this.getElement(this.selectors.CM_NAME).innerHTML = element.name;\n this.element.classList.toggle(this.classes.DRAGGING, element.dragging ?? false);\n this.element.classList.toggle(this.classes.LOCKED, element.locked ?? false);\n this.element.classList.toggle(this.classes.RESTRICTIONS, element.hascmrestrictions ?? false);\n this.element.classList.toggle(this.classes.INDENTED, element.indent);\n this.locked = element.locked;\n }\n\n /**\n * Handle a page item update.\n *\n * @param {Object} details the update details\n * @param {Object} details.element the course state data.\n */\n _refreshPageItem({element}) {\n if (!element.pageItem) {\n return;\n }\n const isPageId = (element.pageItem.type == 'cm' && element.pageItem.id == this.id);\n this.element.classList.toggle(this.classes.PAGEITEM, isPageId);\n if (isPageId && !this.reactive.isEditing) {\n this.element.scrollIntoView({block: \"nearest\"});\n }\n }\n\n /**\n * Update the activity completion icon.\n *\n * @param {Object} details the update details\n * @param {Object} details.state the state data\n * @param {Object} details.element the element data\n */\n async _refreshCompletion({state, element}) {\n // No completion icons are displayed in edit mode.\n if (this.reactive.isEditing || !element.istrackeduser) {\n return;\n }\n // Check if the completion value has changed.\n const completionElement = this.getElement(this.selectors.CM_COMPLETION);\n if (!completionElement || completionElement.dataset.value == element.completionstate) {\n return;\n }\n\n // Collect section information from the state.\n const exporter = this.reactive.getExporter();\n const data = exporter.cmCompletion(state, element);\n\n const {html, js} = await Templates.renderForPromise(completionTemplate, data);\n Templates.replaceNode(completionElement, html, js);\n }\n\n /**\n * The activity anchor event.\n *\n * @param {Event} event\n */\n _activityAnchor(event) {\n const cm = this.reactive.get('cm', this.id);\n // If the user cannot access the element but the element is present in the page\n // the new url should be an anchor link.\n const element = document.getElementById(cm.anchor);\n if (element) {\n // Make sure the section is expanded.\n this.reactive.dispatch('sectionContentCollapsed', [cm.sectionid], false);\n // Marc the element as page item once the event is handled.\n const pendingAnchor = new Pending(`courseformat/activity:openAnchor`);\n setTimeout(() => {\n this.reactive.dispatch('setPageItem', 'cm', cm.id);\n pendingAnchor.resolve();\n }, 50);\n return;\n }\n // If the element is not present in the page we need to go to the specific section.\n event.preventDefault();\n window.location = this._getActivitySectionURL(cm);\n }\n\n /**\n * Get the anchor link in section page for the cm.\n *\n * @param {Object} cm the course module data.\n * @return {String} the anchor link.\n */\n _getActivitySectionURL(cm) {\n let section = this.reactive.get('section', cm.sectionid);\n\n // If the section is delegated get its parent section if it has one.\n if (section.component && section.parentsectionid) {\n section = this.reactive.get('section', section.parentsectionid);\n }\n\n if (!section) {\n return '#';\n }\n\n const sectionurl = section.sectionurl.split(\"#\")[0];\n return `${sectionurl}#${cm.anchor}`;\n }\n}\n"],"names":["prefetchTemplate","Component","DndCmItem","create","name","selectors","CM_NAME","CM_COMPLETION","classes","CMHIDDEN","LOCKED","RESTRICTIONS","PAGEITEM","INDENTED","id","this","element","dataset","target","document","querySelector","debug","getElementById","stateReady","state","configDragDrop","cm","get","course","_refreshCompletion","anchor","URL","window","location","href","hash","replace","url","includes","baseurl","scrollIntoView","block","Config","contextid","courseContextId","contextInstanceId","reactive","dispatch","uservisible","getElement","addEventListener","_activityAnchor","setAttribute","_getActivitySectionURL","getWatchers","watch","handler","remove","_refreshCm","_refreshPageItem","classList","toggle","visible","innerHTML","DRAGGING","dragging","locked","hascmrestrictions","indent","pageItem","isPageId","type","isEditing","istrackeduser","completionElement","value","completionstate","data","getExporter","cmCompletion","html","js","Templates","renderForPromise","replaceNode","event","sectionid","pendingAnchor","Pending","setTimeout","resolve","preventDefault","section","component","parentsectionid","sectionurl","split"],"mappings":"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"} \ No newline at end of file diff --git a/course/format/amd/src/local/courseindex/cm.js b/course/format/amd/src/local/courseindex/cm.js index 677b48b7e70..c861c8e2ec9 100644 --- a/course/format/amd/src/local/courseindex/cm.js +++ b/course/format/amd/src/local/courseindex/cm.js @@ -240,6 +240,7 @@ export default class Component extends DndCmItem { return '#'; } - return `${section.sectionurl}#${cm.anchor}`; + const sectionurl = section.sectionurl.split("#")[0]; + return `${sectionurl}#${cm.anchor}`; } } diff --git a/course/format/classes/output/local/state/section.php b/course/format/classes/output/local/state/section.php index aadbca5e347..0ca2e63bfb5 100644 --- a/course/format/classes/output/local/state/section.php +++ b/course/format/classes/output/local/state/section.php @@ -83,7 +83,7 @@ class section implements renderable { 'rawtitle' => $section->name, 'cmlist' => [], 'visible' => !empty($section->visible), - 'sectionurl' => course_get_url($course, $section->section, ['navigation' => true])->out(), + 'sectionurl' => course_get_url($course, $section->section, ['navigation' => true])->out(false), 'current' => $format->is_section_current($section), 'indexcollapsed' => $indexcollapsed, 'contentcollapsed' => $contentcollapsed,