diff --git a/amd/build/linkmodal.min.js b/amd/build/linkmodal.min.js
index b6beaf1c..754b3e5e 100644
--- a/amd/build/linkmodal.min.js
+++ b/amd/build/linkmodal.min.js
@@ -1,10 +1,10 @@
-define("mod_learningmap/linkmodal",["exports","core/modal","core/ajax","core_course/manual_completion_toggle","mod_learningmap/renderer","core_course/events"],(function(_exports,_modal,_ajax,manualcompletion,_renderer,_events){function _getRequireWildcardCache(nodeInterop){if("function"!=typeof WeakMap)return null;var cacheBabelInterop=new WeakMap,cacheNodeInterop=new WeakMap;return(_getRequireWildcardCache=function(nodeInterop){return nodeInterop?cacheNodeInterop:cacheBabelInterop})(nodeInterop)}function _interopRequireDefault(obj){return obj&&obj.__esModule?obj:{default:obj}}
+define("mod_learningmap/linkmodal",["exports","core/modal","core/modal_events","core/ajax","core/templates","core_course/manual_completion_toggle","mod_learningmap/renderer","core_course/events"],(function(_exports,_modal,_modal_events,_ajax,_templates,manualcompletion,_renderer,_events){function _getRequireWildcardCache(nodeInterop){if("function"!=typeof WeakMap)return null;var cacheBabelInterop=new WeakMap,cacheNodeInterop=new WeakMap;return(_getRequireWildcardCache=function(nodeInterop){return nodeInterop?cacheNodeInterop:cacheBabelInterop})(nodeInterop)}function _interopRequireDefault(obj){return obj&&obj.__esModule?obj:{default:obj}}
/**
* Helper for opening course modules in a modal that do not have a view page.
*
* @module mod_learningmap/linkmodal
* @copyright 2025 ISB Bayern
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
- */Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.openModal=_exports.init=void 0,_modal=_interopRequireDefault(_modal),_ajax=_interopRequireDefault(_ajax),manualcompletion=function(obj,nodeInterop){if(!nodeInterop&&obj&&obj.__esModule)return obj;if(null===obj||"object"!=typeof obj&&"function"!=typeof obj)return{default:obj};var cache=_getRequireWildcardCache(nodeInterop);if(cache&&cache.has(obj))return cache.get(obj);var newObj={},hasPropertyDescriptor=Object.defineProperty&&Object.getOwnPropertyDescriptor;for(var key in obj)if("default"!==key&&Object.prototype.hasOwnProperty.call(obj,key)){var desc=hasPropertyDescriptor?Object.getOwnPropertyDescriptor(obj,key):null;desc&&(desc.get||desc.set)?Object.defineProperty(newObj,key,desc):newObj[key]=obj[key]}newObj.default=obj,cache&&cache.set(obj,newObj);return newObj}(manualcompletion),_events=_interopRequireDefault(_events);_exports.init=async function(learningmapcmid){let inmodal=arguments.length>1&&void 0!==arguments[1]&&arguments[1];const container=document.getElementById("learningmap-render-container-"+learningmapcmid+(inmodal?"-modal":""));container&&(container.addEventListener("click",(async event=>{const target=event.target.closest("a[data-cmid]");if(target&&!target.hasAttribute("xlink:href")){event.preventDefault();target.getAttribute("data-cmid")&&await openModal(event,learningmapcmid,inmodal)}})),container.addEventListener("keydown",(async event=>{if("Enter"===event.key){const target=event.target.closest("a[data-cmid]");if(target&&!target.hasAttribute("xlink:href")){event.preventDefault();target.getAttribute("data-cmid")&&await openModal(event,learningmapcmid,inmodal)}}})))};const openModal=async(event,learningmapcmid,inmodal)=>{const target=event.target.closest("a[data-cmid]");if(target&&!target.hasAttribute("xlink:href")){event.preventDefault();const cmid=target.getAttribute("data-cmid");if(cmid){const data=await _ajax.default.call([{methodname:"mod_learningmap_get_cm",args:{cmid:cmid}}])[0],container=document.createElement("div");container.innerHTML=data.js;let js=Array.from(container.querySelectorAll("script")).map((s=>s.textContent||"")).join("\n");const modal=await _modal.default.create({title:data.name,body:data.completion+data.html,show:!1,removeOnClose:!0,large:!0});modal.bodyJS=js,modal.show(),manualcompletion.init(),document.addEventListener(_events.default.manualCompletionToggled,(()=>{(0,_renderer.renderLearningmap)(learningmapcmid,inmodal)}))}}};_exports.openModal=openModal}));
+ */Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.openModal=_exports.init=void 0,_modal=_interopRequireDefault(_modal),_modal_events=_interopRequireDefault(_modal_events),_ajax=_interopRequireDefault(_ajax),_templates=_interopRequireDefault(_templates),manualcompletion=function(obj,nodeInterop){if(!nodeInterop&&obj&&obj.__esModule)return obj;if(null===obj||"object"!=typeof obj&&"function"!=typeof obj)return{default:obj};var cache=_getRequireWildcardCache(nodeInterop);if(cache&&cache.has(obj))return cache.get(obj);var newObj={},hasPropertyDescriptor=Object.defineProperty&&Object.getOwnPropertyDescriptor;for(var key in obj)if("default"!==key&&Object.prototype.hasOwnProperty.call(obj,key)){var desc=hasPropertyDescriptor?Object.getOwnPropertyDescriptor(obj,key):null;desc&&(desc.get||desc.set)?Object.defineProperty(newObj,key,desc):newObj[key]=obj[key]}newObj.default=obj,cache&&cache.set(obj,newObj);return newObj}(manualcompletion),_events=_interopRequireDefault(_events);_exports.init=async function(learningmapcmid){let inmodal=arguments.length>1&&void 0!==arguments[1]&&arguments[1];const container=document.getElementById("learningmap-render-container-"+learningmapcmid+(inmodal?"-modal":""));container&&(container.addEventListener("click",(async event=>{const target=event.target.closest("a[data-cmid]");if(target&&!target.hasAttribute("xlink:href")){event.preventDefault();target.getAttribute("data-cmid")&&await openModal(event,learningmapcmid,inmodal)}})),container.addEventListener("keydown",(async event=>{if("Enter"===event.key){const target=event.target.closest("a[data-cmid]");if(target&&!target.hasAttribute("xlink:href")){event.preventDefault();target.getAttribute("data-cmid")&&await openModal(event,learningmapcmid,inmodal)}}})))};const openModal=async(event,learningmapcmid,inmodal)=>{const target=event.target.closest("a[data-cmid]");if(target&&!target.hasAttribute("xlink:href")){event.preventDefault();const cmid=target.getAttribute("data-cmid");if(cmid){const data=await _ajax.default.call([{methodname:"mod_learningmap_get_cm",args:{cmid:cmid}}])[0],container=document.createElement("div");container.innerHTML=data.js;let js=Array.from(container.querySelectorAll("script")).map((s=>s.textContent||"")).join("\n");const modalbody=await _templates.default.renderForPromise("mod_learningmap/modal_body",{completion:data.completion,content:data.html}),modal=await _modal.default.create({title:data.name,body:modalbody.html,show:!1,removeOnClose:!0,large:!0});modal.bodyJS=js,modal.show(),manualcompletion.init();const completionToggleHandler=()=>{(0,_renderer.renderLearningmap)(learningmapcmid,inmodal)};document.addEventListener(_events.default.manualCompletionToggled,completionToggleHandler),modal.getRoot().on(_modal_events.default.hidden,(()=>{document.removeEventListener(_events.default.manualCompletionToggled,completionToggleHandler)}))}}};_exports.openModal=openModal}));
//# sourceMappingURL=linkmodal.min.js.map
\ No newline at end of file
diff --git a/amd/build/linkmodal.min.js.map b/amd/build/linkmodal.min.js.map
index 87d14af9..650cd185 100644
--- a/amd/build/linkmodal.min.js.map
+++ b/amd/build/linkmodal.min.js.map
@@ -1 +1 @@
-{"version":3,"file":"linkmodal.min.js","sources":["../src/linkmodal.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 Modal from 'core/modal';\nimport Ajax from 'core/ajax';\nimport * as manualcompletion from 'core_course/manual_completion_toggle';\nimport {renderLearningmap} from 'mod_learningmap/renderer';\nimport CourseEvents from 'core_course/events';\n\n/**\n * Helper for opening course modules in a modal that do not have a view page.\n *\n * @module mod_learningmap/linkmodal\n * @copyright 2025 ISB Bayern\n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\n/**\n * Initialize the link modal listener for a learning map.\n *\n * @param {number} learningmapcmid - The course module ID of the learning map.\n * @param {boolean} inmodal - Whether the learning map is already in a modal.\n */\nexport const init = async(learningmapcmid, inmodal = false) => {\n const container = document.getElementById('learningmap-render-container-' + learningmapcmid + (inmodal ? '-modal' : ''));\n if (container) {\n container.addEventListener('click', async(event) => {\n const target = event.target.closest('a[data-cmid]');\n if (target && !target.hasAttribute('xlink:href')) {\n event.preventDefault();\n const cmid = target.getAttribute('data-cmid');\n if (cmid) {\n await openModal(event, learningmapcmid, inmodal);\n }\n }\n });\n container.addEventListener('keydown', async(event) => {\n if (event.key === 'Enter') {\n const target = event.target.closest('a[data-cmid]');\n if (target && !target.hasAttribute('xlink:href')) {\n event.preventDefault();\n const cmid = target.getAttribute('data-cmid');\n if (cmid) {\n await openModal(event, learningmapcmid, inmodal);\n }\n }\n }\n });\n }\n};\n\n/**\n * Opens a modal for a course module link that does not have a view page.\n * @param {Event} event - The click or keydown event.\n * @param {number} learningmapcmid - The course module ID of the learning map.\n * @param {boolean} inmodal - Whether the learning map is already in a modal.\n */\nexport const openModal = async(event, learningmapcmid, inmodal) => {\n const target = event.target.closest('a[data-cmid]');\n if (target && !target.hasAttribute('xlink:href')) {\n event.preventDefault();\n const cmid = target.getAttribute('data-cmid');\n if (cmid) {\n const data = await Ajax.call([{\n methodname: 'mod_learningmap_get_cm',\n args: {\n cmid: cmid\n },\n }])[0];\n const container = document.createElement('div');\n container.innerHTML = data.js;\n let js = Array.from(container.querySelectorAll('script'))\n .map(s => s.textContent || '')\n .join('\\n');\n const modal = await Modal.create({\n title: data.name,\n body: data.completion + data.html,\n show: false,\n removeOnClose: true,\n large: true,\n });\n modal.bodyJS = js;\n modal.show();\n manualcompletion.init();\n document.addEventListener(CourseEvents.manualCompletionToggled, () => {\n renderLearningmap(learningmapcmid, inmodal);\n });\n }\n }\n};\n"],"names":["async","learningmapcmid","inmodal","container","document","getElementById","addEventListener","target","event","closest","hasAttribute","preventDefault","getAttribute","openModal","key","cmid","data","Ajax","call","methodname","args","createElement","innerHTML","js","Array","from","querySelectorAll","map","s","textContent","join","modal","Modal","create","title","name","body","completion","html","show","removeOnClose","large","bodyJS","manualcompletion","init","CourseEvents","manualCompletionToggled"],"mappings":";;;;;;;q5BAmCoBA,eAAMC,qBAAiBC,sEACjCC,UAAYC,SAASC,eAAe,gCAAkCJ,iBAAmBC,QAAU,SAAW,KAChHC,YACAA,UAAUG,iBAAiB,SAASN,MAAAA,cAC1BO,OAASC,MAAMD,OAAOE,QAAQ,mBAChCF,SAAWA,OAAOG,aAAa,cAAe,CAC9CF,MAAMG,iBACOJ,OAAOK,aAAa,oBAEvBC,UAAUL,MAAOP,gBAAiBC,aAIpDC,UAAUG,iBAAiB,WAAWN,MAAAA,WAChB,UAAdQ,MAAMM,IAAiB,OACjBP,OAASC,MAAMD,OAAOE,QAAQ,mBAChCF,SAAWA,OAAOG,aAAa,cAAe,CAC9CF,MAAMG,iBACOJ,OAAOK,aAAa,oBAEvBC,UAAUL,MAAOP,gBAAiBC,sBAcnDW,UAAYb,MAAMQ,MAAOP,gBAAiBC,iBAC7CK,OAASC,MAAMD,OAAOE,QAAQ,mBAChCF,SAAWA,OAAOG,aAAa,cAAe,CAC9CF,MAAMG,uBACAI,KAAOR,OAAOK,aAAa,gBAC7BG,KAAM,OACAC,WAAaC,cAAKC,KAAK,CAAC,CAC1BC,WAAY,yBACZC,KAAM,CACFL,KAAMA,SAEV,GACEZ,UAAYC,SAASiB,cAAc,OACzClB,UAAUmB,UAAYN,KAAKO,OACvBA,GAAKC,MAAMC,KAAKtB,UAAUuB,iBAAiB,WAC1CC,KAAIC,GAAKA,EAAEC,aAAe,KAC1BC,KAAK,YACJC,YAAcC,eAAMC,OAAO,CAC7BC,MAAOlB,KAAKmB,KACZC,KAAMpB,KAAKqB,WAAarB,KAAKsB,KAC7BC,MAAM,EACNC,eAAe,EACfC,OAAO,IAEXV,MAAMW,OAASnB,GACfQ,MAAMQ,OACNI,iBAAiBC,OACjBxC,SAASE,iBAAiBuC,gBAAaC,yBAAyB,qCAC1C7C,gBAAiBC"}
\ No newline at end of file
+{"version":3,"file":"linkmodal.min.js","sources":["../src/linkmodal.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 Modal from 'core/modal';\nimport ModalEvents from 'core/modal_events';\nimport Ajax from 'core/ajax';\nimport Templates from 'core/templates';\nimport * as manualcompletion from 'core_course/manual_completion_toggle';\nimport {renderLearningmap} from 'mod_learningmap/renderer';\nimport CourseEvents from 'core_course/events';\n\n/**\n * Helper for opening course modules in a modal that do not have a view page.\n *\n * @module mod_learningmap/linkmodal\n * @copyright 2025 ISB Bayern\n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\n/**\n * Initialize the link modal listener for a learning map.\n *\n * @param {number} learningmapcmid - The course module ID of the learning map.\n * @param {boolean} inmodal - Whether the learning map is already in a modal.\n */\nexport const init = async(learningmapcmid, inmodal = false) => {\n const container = document.getElementById('learningmap-render-container-' + learningmapcmid + (inmodal ? '-modal' : ''));\n if (container) {\n container.addEventListener('click', async(event) => {\n const target = event.target.closest('a[data-cmid]');\n if (target && !target.hasAttribute('xlink:href')) {\n event.preventDefault();\n const cmid = target.getAttribute('data-cmid');\n if (cmid) {\n await openModal(event, learningmapcmid, inmodal);\n }\n }\n });\n container.addEventListener('keydown', async(event) => {\n if (event.key === 'Enter') {\n const target = event.target.closest('a[data-cmid]');\n if (target && !target.hasAttribute('xlink:href')) {\n event.preventDefault();\n const cmid = target.getAttribute('data-cmid');\n if (cmid) {\n await openModal(event, learningmapcmid, inmodal);\n }\n }\n }\n });\n }\n};\n\n/**\n * Opens a modal for a course module link that does not have a view page.\n * @param {Event} event - The click or keydown event.\n * @param {number} learningmapcmid - The course module ID of the learning map.\n * @param {boolean} inmodal - Whether the learning map is already in a modal.\n */\nexport const openModal = async(event, learningmapcmid, inmodal) => {\n const target = event.target.closest('a[data-cmid]');\n if (target && !target.hasAttribute('xlink:href')) {\n event.preventDefault();\n const cmid = target.getAttribute('data-cmid');\n if (cmid) {\n const data = await Ajax.call([{\n methodname: 'mod_learningmap_get_cm',\n args: {\n cmid: cmid\n },\n }])[0];\n const container = document.createElement('div');\n container.innerHTML = data.js;\n let js = Array.from(container.querySelectorAll('script'))\n .map(s => s.textContent || '')\n .join('\\n');\n\n const modalbody = await Templates.renderForPromise('mod_learningmap/modal_body', {\n completion: data.completion,\n content: data.html,\n });\n\n const modal = await Modal.create({\n title: data.name,\n body: modalbody.html,\n show: false,\n removeOnClose: true,\n large: true,\n });\n modal.bodyJS = js;\n modal.show();\n manualcompletion.init();\n const completionToggleHandler = () => {\n renderLearningmap(learningmapcmid, inmodal);\n };\n document.addEventListener(CourseEvents.manualCompletionToggled, completionToggleHandler);\n modal.getRoot().on(ModalEvents.hidden, () => {\n document.removeEventListener(CourseEvents.manualCompletionToggled, completionToggleHandler);\n });\n }\n }\n};\n"],"names":["async","learningmapcmid","inmodal","container","document","getElementById","addEventListener","target","event","closest","hasAttribute","preventDefault","getAttribute","openModal","key","cmid","data","Ajax","call","methodname","args","createElement","innerHTML","js","Array","from","querySelectorAll","map","s","textContent","join","modalbody","Templates","renderForPromise","completion","content","html","modal","Modal","create","title","name","body","show","removeOnClose","large","bodyJS","manualcompletion","init","completionToggleHandler","CourseEvents","manualCompletionToggled","getRoot","on","ModalEvents","hidden","removeEventListener"],"mappings":";;;;;;;u/BAqCoBA,eAAMC,qBAAiBC,sEACjCC,UAAYC,SAASC,eAAe,gCAAkCJ,iBAAmBC,QAAU,SAAW,KAChHC,YACAA,UAAUG,iBAAiB,SAASN,MAAAA,cAC1BO,OAASC,MAAMD,OAAOE,QAAQ,mBAChCF,SAAWA,OAAOG,aAAa,cAAe,CAC9CF,MAAMG,iBACOJ,OAAOK,aAAa,oBAEvBC,UAAUL,MAAOP,gBAAiBC,aAIpDC,UAAUG,iBAAiB,WAAWN,MAAAA,WAChB,UAAdQ,MAAMM,IAAiB,OACjBP,OAASC,MAAMD,OAAOE,QAAQ,mBAChCF,SAAWA,OAAOG,aAAa,cAAe,CAC9CF,MAAMG,iBACOJ,OAAOK,aAAa,oBAEvBC,UAAUL,MAAOP,gBAAiBC,sBAcnDW,UAAYb,MAAMQ,MAAOP,gBAAiBC,iBAC7CK,OAASC,MAAMD,OAAOE,QAAQ,mBAChCF,SAAWA,OAAOG,aAAa,cAAe,CAC9CF,MAAMG,uBACAI,KAAOR,OAAOK,aAAa,gBAC7BG,KAAM,OACAC,WAAaC,cAAKC,KAAK,CAAC,CAC1BC,WAAY,yBACZC,KAAM,CACFL,KAAMA,SAEV,GACEZ,UAAYC,SAASiB,cAAc,OACzClB,UAAUmB,UAAYN,KAAKO,OACvBA,GAAKC,MAAMC,KAAKtB,UAAUuB,iBAAiB,WAC1CC,KAAIC,GAAKA,EAAEC,aAAe,KAC1BC,KAAK,YAEJC,gBAAkBC,mBAAUC,iBAAiB,6BAA8B,CAC7EC,WAAYlB,KAAKkB,WACjBC,QAASnB,KAAKoB,OAGZC,YAAcC,eAAMC,OAAO,CAC7BC,MAAOxB,KAAKyB,KACZC,KAAMX,UAAUK,KAChBO,MAAM,EACNC,eAAe,EACfC,OAAO,IAEXR,MAAMS,OAASvB,GACfc,MAAMM,OACNI,iBAAiBC,aACXC,wBAA0B,qCACVhD,gBAAiBC,UAEvCE,SAASE,iBAAiB4C,gBAAaC,wBAAyBF,yBAChEZ,MAAMe,UAAUC,GAAGC,sBAAYC,QAAQ,KACnCnD,SAASoD,oBAAoBN,gBAAaC,wBAAyBF"}
\ No newline at end of file
diff --git a/amd/build/renderer.min.js b/amd/build/renderer.min.js
index 2fc3ed38..2f11d88d 100644
--- a/amd/build/renderer.min.js
+++ b/amd/build/renderer.min.js
@@ -6,6 +6,6 @@ define("mod_learningmap/renderer",["exports","core/ajax","core/log","core/pendin
* @copyright 2021-2024, ISB Bayern
* @author Philipp Memmel
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
- */Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.selectors=_exports.renderLearningmap=_exports.init=void 0,_ajax=_interopRequireDefault(_ajax),_log=_interopRequireDefault(_log),_pending=_interopRequireDefault(_pending);const selectors={LEARNINGMAP_RENDER_CONTAINER_PREFIX:"learningmap-render-container-"};_exports.selectors=selectors;_exports.init=function(cmId){let inmodal=arguments.length>1&&void 0!==arguments[1]&&arguments[1];const rendererPendingPromise=new _pending.default("mod_learningmap/renderer-"+cmId);renderLearningmap(cmId,inmodal),rendererPendingPromise.resolve()};const renderLearningmap=cmId=>{_ajax.default.call([{methodname:"mod_learningmap_get_learningmap",args:{cmId:cmId}}])[0].then((data=>{let targetDiv=document.getElementById(selectors.LEARNINGMAP_RENDER_CONTAINER_PREFIX+cmId),svgnode=(new DOMParser).parseFromString(data.content,"image/svg+xml").querySelector("svg");return targetDiv&&targetDiv.replaceChildren(svgnode),targetDiv=document.getElementById(selectors.LEARNINGMAP_RENDER_CONTAINER_PREFIX+cmId+"-modal"),targetDiv&&(targetDiv.replaceChildren(svgnode),targetDiv.parentElement.previousElementSibling.outerHTML=data.completion),!0})).catch((error=>(_log.default.error(error),!1)))};_exports.renderLearningmap=renderLearningmap}));
+ */Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.selectors=_exports.renderLearningmap=_exports.init=void 0,_ajax=_interopRequireDefault(_ajax),_log=_interopRequireDefault(_log),_pending=_interopRequireDefault(_pending);const selectors={LEARNINGMAP_RENDER_CONTAINER_PREFIX:"learningmap-render-container-",MODAL_BODY:".modal-body",MODAL_COMPLETION_CONTAINER:'[data-region="learningmap-modal-completion"]'};_exports.selectors=selectors;_exports.init=function(cmId){let inmodal=arguments.length>1&&void 0!==arguments[1]&&arguments[1];const rendererPendingPromise=new _pending.default("mod_learningmap/renderer-"+cmId);renderLearningmap(cmId,inmodal),rendererPendingPromise.resolve()};const renderLearningmap=cmId=>{_ajax.default.call([{methodname:"mod_learningmap_get_learningmap",args:{cmId:cmId}}])[0].then((data=>{let targetDiv=document.getElementById(selectors.LEARNINGMAP_RENDER_CONTAINER_PREFIX+cmId),svgnode=(new DOMParser).parseFromString(data.content,"image/svg+xml").querySelector("svg");return targetDiv&&targetDiv.replaceChildren(svgnode),targetDiv=document.getElementById(selectors.LEARNINGMAP_RENDER_CONTAINER_PREFIX+cmId+"-modal"),targetDiv&&(targetDiv.replaceChildren(svgnode),((modalTargetDiv,completionHtml)=>{const modalBody=modalTargetDiv.closest(selectors.MODAL_BODY);if(!modalBody)return;const completionContainer=modalBody.querySelector(selectors.MODAL_COMPLETION_CONTAINER);completionContainer&&(completionContainer.innerHTML=completionHtml)})(targetDiv,data.completion)),!0})).catch((error=>(_log.default.error(error),!1)))};_exports.renderLearningmap=renderLearningmap}));
//# sourceMappingURL=renderer.min.js.map
\ No newline at end of file
diff --git a/amd/build/renderer.min.js.map b/amd/build/renderer.min.js.map
index adb2c52e..ce9d9143 100644
--- a/amd/build/renderer.min.js.map
+++ b/amd/build/renderer.min.js.map
@@ -1 +1 @@
-{"version":3,"file":"renderer.min.js","sources":["../src/renderer.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 * Renderer module for the learningmap.\n *\n * @module mod_learningmap/renderer\n * @copyright 2021-2024, ISB Bayern\n * @author Philipp Memmel\n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\nimport Ajax from 'core/ajax';\nimport Log from 'core/log';\nimport Pending from 'core/pending';\n\nexport const selectors = {\n LEARNINGMAP_RENDER_CONTAINER_PREFIX: 'learningmap-render-container-'\n};\n\n/**\n * Renders the learningmap into the correct div.\n *\n * @param {number} cmId the course module id of the learningmap\n * @param {boolean} inmodal whether the learningmap is being rendered in a modal\n */\nexport const init = (cmId, inmodal = false) => {\n const rendererPendingPromise = new Pending('mod_learningmap/renderer-' + cmId);\n renderLearningmap(cmId, inmodal);\n rendererPendingPromise.resolve();\n};\n\n/**\n * Render the learningmap with the given cmId into the corresponding div in the DOM.\n *\n * @param {number} cmId the course module id of the learningmap\n */\nexport const renderLearningmap = (cmId) => {\n const promises = Ajax.call(\n [\n {\n methodname: 'mod_learningmap_get_learningmap',\n args: {\n 'cmId': cmId\n }\n }\n ]);\n\n promises[0].then(data => {\n let targetDiv = document.getElementById(selectors.LEARNINGMAP_RENDER_CONTAINER_PREFIX + cmId);\n let svgdoc = new DOMParser().parseFromString(data.content, 'image/svg+xml');\n let svgnode = svgdoc.querySelector('svg');\n if (targetDiv) {\n targetDiv.replaceChildren(svgnode);\n }\n targetDiv = document.getElementById(selectors.LEARNINGMAP_RENDER_CONTAINER_PREFIX + cmId + '-modal');\n if (targetDiv) {\n targetDiv.replaceChildren(svgnode);\n targetDiv.parentElement.previousElementSibling.outerHTML = data.completion;\n }\n return true;\n }).catch((error) => {\n Log.error(error);\n return false;\n });\n};\n"],"names":["selectors","LEARNINGMAP_RENDER_CONTAINER_PREFIX","cmId","inmodal","rendererPendingPromise","Pending","renderLearningmap","resolve","Ajax","call","methodname","args","then","data","targetDiv","document","getElementById","svgnode","DOMParser","parseFromString","content","querySelector","replaceChildren","parentElement","previousElementSibling","outerHTML","completion","catch","error"],"mappings":";;;;;;;;sPA2BaA,UAAY,CACrBC,oCAAqC,4EASrB,SAACC,UAAMC,sEACjBC,uBAAyB,IAAIC,iBAAQ,4BAA8BH,MACzEI,kBAAkBJ,KAAMC,SACxBC,uBAAuBG,iBAQdD,kBAAqBJ,OACbM,cAAKC,KAClB,CACI,CACIC,WAAY,kCACZC,KAAM,MACMT,SAKf,GAAGU,MAAKC,WACTC,UAAYC,SAASC,eAAehB,UAAUC,oCAAsCC,MAEpFe,SADS,IAAIC,WAAYC,gBAAgBN,KAAKO,QAAS,iBACtCC,cAAc,cAC/BP,WACAA,UAAUQ,gBAAgBL,SAE9BH,UAAYC,SAASC,eAAehB,UAAUC,oCAAsCC,KAAO,UACvFY,YACAA,UAAUQ,gBAAgBL,SAC1BH,UAAUS,cAAcC,uBAAuBC,UAAYZ,KAAKa,aAE7D,KACRC,OAAOC,qBACFA,MAAMA,QACH"}
\ No newline at end of file
+{"version":3,"file":"renderer.min.js","sources":["../src/renderer.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 * Renderer module for the learningmap.\n *\n * @module mod_learningmap/renderer\n * @copyright 2021-2024, ISB Bayern\n * @author Philipp Memmel\n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\nimport Ajax from 'core/ajax';\nimport Log from 'core/log';\nimport Pending from 'core/pending';\n\nexport const selectors = {\n LEARNINGMAP_RENDER_CONTAINER_PREFIX: 'learningmap-render-container-',\n MODAL_BODY: '.modal-body',\n MODAL_COMPLETION_CONTAINER: '[data-region=\"learningmap-modal-completion\"]',\n};\n\n/**\n * Replace the completion header in the learningmap modal.\n *\n * @param {Element} modalTargetDiv The learningmap render container inside the modal.\n * @param {string} completionHtml The new completion header HTML.\n */\nconst replaceModalCompletionHeader = (modalTargetDiv, completionHtml) => {\n const modalBody = modalTargetDiv.closest(selectors.MODAL_BODY);\n if (!modalBody) {\n return;\n }\n\n const completionContainer = modalBody.querySelector(selectors.MODAL_COMPLETION_CONTAINER);\n if (completionContainer) {\n completionContainer.innerHTML = completionHtml;\n }\n};\n\n/**\n * Renders the learningmap into the correct div.\n *\n * @param {number} cmId the course module id of the learningmap\n * @param {boolean} inmodal whether the learningmap is being rendered in a modal\n */\nexport const init = (cmId, inmodal = false) => {\n const rendererPendingPromise = new Pending('mod_learningmap/renderer-' + cmId);\n renderLearningmap(cmId, inmodal);\n rendererPendingPromise.resolve();\n};\n\n/**\n * Render the learningmap with the given cmId into the corresponding div in the DOM.\n *\n * @param {number} cmId the course module id of the learningmap\n */\nexport const renderLearningmap = (cmId) => {\n const promises = Ajax.call(\n [\n {\n methodname: 'mod_learningmap_get_learningmap',\n args: {\n 'cmId': cmId\n }\n }\n ]);\n\n promises[0].then(data => {\n let targetDiv = document.getElementById(selectors.LEARNINGMAP_RENDER_CONTAINER_PREFIX + cmId);\n let svgdoc = new DOMParser().parseFromString(data.content, 'image/svg+xml');\n let svgnode = svgdoc.querySelector('svg');\n if (targetDiv) {\n targetDiv.replaceChildren(svgnode);\n }\n targetDiv = document.getElementById(selectors.LEARNINGMAP_RENDER_CONTAINER_PREFIX + cmId + '-modal');\n if (targetDiv) {\n targetDiv.replaceChildren(svgnode);\n replaceModalCompletionHeader(targetDiv, data.completion);\n }\n return true;\n }).catch((error) => {\n Log.error(error);\n return false;\n });\n};\n"],"names":["selectors","LEARNINGMAP_RENDER_CONTAINER_PREFIX","MODAL_BODY","MODAL_COMPLETION_CONTAINER","cmId","inmodal","rendererPendingPromise","Pending","renderLearningmap","resolve","Ajax","call","methodname","args","then","data","targetDiv","document","getElementById","svgnode","DOMParser","parseFromString","content","querySelector","replaceChildren","modalTargetDiv","completionHtml","modalBody","closest","completionContainer","innerHTML","replaceModalCompletionHeader","completion","catch","error"],"mappings":";;;;;;;;sPA2BaA,UAAY,CACrBC,oCAAqC,gCACrCC,WAAY,cACZC,2BAA4B,2FA2BZ,SAACC,UAAMC,sEACjBC,uBAAyB,IAAIC,iBAAQ,4BAA8BH,MACzEI,kBAAkBJ,KAAMC,SACxBC,uBAAuBG,iBAQdD,kBAAqBJ,OACbM,cAAKC,KAClB,CACI,CACIC,WAAY,kCACZC,KAAM,MACMT,SAKf,GAAGU,MAAKC,WACTC,UAAYC,SAASC,eAAelB,UAAUC,oCAAsCG,MAEpFe,SADS,IAAIC,WAAYC,gBAAgBN,KAAKO,QAAS,iBACtCC,cAAc,cAC/BP,WACAA,UAAUQ,gBAAgBL,SAE9BH,UAAYC,SAASC,eAAelB,UAAUC,oCAAsCG,KAAO,UACvFY,YACAA,UAAUQ,gBAAgBL,SAjDD,EAACM,eAAgBC,wBAC5CC,UAAYF,eAAeG,QAAQ5B,UAAUE,gBAC9CyB,uBAICE,oBAAsBF,UAAUJ,cAAcvB,UAAUG,4BAC1D0B,sBACAA,oBAAoBC,UAAYJ,iBA0C5BK,CAA6Bf,UAAWD,KAAKiB,cAE1C,KACRC,OAAOC,qBACFA,MAAMA,QACH"}
\ No newline at end of file
diff --git a/amd/src/linkmodal.js b/amd/src/linkmodal.js
index 4d35a778..77328fa5 100644
--- a/amd/src/linkmodal.js
+++ b/amd/src/linkmodal.js
@@ -14,7 +14,9 @@
// along with Moodle. If not, see .
import Modal from 'core/modal';
+import ModalEvents from 'core/modal_events';
import Ajax from 'core/ajax';
+import Templates from 'core/templates';
import * as manualcompletion from 'core_course/manual_completion_toggle';
import {renderLearningmap} from 'mod_learningmap/renderer';
import CourseEvents from 'core_course/events';
@@ -84,9 +86,15 @@ export const openModal = async(event, learningmapcmid, inmodal) => {
let js = Array.from(container.querySelectorAll('script'))
.map(s => s.textContent || '')
.join('\n');
+
+ const modalbody = await Templates.renderForPromise('mod_learningmap/modal_body', {
+ completion: data.completion,
+ content: data.html,
+ });
+
const modal = await Modal.create({
title: data.name,
- body: data.completion + data.html,
+ body: modalbody.html,
show: false,
removeOnClose: true,
large: true,
@@ -94,8 +102,12 @@ export const openModal = async(event, learningmapcmid, inmodal) => {
modal.bodyJS = js;
modal.show();
manualcompletion.init();
- document.addEventListener(CourseEvents.manualCompletionToggled, () => {
+ const completionToggleHandler = () => {
renderLearningmap(learningmapcmid, inmodal);
+ };
+ document.addEventListener(CourseEvents.manualCompletionToggled, completionToggleHandler);
+ modal.getRoot().on(ModalEvents.hidden, () => {
+ document.removeEventListener(CourseEvents.manualCompletionToggled, completionToggleHandler);
});
}
}
diff --git a/amd/src/renderer.js b/amd/src/renderer.js
index 29739bd8..9178915e 100644
--- a/amd/src/renderer.js
+++ b/amd/src/renderer.js
@@ -26,7 +26,27 @@ import Log from 'core/log';
import Pending from 'core/pending';
export const selectors = {
- LEARNINGMAP_RENDER_CONTAINER_PREFIX: 'learningmap-render-container-'
+ LEARNINGMAP_RENDER_CONTAINER_PREFIX: 'learningmap-render-container-',
+ MODAL_BODY: '.modal-body',
+ MODAL_COMPLETION_CONTAINER: '[data-region="learningmap-modal-completion"]',
+};
+
+/**
+ * Replace the completion header in the learningmap modal.
+ *
+ * @param {Element} modalTargetDiv The learningmap render container inside the modal.
+ * @param {string} completionHtml The new completion header HTML.
+ */
+const replaceModalCompletionHeader = (modalTargetDiv, completionHtml) => {
+ const modalBody = modalTargetDiv.closest(selectors.MODAL_BODY);
+ if (!modalBody) {
+ return;
+ }
+
+ const completionContainer = modalBody.querySelector(selectors.MODAL_COMPLETION_CONTAINER);
+ if (completionContainer) {
+ completionContainer.innerHTML = completionHtml;
+ }
};
/**
@@ -67,7 +87,7 @@ export const renderLearningmap = (cmId) => {
targetDiv = document.getElementById(selectors.LEARNINGMAP_RENDER_CONTAINER_PREFIX + cmId + '-modal');
if (targetDiv) {
targetDiv.replaceChildren(svgnode);
- targetDiv.parentElement.previousElementSibling.outerHTML = data.completion;
+ replaceModalCompletionHeader(targetDiv, data.completion);
}
return true;
}).catch((error) => {
diff --git a/templates/modal_body.mustache b/templates/modal_body.mustache
new file mode 100644
index 00000000..459c0252
--- /dev/null
+++ b/templates/modal_body.mustache
@@ -0,0 +1,37 @@
+{{!
+ This file is part of Moodle - https://moodle.org/
+
+ Moodle is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ Moodle is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with Moodle. If not, see .
+}}
+{{!
+ @template mod_learningmap/modal_body
+
+ Template for the learningmap modal body split into dedicated regions.
+
+ Context variables required for this template:
+ * completion Completion header html
+ * content Main activity html
+
+ Example context (json):
+ {
+ "completion": "
...
",
+ "content": "...
"
+ }
+}}
+
+ {{{completion}}}
+
+
+ {{{content}}}
+