diff --git a/public/admin/tool/moodlenet/amd/build/instance_form.min.js b/public/admin/tool/moodlenet/amd/build/instance_form.min.js index c9c382b3944..878e87cd1a0 100644 --- a/public/admin/tool/moodlenet/amd/build/instance_form.min.js +++ b/public/admin/tool/moodlenet/amd/build/instance_form.min.js @@ -12,6 +12,6 @@ * @copyright 2020 Mathew May * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ -define("tool_moodlenet/instance_form",["tool_moodlenet/validator","tool_moodlenet/selectors","core/loadingicon","core/templates","core/notification","jquery","theme_boost/bootstrap/carousel","core/normalise"],(function(Validator,Selectors,LoadingIcon,Templates,Notification,$,Carousel,Normalise){var chooserNavigateToMnet=function(showMoodleNet,footerData,carousel,modal){showMoodleNet.innerHTML="";var page,spinnerPromise=LoadingIcon.addIconToContainer(showMoodleNet),transitionPromiseResolver=null,transitionPromise=new Promise((resolve=>{transitionPromiseResolver=resolve}));$.when(spinnerPromise,transitionPromise).then((function(){Templates.replaceNodeContents(showMoodleNet,footerData.customcarouseltemplate,"")})).catch(Notification.exception),(page=showMoodleNet).addEventListener("click",(function(e){if(e.target.matches(Selectors.action.submit)){var input=page.querySelector('[data-var="mnet-link"]'),overlay=page.querySelector(Selectors.region.spinner),validationArea=document.querySelector(Selectors.region.validationArea);overlay.classList.remove("d-none");var spinner=LoadingIcon.addIconToContainerWithPromise(overlay);Validator.validation(input).then((function(result){spinner.resolve(),overlay.classList.add("d-none"),result.result?(input.classList.remove("is-invalid"),input.classList.add("is-valid"),validationArea.innerText=result.message,validationArea.classList.remove("text-danger"),validationArea.classList.add("text-success"),setTimeout((function(){window.location=result.domain}),1e3)):(input.classList.add("is-invalid"),validationArea.innerText=result.message,validationArea.classList.add("text-danger"))})).catch(Notification.exception)}})),carousel.addEventListener("slid.bs.carousel",(function(){transitionPromiseResolver()}),{once:!0}),Carousel.getInstance(carousel).to(2),modal.setFooter(Templates.render("tool_moodlenet/chooser_footer_close_mnet",{}))};return{footerClickListener:function(e,footerData,modal){const modalBody=Normalise.getFirst(modal.getBody());if(e.target.matches(Selectors.action.showMoodleNet)||e.target.closest(Selectors.action.showMoodleNet)){e.preventDefault();const carousel=modalBody.querySelector(Selectors.region.carousel),showMoodleNet=carousel.querySelector(Selectors.region.moodleNet);chooserNavigateToMnet(showMoodleNet,footerData,carousel,modal)}if(e.target.matches(Selectors.action.closeOption)){!function(carousel,modal,footerData){Carousel.getInstance(carousel).to(0),modal.setFooter(footerData.customfootertemplate)}(modalBody.querySelector(Selectors.region.carousel),modal,footerData)}}}})); +define("tool_moodlenet/instance_form",["tool_moodlenet/validator","tool_moodlenet/selectors","core/loadingicon","core/templates","core/notification","jquery","theme_boost/bootstrap/carousel","core/normalise"],(function(Validator,Selectors,LoadingIcon,Templates,Notification,$,Carousel,Normalise){var chooserNavigateToMnet=function(showMoodleNet,footerData,carousel){showMoodleNet.innerHTML="";var page,spinnerPromise=LoadingIcon.addIconToContainer(showMoodleNet),transitionPromiseResolver=null,transitionPromise=new Promise((resolve=>{transitionPromiseResolver=resolve}));$.when(spinnerPromise,transitionPromise).then((function(){Templates.replaceNodeContents(showMoodleNet,footerData.customcarouseltemplate,"")})).catch(Notification.exception),(page=showMoodleNet).addEventListener("click",(function(e){if(e.target.matches(Selectors.action.submit)){var input=page.querySelector('[data-var="mnet-link"]'),overlay=page.querySelector(Selectors.region.spinner),validationArea=document.querySelector(Selectors.region.validationArea);overlay.classList.remove("d-none");var spinner=LoadingIcon.addIconToContainerWithPromise(overlay);Validator.validation(input).then((function(result){spinner.resolve(),overlay.classList.add("d-none"),result.result?(input.classList.remove("is-invalid"),input.classList.add("is-valid"),validationArea.innerText=result.message,validationArea.classList.remove("text-danger"),validationArea.classList.add("text-success"),setTimeout((function(){window.location=result.domain}),1e3)):(input.classList.add("is-invalid"),validationArea.innerText=result.message,validationArea.classList.add("text-danger"))})).catch(Notification.exception)}})),carousel.addEventListener("slid.bs.carousel",(function(){transitionPromiseResolver()}),{once:!0}),Carousel.getInstance(carousel).to(2)};return{footerClickListener:function(e,footerData,modal){const modalBody=Normalise.getFirst(modal.getBody());if(e.target.matches(Selectors.action.showMoodleNet)||e.target.closest(Selectors.action.showMoodleNet)){e.preventDefault();const carousel=modalBody.querySelector(Selectors.region.carousel),showMoodleNet=carousel.querySelector(Selectors.region.moodleNet);chooserNavigateToMnet(showMoodleNet,footerData,carousel)}if(e.target.matches(Selectors.action.closeOption)){!function(carousel,modal,footerData){Carousel.getInstance(carousel).to(0),modal.setFooter(footerData.customfootertemplate)}(modalBody.querySelector(Selectors.region.carousel),modal,footerData)}}}})); //# sourceMappingURL=instance_form.min.js.map \ No newline at end of file diff --git a/public/admin/tool/moodlenet/amd/build/instance_form.min.js.map b/public/admin/tool/moodlenet/amd/build/instance_form.min.js.map index d7f166319b0..54c22d57953 100644 --- a/public/admin/tool/moodlenet/amd/build/instance_form.min.js.map +++ b/public/admin/tool/moodlenet/amd/build/instance_form.min.js.map @@ -1 +1 @@ -{"version":3,"file":"instance_form.min.js","sources":["../src/instance_form.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 * Our basic form manager for when a user either enters\n * their profile url or just wants to browse.\n *\n * This file is a mishmash of JS functions we need for both the standalone (M3.7, M3.8)\n * plugin & Moodle 3.9 functions. The 3.9 Functions have a base understanding that certain\n * things exist i.e. directory structures for templates. When this feature goes 3.9+ only\n * The goal is that we can quickly gut all AMD modules into bare JS files and use ES6 guidelines.\n * Till then this will have to do.\n *\n * @module tool_moodlenet/instance_form\n * @copyright 2020 Mathew May \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\ndefine(['tool_moodlenet/validator',\n 'tool_moodlenet/selectors',\n 'core/loadingicon',\n 'core/templates',\n 'core/notification',\n 'jquery',\n 'theme_boost/bootstrap/carousel',\n 'core/normalise'],\n function(Validator,\n Selectors,\n LoadingIcon,\n Templates,\n Notification,\n $,\n Carousel,\n Normalise) {\n\n /**\n * Add the event listeners to our form.\n *\n * @method registerListenerEvents\n * @param {HTMLElement} page The whole page element for our form area\n */\n var registerListenerEvents = function registerListenerEvents(page) {\n page.addEventListener('click', function(e) {\n\n // Our fake submit button / browse button.\n if (e.target.matches(Selectors.action.submit)) {\n var input = page.querySelector('[data-var=\"mnet-link\"]');\n var overlay = page.querySelector(Selectors.region.spinner);\n var validationArea = document.querySelector(Selectors.region.validationArea);\n\n overlay.classList.remove('d-none');\n var spinner = LoadingIcon.addIconToContainerWithPromise(overlay);\n Validator.validation(input)\n .then(function(result) {\n spinner.resolve();\n overlay.classList.add('d-none');\n if (result.result) {\n input.classList.remove('is-invalid'); // Just in case the class has been applied already.\n input.classList.add('is-valid');\n validationArea.innerText = result.message;\n validationArea.classList.remove('text-danger');\n validationArea.classList.add('text-success');\n // Give the user some time to see their input is valid.\n setTimeout(function() {\n window.location = result.domain;\n }, 1000);\n } else {\n input.classList.add('is-invalid');\n validationArea.innerText = result.message;\n validationArea.classList.add('text-danger');\n }\n return;\n }).catch(Notification.exception);\n }\n });\n };\n\n /**\n * Given a user wishes to see the MoodleNet profile url form transition them there.\n *\n * @method chooserNavigateToMnet\n * @param {HTMLElement} showMoodleNet The chooser's area for ment\n * @param {Object} footerData Our footer object to render out\n * @param {Element} carousel Our carousel instance to manage\n * @param {jQuery} modal Our modal instance to manage\n */\n var chooserNavigateToMnet = function(showMoodleNet, footerData, carousel, modal) {\n showMoodleNet.innerHTML = '';\n\n // Add a spinner.\n var spinnerPromise = LoadingIcon.addIconToContainer(showMoodleNet);\n\n // Used later...\n var transitionPromiseResolver = null;\n var transitionPromise = new Promise(resolve => {\n transitionPromiseResolver = resolve;\n });\n\n $.when(\n spinnerPromise,\n transitionPromise\n ).then(function() {\n Templates.replaceNodeContents(showMoodleNet, footerData.customcarouseltemplate, '');\n return;\n }).catch(Notification.exception);\n\n // We apply our handlers in here to minimise plugin dependency in the Chooser.\n registerListenerEvents(showMoodleNet);\n\n // Move to the next slide, and resolve the transition promise when it's done.\n carousel.addEventListener('slid.bs.carousel', function() {\n transitionPromiseResolver();\n }, {once: true});\n // Trigger the transition between 'pages'.\n Carousel.getInstance(carousel).to(2);\n modal.setFooter(Templates.render('tool_moodlenet/chooser_footer_close_mnet', {}));\n };\n\n /**\n * Given a user no longer wishes to see the MoodleNet profile url form transition them from there.\n *\n * @method chooserNavigateFromMnet\n * @param {Element} carousel Our carousel instance to manage\n * @param {jQuery} modal Our modal instance to manage\n * @param {Object} footerData Our footer object to render out\n */\n var chooserNavigateFromMnet = function(carousel, modal, footerData) {\n // Trigger the transition between 'pages'.\n Carousel.getInstance(carousel).to(0);\n modal.setFooter(footerData.customfootertemplate);\n };\n\n /**\n * Create the custom listener that would handle anything in the footer.\n *\n * @param {Event} e The event being triggered.\n * @param {Object} footerData The data generated from the exporter.\n * @param {Object} modal The chooser modal.\n */\n var footerClickListener = function(e, footerData, modal) {\n const modalBody = Normalise.getFirst(modal.getBody());\n if (e.target.matches(Selectors.action.showMoodleNet) || e.target.closest(Selectors.action.showMoodleNet)) {\n e.preventDefault();\n const carousel = modalBody.querySelector(Selectors.region.carousel);\n const showMoodleNet = carousel.querySelector(Selectors.region.moodleNet);\n\n chooserNavigateToMnet(showMoodleNet, footerData, carousel, modal);\n }\n // From the help screen go back to the module overview.\n if (e.target.matches(Selectors.action.closeOption)) {\n const carousel = modalBody.querySelector(Selectors.region.carousel);\n\n chooserNavigateFromMnet(carousel, modal, footerData);\n }\n };\n\n return {\n footerClickListener: footerClickListener\n };\n});\n"],"names":["define","Validator","Selectors","LoadingIcon","Templates","Notification","$","Carousel","Normalise","chooserNavigateToMnet","showMoodleNet","footerData","carousel","modal","innerHTML","page","spinnerPromise","addIconToContainer","transitionPromiseResolver","transitionPromise","Promise","resolve","when","then","replaceNodeContents","customcarouseltemplate","catch","exception","addEventListener","e","target","matches","action","submit","input","querySelector","overlay","region","spinner","validationArea","document","classList","remove","addIconToContainerWithPromise","validation","result","add","innerText","message","setTimeout","window","location","domain","once","getInstance","to","setFooter","render","footerClickListener","modalBody","getFirst","getBody","closest","preventDefault","moodleNet","closeOption","customfootertemplate","chooserNavigateFromMnet"],"mappings":";;;;;;;;;;;;;;AA8BAA,sCAAO,CAAC,2BACA,2BACA,mBACA,iBACA,oBACA,SACA,iCACA,mBACJ,SAASC,UACAC,UACAC,YACAC,UACAC,aACAC,EACAC,SACAC,eAqDLC,sBAAwB,SAASC,cAAeC,WAAYC,SAAUC,OACtEH,cAAcI,UAAY,OA9C+BC,KAiDrDC,eAAiBb,YAAYc,mBAAmBP,eAGhDQ,0BAA4B,KAC5BC,kBAAoB,IAAIC,SAAQC,UAChCH,0BAA4BG,WAGhCf,EAAEgB,KACEN,eACAG,mBACFI,MAAK,WACCnB,UAAUoB,oBAAoBd,cAAeC,WAAWc,uBAAwB,OAErFC,MAAMrB,aAAasB,YA/DmCZ,KAkElCL,eAjElBkB,iBAAiB,SAAS,SAASC,MAGhCA,EAAEC,OAAOC,QAAQ7B,UAAU8B,OAAOC,QAAS,KACvCC,MAAQnB,KAAKoB,cAAc,0BAC3BC,QAAUrB,KAAKoB,cAAcjC,UAAUmC,OAAOC,SAC9CC,eAAiBC,SAASL,cAAcjC,UAAUmC,OAAOE,gBAE7DH,QAAQK,UAAUC,OAAO,cACrBJ,QAAUnC,YAAYwC,8BAA8BP,SACxDnC,UAAU2C,WAAWV,OAChBX,MAAK,SAASsB,QACXP,QAAQjB,UACRe,QAAQK,UAAUK,IAAI,UAClBD,OAAOA,QACPX,MAAMO,UAAUC,OAAO,cACvBR,MAAMO,UAAUK,IAAI,YACpBP,eAAeQ,UAAYF,OAAOG,QAClCT,eAAeE,UAAUC,OAAO,eAChCH,eAAeE,UAAUK,IAAI,gBAE7BG,YAAW,WACPC,OAAOC,SAAWN,OAAOO,SAC1B,OAEHlB,MAAMO,UAAUK,IAAI,cACpBP,eAAeQ,UAAYF,OAAOG,QAClCT,eAAeE,UAAUK,IAAI,mBAGtCpB,MAAMrB,aAAasB,eAsC9Bf,SAASgB,iBAAiB,oBAAoB,WAC1CV,8BACD,CAACmC,MAAM,IAEV9C,SAAS+C,YAAY1C,UAAU2C,GAAG,GAClC1C,MAAM2C,UAAUpD,UAAUqD,OAAO,2CAA4C,YAyC1E,CACHC,oBAlBsB,SAAS7B,EAAGlB,WAAYE,aACxC8C,UAAYnD,UAAUoD,SAAS/C,MAAMgD,cACvChC,EAAEC,OAAOC,QAAQ7B,UAAU8B,OAAOtB,gBAAkBmB,EAAEC,OAAOgC,QAAQ5D,UAAU8B,OAAOtB,eAAgB,CACtGmB,EAAEkC,uBACInD,SAAW+C,UAAUxB,cAAcjC,UAAUmC,OAAOzB,UACpDF,cAAgBE,SAASuB,cAAcjC,UAAUmC,OAAO2B,WAE9DvD,sBAAsBC,cAAeC,WAAYC,SAAUC,UAG3DgB,EAAEC,OAAOC,QAAQ7B,UAAU8B,OAAOiC,aAAc,EAvB1B,SAASrD,SAAUC,MAAOF,YAEpDJ,SAAS+C,YAAY1C,UAAU2C,GAAG,GAClC1C,MAAM2C,UAAU7C,WAAWuD,sBAuBvBC,CAFiBR,UAAUxB,cAAcjC,UAAUmC,OAAOzB,UAExBC,MAAOF"} \ No newline at end of file +{"version":3,"file":"instance_form.min.js","sources":["../src/instance_form.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 * Our basic form manager for when a user either enters\n * their profile url or just wants to browse.\n *\n * This file is a mishmash of JS functions we need for both the standalone (M3.7, M3.8)\n * plugin & Moodle 3.9 functions. The 3.9 Functions have a base understanding that certain\n * things exist i.e. directory structures for templates. When this feature goes 3.9+ only\n * The goal is that we can quickly gut all AMD modules into bare JS files and use ES6 guidelines.\n * Till then this will have to do.\n *\n * @module tool_moodlenet/instance_form\n * @copyright 2020 Mathew May \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\ndefine(['tool_moodlenet/validator',\n 'tool_moodlenet/selectors',\n 'core/loadingicon',\n 'core/templates',\n 'core/notification',\n 'jquery',\n 'theme_boost/bootstrap/carousel',\n 'core/normalise'],\n function(Validator,\n Selectors,\n LoadingIcon,\n Templates,\n Notification,\n $,\n Carousel,\n Normalise) {\n\n /**\n * Add the event listeners to our form.\n *\n * @method registerListenerEvents\n * @param {HTMLElement} page The whole page element for our form area\n */\n var registerListenerEvents = function registerListenerEvents(page) {\n page.addEventListener('click', function(e) {\n\n // Our fake submit button / browse button.\n if (e.target.matches(Selectors.action.submit)) {\n var input = page.querySelector('[data-var=\"mnet-link\"]');\n var overlay = page.querySelector(Selectors.region.spinner);\n var validationArea = document.querySelector(Selectors.region.validationArea);\n\n overlay.classList.remove('d-none');\n var spinner = LoadingIcon.addIconToContainerWithPromise(overlay);\n Validator.validation(input)\n .then(function(result) {\n spinner.resolve();\n overlay.classList.add('d-none');\n if (result.result) {\n input.classList.remove('is-invalid'); // Just in case the class has been applied already.\n input.classList.add('is-valid');\n validationArea.innerText = result.message;\n validationArea.classList.remove('text-danger');\n validationArea.classList.add('text-success');\n // Give the user some time to see their input is valid.\n setTimeout(function() {\n window.location = result.domain;\n }, 1000);\n } else {\n input.classList.add('is-invalid');\n validationArea.innerText = result.message;\n validationArea.classList.add('text-danger');\n }\n return;\n }).catch(Notification.exception);\n }\n });\n };\n\n /**\n * Given a user wishes to see the MoodleNet profile url form transition them there.\n *\n * @method chooserNavigateToMnet\n * @param {HTMLElement} showMoodleNet The chooser's area for ment\n * @param {Object} footerData Our footer object to render out\n * @param {Element} carousel Our carousel instance to manage\n */\n var chooserNavigateToMnet = function(showMoodleNet, footerData, carousel) {\n showMoodleNet.innerHTML = '';\n\n // Add a spinner.\n var spinnerPromise = LoadingIcon.addIconToContainer(showMoodleNet);\n\n // Used later...\n var transitionPromiseResolver = null;\n var transitionPromise = new Promise(resolve => {\n transitionPromiseResolver = resolve;\n });\n\n $.when(\n spinnerPromise,\n transitionPromise\n ).then(function() {\n Templates.replaceNodeContents(showMoodleNet, footerData.customcarouseltemplate, '');\n return;\n }).catch(Notification.exception);\n\n // We apply our handlers in here to minimise plugin dependency in the Chooser.\n registerListenerEvents(showMoodleNet);\n\n // Move to the next slide, and resolve the transition promise when it's done.\n carousel.addEventListener('slid.bs.carousel', function() {\n transitionPromiseResolver();\n }, {once: true});\n // Trigger the transition between 'pages'.\n Carousel.getInstance(carousel).to(2);\n };\n\n /**\n * Given a user no longer wishes to see the MoodleNet profile url form transition them from there.\n *\n * @method chooserNavigateFromMnet\n * @param {Element} carousel Our carousel instance to manage\n * @param {jQuery} modal Our modal instance to manage\n * @param {Object} footerData Our footer object to render out\n */\n var chooserNavigateFromMnet = function(carousel, modal, footerData) {\n // Trigger the transition between 'pages'.\n Carousel.getInstance(carousel).to(0);\n modal.setFooter(footerData.customfootertemplate);\n };\n\n /**\n * Create the custom listener that would handle anything in the footer.\n *\n * @param {Event} e The event being triggered.\n * @param {Object} footerData The data generated from the exporter.\n * @param {Object} modal The chooser modal.\n */\n var footerClickListener = function(e, footerData, modal) {\n const modalBody = Normalise.getFirst(modal.getBody());\n if (e.target.matches(Selectors.action.showMoodleNet) || e.target.closest(Selectors.action.showMoodleNet)) {\n e.preventDefault();\n const carousel = modalBody.querySelector(Selectors.region.carousel);\n const showMoodleNet = carousel.querySelector(Selectors.region.moodleNet);\n\n chooserNavigateToMnet(showMoodleNet, footerData, carousel, modal);\n }\n // From the help screen go back to the module overview.\n if (e.target.matches(Selectors.action.closeOption)) {\n const carousel = modalBody.querySelector(Selectors.region.carousel);\n\n chooserNavigateFromMnet(carousel, modal, footerData);\n }\n };\n\n return {\n footerClickListener: footerClickListener\n };\n});\n"],"names":["define","Validator","Selectors","LoadingIcon","Templates","Notification","$","Carousel","Normalise","chooserNavigateToMnet","showMoodleNet","footerData","carousel","innerHTML","page","spinnerPromise","addIconToContainer","transitionPromiseResolver","transitionPromise","Promise","resolve","when","then","replaceNodeContents","customcarouseltemplate","catch","exception","addEventListener","e","target","matches","action","submit","input","querySelector","overlay","region","spinner","validationArea","document","classList","remove","addIconToContainerWithPromise","validation","result","add","innerText","message","setTimeout","window","location","domain","once","getInstance","to","footerClickListener","modal","modalBody","getFirst","getBody","closest","preventDefault","moodleNet","closeOption","setFooter","customfootertemplate","chooserNavigateFromMnet"],"mappings":";;;;;;;;;;;;;;AA8BAA,sCAAO,CAAC,2BACA,2BACA,mBACA,iBACA,oBACA,SACA,iCACA,mBACJ,SAASC,UACAC,UACAC,YACAC,UACAC,aACAC,EACAC,SACAC,eAoDLC,sBAAwB,SAASC,cAAeC,WAAYC,UAC5DF,cAAcG,UAAY,OA7C+BC,KAgDrDC,eAAiBZ,YAAYa,mBAAmBN,eAGhDO,0BAA4B,KAC5BC,kBAAoB,IAAIC,SAAQC,UAChCH,0BAA4BG,WAGhCd,EAAEe,KACEN,eACAG,mBACFI,MAAK,WACClB,UAAUmB,oBAAoBb,cAAeC,WAAWa,uBAAwB,OAErFC,MAAMpB,aAAaqB,YA9DmCZ,KAiElCJ,eAhElBiB,iBAAiB,SAAS,SAASC,MAGhCA,EAAEC,OAAOC,QAAQ5B,UAAU6B,OAAOC,QAAS,KACvCC,MAAQnB,KAAKoB,cAAc,0BAC3BC,QAAUrB,KAAKoB,cAAchC,UAAUkC,OAAOC,SAC9CC,eAAiBC,SAASL,cAAchC,UAAUkC,OAAOE,gBAE7DH,QAAQK,UAAUC,OAAO,cACrBJ,QAAUlC,YAAYuC,8BAA8BP,SACxDlC,UAAU0C,WAAWV,OAChBX,MAAK,SAASsB,QACXP,QAAQjB,UACRe,QAAQK,UAAUK,IAAI,UAClBD,OAAOA,QACPX,MAAMO,UAAUC,OAAO,cACvBR,MAAMO,UAAUK,IAAI,YACpBP,eAAeQ,UAAYF,OAAOG,QAClCT,eAAeE,UAAUC,OAAO,eAChCH,eAAeE,UAAUK,IAAI,gBAE7BG,YAAW,WACPC,OAAOC,SAAWN,OAAOO,SAC1B,OAEHlB,MAAMO,UAAUK,IAAI,cACpBP,eAAeQ,UAAYF,OAAOG,QAClCT,eAAeE,UAAUK,IAAI,mBAGtCpB,MAAMpB,aAAaqB,eAqC9Bd,SAASe,iBAAiB,oBAAoB,WAC1CV,8BACD,CAACmC,MAAM,IAEV7C,SAAS8C,YAAYzC,UAAU0C,GAAG,UAyC/B,CACHC,oBAlBsB,SAAS3B,EAAGjB,WAAY6C,aACxCC,UAAYjD,UAAUkD,SAASF,MAAMG,cACvC/B,EAAEC,OAAOC,QAAQ5B,UAAU6B,OAAOrB,gBAAkBkB,EAAEC,OAAO+B,QAAQ1D,UAAU6B,OAAOrB,eAAgB,CACtGkB,EAAEiC,uBACIjD,SAAW6C,UAAUvB,cAAchC,UAAUkC,OAAOxB,UACpDF,cAAgBE,SAASsB,cAAchC,UAAUkC,OAAO0B,WAE9DrD,sBAAsBC,cAAeC,WAAYC,aAGjDgB,EAAEC,OAAOC,QAAQ5B,UAAU6B,OAAOgC,aAAc,EAvB1B,SAASnD,SAAU4C,MAAO7C,YAEpDJ,SAAS8C,YAAYzC,UAAU0C,GAAG,GAClCE,MAAMQ,UAAUrD,WAAWsD,sBAuBvBC,CAFiBT,UAAUvB,cAAchC,UAAUkC,OAAOxB,UAExB4C,MAAO7C"} \ No newline at end of file diff --git a/public/admin/tool/moodlenet/amd/src/instance_form.js b/public/admin/tool/moodlenet/amd/src/instance_form.js index b28a2382d11..768501a05d4 100644 --- a/public/admin/tool/moodlenet/amd/src/instance_form.js +++ b/public/admin/tool/moodlenet/amd/src/instance_form.js @@ -94,9 +94,8 @@ define(['tool_moodlenet/validator', * @param {HTMLElement} showMoodleNet The chooser's area for ment * @param {Object} footerData Our footer object to render out * @param {Element} carousel Our carousel instance to manage - * @param {jQuery} modal Our modal instance to manage */ - var chooserNavigateToMnet = function(showMoodleNet, footerData, carousel, modal) { + var chooserNavigateToMnet = function(showMoodleNet, footerData, carousel) { showMoodleNet.innerHTML = ''; // Add a spinner. @@ -125,7 +124,6 @@ define(['tool_moodlenet/validator', }, {once: true}); // Trigger the transition between 'pages'. Carousel.getInstance(carousel).to(2); - modal.setFooter(Templates.render('tool_moodlenet/chooser_footer_close_mnet', {})); }; /** diff --git a/public/course/amd/build/local/activitychooser/dialogue.min.js b/public/course/amd/build/local/activitychooser/dialogue.min.js index 2e6520cb797..f376394592d 100644 --- a/public/course/amd/build/local/activitychooser/dialogue.min.js +++ b/public/course/amd/build/local/activitychooser/dialogue.min.js @@ -1,3 +1,3 @@ -define("core_course/local/activitychooser/dialogue",["exports","core/utils","core_course/local/activitychooser/dialoguedom","core/key_codes","core_course/local/activitychooser/exporter","core/normalise","core/str","core/modal","core/modal_events","core/notification","core_course/local/activitychooser/repository","core_course/local/activitychooser/selectors","core/templates"],(function(_exports,_utils,_dialoguedom,_key_codes,_exporter,_normalise,_str,_modal,ModalEvents,_notification,Repository,_selectors,Templates){Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.displayActivityChooserModal=async function(footerDataPromise,modulesDataPromise){let bodyPromiseResolver;const bodyPromise=new Promise((resolve=>{bodyPromiseResolver=resolve})),footerData=await footerDataPromise,sectionModal=_modal.default.create({body:bodyPromise,title:(0,_str.getString)("addresourceoractivity"),footer:footerData.customfootertemplate,large:!0,scrollable:!1,templateContext:{classes:"modchooser"},show:!0});try{const modulesData=await modulesDataPromise;if(!modulesData)return;const modal=await sectionModal,dialogue=new ActivityChooserDialogue(modal,modulesData,footerData),templateData=await dialogue.exporter.getModChooserTemplateData(modulesData);bodyPromiseResolver(await Templates.render("core_course/activitychooser",templateData))}catch(error){const errorTemplateData={errormessage:error.message};return void bodyPromiseResolver(await Templates.render("core_course/local/activitychooser/error",errorTemplateData))}},_exports.displayChooser=void 0,_dialoguedom=_interopRequireDefault(_dialoguedom),_exporter=_interopRequireDefault(_exporter),_modal=_interopRequireDefault(_modal),ModalEvents=_interopRequireWildcard(ModalEvents),_notification=_interopRequireDefault(_notification),Repository=_interopRequireWildcard(Repository),_selectors=_interopRequireDefault(_selectors),Templates=_interopRequireWildcard(Templates);var _systemImportTransformerGlobalIdentifier="undefined"!=typeof window?window:"undefined"!=typeof self?self:"undefined"!=typeof global?global:{};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 _interopRequireWildcard(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]}return newObj.default=obj,cache&&cache.set(obj,newObj),newObj}function _interopRequireDefault(obj){return obj&&obj.__esModule?obj:{default:obj}}_exports.displayChooser=(modalPromise,sectionModules,partialFavourite,footerData)=>{window.console.warn("The displayChooser function is deprecated. Please displayActivityChooserModal instead."),modalPromise.then((modal=>(new ActivityChooserDialogue(modal,sectionModules,footerData),modal))).catch(_notification.default.exception)};class ActivityChooserDialogue{constructor(modal,modulesData,footerData){this.modal=modal,this.dialogueDom=null,this.footerData=footerData,this.exporter=new _exporter.default,this.isFavouriteTabDirty=!1,this.mappedModules=new Map,modulesData.forEach((module=>{this.mappedModules.set(module.componentname+"_"+module.link,module)})),this.init()}async init(){const modalBody=(0,_normalise.getFirst)(await this.modal.getBodyPromise());this.dialogueDom=new _dialoguedom.default(this,modalBody,this.exporter),this.registerModalListenerEvents(),this.setupKeyboardAccessibility(),this.modal.getRoot().on(ModalEvents.hidden,(()=>{this.modal.destroy()}))}async registerModalListenerEvents(){const modalRoot=(0,_normalise.getFirst)(this.modal.getRoot());modalRoot.addEventListener("shown.bs.tab",(event=>{if(event.target.closest(_selectors.default.regions.allTabNav))return;const searchInput=this.dialogueDom.getSearchInputElement();searchInput.value.length>0&&(searchInput.value="",this.toggleSearchResultsView(searchInput.value))})),modalRoot.addEventListener("click",this.handleModalClick.bind(this));const searchInput=this.dialogueDom.getSearchInputElement();searchInput.addEventListener("input",(0,_utils.debounce)((()=>{this.toggleSearchResultsView(searchInput.value)}),300,{pending:!0})),this.dialogueDom.initBootstrapComponents(),modalRoot.addEventListener("shown.bs.tab",(event=>{event.relatedTarget&&this.dialogueDom.disableFocusAllChooserOptions(event.relatedTarget),this.dialogueDom.initActiveTabNavigation()})),modalRoot.addEventListener("shown.bs.tab",(()=>{this.isFavouriteTabDirty&&!this.dialogueDom.isFavoutiteTabActive()&&this.refreshFavouritesTabContent()})),this.dialogueDom.initActiveTabNavigation();(0,_normalise.getFirst)(await this.modal.getFooterPromise()).addEventListener("click",this.handleFooterClick.bind(this))}async handleFooterClick(event){if(!0===this.footerData.footer){const footerjs=await(pluginName=this.footerData.customfooterjs,"function"==typeof _systemImportTransformerGlobalIdentifier.define&&_systemImportTransformerGlobalIdentifier.define.amd?new Promise((function(resolve,reject){_systemImportTransformerGlobalIdentifier.require([pluginName],resolve,reject)})):"undefined"!=typeof module&&module.exports&&"undefined"!=typeof require||"undefined"!=typeof module&&module.component&&_systemImportTransformerGlobalIdentifier.require&&"component"===_systemImportTransformerGlobalIdentifier.require.loader?Promise.resolve(require(pluginName)):Promise.resolve(_systemImportTransformerGlobalIdentifier[pluginName]));await footerjs.footerClickListener(event,this.footerData,this.modal)}var pluginName}async handleModalClick(event){const target=event.target;target.closest(_selectors.default.actions.optionActions.showSummary)&&this.handleShowSummary(target),target.closest(_selectors.default.actions.optionActions.manageFavourite)&&await this.handleFavouriteClick(target),target.matches(_selectors.default.actions.closeOption)&&this.dialogueDom.hideModuleHelp(target.dataset.modname),target.closest(_selectors.default.actions.clearSearch)&&this.handleClearSearch()}handleShowSummary(target){const moduleName=this.dialogueDom.getClosestChooserOption(target).dataset.modname,moduleData=this.mappedModules.get(moduleName);moduleData.showFooter=this.modal.hasFooterContent(),this.dialogueDom.showModuleHelp(moduleData,this.modal)}async handleFavouriteClick(target){const caller=target.closest(_selectors.default.actions.optionActions.manageFavourite),id=caller.dataset.id,name=caller.dataset.name,internal=caller.dataset.internal;"true"===caller.dataset.favourited?(await Repository.unfavouriteModule(name,id),this.updateFavouriteItemValue(internal,!1)):(await Repository.favouriteModule(name,id),this.updateFavouriteItemValue(internal,!0))}handleClearSearch(){const searchInput=this.dialogueDom.getSearchInputElement();searchInput.value="",searchInput.focus(),this.toggleSearchResultsView(searchInput.value)}setupKeyboardAccessibility(){const mainElement=(0,_normalise.getFirst)(this.modal.getModal());mainElement.tabIndex=-1,mainElement.addEventListener("keydown",(e=>{const currentOption=this.dialogueDom.getClosestChooserOption(e.target);null!==currentOption&&(e.keyCode!==_key_codes.enter&&e.keyCode!==_key_codes.space||e.target.matches(_selectors.default.actions.optionActions.showSummary)&&(e.preventDefault(),this.handleShowSummary(e.target)),e.keyCode===_key_codes.arrowRight&&(e.preventDefault(),this.dialogueDom.focusNextChooserOption(currentOption)),e.keyCode===_key_codes.arrowLeft&&(e.preventDefault(),this.dialogueDom.focusPreviousChooserOption(currentOption)),e.keyCode===_key_codes.home&&(e.preventDefault(),this.dialogueDom.focusFirstChooserOption(currentOption)),e.keyCode===_key_codes.end&&(e.preventDefault(),this.dialogueDom.focusLastChooserOption(currentOption)))}))}async toggleSearchResultsView(searchQuery){const searchResultsData=this.searchModules(searchQuery);searchQuery.length>0?(await this.dialogueDom.refreshSearchResults(searchResultsData),this.dialogueDom.showAllActivitiesTab(!0)):this.dialogueDom.cleanSearchResults()}searchModules(searchTerm){if(""===searchTerm)return this.mappedModules;searchTerm=searchTerm.toLowerCase();const searchResults=[];return this.mappedModules.forEach((activity=>{const activityName=activity.title.toLowerCase(),activityDesc=activity.help.toLowerCase();(activityName.includes(searchTerm)||activityDesc.includes(searchTerm))&&searchResults.push(activity)})),searchResults}async updateFavouriteItemValue(internal,favourite){const moduleItem=this.mappedModules.find((_ref=>{let{name:name}=_ref;return name===internal}));moduleItem&&(moduleItem.favourite=favourite,this.dialogueDom.updateItemStarredIcons(internal,favourite),this.dialogueDom.isFavoutiteTabActive()?this.isFavouriteTabDirty=!0:this.refreshFavouritesTabContent())}async refreshFavouritesTabContent(){this.isFavouriteTabDirty=!1;const favouriteCount=this.mappedModules.filter((mod=>!0===mod.favourite)).size;this.dialogueDom.toggleFavouriteTabDisplay(favouriteCount>0),await this.dialogueDom.refreshFavouritesTabContent(this.mappedModules)}}})); +define("core_course/local/activitychooser/dialogue",["exports","core/utils","core_course/local/activitychooser/dialoguedom","core/key_codes","core_course/local/activitychooser/exporter","core/normalise","core/str","core/modal","core/modal_events","core/notification","core_course/local/activitychooser/repository","core_course/local/activitychooser/selectors","core/templates"],(function(_exports,_utils,_dialoguedom,_key_codes,_exporter,_normalise,_str,_modal,ModalEvents,_notification,Repository,_selectors,Templates){Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.displayActivityChooserModal=async function(footerDataPromise,modulesDataPromise){let bodyPromiseResolver;const bodyPromise=new Promise((resolve=>{bodyPromiseResolver=resolve})),exporter=new _exporter.default,footerData=await footerDataPromise,footerPromise=Templates.render("core_course/local/activitychooser/footer",exporter.getFooterData(footerData)),sectionModal=_modal.default.create({body:bodyPromise,title:(0,_str.getString)("addresourceoractivity"),footer:footerPromise,large:!0,scrollable:!1,templateContext:{classes:"modchooser"},show:!0});try{const modulesData=await modulesDataPromise;if(!modulesData)return;const modal=await sectionModal;new ActivityChooserDialogue(modal,modulesData,footerData);const templateData=await exporter.getModChooserTemplateData(modulesData);bodyPromiseResolver(await Templates.render("core_course/activitychooser",templateData))}catch(error){const errorTemplateData={errormessage:error.message};return void bodyPromiseResolver(await Templates.render("core_course/local/activitychooser/error",errorTemplateData))}},_exports.displayChooser=void 0,_dialoguedom=_interopRequireDefault(_dialoguedom),_exporter=_interopRequireDefault(_exporter),_modal=_interopRequireDefault(_modal),ModalEvents=_interopRequireWildcard(ModalEvents),_notification=_interopRequireDefault(_notification),Repository=_interopRequireWildcard(Repository),_selectors=_interopRequireDefault(_selectors),Templates=_interopRequireWildcard(Templates);var _systemImportTransformerGlobalIdentifier="undefined"!=typeof window?window:"undefined"!=typeof self?self:"undefined"!=typeof global?global:{};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 _interopRequireWildcard(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]}return newObj.default=obj,cache&&cache.set(obj,newObj),newObj}function _interopRequireDefault(obj){return obj&&obj.__esModule?obj:{default:obj}}_exports.displayChooser=(modalPromise,sectionModules,partialFavourite,footerData)=>{window.console.warn("The displayChooser function is deprecated. Please displayActivityChooserModal instead."),modalPromise.then((modal=>(new ActivityChooserDialogue(modal,sectionModules,footerData),modal))).catch(_notification.default.exception)};class ActivityChooserDialogue{constructor(modal,modulesData,footerData){this.modal=modal,this.dialogueDom=null,this.footerData=footerData,this.exporter=new _exporter.default,this.selectedModule=null,this.isFavouriteTabDirty=!1,this.mappedModules=new Map,modulesData.forEach((module=>{this.mappedModules.set(module.componentname+"_"+module.link,module)})),this.init()}async init(){await this.modal.getBodyPromise(),await this.modal.getFooterPromise(),this.dialogueDom=new _dialoguedom.default(this,this.modal,this.exporter),this.registerModalListenerEvents(),this.setupKeyboardAccessibility(),this.modal.getRoot().on(ModalEvents.hidden,(()=>{this.modal.destroy()})),this.dialogueDom.unmarkAllChooserOptionAsSelected()}async registerModalListenerEvents(){const modalRoot=(0,_normalise.getFirst)(this.modal.getRoot());modalRoot.addEventListener("shown.bs.tab",(event=>{if(this.dialogueDom.unmarkAllChooserOptionAsSelected(),event.target.closest(_selectors.default.regions.allTabNav))return;const searchInput=this.dialogueDom.getSearchInputElement();searchInput.value.length>0&&(searchInput.value="",this.toggleSearchResultsView(searchInput.value))})),modalRoot.addEventListener("click",this.handleModalClick.bind(this)),modalRoot.addEventListener("dblclick",this.handleModalDoubleClick.bind(this));const searchInput=this.dialogueDom.getSearchInputElement();searchInput.addEventListener("input",(0,_utils.debounce)((()=>{this.toggleSearchResultsView(searchInput.value)}),300,{pending:!0})),this.dialogueDom.initBootstrapComponents(),modalRoot.addEventListener("shown.bs.tab",(event=>{event.relatedTarget&&this.dialogueDom.disableFocusAllChooserOptions(event.relatedTarget),this.dialogueDom.initActiveTabNavigation()})),modalRoot.addEventListener("shown.bs.tab",(()=>{this.isFavouriteTabDirty&&!this.dialogueDom.isFavoutiteTabActive()&&this.refreshFavouritesTabContent()})),this.dialogueDom.initActiveTabNavigation();(0,_normalise.getFirst)(await this.modal.getFooterPromise()).addEventListener("click",this.handleFooterClick.bind(this)),modalRoot.addEventListener("slide.bs.carousel",(event=>{void 0!==event.to&&(this.dialogueDom.toggleActiveFooter(0===event.to),this.dialogueDom.toggleBackButton(0!==event.to),this.dialogueDom.toggleAddButton(event.to<2))}))}async handleFooterClick(event){if(event.target.closest(_selectors.default.actions.addSelectedChooserOption)&&this.submitAddSelectedModule(),event.target.closest(_selectors.default.regions.activeFooter)&&!0===this.footerData.footer){const footerjs=await(pluginName=this.footerData.customfooterjs,"function"==typeof _systemImportTransformerGlobalIdentifier.define&&_systemImportTransformerGlobalIdentifier.define.amd?new Promise((function(resolve,reject){_systemImportTransformerGlobalIdentifier.require([pluginName],resolve,reject)})):"undefined"!=typeof module&&module.exports&&"undefined"!=typeof require||"undefined"!=typeof module&&module.component&&_systemImportTransformerGlobalIdentifier.require&&"component"===_systemImportTransformerGlobalIdentifier.require.loader?Promise.resolve(require(pluginName)):Promise.resolve(_systemImportTransformerGlobalIdentifier[pluginName]));await footerjs.footerClickListener(event,this.footerData,this.modal)}var pluginName}async handleModalClick(event){const target=event.target;if(target.closest(_selectors.default.actions.optionActions.showSummary))this.handleShowSummary(target);else if(target.closest(_selectors.default.actions.optionActions.manageFavourite))await this.handleFavouriteClick(target);else if(target.matches(_selectors.default.actions.closeOption))this.dialogueDom.hideModuleHelp(target.dataset.modname);else{if(!target.closest(_selectors.default.actions.clearSearch))return target.closest(_selectors.default.regions.chooserOption.container)?(event.preventDefault(),void this.handleOptionSelection(target)):void 0;this.handleClearSearch()}}handleModalDoubleClick(event){null!==this.dialogueDom.getClosestChooserOption(event.target)&&this.submitAddSelectedModule(this.selectedModule)}handleShowSummary(target){const moduleName=this.dialogueDom.getClosestChooserOption(target).dataset.modname,moduleData=this.mappedModules.get(moduleName);this.handleOptionSelection(target),moduleData.showFooter=this.modal.hasFooterContent(),this.dialogueDom.setBackButtonModuleData(moduleData),this.dialogueDom.showModuleHelp(moduleData,this.modal)}async handleFavouriteClick(target){const caller=target.closest(_selectors.default.actions.optionActions.manageFavourite),id=caller.dataset.id,name=caller.dataset.name,internal=caller.dataset.internal;"true"===caller.dataset.favourited?(await Repository.unfavouriteModule(name,id),this.updateFavouriteItemValue(internal,!1)):(await Repository.favouriteModule(name,id),this.updateFavouriteItemValue(internal,!0))}handleClearSearch(){const searchInput=this.dialogueDom.getSearchInputElement();searchInput.value="",searchInput.focus(),this.toggleSearchResultsView(searchInput.value)}handleOptionSelection(target){const option=this.dialogueDom.getClosestChooserOption(target);null!==option&&(this.selectedModule=option,this.dialogueDom.markChooserOptionAsSelected(option,(0,_normalise.getFirst)(this.modal.getFooter())))}submitAddSelectedModule(){let newSelectedModule=arguments.length>0&&void 0!==arguments[0]?arguments[0]:null;newSelectedModule&&this.handleOptionSelection(newSelectedModule),null!==this.selectedModule&&(window.location.href=this.dialogueDom.getChooserOptionUrl(this.selectedModule))}setupKeyboardAccessibility(){const mainElement=(0,_normalise.getFirst)(this.modal.getModal());mainElement.tabIndex=-1,mainElement.addEventListener("keydown",(e=>{const currentOption=this.dialogueDom.getClosestChooserOption(e.target);null!==currentOption&&(e.keyCode!==_key_codes.enter&&e.keyCode!==_key_codes.space||!e.target.closest(_selectors.default.regions.chooserOption.actions))&&(e.keyCode===_key_codes.space&&(e.preventDefault(),this.handleOptionSelection(currentOption)),e.keyCode===_key_codes.enter&&(e.preventDefault(),this.submitAddSelectedModule(currentOption)),e.keyCode===_key_codes.arrowRight&&(e.preventDefault(),this.dialogueDom.focusNextChooserOption(currentOption)),e.keyCode===_key_codes.arrowLeft&&(e.preventDefault(),this.dialogueDom.focusPreviousChooserOption(currentOption)),e.keyCode===_key_codes.home&&(e.preventDefault(),this.dialogueDom.focusFirstChooserOption(currentOption)),e.keyCode===_key_codes.end&&(e.preventDefault(),this.dialogueDom.focusLastChooserOption(currentOption)))}))}async toggleSearchResultsView(searchQuery){const searchResultsData=this.searchModules(searchQuery);searchQuery.length>0?(await this.dialogueDom.refreshSearchResults(searchResultsData),this.dialogueDom.showAllActivitiesTab(!0)):this.dialogueDom.cleanSearchResults()}searchModules(searchTerm){if(""===searchTerm)return this.mappedModules;searchTerm=searchTerm.toLowerCase();const searchResults=[];return this.mappedModules.forEach((activity=>{const activityName=activity.title.toLowerCase(),activityDesc=activity.help.toLowerCase();(activityName.includes(searchTerm)||activityDesc.includes(searchTerm))&&searchResults.push(activity)})),searchResults}async updateFavouriteItemValue(internal,favourite){const moduleItem=this.mappedModules.find((_ref=>{let{name:name}=_ref;return name===internal}));moduleItem&&(moduleItem.favourite=favourite,this.dialogueDom.updateItemStarredIcons(internal,favourite),this.dialogueDom.isFavoutiteTabActive()?this.isFavouriteTabDirty=!0:this.refreshFavouritesTabContent())}async refreshFavouritesTabContent(){this.isFavouriteTabDirty=!1;const favouriteCount=this.mappedModules.filter((mod=>!0===mod.favourite)).size;this.dialogueDom.toggleFavouriteTabDisplay(favouriteCount>0),await this.dialogueDom.refreshFavouritesTabContent(this.mappedModules)}}})); //# sourceMappingURL=dialogue.min.js.map \ No newline at end of file diff --git a/public/course/amd/build/local/activitychooser/dialogue.min.js.map b/public/course/amd/build/local/activitychooser/dialogue.min.js.map index 02cd43e2b50..dda3be353ce 100644 --- a/public/course/amd/build/local/activitychooser/dialogue.min.js.map +++ b/public/course/amd/build/local/activitychooser/dialogue.min.js.map @@ -1 +1 @@ -{"version":3,"file":"dialogue.min.js","sources":["../../../src/local/activitychooser/dialogue.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 * A type of dialogue used as for choosing options.\n *\n * @module core_course/local/activitychooser/dialogue\n * @copyright 2019 Mihail Geshoski \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\n// import {addIconToContainer} from 'core/loadingicon';\nimport {debounce} from 'core/utils';\nimport DialogueDom from 'core_course/local/activitychooser/dialoguedom';\nimport {end, arrowLeft, arrowRight, home, enter, space} from 'core/key_codes';\nimport Exporter from 'core_course/local/activitychooser/exporter';\nimport {getFirst} from 'core/normalise';\nimport {getString} from 'core/str';\nimport Modal from 'core/modal';\nimport * as ModalEvents from 'core/modal_events';\nimport Notification from 'core/notification';\nimport * as Repository from 'core_course/local/activitychooser/repository';\nimport selectors from 'core_course/local/activitychooser/selectors';\nimport * as Templates from 'core/templates';\nconst getPlugin = pluginName => import(pluginName);\n\n/**\n * Display the activity chooser modal.\n *\n * @method displayActivityChooser\n * @param {Promise} footerDataPromise Promise for the footer data.\n * @param {Promise} modulesDataPromise Promise for the modules data.\n */\nexport async function displayActivityChooserModal(\n footerDataPromise,\n modulesDataPromise,\n) {\n // We want to show the modal instantly but loading whilst waiting for our data.\n let bodyPromiseResolver;\n const bodyPromise = new Promise(resolve => {\n bodyPromiseResolver = resolve;\n });\n\n const footerData = await footerDataPromise;\n\n const sectionModal = Modal.create({\n body: bodyPromise,\n title: getString('addresourceoractivity'),\n footer: footerData.customfootertemplate,\n large: true,\n scrollable: false,\n templateContext: {\n classes: 'modchooser'\n },\n show: true,\n });\n\n try {\n const modulesData = await modulesDataPromise;\n\n if (!modulesData) {\n return;\n }\n\n const modal = await sectionModal;\n const dialogue = new ActivityChooserDialogue(modal, modulesData, footerData);\n\n const templateData = await dialogue.exporter.getModChooserTemplateData(modulesData);\n bodyPromiseResolver(await Templates.render('core_course/activitychooser', templateData));\n } catch (error) {\n const errorTemplateData = {\n 'errormessage': error.message\n };\n bodyPromiseResolver(\n await Templates.render('core_course/local/activitychooser/error', errorTemplateData)\n );\n return;\n }\n}\n\n/**\n * Display the module chooser.\n *\n * @deprecated since Moodle 5.1\n * @todo Remove the method in Moodle 6.0 (MDL-85655).\n * @method displayChooser\n * @param {Promise} modalPromise Our created modal for the section\n * @param {Array} sectionModules An array of all of the built module information\n * @param {Function} partialFavourite Partially applied function we need to manage favourite status\n * @param {Object} footerData Our base footer object.\n */\nexport const displayChooser = (modalPromise, sectionModules, partialFavourite, footerData) => {\n window.console.warn(\n 'The displayChooser function is deprecated. ' +\n 'Please displayActivityChooserModal instead.'\n );\n\n // Register event listeners.\n modalPromise.then(modal => {\n new ActivityChooserDialogue(modal, sectionModules, footerData);\n return modal;\n }).catch(Notification.exception);\n};\n\n/**\n * Activity Chooser Dialogue class.\n *\n * @private\n */\nclass ActivityChooserDialogue {\n /**\n * Constructor for the ActivityChooserDialogue class.\n * @param {Modal} modal The modal object.\n * @param {Object} modulesData The data for the modules.\n * @param {Object} footerData The data for the footer.\n */\n constructor(modal, modulesData, footerData) {\n this.modal = modal;\n this.dialogueDom = null; // We cannot init until we have the modal body loaded.\n this.footerData = footerData;\n this.exporter = new Exporter();\n // This attribute marks when the tab content is dirty and needs to be refreshed when the user changes the tab.\n // We don't want the content to be updated while the user is managing their favourites.\n this.isFavouriteTabDirty = false;\n // Make a map so we can quickly fetch a specific module's object for either rendering or searching.\n this.mappedModules = new Map();\n modulesData.forEach((module) => {\n this.mappedModules.set(module.componentname + '_' + module.link, module);\n });\n this.init();\n }\n\n /**\n * Initialise the activity chooser dialogue.\n *\n * @return {Promise} A promise that resolves when the modal is ready.\n */\n async init() {\n const modalBody = getFirst(await this.modal.getBodyPromise());\n this.dialogueDom = new DialogueDom(this, modalBody, this.exporter);\n this.registerModalListenerEvents();\n this.setupKeyboardAccessibility();\n // We want to focus on the action select when the dialog is closed.\n this.modal.getRoot().on(ModalEvents.hidden, () => {\n this.modal.destroy();\n });\n }\n\n /**\n * Register chooser related event listeners.\n *\n * @returns {Promise} A promise that resolves when events are registered\n */\n async registerModalListenerEvents() {\n const modalRoot = getFirst(this.modal.getRoot());\n\n // Changing the tab should cancel any active search.\n modalRoot.addEventListener(\n 'shown.bs.tab',\n (event) => {\n // The all tab has the search result, so we do not want to clear the search input.\n if (event.target.closest(selectors.regions.allTabNav)) {\n return;\n }\n const searchInput = this.dialogueDom.getSearchInputElement();\n if (searchInput.value.length > 0) {\n searchInput.value = \"\";\n this.toggleSearchResultsView(searchInput.value);\n }\n },\n );\n\n // Add the listener for clicks on the full modal.\n modalRoot.addEventListener(\n 'click',\n this.handleModalClick.bind(this),\n );\n\n // Add a listener for an input change in the activity chooser's search bar.\n const searchInput = this.dialogueDom.getSearchInputElement();\n searchInput.addEventListener(\n 'input',\n debounce(\n () => {\n this.toggleSearchResultsView(searchInput.value);\n },\n 300,\n {pending: true},\n ),\n );\n\n this.dialogueDom.initBootstrapComponents();\n\n // Handle focus when a new tab is shown.\n modalRoot.addEventListener('shown.bs.tab', (event) => {\n if (event.relatedTarget) {\n this.dialogueDom.disableFocusAllChooserOptions(event.relatedTarget);\n }\n this.dialogueDom.initActiveTabNavigation();\n });\n\n // Update the favourite tab content when the user changes the tab.\n modalRoot.addEventListener('shown.bs.tab', () => {\n if (this.isFavouriteTabDirty && !this.dialogueDom.isFavoutiteTabActive()) {\n this.refreshFavouritesTabContent();\n }\n });\n\n this.dialogueDom.initActiveTabNavigation();\n\n const modalFooter = getFirst(await this.modal.getFooterPromise());\n\n // Add the listener for clicks on the footer.\n modalFooter.addEventListener(\n 'click',\n this.handleFooterClick.bind(this),\n );\n }\n\n /**\n * Handle the click event on the footer of the modal.\n *\n * @param {Object} event The event object\n * @return {Promise} A promise that resolves when the event is handled\n */\n async handleFooterClick(event) {\n if (this.footerData.footer === true) {\n const footerjs = await getPlugin(this.footerData.customfooterjs);\n await footerjs.footerClickListener(event, this.footerData, this.modal);\n }\n }\n\n /**\n * Modal click handler.\n *\n * @param {Object} event The event object\n * @return {Promise} A promise that resolves when the event is handled\n */\n async handleModalClick(event) {\n const target = event.target;\n\n if (target.closest(selectors.actions.optionActions.showSummary)) {\n this.handleShowSummary(target);\n }\n\n if (target.closest(selectors.actions.optionActions.manageFavourite)) {\n await this.handleFavouriteClick(target);\n }\n\n // From the help screen go back to the module overview.\n if (target.matches(selectors.actions.closeOption)) {\n this.dialogueDom.hideModuleHelp(target.dataset.modname);\n }\n\n // The \"clear search\" button is triggered.\n if (target.closest(selectors.actions.clearSearch)) {\n this.handleClearSearch();\n }\n }\n\n /**\n * Show the summary of a module when the user clicks on the \"show summary\" button.\n *\n * @param {HTMLElement} target The target element that triggered the event\n */\n handleShowSummary(target) {\n const module = this.dialogueDom.getClosestChooserOption(target);\n const moduleName = module.dataset.modname;\n const moduleData = this.mappedModules.get(moduleName);\n // We need to know if the overall modal has a footer so we know when to show a real / vs fake footer.\n moduleData.showFooter = this.modal.hasFooterContent();\n this.dialogueDom.showModuleHelp(moduleData, this.modal);\n }\n\n /**\n * Handle the favourite state of a module when the user clicks on the \"starred\" button.\n *\n * @param {HTMLElement} target The target element that triggered the event\n * @return {Promise} A promise that resolves when the event is handled\n */\n async handleFavouriteClick(target) {\n const caller = target.closest(selectors.actions.optionActions.manageFavourite);\n const id = caller.dataset.id;\n const name = caller.dataset.name;\n const internal = caller.dataset.internal;\n const isFavourite = caller.dataset.favourited;\n\n // Switch on fave or not.\n if (isFavourite === 'true') {\n await Repository.unfavouriteModule(name, id);\n this.updateFavouriteItemValue(internal, false);\n } else {\n await Repository.favouriteModule(name, id);\n this.updateFavouriteItemValue(internal, true);\n }\n }\n\n /**\n * Handle a clear search action.\n */\n handleClearSearch() {\n const searchInput = this.dialogueDom.getSearchInputElement();\n searchInput.value = \"\";\n searchInput.focus();\n this.toggleSearchResultsView(searchInput.value);\n }\n\n /**\n * Set up our tabindex information across the chooser.\n *\n * @method setupKeyboardAccessibility\n */\n setupKeyboardAccessibility() {\n const mainElement = getFirst(this.modal.getModal());\n\n mainElement.tabIndex = -1;\n\n mainElement.addEventListener('keydown', (e) => {\n const currentOption = this.dialogueDom.getClosestChooserOption(e.target);\n if (currentOption === null) {\n return;\n }\n\n // Check for enter/ space triggers for showing the help.\n if (e.keyCode === enter || e.keyCode === space) {\n if (e.target.matches(selectors.actions.optionActions.showSummary)) {\n e.preventDefault();\n this.handleShowSummary(e.target);\n }\n }\n\n if (e.keyCode === arrowRight) {\n e.preventDefault();\n this.dialogueDom.focusNextChooserOption(currentOption);\n }\n if (e.keyCode === arrowLeft) {\n e.preventDefault();\n this.dialogueDom.focusPreviousChooserOption(currentOption);\n }\n if (e.keyCode === home) {\n e.preventDefault();\n this.dialogueDom.focusFirstChooserOption(currentOption);\n }\n if (e.keyCode === end) {\n e.preventDefault();\n this.dialogueDom.focusLastChooserOption(currentOption);\n }\n });\n }\n\n /**\n * Toggle (display/hide) the search results depending on the value of the search query\n *\n * @method toggleSearchResultsView\n * @param {String} searchQuery The search query\n */\n async toggleSearchResultsView(searchQuery) {\n const searchResultsData = this.searchModules(searchQuery);\n\n if (searchQuery.length > 0) {\n await this.dialogueDom.refreshSearchResults(searchResultsData);\n this.dialogueDom.showAllActivitiesTab(true);\n } else {\n this.dialogueDom.cleanSearchResults();\n }\n }\n\n /**\n * Return the list of modules which have a name or description that matches the given search term.\n *\n * @method searchModules\n * @param {String} searchTerm The search term to match\n * @return {Array}\n */\n searchModules(searchTerm) {\n if (searchTerm === '') {\n return this.mappedModules;\n }\n searchTerm = searchTerm.toLowerCase();\n const searchResults = [];\n this.mappedModules.forEach((activity) => {\n const activityName = activity.title.toLowerCase();\n const activityDesc = activity.help.toLowerCase();\n if (activityName.includes(searchTerm) || activityDesc.includes(searchTerm)) {\n searchResults.push(activity);\n }\n });\n\n return searchResults;\n }\n\n /**\n * Update the favourite item value in the mapped modules.\n *\n * @param {String} internal The internal name of the module.\n * @param {Boolean} favourite Whether the module is a favourite or not.\n * @return {Promise} A promise that resolves when the item is updated.\n */\n async updateFavouriteItemValue(internal, favourite) {\n const moduleItem = this.mappedModules.find(({name}) => name === internal);\n if (!moduleItem) {\n return;\n }\n moduleItem.favourite = favourite;\n\n this.dialogueDom.updateItemStarredIcons(internal, favourite);\n\n if (this.dialogueDom.isFavoutiteTabActive()) {\n this.isFavouriteTabDirty = true;\n } else {\n this.refreshFavouritesTabContent();\n }\n }\n\n /**\n * Refresh the favourites tab content.\n *\n * Note: this method will also hide the favourites tab if there are no favourite modules\n * to keep the modal consistent.\n *\n * @return {Promise} A promise that resolves when the content is refreshed.\n */\n async refreshFavouritesTabContent() {\n this.isFavouriteTabDirty = false;\n const favouriteCount = this.mappedModules.filter(mod => mod.favourite === true).size;\n this.dialogueDom.toggleFavouriteTabDisplay(favouriteCount > 0);\n await this.dialogueDom.refreshFavouritesTabContent(this.mappedModules);\n }\n}\n"],"names":["footerDataPromise","modulesDataPromise","bodyPromiseResolver","bodyPromise","Promise","resolve","footerData","sectionModal","Modal","create","body","title","footer","customfootertemplate","large","scrollable","templateContext","classes","show","modulesData","modal","dialogue","ActivityChooserDialogue","templateData","exporter","getModChooserTemplateData","Templates","render","error","errorTemplateData","message","modalPromise","sectionModules","partialFavourite","window","console","warn","then","catch","Notification","exception","constructor","dialogueDom","Exporter","isFavouriteTabDirty","mappedModules","Map","forEach","module","set","componentname","link","init","modalBody","this","getBodyPromise","DialogueDom","registerModalListenerEvents","setupKeyboardAccessibility","getRoot","on","ModalEvents","hidden","destroy","modalRoot","addEventListener","event","target","closest","selectors","regions","allTabNav","searchInput","getSearchInputElement","value","length","toggleSearchResultsView","handleModalClick","bind","pending","initBootstrapComponents","relatedTarget","disableFocusAllChooserOptions","initActiveTabNavigation","isFavoutiteTabActive","refreshFavouritesTabContent","getFooterPromise","handleFooterClick","footerjs","pluginName","customfooterjs","footerClickListener","actions","optionActions","showSummary","handleShowSummary","manageFavourite","handleFavouriteClick","matches","closeOption","hideModuleHelp","dataset","modname","clearSearch","handleClearSearch","moduleName","getClosestChooserOption","moduleData","get","showFooter","hasFooterContent","showModuleHelp","caller","id","name","internal","favourited","Repository","unfavouriteModule","updateFavouriteItemValue","favouriteModule","focus","mainElement","getModal","tabIndex","e","currentOption","keyCode","enter","space","preventDefault","arrowRight","focusNextChooserOption","arrowLeft","focusPreviousChooserOption","home","focusFirstChooserOption","end","focusLastChooserOption","searchQuery","searchResultsData","searchModules","refreshSearchResults","showAllActivitiesTab","cleanSearchResults","searchTerm","toLowerCase","searchResults","activity","activityName","activityDesc","help","includes","push","favourite","moduleItem","find","_ref","updateItemStarredIcons","favouriteCount","filter","mod","size","toggleFavouriteTabDisplay"],"mappings":"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"} \ No newline at end of file +{"version":3,"file":"dialogue.min.js","sources":["../../../src/local/activitychooser/dialogue.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 * A type of dialogue used as for choosing options.\n *\n * @module core_course/local/activitychooser/dialogue\n * @copyright 2019 Mihail Geshoski \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\n// import {addIconToContainer} from 'core/loadingicon';\nimport {debounce} from 'core/utils';\nimport DialogueDom from 'core_course/local/activitychooser/dialoguedom';\nimport {end, arrowLeft, arrowRight, home, enter, space} from 'core/key_codes';\nimport Exporter from 'core_course/local/activitychooser/exporter';\nimport {getFirst} from 'core/normalise';\nimport {getString} from 'core/str';\nimport Modal from 'core/modal';\nimport * as ModalEvents from 'core/modal_events';\nimport Notification from 'core/notification';\nimport * as Repository from 'core_course/local/activitychooser/repository';\nimport selectors from 'core_course/local/activitychooser/selectors';\nimport * as Templates from 'core/templates';\nconst getPlugin = pluginName => import(pluginName);\n\n/**\n * Display the activity chooser modal.\n *\n * @method displayActivityChooser\n * @param {Promise} footerDataPromise Promise for the footer data.\n * @param {Promise} modulesDataPromise Promise for the modules data.\n */\nexport async function displayActivityChooserModal(\n footerDataPromise,\n modulesDataPromise,\n) {\n // We want to show the modal instantly but loading whilst waiting for our data.\n let bodyPromiseResolver;\n const bodyPromise = new Promise(resolve => {\n bodyPromiseResolver = resolve;\n });\n\n const exporter = new Exporter();\n\n const footerData = await footerDataPromise;\n\n const footerPromise = Templates.render(\n 'core_course/local/activitychooser/footer',\n exporter.getFooterData(footerData),\n );\n\n const sectionModal = Modal.create({\n body: bodyPromise,\n title: getString('addresourceoractivity'),\n footer: footerPromise,\n large: true,\n scrollable: false,\n templateContext: {\n classes: 'modchooser'\n },\n show: true,\n });\n\n try {\n const modulesData = await modulesDataPromise;\n\n if (!modulesData) {\n return;\n }\n\n const modal = await sectionModal;\n new ActivityChooserDialogue(modal, modulesData, footerData);\n\n const templateData = await exporter.getModChooserTemplateData(modulesData);\n bodyPromiseResolver(await Templates.render('core_course/activitychooser', templateData));\n } catch (error) {\n const errorTemplateData = {\n 'errormessage': error.message\n };\n bodyPromiseResolver(\n await Templates.render('core_course/local/activitychooser/error', errorTemplateData)\n );\n return;\n }\n}\n\n/**\n * Display the module chooser.\n *\n * @deprecated since Moodle 5.1\n * @todo Remove the method in Moodle 6.0 (MDL-85655).\n * @method displayChooser\n * @param {Promise} modalPromise Our created modal for the section\n * @param {Array} sectionModules An array of all of the built module information\n * @param {Function} partialFavourite Partially applied function we need to manage favourite status\n * @param {Object} footerData Our base footer object.\n */\nexport const displayChooser = (modalPromise, sectionModules, partialFavourite, footerData) => {\n window.console.warn(\n 'The displayChooser function is deprecated. ' +\n 'Please displayActivityChooserModal instead.'\n );\n\n // Register event listeners.\n modalPromise.then(modal => {\n new ActivityChooserDialogue(modal, sectionModules, footerData);\n return modal;\n }).catch(Notification.exception);\n};\n\n/**\n * Activity Chooser Dialogue class.\n *\n * @private\n */\nclass ActivityChooserDialogue {\n /**\n * Constructor for the ActivityChooserDialogue class.\n * @param {Modal} modal The modal object.\n * @param {Object} modulesData The data for the modules.\n * @param {Object} footerData The data for the footer.\n */\n constructor(modal, modulesData, footerData) {\n this.modal = modal;\n this.dialogueDom = null; // We cannot init until we have the modal body loaded.\n this.footerData = footerData;\n this.exporter = new Exporter();\n this.selectedModule = null;\n // This attribute marks when the tab content is dirty and needs to be refreshed when the user changes the tab.\n // We don't want the content to be updated while the user is managing their favourites.\n this.isFavouriteTabDirty = false;\n // Make a map so we can quickly fetch a specific module's object for either rendering or searching.\n this.mappedModules = new Map();\n modulesData.forEach((module) => {\n this.mappedModules.set(module.componentname + '_' + module.link, module);\n });\n this.init();\n }\n\n /**\n * Initialise the activity chooser dialogue.\n *\n * @return {Promise} A promise that resolves when the modal is ready.\n */\n async init() {\n await this.modal.getBodyPromise();\n await this.modal.getFooterPromise();\n this.dialogueDom = new DialogueDom(this, this.modal, this.exporter);\n this.registerModalListenerEvents();\n this.setupKeyboardAccessibility();\n // We want to focus on the action select when the dialog is closed.\n this.modal.getRoot().on(ModalEvents.hidden, () => {\n this.modal.destroy();\n });\n // When modal is open the add button should be disabled because there is no activity selected.\n this.dialogueDom.unmarkAllChooserOptionAsSelected();\n }\n\n /**\n * Register chooser related event listeners.\n *\n * @returns {Promise} A promise that resolves when events are registered\n */\n async registerModalListenerEvents() {\n const modalRoot = getFirst(this.modal.getRoot());\n\n // Changing the tab should cancel any active search.\n modalRoot.addEventListener(\n 'shown.bs.tab',\n (event) => {\n this.dialogueDom.unmarkAllChooserOptionAsSelected();\n // The all tab has the search result, so we do not want to clear the search input.\n if (event.target.closest(selectors.regions.allTabNav)) {\n return;\n }\n const searchInput = this.dialogueDom.getSearchInputElement();\n if (searchInput.value.length > 0) {\n searchInput.value = \"\";\n this.toggleSearchResultsView(searchInput.value);\n }\n },\n );\n\n // Add the listener for clicks on the full modal.\n modalRoot.addEventListener(\n 'click',\n this.handleModalClick.bind(this),\n );\n modalRoot.addEventListener(\n 'dblclick',\n this.handleModalDoubleClick.bind(this),\n );\n\n // Add a listener for an input change in the activity chooser's search bar.\n const searchInput = this.dialogueDom.getSearchInputElement();\n searchInput.addEventListener(\n 'input',\n debounce(\n () => {\n this.toggleSearchResultsView(searchInput.value);\n },\n 300,\n {pending: true},\n ),\n );\n\n this.dialogueDom.initBootstrapComponents();\n\n // Handle focus when a new tab is shown.\n modalRoot.addEventListener('shown.bs.tab', (event) => {\n if (event.relatedTarget) {\n this.dialogueDom.disableFocusAllChooserOptions(event.relatedTarget);\n }\n this.dialogueDom.initActiveTabNavigation();\n });\n\n // Update the favourite tab content when the user changes the tab.\n modalRoot.addEventListener('shown.bs.tab', () => {\n if (this.isFavouriteTabDirty && !this.dialogueDom.isFavoutiteTabActive()) {\n this.refreshFavouritesTabContent();\n }\n });\n\n this.dialogueDom.initActiveTabNavigation();\n\n const modalFooter = getFirst(await this.modal.getFooterPromise());\n\n // Add the listener for clicks on the footer.\n modalFooter.addEventListener(\n 'click',\n this.handleFooterClick.bind(this),\n );\n\n // Adapt modal footer depending on the displayed carousel page.\n modalRoot.addEventListener('slide.bs.carousel', (event) => {\n if (event.to === undefined) {\n return;\n }\n // The boostrap carousel event.to contains the index of the newly active item.\n // The zero index is the chooser options, the first index is the module help,\n // any other index are custom footer pages.\n this.dialogueDom.toggleActiveFooter(event.to === 0);\n this.dialogueDom.toggleBackButton(event.to !== 0);\n this.dialogueDom.toggleAddButton(event.to < 2);\n });\n }\n\n /**\n * Handle the click event on the footer of the modal.\n *\n * @param {Object} event The event object\n * @return {Promise} A promise that resolves when the event is handled\n */\n async handleFooterClick(event) {\n if (event.target.closest(selectors.actions.addSelectedChooserOption)) {\n this.submitAddSelectedModule();\n }\n\n if (\n event.target.closest(selectors.regions.activeFooter)\n && this.footerData.footer === true\n ) {\n const footerjs = await getPlugin(this.footerData.customfooterjs);\n await footerjs.footerClickListener(event, this.footerData, this.modal);\n }\n }\n\n /**\n * Modal click handler.\n *\n * @param {Object} event The event object\n * @return {Promise} A promise that resolves when the event is handled\n */\n async handleModalClick(event) {\n const target = event.target;\n\n if (target.closest(selectors.actions.optionActions.showSummary)) {\n this.handleShowSummary(target);\n return;\n }\n\n if (target.closest(selectors.actions.optionActions.manageFavourite)) {\n await this.handleFavouriteClick(target);\n return;\n }\n\n // From the help screen go back to the module overview.\n if (target.matches(selectors.actions.closeOption)) {\n this.dialogueDom.hideModuleHelp(target.dataset.modname);\n return;\n }\n\n // The \"clear search\" button is triggered.\n if (target.closest(selectors.actions.clearSearch)) {\n this.handleClearSearch();\n return;\n }\n\n if (target.closest(selectors.regions.chooserOption.container)) {\n event.preventDefault();\n this.handleOptionSelection(target);\n return;\n }\n }\n\n /**\n * Handle the double click event on the modal.\n * @param {Object} event The event object\n */\n handleModalDoubleClick(event) {\n const option = this.dialogueDom.getClosestChooserOption(event.target);\n if (option !== null) {\n this.submitAddSelectedModule(this.selectedModule);\n }\n }\n\n /**\n * Show the summary of a module when the user clicks on the \"show summary\" button.\n *\n * @param {HTMLElement} target The target element that triggered the event\n */\n handleShowSummary(target) {\n const module = this.dialogueDom.getClosestChooserOption(target);\n const moduleName = module.dataset.modname;\n const moduleData = this.mappedModules.get(moduleName);\n // We select the module now. This way the back button will keep the module selected.\n this.handleOptionSelection(target);\n // We need to know if the overall modal has a footer so we know when to show a real / vs fake footer.\n moduleData.showFooter = this.modal.hasFooterContent();\n this.dialogueDom.setBackButtonModuleData(moduleData);\n this.dialogueDom.showModuleHelp(moduleData, this.modal);\n }\n\n /**\n * Handle the favourite state of a module when the user clicks on the \"starred\" button.\n *\n * @param {HTMLElement} target The target element that triggered the event\n * @return {Promise} A promise that resolves when the event is handled\n */\n async handleFavouriteClick(target) {\n const caller = target.closest(selectors.actions.optionActions.manageFavourite);\n const id = caller.dataset.id;\n const name = caller.dataset.name;\n const internal = caller.dataset.internal;\n const isFavourite = caller.dataset.favourited;\n\n // Switch on fave or not.\n if (isFavourite === 'true') {\n await Repository.unfavouriteModule(name, id);\n this.updateFavouriteItemValue(internal, false);\n } else {\n await Repository.favouriteModule(name, id);\n this.updateFavouriteItemValue(internal, true);\n }\n }\n\n /**\n * Handle a clear search action.\n */\n handleClearSearch() {\n const searchInput = this.dialogueDom.getSearchInputElement();\n searchInput.value = \"\";\n searchInput.focus();\n this.toggleSearchResultsView(searchInput.value);\n }\n\n /**\n * Handle the click on a chooser option.\n *\n * @param {HTMLElement} target The target element that triggered the event\n */\n handleOptionSelection(target) {\n const option = this.dialogueDom.getClosestChooserOption(target);\n if (option === null) {\n return;\n }\n this.selectedModule = option;\n this.dialogueDom.markChooserOptionAsSelected(option, getFirst(this.modal.getFooter()));\n }\n\n /**\n * Submit the selected module to the chooser.\n *\n * This method will redirect the user to the URL of the selected module, if one is selected.\n *\n * @param {HTMLElement} newSelectedModule optional new selected module element.\n * @return {void}\n */\n submitAddSelectedModule(newSelectedModule = null) {\n if (newSelectedModule) {\n this.handleOptionSelection(newSelectedModule);\n }\n if (this.selectedModule === null) {\n return;\n }\n window.location.href = this.dialogueDom.getChooserOptionUrl(this.selectedModule);\n }\n\n /**\n * Set up our tabindex information across the chooser.\n *\n * @method setupKeyboardAccessibility\n */\n setupKeyboardAccessibility() {\n const mainElement = getFirst(this.modal.getModal());\n\n mainElement.tabIndex = -1;\n\n mainElement.addEventListener('keydown', (e) => {\n const currentOption = this.dialogueDom.getClosestChooserOption(e.target);\n if (currentOption === null) {\n return;\n }\n\n if (e.keyCode === enter || e.keyCode === space) {\n // Check first if the target is an internal control button (favourite or help).\n // If that is the case, the regular click will handle the event.\n if (e.target.closest(selectors.regions.chooserOption.actions)) {\n return;\n }\n }\n\n if (e.keyCode === space) {\n e.preventDefault();\n this.handleOptionSelection(currentOption);\n }\n if (e.keyCode === enter) {\n e.preventDefault();\n this.submitAddSelectedModule(currentOption);\n }\n if (e.keyCode === arrowRight) {\n e.preventDefault();\n this.dialogueDom.focusNextChooserOption(currentOption);\n }\n if (e.keyCode === arrowLeft) {\n e.preventDefault();\n this.dialogueDom.focusPreviousChooserOption(currentOption);\n }\n if (e.keyCode === home) {\n e.preventDefault();\n this.dialogueDom.focusFirstChooserOption(currentOption);\n }\n if (e.keyCode === end) {\n e.preventDefault();\n this.dialogueDom.focusLastChooserOption(currentOption);\n }\n });\n }\n\n /**\n * Toggle (display/hide) the search results depending on the value of the search query\n *\n * @method toggleSearchResultsView\n * @param {String} searchQuery The search query\n */\n async toggleSearchResultsView(searchQuery) {\n const searchResultsData = this.searchModules(searchQuery);\n\n if (searchQuery.length > 0) {\n await this.dialogueDom.refreshSearchResults(searchResultsData);\n this.dialogueDom.showAllActivitiesTab(true);\n } else {\n this.dialogueDom.cleanSearchResults();\n }\n }\n\n /**\n * Return the list of modules which have a name or description that matches the given search term.\n *\n * @method searchModules\n * @param {String} searchTerm The search term to match\n * @return {Array}\n */\n searchModules(searchTerm) {\n if (searchTerm === '') {\n return this.mappedModules;\n }\n searchTerm = searchTerm.toLowerCase();\n const searchResults = [];\n this.mappedModules.forEach((activity) => {\n const activityName = activity.title.toLowerCase();\n const activityDesc = activity.help.toLowerCase();\n if (activityName.includes(searchTerm) || activityDesc.includes(searchTerm)) {\n searchResults.push(activity);\n }\n });\n\n return searchResults;\n }\n\n /**\n * Update the favourite item value in the mapped modules.\n *\n * @param {String} internal The internal name of the module.\n * @param {Boolean} favourite Whether the module is a favourite or not.\n * @return {Promise} A promise that resolves when the item is updated.\n */\n async updateFavouriteItemValue(internal, favourite) {\n const moduleItem = this.mappedModules.find(({name}) => name === internal);\n if (!moduleItem) {\n return;\n }\n moduleItem.favourite = favourite;\n\n this.dialogueDom.updateItemStarredIcons(internal, favourite);\n\n if (this.dialogueDom.isFavoutiteTabActive()) {\n this.isFavouriteTabDirty = true;\n } else {\n this.refreshFavouritesTabContent();\n }\n }\n\n /**\n * Refresh the favourites tab content.\n *\n * Note: this method will also hide the favourites tab if there are no favourite modules\n * to keep the modal consistent.\n *\n * @return {Promise} A promise that resolves when the content is refreshed.\n */\n async refreshFavouritesTabContent() {\n this.isFavouriteTabDirty = false;\n const favouriteCount = this.mappedModules.filter(mod => mod.favourite === true).size;\n this.dialogueDom.toggleFavouriteTabDisplay(favouriteCount > 0);\n await this.dialogueDom.refreshFavouritesTabContent(this.mappedModules);\n }\n}\n"],"names":["footerDataPromise","modulesDataPromise","bodyPromiseResolver","bodyPromise","Promise","resolve","exporter","Exporter","footerData","footerPromise","Templates","render","getFooterData","sectionModal","Modal","create","body","title","footer","large","scrollable","templateContext","classes","show","modulesData","modal","ActivityChooserDialogue","templateData","getModChooserTemplateData","error","errorTemplateData","message","modalPromise","sectionModules","partialFavourite","window","console","warn","then","catch","Notification","exception","constructor","dialogueDom","selectedModule","isFavouriteTabDirty","mappedModules","Map","forEach","module","set","componentname","link","init","this","getBodyPromise","getFooterPromise","DialogueDom","registerModalListenerEvents","setupKeyboardAccessibility","getRoot","on","ModalEvents","hidden","destroy","unmarkAllChooserOptionAsSelected","modalRoot","addEventListener","event","target","closest","selectors","regions","allTabNav","searchInput","getSearchInputElement","value","length","toggleSearchResultsView","handleModalClick","bind","handleModalDoubleClick","pending","initBootstrapComponents","relatedTarget","disableFocusAllChooserOptions","initActiveTabNavigation","isFavoutiteTabActive","refreshFavouritesTabContent","handleFooterClick","undefined","to","toggleActiveFooter","toggleBackButton","toggleAddButton","actions","addSelectedChooserOption","submitAddSelectedModule","activeFooter","footerjs","pluginName","customfooterjs","footerClickListener","optionActions","showSummary","handleShowSummary","manageFavourite","handleFavouriteClick","matches","closeOption","hideModuleHelp","dataset","modname","clearSearch","chooserOption","container","preventDefault","handleOptionSelection","handleClearSearch","getClosestChooserOption","moduleName","moduleData","get","showFooter","hasFooterContent","setBackButtonModuleData","showModuleHelp","caller","id","name","internal","favourited","Repository","unfavouriteModule","updateFavouriteItemValue","favouriteModule","focus","option","markChooserOptionAsSelected","getFooter","newSelectedModule","location","href","getChooserOptionUrl","mainElement","getModal","tabIndex","e","currentOption","keyCode","enter","space","arrowRight","focusNextChooserOption","arrowLeft","focusPreviousChooserOption","home","focusFirstChooserOption","end","focusLastChooserOption","searchQuery","searchResultsData","searchModules","refreshSearchResults","showAllActivitiesTab","cleanSearchResults","searchTerm","toLowerCase","searchResults","activity","activityName","activityDesc","help","includes","push","favourite","moduleItem","find","_ref","updateItemStarredIcons","favouriteCount","filter","mod","size","toggleFavouriteTabDisplay"],"mappings":"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"} \ No newline at end of file diff --git a/public/course/amd/build/local/activitychooser/dialoguedom.min.js b/public/course/amd/build/local/activitychooser/dialoguedom.min.js index cae5d721c7d..3daca0da18f 100644 --- a/public/course/amd/build/local/activitychooser/dialoguedom.min.js +++ b/public/course/amd/build/local/activitychooser/dialoguedom.min.js @@ -1,10 +1,10 @@ -define("core_course/local/activitychooser/dialoguedom",["exports","core/loadingicon","theme_boost/bootstrap/carousel","core/notification","core/pending","core_course/local/activitychooser/selectors","theme_boost/bootstrap/tab","core/templates"],(function(_exports,_loadingicon,_carousel,_notification,_pending,_selectors,_tab,Templates){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("core_course/local/activitychooser/dialoguedom",["exports","core/loadingicon","theme_boost/bootstrap/carousel","core/normalise","core/notification","core/pending","core_course/local/activitychooser/selectors","theme_boost/bootstrap/tab","core/templates"],(function(_exports,_loadingicon,_carousel,_normalise,_notification,_pending,_selectors,_tab,Templates){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}} /** * The activity changer dialogue DOM manipulation module. * * @module core_course/local/activitychooser/dialoguedom * @copyright 2025 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,_carousel=_interopRequireDefault(_carousel),_notification=_interopRequireDefault(_notification),_pending=_interopRequireDefault(_pending),_selectors=_interopRequireDefault(_selectors),_tab=_interopRequireDefault(_tab),Templates=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}(Templates);return _exports.default=class{constructor(dialogue,modalBody,exporter){this.modalBody=modalBody,this.exporter=exporter,this.dialogue=dialogue}getSearchInputElement(){return this.modalBody.querySelector(_selectors.default.actions.search)}getClosestChooserOption(element){return element.closest(_selectors.default.regions.chooserOption.container)}isFavoutiteTabActive(){const favouriteTab=this.modalBody.querySelector(_selectors.default.regions.favouriteTabNav);return favouriteTab&&favouriteTab.classList.contains("active")}async refreshSearchResults(searchResultsData){const searchResultsContainer=this.modalBody.querySelector(_selectors.default.regions.searchResults),clearSearchButton=this.modalBody.querySelector(_selectors.default.actions.clearSearch);await this.renderSearchResults(searchResultsContainer,searchResultsData);const firstSearchResultItem=searchResultsContainer.querySelector(_selectors.default.regions.chooserOptions).querySelector(_selectors.default.regions.chooserOption.container);firstSearchResultItem&&this.toggleFocusableChooserOption(firstSearchResultItem,!0),clearSearchButton.classList.remove("d-none");searchResultsContainer.closest(_selectors.default.regions.tabContent).querySelector(_selectors.default.regions.categoryContent).classList.add("d-none")}cleanSearchResults(){const searchResultsContainer=this.modalBody.querySelector(_selectors.default.regions.searchResults),clearSearchButton=this.modalBody.querySelector(_selectors.default.actions.clearSearch);searchResultsContainer.innerHTML="",clearSearchButton.classList.add("d-none");searchResultsContainer.closest(_selectors.default.regions.tabContent).querySelector(_selectors.default.regions.categoryContent).classList.remove("d-none")}async renderSearchResults(searchResultsContainer,searchResultsData){const templateData=this.exporter.getSearchResultData(searchResultsData),{html:html,js:js}=await Templates.renderForPromise("core_course/local/activitychooser/search_results",templateData);await Templates.replaceNodeContents(searchResultsContainer,html,js)}showAllActivitiesTab(){const navTab=this.modalBody.querySelector(_selectors.default.regions.allTabNav);if(navTab.classList.contains("active"))return navTab;const pendingPromise=new _pending.default("core_course/activitychooser:alltab");return navTab.addEventListener("shown.bs.tab",pendingPromise.resolve,{once:!0}),_tab.default.getOrCreateInstance(navTab).show(),navTab}updateItemStarredIcons(internal,favourite){const favouriteButtons=this.modalBody.querySelectorAll("".concat(_selectors.default.elements.moduleItem(internal)," ").concat(_selectors.default.actions.optionActions.manageFavourite));Array.from(favouriteButtons).forEach((element=>{var _element$querySelecto,_element$querySelecto2;element.classList.toggle("text-muted",!favourite),element.classList.toggle("text-primary",favourite),element.dataset.favourited=favourite,element.setAttribute("aria-pressed",favourite),null===(_element$querySelecto=element.querySelector(_selectors.default.elements.favouriteIconActive))||void 0===_element$querySelecto||_element$querySelecto.classList.toggle("d-none",!favourite),null===(_element$querySelecto2=element.querySelector(_selectors.default.elements.favouriteIconInactive))||void 0===_element$querySelecto2||_element$querySelecto2.classList.toggle("d-none",favourite);const iconSelectsor=favourite?_selectors.default.elements.favouriteIconActive:_selectors.default.elements.favouriteIconInactive,favouriteIcon=element.querySelector(iconSelectsor);element.setAttribute("aria-label",(null==favouriteIcon?void 0:favouriteIcon.getAttribute("data-action-label"))||"")}))}async refreshFavouritesTabContent(mappedModules){const templateData=await this.exporter.getFavouriteTabData(mappedModules),favouriteArea=this.modalBody.querySelector(_selectors.default.regions.favouriteTab),{html:html,js:js}=await Templates.renderForPromise("core_course/local/activitychooser/tabcontent",templateData);await Templates.replaceNodeContents(favouriteArea,html,js)}toggleFavouriteTabDisplay(displayed){const favouriteTabNav=this.modalBody.querySelector(_selectors.default.regions.favouriteTabNav);let moveFocusTo;!displayed&&favouriteTabNav.classList.contains("active")&&(moveFocusTo=this.showAllActivitiesTab()),null==favouriteTabNav||favouriteTabNav.classList.toggle("d-none",!displayed),favouriteTabNav.tabIndex=displayed?0:-1,displayed?favouriteTabNav.classList.remove("disabled"):favouriteTabNav.classList.add("disabled"),moveFocusTo&&moveFocusTo.focus(),this.initActiveTabNavigation()}showModuleHelp(moduleData,modal){const carousel=this.modalBody.querySelector(_selectors.default.regions.carousel);!0===moduleData.showFooter&&modal.setFooter(Templates.render("core_course/local/activitychooser/footer_partial",moduleData));const help=carousel.querySelector(_selectors.default.regions.help);help.innerHTML="",help.classList.add("m-auto");const spinnerPromise=(0,_loadingicon.addIconToContainer)(help);let transitionPromiseResolver=null;const transitionPromise=new Promise((resolve=>{transitionPromiseResolver=resolve})),contentPromise=Templates.renderForPromise("core_course/local/activitychooser/help",moduleData);Promise.all([contentPromise,spinnerPromise,transitionPromise]).then((_ref=>{let[{html:html,js:js}]=_ref;return Templates.replaceNodeContents(help,html,js)})).then((()=>(help.querySelector(_selectors.default.regions.chooserSummary.header).focus(),help))).catch(_notification.default.exception),carousel.addEventListener("slid.bs.carousel",(()=>{transitionPromiseResolver()}),{once:!0}),_carousel.default.getInstance(carousel).next()}hideModuleHelp(){let internal=arguments.length>0&&void 0!==arguments[0]?arguments[0]:null;const carousel=this.modalBody.querySelector(_selectors.default.regions.carousel);_carousel.default.getInstance(carousel).prev(),null!==internal&&carousel.addEventListener("slid.bs.carousel",(()=>{this.focusChooserOption(internal)}),{once:!0})}focusChooserOption(internal){const currentTabNav=this.modalBody.querySelector(_selectors.default.elements.activetab),activeSectionId=currentTabNav.getAttribute("href"),newCurrent=this.modalBody.querySelector(_selectors.default.regions.getSectionChooserOptions(activeSectionId)).querySelector(_selectors.default.regions.getModuleSelector(internal));if(!newCurrent)throw new Error("Invalid chooser option to focus on: ".concat(internal));this.disableFocusAllChooserOptions(currentTabNav),this.toggleFocusableChooserOption(newCurrent,!0),newCurrent.contentEditable=!0,newCurrent.focus(),newCurrent.contentEditable=!1}initActiveTabNavigation(){const activeSectionId=this.modalBody.querySelector(_selectors.default.elements.activetab).getAttribute("href"),sectionChooserOptions=this.modalBody.querySelector(_selectors.default.regions.getSectionChooserOptions(activeSectionId)),firstChooserOption=null==sectionChooserOptions?void 0:sectionChooserOptions.querySelector(_selectors.default.regions.chooserOption.container);firstChooserOption&&this.toggleFocusableChooserOption(firstChooserOption,!0)}initBootstrapComponents(){this.modalBody.querySelectorAll(_selectors.default.elements.tab).forEach((navTab=>{_tab.default.getOrCreateInstance(navTab)}));const carousel=this.modalBody.querySelector(_selectors.default.regions.carousel);new _carousel.default(carousel,{interval:!1,pause:!0,keyboard:!1})}disableFocusAllChooserOptions(tabNav){const tabId=tabNav.getAttribute("href"),chooserOptions=this.modalBody.querySelector(_selectors.default.regions.getSectionChooserOptions(tabId));if(null===chooserOptions)return;chooserOptions.querySelectorAll(_selectors.default.regions.chooserOption.container).forEach((chooserOption=>{this.toggleFocusableChooserOption(chooserOption,!1)}))}toggleFocusableChooserOption(chooserOption,isFocusable){const chooserOptionLink=chooserOption.querySelector(_selectors.default.actions.addChooser),chooserOptionHelp=chooserOption.querySelector(_selectors.default.actions.optionActions.showSummary),chooserOptionFavourite=chooserOption.querySelector(_selectors.default.actions.optionActions.manageFavourite);isFocusable?(chooserOption.tabIndex=0,chooserOptionLink.tabIndex=0,chooserOptionHelp.tabIndex=0,chooserOptionFavourite.tabIndex=0):(chooserOption.tabIndex=-1,chooserOptionLink.tabIndex=-1,chooserOptionHelp.tabIndex=-1,chooserOptionFavourite.tabIndex=-1)}focusNextChooserOption(current){this.moveChooserOptionFocus(current,(currentOption=>{var _currentOption$nextEl;return null!==(_currentOption$nextEl=currentOption.nextElementSibling)&&void 0!==_currentOption$nextEl?_currentOption$nextEl:currentOption}))}focusPreviousChooserOption(current){this.moveChooserOptionFocus(current,(currentOption=>{var _currentOption$previo;return null!==(_currentOption$previo=currentOption.previousElementSibling)&&void 0!==_currentOption$previo?_currentOption$previo:currentOption}))}focusFirstChooserOption(current){this.moveChooserOptionFocus(current,((currentOption,container)=>{var _container$firstEleme;return null!==(_container$firstEleme=container.firstElementChild)&&void 0!==_container$firstEleme?_container$firstEleme:currentOption}))}focusLastChooserOption(current){this.moveChooserOptionFocus(current,((currentOption,container)=>{var _container$lastElemen;return null!==(_container$lastElemen=container.lastElementChild)&&void 0!==_container$lastElemen?_container$lastElemen:currentOption}))}moveChooserOptionFocus(current,getNextFocus){const currentOption=this.getClosestChooserOption(current),container=current.closest(_selectors.default.regions.chooserOptions);if(!container||!currentOption)throw new Error("Invalid chooser options container or current option");const newFocusOption=getNextFocus(currentOption,container);newFocusOption&&(this.toggleFocusableChooserOption(currentOption,!1),this.toggleFocusableChooserOption(newFocusOption,!0),newFocusOption.focus())}},_exports.default})); + */Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.default=void 0,_carousel=_interopRequireDefault(_carousel),_notification=_interopRequireDefault(_notification),_pending=_interopRequireDefault(_pending),_selectors=_interopRequireDefault(_selectors),_tab=_interopRequireDefault(_tab),Templates=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}(Templates);return _exports.default=class{constructor(dialogue,modal,exporter){this.modal=modal,this.modalBody=(0,_normalise.getFirst)(this.modal.getBody()),this.exporter=exporter,this.dialogue=dialogue}getSearchInputElement(){return this.modalBody.querySelector(_selectors.default.actions.search)}getClosestChooserOption(element){return element.closest(_selectors.default.regions.chooserOption.container)}isFavoutiteTabActive(){const favouriteTab=this.modalBody.querySelector(_selectors.default.regions.favouriteTabNav);return favouriteTab&&favouriteTab.classList.contains("active")}getChooserOptionUrl(optionContainer){const optionLink=optionContainer.querySelector(_selectors.default.actions.addChooser);if(!optionLink)throw new Error("Invalid chooser option container: no link found");return optionLink.getAttribute("href")}async refreshSearchResults(searchResultsData){const searchResultsContainer=this.modalBody.querySelector(_selectors.default.regions.searchResults),clearSearchButton=this.modalBody.querySelector(_selectors.default.actions.clearSearch);await this.renderSearchResults(searchResultsContainer,searchResultsData);const firstSearchResultItem=searchResultsContainer.querySelector(_selectors.default.regions.chooserOptions).querySelector(_selectors.default.regions.chooserOption.container);firstSearchResultItem&&this.toggleFocusableChooserOption(firstSearchResultItem,!0),clearSearchButton.classList.remove("d-none");searchResultsContainer.closest(_selectors.default.regions.tabContent).querySelector(_selectors.default.regions.categoryContent).classList.add("d-none")}cleanSearchResults(){const searchResultsContainer=this.modalBody.querySelector(_selectors.default.regions.searchResults),clearSearchButton=this.modalBody.querySelector(_selectors.default.actions.clearSearch);searchResultsContainer.innerHTML="",clearSearchButton.classList.add("d-none");searchResultsContainer.closest(_selectors.default.regions.tabContent).querySelector(_selectors.default.regions.categoryContent).classList.remove("d-none")}async renderSearchResults(searchResultsContainer,searchResultsData){const templateData=this.exporter.getSearchResultData(searchResultsData),{html:html,js:js}=await Templates.renderForPromise("core_course/local/activitychooser/search_results",templateData);await Templates.replaceNodeContents(searchResultsContainer,html,js)}showAllActivitiesTab(){const navTab=this.modalBody.querySelector(_selectors.default.regions.allTabNav);if(navTab.classList.contains("active"))return navTab;const pendingPromise=new _pending.default("core_course/activitychooser:alltab");return navTab.addEventListener("shown.bs.tab",pendingPromise.resolve,{once:!0}),_tab.default.getOrCreateInstance(navTab).show(),navTab}updateItemStarredIcons(internal,favourite){const favouriteButtons=this.modalBody.querySelectorAll("".concat(_selectors.default.elements.moduleItem(internal)," ").concat(_selectors.default.actions.optionActions.manageFavourite));Array.from(favouriteButtons).forEach((element=>{var _element$querySelecto,_element$querySelecto2;element.classList.toggle("text-muted",!favourite),element.classList.toggle("text-primary",favourite),element.dataset.favourited=favourite,element.setAttribute("aria-pressed",favourite),null===(_element$querySelecto=element.querySelector(_selectors.default.elements.favouriteIconActive))||void 0===_element$querySelecto||_element$querySelecto.classList.toggle("d-none",!favourite),null===(_element$querySelecto2=element.querySelector(_selectors.default.elements.favouriteIconInactive))||void 0===_element$querySelecto2||_element$querySelecto2.classList.toggle("d-none",favourite);const iconSelectsor=favourite?_selectors.default.elements.favouriteIconActive:_selectors.default.elements.favouriteIconInactive,favouriteIcon=element.querySelector(iconSelectsor);element.setAttribute("aria-label",(null==favouriteIcon?void 0:favouriteIcon.getAttribute("data-action-label"))||"")}))}async refreshFavouritesTabContent(mappedModules){const templateData=await this.exporter.getFavouriteTabData(mappedModules),favouriteArea=this.modalBody.querySelector(_selectors.default.regions.favouriteTab),{html:html,js:js}=await Templates.renderForPromise("core_course/local/activitychooser/tabcontent",templateData);await Templates.replaceNodeContents(favouriteArea,html,js)}toggleFavouriteTabDisplay(displayed){const favouriteTabNav=this.modalBody.querySelector(_selectors.default.regions.favouriteTabNav);let moveFocusTo;!displayed&&favouriteTabNav.classList.contains("active")&&(moveFocusTo=this.showAllActivitiesTab()),null==favouriteTabNav||favouriteTabNav.classList.toggle("d-none",!displayed),favouriteTabNav.tabIndex=displayed?0:-1,displayed?favouriteTabNav.classList.remove("disabled"):favouriteTabNav.classList.add("disabled"),moveFocusTo&&moveFocusTo.focus(),this.initActiveTabNavigation()}showModuleHelp(moduleData){const carousel=this.modalBody.querySelector(_selectors.default.regions.carousel),help=carousel.querySelector(_selectors.default.regions.help);help.innerHTML="",help.classList.add("m-auto");const spinnerPromise=(0,_loadingicon.addIconToContainer)(help);let transitionPromiseResolver=null;const transitionPromise=new Promise((resolve=>{transitionPromiseResolver=resolve})),contentPromise=Templates.renderForPromise("core_course/local/activitychooser/help",moduleData);Promise.all([contentPromise,spinnerPromise,transitionPromise]).then((_ref=>{let[{html:html,js:js}]=_ref;return Templates.replaceNodeContents(help,html,js)})).then((()=>(help.querySelector(_selectors.default.regions.chooserSummary.header).focus(),help))).catch(_notification.default.exception),carousel.addEventListener("slid.bs.carousel",(()=>{transitionPromiseResolver()}),{once:!0}),_carousel.default.getInstance(carousel).next()}hideModuleHelp(){let internal=arguments.length>0&&void 0!==arguments[0]?arguments[0]:null;const carousel=this.modalBody.querySelector(_selectors.default.regions.carousel);_carousel.default.getInstance(carousel).to(0),internal&&carousel.addEventListener("slid.bs.carousel",(()=>{this.focusChooserOption(internal)}),{once:!0})}setBackButtonModuleData(moduleData){const footer=(0,_normalise.getFirst)(this.modal.getFooter()),modnameValue="".concat(moduleData.componentname,"_").concat(moduleData.link);footer.querySelector(_selectors.default.actions.closeOption).dataset.modname=modnameValue}toggleBackButton(show){(0,_normalise.getFirst)(this.modal.getFooter()).querySelector(_selectors.default.actions.closeOption).classList.toggle("d-none",!show)}toggleAddButton(show){(0,_normalise.getFirst)(this.modal.getFooter()).querySelector(_selectors.default.actions.addSelectedChooserOption).classList.toggle("d-none",!show)}toggleActiveFooter(show){(0,_normalise.getFirst)(this.modal.getFooter()).querySelector(_selectors.default.regions.activeFooter).classList.toggle("d-none",!show)}focusChooserOption(internal){const currentTabNav=this.modalBody.querySelector(_selectors.default.elements.activetab),activeSectionId=currentTabNav.getAttribute("href"),newCurrent=this.modalBody.querySelector(_selectors.default.regions.getSectionChooserOptions(activeSectionId)).querySelector(_selectors.default.regions.getModuleSelector(internal));if(!newCurrent)throw new Error("Invalid chooser option to focus on: ".concat(internal));this.disableFocusAllChooserOptions(currentTabNav),this.toggleFocusableChooserOption(newCurrent,!0),newCurrent.contentEditable=!0,newCurrent.focus(),newCurrent.contentEditable=!1}initActiveTabNavigation(){const activeSectionId=this.modalBody.querySelector(_selectors.default.elements.activetab).getAttribute("href"),sectionChooserOptions=this.modalBody.querySelector(_selectors.default.regions.getSectionChooserOptions(activeSectionId)),firstChooserOption=null==sectionChooserOptions?void 0:sectionChooserOptions.querySelector(_selectors.default.regions.chooserOption.container);firstChooserOption&&this.toggleFocusableChooserOption(firstChooserOption,!0)}initBootstrapComponents(){this.modalBody.querySelectorAll(_selectors.default.elements.tab).forEach((navTab=>{_tab.default.getOrCreateInstance(navTab)}));const carousel=this.modalBody.querySelector(_selectors.default.regions.carousel);new _carousel.default(carousel,{interval:!1,pause:!0,keyboard:!1})}disableFocusAllChooserOptions(tabNav){const tabId=tabNav.getAttribute("href"),chooserOptions=this.modalBody.querySelector(_selectors.default.regions.getSectionChooserOptions(tabId));if(null===chooserOptions)return;chooserOptions.querySelectorAll(_selectors.default.regions.chooserOption.container).forEach((chooserOption=>{this.toggleFocusableChooserOption(chooserOption,!1)}))}toggleFocusableChooserOption(chooserOption,isFocusable){const chooserOptionLink=chooserOption.querySelector(_selectors.default.actions.addChooser),chooserOptionHelp=chooserOption.querySelector(_selectors.default.actions.optionActions.showSummary),chooserOptionFavourite=chooserOption.querySelector(_selectors.default.actions.optionActions.manageFavourite);isFocusable?(chooserOption.tabIndex=0,chooserOptionLink.tabIndex=0,chooserOptionHelp.tabIndex=0,chooserOptionFavourite.tabIndex=0):(chooserOption.tabIndex=-1,chooserOptionLink.tabIndex=-1,chooserOptionHelp.tabIndex=-1,chooserOptionFavourite.tabIndex=-1)}focusNextChooserOption(current){this.moveChooserOptionFocus(current,(currentOption=>{var _currentOption$nextEl;return null!==(_currentOption$nextEl=currentOption.nextElementSibling)&&void 0!==_currentOption$nextEl?_currentOption$nextEl:currentOption}))}focusPreviousChooserOption(current){this.moveChooserOptionFocus(current,(currentOption=>{var _currentOption$previo;return null!==(_currentOption$previo=currentOption.previousElementSibling)&&void 0!==_currentOption$previo?_currentOption$previo:currentOption}))}focusFirstChooserOption(current){this.moveChooserOptionFocus(current,((currentOption,container)=>{var _container$firstEleme;return null!==(_container$firstEleme=container.firstElementChild)&&void 0!==_container$firstEleme?_container$firstEleme:currentOption}))}focusLastChooserOption(current){this.moveChooserOptionFocus(current,((currentOption,container)=>{var _container$lastElemen;return null!==(_container$lastElemen=container.lastElementChild)&&void 0!==_container$lastElemen?_container$lastElemen:currentOption}))}moveChooserOptionFocus(current,getNextFocus){const currentOption=this.getClosestChooserOption(current),container=current.closest(_selectors.default.regions.chooserOptions);if(!container||!currentOption)throw new Error("Invalid chooser options container or current option");const newFocusOption=getNextFocus(currentOption,container);newFocusOption&&(this.toggleFocusableChooserOption(currentOption,!1),this.toggleFocusableChooserOption(newFocusOption,!0),newFocusOption.focus())}markChooserOptionAsSelected(chooserOption){this.unmarkAllChooserOptionAsSelected(),chooserOption.classList.add("selected"),chooserOption.classList.add("border-primary"),chooserOption.setAttribute("aria-selected","true");const addButton=(0,_normalise.getFirst)(this.modal.getFooter()).querySelector(_selectors.default.actions.addSelectedChooserOption);addButton&&addButton.removeAttribute("disabled")}unmarkAllChooserOptionAsSelected(){this.modalBody.querySelectorAll("".concat(_selectors.default.regions.chooserOption.container,".selected")).forEach((option=>{option.classList.remove("selected"),option.classList.remove("border-primary"),option.setAttribute("aria-selected","false")}));const addButton=(0,_normalise.getFirst)(this.modal.getFooter()).querySelector(_selectors.default.actions.addSelectedChooserOption);addButton&&addButton.setAttribute("disabled","disabled")}},_exports.default})); //# sourceMappingURL=dialoguedom.min.js.map \ No newline at end of file diff --git a/public/course/amd/build/local/activitychooser/dialoguedom.min.js.map b/public/course/amd/build/local/activitychooser/dialoguedom.min.js.map index 6342ae8214a..4894adcf7aa 100644 --- a/public/course/amd/build/local/activitychooser/dialoguedom.min.js.map +++ b/public/course/amd/build/local/activitychooser/dialoguedom.min.js.map @@ -1 +1 @@ -{"version":3,"file":"dialoguedom.min.js","sources":["../../../src/local/activitychooser/dialoguedom.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 {addIconToContainer} from 'core/loadingicon';\nimport Carousel from 'theme_boost/bootstrap/carousel';\nimport Notification from 'core/notification';\nimport Pending from 'core/pending';\nimport selectors from 'core_course/local/activitychooser/selectors';\nimport Tab from 'theme_boost/bootstrap/tab';\nimport * as Templates from 'core/templates';\n\n\n/**\n * The activity changer dialogue DOM manipulation module.\n *\n * @module core_course/local/activitychooser/dialoguedom\n * @copyright 2025 Ferran Recio \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\nexport default class ChooserDialogueDOM {\n\n constructor(dialogue, modalBody, exporter) {\n this.modalBody = modalBody;\n this.exporter = exporter;\n // Temporal variable while migrating methods.\n this.dialogue = dialogue;\n }\n\n /**\n * Get the search input element.\n *\n * @return {HTMLElement} The search input element.\n */\n getSearchInputElement() {\n return this.modalBody.querySelector(selectors.actions.search);\n }\n\n /**\n * Get the closest chooser option element.\n *\n * @param {HTMLElement} element\n * @return {HTMLElement|null} element\n */\n getClosestChooserOption(element) {\n return element.closest(selectors.regions.chooserOption.container);\n }\n\n /**\n * Check if the search tab is active.\n *\n * @return {Boolean} True if the search tab is active, false otherwise.\n */\n isFavoutiteTabActive() {\n const favouriteTab = this.modalBody.querySelector(selectors.regions.favouriteTabNav);\n return favouriteTab && favouriteTab.classList.contains('active');\n }\n\n /**\n * Show the search results.\n *\n * @param {Object} searchResultsData Data containing the module items that satisfy the search criteria\n */\n async refreshSearchResults(searchResultsData) {\n const searchResultsContainer = this.modalBody.querySelector(selectors.regions.searchResults);\n const clearSearchButton = this.modalBody.querySelector(selectors.actions.clearSearch);\n\n await this.renderSearchResults(searchResultsContainer, searchResultsData);\n const chooserOptionsContainer = searchResultsContainer.querySelector(selectors.regions.chooserOptions);\n const firstSearchResultItem = chooserOptionsContainer.querySelector(selectors.regions.chooserOption.container);\n if (firstSearchResultItem) {\n // Set the first result item to be focusable.\n this.toggleFocusableChooserOption(firstSearchResultItem, true);\n // Register keyboard events on the created search result items.\n }\n clearSearchButton.classList.remove('d-none');\n\n // Results are rendered in the all activities tab, so we need to hide the category content.\n const tabContent = searchResultsContainer.closest(selectors.regions.tabContent);\n const categoryContent = tabContent.querySelector(selectors.regions.categoryContent);\n categoryContent.classList.add('d-none');\n }\n\n /**\n * Clear the search results.\n */\n cleanSearchResults() {\n const searchResultsContainer = this.modalBody.querySelector(selectors.regions.searchResults);\n const clearSearchButton = this.modalBody.querySelector(selectors.actions.clearSearch);\n searchResultsContainer.innerHTML = '';\n clearSearchButton.classList.add('d-none');\n\n // Results are rendered in the all activities tab, so we need to show the category content again.\n const tabContent = searchResultsContainer.closest(selectors.regions.tabContent);\n const categoryContent = tabContent.querySelector(selectors.regions.categoryContent);\n categoryContent.classList.remove('d-none');\n }\n\n /**\n * Render the search results in a defined container\n *\n * @private\n * @method renderSearchResults\n * @param {HTMLElement} searchResultsContainer The container where the data should be rendered\n * @param {Object} searchResultsData Data containing the module items that satisfy the search criteria\n */\n async renderSearchResults(searchResultsContainer, searchResultsData) {\n const templateData = this.exporter.getSearchResultData(searchResultsData);\n // Build up the html & js ready to place into the help section.\n const {html, js} = await Templates.renderForPromise(\n 'core_course/local/activitychooser/search_results',\n templateData\n );\n await Templates.replaceNodeContents(searchResultsContainer, html, js);\n }\n\n /**\n * Show the \"All activities\" tab.\n *\n * @method showAllActivitiesTab\n * @return {HTMLElement} The \"All activities\" tab element.\n */\n showAllActivitiesTab() {\n const navTab = this.modalBody.querySelector(selectors.regions.allTabNav);\n\n if (navTab.classList.contains('active')) {\n return navTab;\n }\n\n const pendingPromise = new Pending('core_course/activitychooser:alltab');\n navTab.addEventListener('shown.bs.tab', pendingPromise.resolve, {once: true});\n\n Tab.getOrCreateInstance(navTab).show();\n return navTab;\n }\n\n /**\n * Update the starred icons in the chooser modal.\n *\n * @method updateItemStarredIcons\n * @param {String} internal The internal name of the module.\n * @param {Boolean} favourite Whether the module is a favourite or not.\n */\n updateItemStarredIcons(internal, favourite) {\n const favouriteButtons = this.modalBody.querySelectorAll(\n `${selectors.elements.moduleItem(internal)} ${selectors.actions.optionActions.manageFavourite}`\n );\n Array.from(favouriteButtons).forEach((element) => {\n element.classList.toggle('text-muted', !favourite);\n element.classList.toggle('text-primary', favourite);\n element.dataset.favourited = favourite;\n element.setAttribute('aria-pressed', favourite);\n element.querySelector(selectors.elements.favouriteIconActive)?.classList.toggle('d-none', !favourite);\n element.querySelector(selectors.elements.favouriteIconInactive)?.classList.toggle('d-none', favourite);\n\n const iconSelectsor = favourite ? selectors.elements.favouriteIconActive : selectors.elements.favouriteIconInactive;\n const favouriteIcon = element.querySelector(iconSelectsor);\n element.setAttribute('aria-label', favouriteIcon?.getAttribute('data-action-label') || '');\n });\n }\n\n /**\n * Refresh the favourite content.\n *\n * @param {Array} mappedModules The modules to be displayed in the favourite tab.\n */\n async refreshFavouritesTabContent(mappedModules) {\n const templateData = await this.exporter.getFavouriteTabData(mappedModules);\n const favouriteArea = this.modalBody.querySelector(selectors.regions.favouriteTab);\n const {html, js} = await Templates.renderForPromise(\n 'core_course/local/activitychooser/tabcontent',\n templateData,\n );\n await Templates.replaceNodeContents(favouriteArea, html, js);\n }\n\n /**\n * Toggle the display of the favourite tab.\n *\n * The favourite tab is only displayed when there are favourite modules\n * or when it is the active tab.\n *\n * @param {Boolean} displayed Whether we want to show or hide the favourite tab\n */\n toggleFavouriteTabDisplay(displayed) {\n const favouriteTabNav = this.modalBody.querySelector(selectors.regions.favouriteTabNav);\n\n let moveFocusTo;\n if (!displayed && favouriteTabNav.classList.contains('active')) {\n moveFocusTo = this.showAllActivitiesTab();\n }\n\n favouriteTabNav?.classList.toggle('d-none', !displayed);\n favouriteTabNav.tabIndex = displayed ? 0 : -1;\n // The disabled class is used by Boostrap Tab for keyboard navigation.\n if (displayed) {\n favouriteTabNav.classList.remove('disabled');\n } else {\n favouriteTabNav.classList.add('disabled');\n }\n\n if (moveFocusTo) {\n moveFocusTo.focus();\n }\n this.initActiveTabNavigation();\n }\n\n /**\n * Given an event from the main module 'page' navigate to it's help section via a carousel.\n *\n * @method showModuleHelp\n * @param {Object} moduleData Data of the module to carousel to\n * @param {Modal} modal The modal object\n */\n showModuleHelp(moduleData, modal) {\n const carousel = this.modalBody.querySelector(selectors.regions.carousel);\n // If we have a real footer then we need to change temporarily.\n if (moduleData.showFooter === true) {\n modal.setFooter(Templates.render(\n 'core_course/local/activitychooser/footer_partial',\n moduleData\n ));\n }\n const help = carousel.querySelector(selectors.regions.help);\n help.innerHTML = '';\n help.classList.add('m-auto');\n\n // Add a spinner.\n const spinnerPromise = addIconToContainer(help);\n\n // Used later...\n let transitionPromiseResolver = null;\n const transitionPromise = new Promise(resolve => {\n transitionPromiseResolver = resolve;\n });\n\n // Build up the html & js ready to place into the help section.\n const contentPromise = Templates.renderForPromise(\n 'core_course/local/activitychooser/help',\n moduleData\n );\n\n // Wait for the content to be ready, and for the transition to be complet.\n Promise.all([contentPromise, spinnerPromise, transitionPromise])\n .then(([{html, js}]) => Templates.replaceNodeContents(help, html, js))\n .then(() => {\n help.querySelector(selectors.regions.chooserSummary.header).focus();\n return help;\n })\n .catch(Notification.exception);\n\n // Move to the next slide, and resolve the transition promise when it's done.\n carousel.addEventListener(\n 'slid.bs.carousel',\n () => {\n transitionPromiseResolver();\n },\n {once: true}\n );\n // Trigger the transition between 'pages'.\n Carousel.getInstance(carousel).next();\n }\n\n /**\n * Hide the help section of the chooser.\n *\n * @param {String|null} internal The internal name of the module to return to, if any.\n */\n hideModuleHelp(internal = null) {\n const carousel = this.modalBody.querySelector(selectors.regions.carousel);\n // Trigger the transition between 'pages'.\n Carousel.getInstance(carousel).prev();\n if (internal === null) {\n return;\n }\n carousel.addEventListener(\n 'slid.bs.carousel',\n () => {\n this.focusChooserOption(internal);\n },\n {once: true}\n );\n }\n\n /**\n * Focus on a specific activity inside the active tab (if present).\n *\n * @private\n * @method focusChooserOption\n * @param {String} internal The internal name of the module.\n */\n focusChooserOption(internal) {\n const currentTabNav = this.modalBody.querySelector(selectors.elements.activetab);\n const activeSectionId = currentTabNav.getAttribute(\"href\");\n const sectionChooserOptions = this.modalBody.querySelector(selectors.regions.getSectionChooserOptions(activeSectionId));\n const newCurrent = sectionChooserOptions.querySelector(selectors.regions.getModuleSelector(internal));\n\n if (!newCurrent) {\n throw new Error(`Invalid chooser option to focus on: ${internal}`);\n }\n\n // Chooser can only have one element focusable at a time, so we disable them all first.\n this.disableFocusAllChooserOptions(currentTabNav);\n this.toggleFocusableChooserOption(newCurrent, true);\n\n // Little hack: we want the element considered a focus-visible element.\n // But the focus method does not trigger the focus-visible class. There's an\n // experimental \"{focusVisible: true}\" option in the focus method, but it's not\n // supported in all browsers yet so we need to fake an editable element.\n newCurrent.contentEditable = true;\n newCurrent.focus();\n newCurrent.contentEditable = false;\n }\n\n /**\n * Initialise the active tab navigation.\n */\n initActiveTabNavigation() {\n const activeSectionId = this.modalBody.querySelector(selectors.elements.activetab).getAttribute(\"href\");\n const sectionChooserOptions = this.modalBody.querySelector(selectors.regions.getSectionChooserOptions(activeSectionId));\n const firstChooserOption = sectionChooserOptions?.querySelector(selectors.regions.chooserOption.container);\n if (!firstChooserOption) {\n return;\n }\n this.toggleFocusableChooserOption(firstChooserOption, true);\n }\n\n /**\n * Initialise all Boostrap components.\n */\n initBootstrapComponents() {\n this.modalBody.querySelectorAll(selectors.elements.tab).forEach((navTab) => {\n // Init the Bootstrap Tab navigation.\n Tab.getOrCreateInstance(navTab);\n });\n\n // Set up the carousel.\n const carousel = this.modalBody.querySelector(selectors.regions.carousel);\n new Carousel(carousel, {\n interval: false,\n pause: true,\n keyboard: false\n });\n }\n\n /**\n * Disable the focus of all chooser options in a specific container (section).\n *\n * @method disableFocusAllChooserOptions\n * @param {HTMLElement} tabNav The tab navigation element (from the shown.bs.ta event).\n */\n disableFocusAllChooserOptions(tabNav) {\n const tabId = tabNav.getAttribute(\"href\");\n const chooserOptions = this.modalBody.querySelector(\n selectors.regions.getSectionChooserOptions(tabId)\n );\n\n if (chooserOptions === null) {\n return;\n }\n\n const allChooserOptions = chooserOptions.querySelectorAll(selectors.regions.chooserOption.container);\n allChooserOptions.forEach((chooserOption) => {\n this.toggleFocusableChooserOption(chooserOption, false);\n });\n }\n\n /**\n * Add or remove a chooser option from the focus order.\n *\n * @private\n * @method toggleFocusableChooserOption\n * @param {HTMLElement} chooserOption The chooser option element which should be added or removed from the focus order\n * @param {Boolean} isFocusable Whether the chooser element is focusable or not\n */\n toggleFocusableChooserOption(chooserOption, isFocusable) {\n const chooserOptionLink = chooserOption.querySelector(selectors.actions.addChooser);\n const chooserOptionHelp = chooserOption.querySelector(selectors.actions.optionActions.showSummary);\n const chooserOptionFavourite = chooserOption.querySelector(selectors.actions.optionActions.manageFavourite);\n\n if (isFocusable) {\n // Set tabindex to 0 to add current chooser option element to the focus order.\n chooserOption.tabIndex = 0;\n chooserOptionLink.tabIndex = 0;\n chooserOptionHelp.tabIndex = 0;\n chooserOptionFavourite.tabIndex = 0;\n } else {\n // Set tabindex to -1 to remove the previous chooser option element from the focus order.\n chooserOption.tabIndex = -1;\n chooserOptionLink.tabIndex = -1;\n chooserOptionHelp.tabIndex = -1;\n chooserOptionFavourite.tabIndex = -1;\n }\n }\n\n /**\n * Move the focus to the previous chooser option element.\n *\n * @param {HTMLElement} current The current chooser option element\n */\n focusNextChooserOption(current) {\n this.moveChooserOptionFocus(\n current,\n (currentOption) => currentOption.nextElementSibling ?? currentOption,\n );\n }\n\n /**\n * Move the focus to the previous chooser option element.\n *\n * @param {HTMLElement} current The current chooser option element\n */\n focusPreviousChooserOption(current) {\n this.moveChooserOptionFocus(\n current,\n (currentOption) => currentOption.previousElementSibling ?? currentOption,\n );\n }\n\n /**\n * Move the focus to the first chooser option element.\n *\n * @param {HTMLElement} current The current chooser option element\n */\n focusFirstChooserOption(current) {\n this.moveChooserOptionFocus(\n current,\n (currentOption, container) => container.firstElementChild ?? currentOption,\n );\n }\n\n /**\n * Move the focus to the last chooser option element.\n *\n * @param {HTMLElement} current The current chooser option element\n */\n focusLastChooserOption(current) {\n this.moveChooserOptionFocus(\n current,\n (currentOption, container) => container.lastElementChild ?? currentOption,\n );\n }\n\n /**\n * Move the focus to the next chooser option element.\n *\n * @private\n * @param {HTMLElement} current The current chooser option element\n * @param {Function} getNextFocus Function to get the next focusable element\n */\n moveChooserOptionFocus(current, getNextFocus) {\n const currentOption = this.getClosestChooserOption(current);\n const container = current.closest(selectors.regions.chooserOptions);\n\n if (!container || !currentOption) {\n throw new Error('Invalid chooser options container or current option');\n }\n\n const newFocusOption = getNextFocus(currentOption, container);\n if (!newFocusOption) {\n return;\n }\n\n this.toggleFocusableChooserOption(currentOption, false);\n this.toggleFocusableChooserOption(newFocusOption, true);\n newFocusOption.focus();\n }\n}\n"],"names":["constructor","dialogue","modalBody","exporter","getSearchInputElement","this","querySelector","selectors","actions","search","getClosestChooserOption","element","closest","regions","chooserOption","container","isFavoutiteTabActive","favouriteTab","favouriteTabNav","classList","contains","searchResultsData","searchResultsContainer","searchResults","clearSearchButton","clearSearch","renderSearchResults","firstSearchResultItem","chooserOptions","toggleFocusableChooserOption","remove","tabContent","categoryContent","add","cleanSearchResults","innerHTML","templateData","getSearchResultData","html","js","Templates","renderForPromise","replaceNodeContents","showAllActivitiesTab","navTab","allTabNav","pendingPromise","Pending","addEventListener","resolve","once","getOrCreateInstance","show","updateItemStarredIcons","internal","favourite","favouriteButtons","querySelectorAll","elements","moduleItem","optionActions","manageFavourite","Array","from","forEach","toggle","dataset","favourited","setAttribute","favouriteIconActive","favouriteIconInactive","iconSelectsor","favouriteIcon","getAttribute","mappedModules","getFavouriteTabData","favouriteArea","toggleFavouriteTabDisplay","displayed","moveFocusTo","tabIndex","focus","initActiveTabNavigation","showModuleHelp","moduleData","modal","carousel","showFooter","setFooter","render","help","spinnerPromise","transitionPromiseResolver","transitionPromise","Promise","contentPromise","all","then","_ref","chooserSummary","header","catch","Notification","exception","getInstance","next","hideModuleHelp","prev","focusChooserOption","currentTabNav","activetab","activeSectionId","newCurrent","getSectionChooserOptions","getModuleSelector","Error","disableFocusAllChooserOptions","contentEditable","sectionChooserOptions","firstChooserOption","initBootstrapComponents","tab","Carousel","interval","pause","keyboard","tabNav","tabId","isFocusable","chooserOptionLink","addChooser","chooserOptionHelp","showSummary","chooserOptionFavourite","focusNextChooserOption","current","moveChooserOptionFocus","currentOption","nextElementSibling","focusPreviousChooserOption","previousElementSibling","focusFirstChooserOption","firstElementChild","focusLastChooserOption","lastElementChild","getNextFocus","newFocusOption"],"mappings":"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"} \ No newline at end of file +{"version":3,"file":"dialoguedom.min.js","sources":["../../../src/local/activitychooser/dialoguedom.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 {addIconToContainer} from 'core/loadingicon';\nimport Carousel from 'theme_boost/bootstrap/carousel';\nimport {getFirst} from 'core/normalise';\nimport Notification from 'core/notification';\nimport Pending from 'core/pending';\nimport selectors from 'core_course/local/activitychooser/selectors';\nimport Tab from 'theme_boost/bootstrap/tab';\nimport * as Templates from 'core/templates';\n\n\n/**\n * The activity changer dialogue DOM manipulation module.\n *\n * @module core_course/local/activitychooser/dialoguedom\n * @copyright 2025 Ferran Recio \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\nexport default class ChooserDialogueDOM {\n /**\n * ChooserDialogueDOM constructor.\n *\n * @param {Object} dialogue The dialogue object.\n * @param {Modal} modal The modal object.\n * @param {Object} exporter The exporter object to generate template data.\n */\n constructor(dialogue, modal, exporter) {\n this.modal = modal;\n this.modalBody = getFirst(this.modal.getBody());\n this.exporter = exporter;\n // Temporal variable while migrating methods.\n this.dialogue = dialogue;\n }\n\n /**\n * Get the search input element.\n *\n * @return {HTMLElement} The search input element.\n */\n getSearchInputElement() {\n return this.modalBody.querySelector(selectors.actions.search);\n }\n\n /**\n * Get the closest chooser option element.\n *\n * @param {HTMLElement} element\n * @return {HTMLElement|null} element\n */\n getClosestChooserOption(element) {\n return element.closest(selectors.regions.chooserOption.container);\n }\n\n /**\n * Check if the search tab is active.\n *\n * @return {Boolean} True if the search tab is active, false otherwise.\n */\n isFavoutiteTabActive() {\n const favouriteTab = this.modalBody.querySelector(selectors.regions.favouriteTabNav);\n return favouriteTab && favouriteTab.classList.contains('active');\n }\n\n /**\n * Get the URL of a chooser option.\n *\n * @param {HTMLElement} optionContainer The container of the chooser option.\n * @return {String} The URL of the chooser option.\n * @throws {Error} If the option container does not contain a link.\n */\n getChooserOptionUrl(optionContainer) {\n const optionLink = optionContainer.querySelector(selectors.actions.addChooser);\n if (!optionLink) {\n throw new Error('Invalid chooser option container: no link found');\n }\n return optionLink.getAttribute('href');\n }\n\n /**\n * Show the search results.\n *\n * @param {Object} searchResultsData Data containing the module items that satisfy the search criteria\n */\n async refreshSearchResults(searchResultsData) {\n const searchResultsContainer = this.modalBody.querySelector(selectors.regions.searchResults);\n const clearSearchButton = this.modalBody.querySelector(selectors.actions.clearSearch);\n\n await this.renderSearchResults(searchResultsContainer, searchResultsData);\n const chooserOptionsContainer = searchResultsContainer.querySelector(selectors.regions.chooserOptions);\n const firstSearchResultItem = chooserOptionsContainer.querySelector(selectors.regions.chooserOption.container);\n if (firstSearchResultItem) {\n // Set the first result item to be focusable.\n this.toggleFocusableChooserOption(firstSearchResultItem, true);\n // Register keyboard events on the created search result items.\n }\n clearSearchButton.classList.remove('d-none');\n\n // Results are rendered in the all activities tab, so we need to hide the category content.\n const tabContent = searchResultsContainer.closest(selectors.regions.tabContent);\n const categoryContent = tabContent.querySelector(selectors.regions.categoryContent);\n categoryContent.classList.add('d-none');\n }\n\n /**\n * Clear the search results.\n */\n cleanSearchResults() {\n const searchResultsContainer = this.modalBody.querySelector(selectors.regions.searchResults);\n const clearSearchButton = this.modalBody.querySelector(selectors.actions.clearSearch);\n searchResultsContainer.innerHTML = '';\n clearSearchButton.classList.add('d-none');\n\n // Results are rendered in the all activities tab, so we need to show the category content again.\n const tabContent = searchResultsContainer.closest(selectors.regions.tabContent);\n const categoryContent = tabContent.querySelector(selectors.regions.categoryContent);\n categoryContent.classList.remove('d-none');\n }\n\n /**\n * Render the search results in a defined container\n *\n * @private\n * @method renderSearchResults\n * @param {HTMLElement} searchResultsContainer The container where the data should be rendered\n * @param {Object} searchResultsData Data containing the module items that satisfy the search criteria\n */\n async renderSearchResults(searchResultsContainer, searchResultsData) {\n const templateData = this.exporter.getSearchResultData(searchResultsData);\n // Build up the html & js ready to place into the help section.\n const {html, js} = await Templates.renderForPromise(\n 'core_course/local/activitychooser/search_results',\n templateData\n );\n await Templates.replaceNodeContents(searchResultsContainer, html, js);\n }\n\n /**\n * Show the \"All activities\" tab.\n *\n * @method showAllActivitiesTab\n * @return {HTMLElement} The \"All activities\" tab element.\n */\n showAllActivitiesTab() {\n const navTab = this.modalBody.querySelector(selectors.regions.allTabNav);\n\n if (navTab.classList.contains('active')) {\n return navTab;\n }\n\n const pendingPromise = new Pending('core_course/activitychooser:alltab');\n navTab.addEventListener('shown.bs.tab', pendingPromise.resolve, {once: true});\n\n Tab.getOrCreateInstance(navTab).show();\n return navTab;\n }\n\n /**\n * Update the starred icons in the chooser modal.\n *\n * @method updateItemStarredIcons\n * @param {String} internal The internal name of the module.\n * @param {Boolean} favourite Whether the module is a favourite or not.\n */\n updateItemStarredIcons(internal, favourite) {\n const favouriteButtons = this.modalBody.querySelectorAll(\n `${selectors.elements.moduleItem(internal)} ${selectors.actions.optionActions.manageFavourite}`\n );\n Array.from(favouriteButtons).forEach((element) => {\n element.classList.toggle('text-muted', !favourite);\n element.classList.toggle('text-primary', favourite);\n element.dataset.favourited = favourite;\n element.setAttribute('aria-pressed', favourite);\n element.querySelector(selectors.elements.favouriteIconActive)?.classList.toggle('d-none', !favourite);\n element.querySelector(selectors.elements.favouriteIconInactive)?.classList.toggle('d-none', favourite);\n\n const iconSelectsor = favourite ? selectors.elements.favouriteIconActive : selectors.elements.favouriteIconInactive;\n const favouriteIcon = element.querySelector(iconSelectsor);\n element.setAttribute('aria-label', favouriteIcon?.getAttribute('data-action-label') || '');\n });\n }\n\n /**\n * Refresh the favourite content.\n *\n * @param {Array} mappedModules The modules to be displayed in the favourite tab.\n */\n async refreshFavouritesTabContent(mappedModules) {\n const templateData = await this.exporter.getFavouriteTabData(mappedModules);\n const favouriteArea = this.modalBody.querySelector(selectors.regions.favouriteTab);\n const {html, js} = await Templates.renderForPromise(\n 'core_course/local/activitychooser/tabcontent',\n templateData,\n );\n await Templates.replaceNodeContents(favouriteArea, html, js);\n }\n\n /**\n * Toggle the display of the favourite tab.\n *\n * The favourite tab is only displayed when there are favourite modules\n * or when it is the active tab.\n *\n * @param {Boolean} displayed Whether we want to show or hide the favourite tab\n */\n toggleFavouriteTabDisplay(displayed) {\n const favouriteTabNav = this.modalBody.querySelector(selectors.regions.favouriteTabNav);\n\n let moveFocusTo;\n if (!displayed && favouriteTabNav.classList.contains('active')) {\n moveFocusTo = this.showAllActivitiesTab();\n }\n\n favouriteTabNav?.classList.toggle('d-none', !displayed);\n favouriteTabNav.tabIndex = displayed ? 0 : -1;\n // The disabled class is used by Boostrap Tab for keyboard navigation.\n if (displayed) {\n favouriteTabNav.classList.remove('disabled');\n } else {\n favouriteTabNav.classList.add('disabled');\n }\n\n if (moveFocusTo) {\n moveFocusTo.focus();\n }\n this.initActiveTabNavigation();\n }\n\n /**\n * Given an event from the main module 'page' navigate to it's help section via a carousel.\n *\n * @method showModuleHelp\n * @param {Object} moduleData Data of the module to carousel to\n */\n showModuleHelp(moduleData) {\n const carousel = this.modalBody.querySelector(selectors.regions.carousel);\n\n const help = carousel.querySelector(selectors.regions.help);\n help.innerHTML = '';\n help.classList.add('m-auto');\n\n // Add a spinner.\n const spinnerPromise = addIconToContainer(help);\n\n // Used later...\n let transitionPromiseResolver = null;\n const transitionPromise = new Promise(resolve => {\n transitionPromiseResolver = resolve;\n });\n\n // Build up the html & js ready to place into the help section.\n const contentPromise = Templates.renderForPromise(\n 'core_course/local/activitychooser/help',\n moduleData\n );\n\n // Wait for the content to be ready, and for the transition to be complet.\n Promise.all([contentPromise, spinnerPromise, transitionPromise])\n .then(([{html, js}]) => Templates.replaceNodeContents(help, html, js))\n .then(() => {\n help.querySelector(selectors.regions.chooserSummary.header).focus();\n return help;\n })\n .catch(Notification.exception);\n\n // Move to the next slide, and resolve the transition promise when it's done.\n carousel.addEventListener(\n 'slid.bs.carousel',\n () => {\n transitionPromiseResolver();\n },\n {once: true}\n );\n // Trigger the transition between 'pages'.\n Carousel.getInstance(carousel).next();\n }\n\n /**\n * Hide the help section of the chooser.\n *\n * @param {String|null} internal The internal name of the module to return to, if any.\n */\n hideModuleHelp(internal = null) {\n const carousel = this.modalBody.querySelector(selectors.regions.carousel);\n // Trigger the transition between 'pages'.\n Carousel.getInstance(carousel).to(0);\n // Some active footers may not provide a valid internal value.\n if (!internal) {\n return;\n }\n carousel.addEventListener(\n 'slid.bs.carousel',\n () => {\n this.focusChooserOption(internal);\n },\n {once: true}\n );\n }\n\n /**\n * Set the module data for the back button.\n *\n * @param {Object} moduleData The module data to set for the back button.\n */\n setBackButtonModuleData(moduleData) {\n const footer = getFirst(this.modal.getFooter());\n const modnameValue = `${moduleData.componentname}_${moduleData.link}`;\n footer.querySelector(selectors.actions.closeOption).dataset.modname = modnameValue;\n }\n\n /**\n * Toggle the visibility of the back button.\n *\n * @param {Boolean} show Whether to show or hide the back button.\n */\n toggleBackButton(show) {\n const footer = getFirst(this.modal.getFooter());\n footer.querySelector(selectors.actions.closeOption).classList.toggle('d-none', !show);\n }\n\n /**\n * Toggle the visibility of the \"Add selected\" button.\n *\n * @param {Boolean} show Whether to show or hide the \"Add selected\" button.\n */\n toggleAddButton(show) {\n const footer = getFirst(this.modal.getFooter());\n footer.querySelector(selectors.actions.addSelectedChooserOption).classList.toggle('d-none', !show);\n }\n\n /**\n * Toggle the visibility of the active footer.\n *\n * @param {Boolean} show Whether to show or hide the active footer.\n */\n toggleActiveFooter(show) {\n const footer = getFirst(this.modal.getFooter());\n footer.querySelector(selectors.regions.activeFooter).classList.toggle('d-none', !show);\n }\n\n /**\n * Focus on a specific activity inside the active tab (if present).\n *\n * @private\n * @method focusChooserOption\n * @param {String} internal The internal name of the module.\n */\n focusChooserOption(internal) {\n const currentTabNav = this.modalBody.querySelector(selectors.elements.activetab);\n const activeSectionId = currentTabNav.getAttribute(\"href\");\n const sectionChooserOptions = this.modalBody.querySelector(selectors.regions.getSectionChooserOptions(activeSectionId));\n const newCurrent = sectionChooserOptions.querySelector(selectors.regions.getModuleSelector(internal));\n\n if (!newCurrent) {\n throw new Error(`Invalid chooser option to focus on: ${internal}`);\n }\n\n // Chooser can only have one element focusable at a time, so we disable them all first.\n this.disableFocusAllChooserOptions(currentTabNav);\n this.toggleFocusableChooserOption(newCurrent, true);\n\n // Little hack: we want the element considered a focus-visible element.\n // But the focus method does not trigger the focus-visible class. There's an\n // experimental \"{focusVisible: true}\" option in the focus method, but it's not\n // supported in all browsers yet so we need to fake an editable element.\n newCurrent.contentEditable = true;\n newCurrent.focus();\n newCurrent.contentEditable = false;\n }\n\n /**\n * Initialise the active tab navigation.\n */\n initActiveTabNavigation() {\n const activeSectionId = this.modalBody.querySelector(selectors.elements.activetab).getAttribute(\"href\");\n const sectionChooserOptions = this.modalBody.querySelector(selectors.regions.getSectionChooserOptions(activeSectionId));\n const firstChooserOption = sectionChooserOptions?.querySelector(selectors.regions.chooserOption.container);\n if (!firstChooserOption) {\n return;\n }\n this.toggleFocusableChooserOption(firstChooserOption, true);\n }\n\n /**\n * Initialise all Boostrap components.\n */\n initBootstrapComponents() {\n this.modalBody.querySelectorAll(selectors.elements.tab).forEach((navTab) => {\n // Init the Bootstrap Tab navigation.\n Tab.getOrCreateInstance(navTab);\n });\n\n // Set up the carousel.\n const carousel = this.modalBody.querySelector(selectors.regions.carousel);\n new Carousel(carousel, {\n interval: false,\n pause: true,\n keyboard: false\n });\n }\n\n /**\n * Disable the focus of all chooser options in a specific container (section).\n *\n * @method disableFocusAllChooserOptions\n * @param {HTMLElement} tabNav The tab navigation element (from the shown.bs.ta event).\n */\n disableFocusAllChooserOptions(tabNav) {\n const tabId = tabNav.getAttribute(\"href\");\n const chooserOptions = this.modalBody.querySelector(\n selectors.regions.getSectionChooserOptions(tabId)\n );\n\n if (chooserOptions === null) {\n return;\n }\n\n const allChooserOptions = chooserOptions.querySelectorAll(selectors.regions.chooserOption.container);\n allChooserOptions.forEach((chooserOption) => {\n this.toggleFocusableChooserOption(chooserOption, false);\n });\n }\n\n /**\n * Add or remove a chooser option from the focus order.\n *\n * @private\n * @method toggleFocusableChooserOption\n * @param {HTMLElement} chooserOption The chooser option element which should be added or removed from the focus order\n * @param {Boolean} isFocusable Whether the chooser element is focusable or not\n */\n toggleFocusableChooserOption(chooserOption, isFocusable) {\n const chooserOptionLink = chooserOption.querySelector(selectors.actions.addChooser);\n const chooserOptionHelp = chooserOption.querySelector(selectors.actions.optionActions.showSummary);\n const chooserOptionFavourite = chooserOption.querySelector(selectors.actions.optionActions.manageFavourite);\n\n if (isFocusable) {\n // Set tabindex to 0 to add current chooser option element to the focus order.\n chooserOption.tabIndex = 0;\n chooserOptionLink.tabIndex = 0;\n chooserOptionHelp.tabIndex = 0;\n chooserOptionFavourite.tabIndex = 0;\n } else {\n // Set tabindex to -1 to remove the previous chooser option element from the focus order.\n chooserOption.tabIndex = -1;\n chooserOptionLink.tabIndex = -1;\n chooserOptionHelp.tabIndex = -1;\n chooserOptionFavourite.tabIndex = -1;\n }\n }\n\n /**\n * Move the focus to the previous chooser option element.\n *\n * @param {HTMLElement} current The current chooser option element\n */\n focusNextChooserOption(current) {\n this.moveChooserOptionFocus(\n current,\n (currentOption) => currentOption.nextElementSibling ?? currentOption,\n );\n }\n\n /**\n * Move the focus to the previous chooser option element.\n *\n * @param {HTMLElement} current The current chooser option element\n */\n focusPreviousChooserOption(current) {\n this.moveChooserOptionFocus(\n current,\n (currentOption) => currentOption.previousElementSibling ?? currentOption,\n );\n }\n\n /**\n * Move the focus to the first chooser option element.\n *\n * @param {HTMLElement} current The current chooser option element\n */\n focusFirstChooserOption(current) {\n this.moveChooserOptionFocus(\n current,\n (currentOption, container) => container.firstElementChild ?? currentOption,\n );\n }\n\n /**\n * Move the focus to the last chooser option element.\n *\n * @param {HTMLElement} current The current chooser option element\n */\n focusLastChooserOption(current) {\n this.moveChooserOptionFocus(\n current,\n (currentOption, container) => container.lastElementChild ?? currentOption,\n );\n }\n\n /**\n * Move the focus to the next chooser option element.\n *\n * @private\n * @param {HTMLElement} current The current chooser option element\n * @param {Function} getNextFocus Function to get the next focusable element\n */\n moveChooserOptionFocus(current, getNextFocus) {\n const currentOption = this.getClosestChooserOption(current);\n const container = current.closest(selectors.regions.chooserOptions);\n\n if (!container || !currentOption) {\n throw new Error('Invalid chooser options container or current option');\n }\n\n const newFocusOption = getNextFocus(currentOption, container);\n if (!newFocusOption) {\n return;\n }\n\n this.toggleFocusableChooserOption(currentOption, false);\n this.toggleFocusableChooserOption(newFocusOption, true);\n newFocusOption.focus();\n }\n\n /**\n * Mark a chooser option as selected.\n *\n * @param {HTMLElement} chooserOption The chooser option element to mark as selected.\n */\n markChooserOptionAsSelected(chooserOption) {\n this.unmarkAllChooserOptionAsSelected();\n chooserOption.classList.add('selected');\n chooserOption.classList.add('border-primary');\n chooserOption.setAttribute('aria-selected', 'true');\n\n const footer = getFirst(this.modal.getFooter());\n const addButton = footer.querySelector(selectors.actions.addSelectedChooserOption);\n if (addButton) {\n addButton.removeAttribute('disabled');\n }\n }\n\n /**\n * Unmark all chooser options as selected.\n */\n unmarkAllChooserOptionAsSelected() {\n const selectedOptions = this.modalBody.querySelectorAll(`${selectors.regions.chooserOption.container}.selected`);\n selectedOptions.forEach((option) => {\n option.classList.remove('selected');\n option.classList.remove('border-primary');\n option.setAttribute('aria-selected', 'false');\n });\n\n const footer = getFirst(this.modal.getFooter());\n const addButton = footer.querySelector(selectors.actions.addSelectedChooserOption);\n if (addButton) {\n addButton.setAttribute('disabled', 'disabled');\n }\n }\n}\n"],"names":["constructor","dialogue","modal","exporter","modalBody","this","getBody","getSearchInputElement","querySelector","selectors","actions","search","getClosestChooserOption","element","closest","regions","chooserOption","container","isFavoutiteTabActive","favouriteTab","favouriteTabNav","classList","contains","getChooserOptionUrl","optionContainer","optionLink","addChooser","Error","getAttribute","searchResultsData","searchResultsContainer","searchResults","clearSearchButton","clearSearch","renderSearchResults","firstSearchResultItem","chooserOptions","toggleFocusableChooserOption","remove","tabContent","categoryContent","add","cleanSearchResults","innerHTML","templateData","getSearchResultData","html","js","Templates","renderForPromise","replaceNodeContents","showAllActivitiesTab","navTab","allTabNav","pendingPromise","Pending","addEventListener","resolve","once","getOrCreateInstance","show","updateItemStarredIcons","internal","favourite","favouriteButtons","querySelectorAll","elements","moduleItem","optionActions","manageFavourite","Array","from","forEach","toggle","dataset","favourited","setAttribute","favouriteIconActive","favouriteIconInactive","iconSelectsor","favouriteIcon","mappedModules","getFavouriteTabData","favouriteArea","toggleFavouriteTabDisplay","displayed","moveFocusTo","tabIndex","focus","initActiveTabNavigation","showModuleHelp","moduleData","carousel","help","spinnerPromise","transitionPromiseResolver","transitionPromise","Promise","contentPromise","all","then","_ref","chooserSummary","header","catch","Notification","exception","getInstance","next","hideModuleHelp","to","focusChooserOption","setBackButtonModuleData","footer","getFooter","modnameValue","componentname","link","closeOption","modname","toggleBackButton","toggleAddButton","addSelectedChooserOption","toggleActiveFooter","activeFooter","currentTabNav","activetab","activeSectionId","newCurrent","getSectionChooserOptions","getModuleSelector","disableFocusAllChooserOptions","contentEditable","sectionChooserOptions","firstChooserOption","initBootstrapComponents","tab","Carousel","interval","pause","keyboard","tabNav","tabId","isFocusable","chooserOptionLink","chooserOptionHelp","showSummary","chooserOptionFavourite","focusNextChooserOption","current","moveChooserOptionFocus","currentOption","nextElementSibling","focusPreviousChooserOption","previousElementSibling","focusFirstChooserOption","firstElementChild","focusLastChooserOption","lastElementChild","getNextFocus","newFocusOption","markChooserOptionAsSelected","unmarkAllChooserOptionAsSelected","addButton","removeAttribute","option"],"mappings":"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"} \ No newline at end of file diff --git a/public/course/amd/build/local/activitychooser/exporter.min.js b/public/course/amd/build/local/activitychooser/exporter.min.js index e1e0c861db7..2d8f072fd9c 100644 --- a/public/course/amd/build/local/activitychooser/exporter.min.js +++ b/public/course/amd/build/local/activitychooser/exporter.min.js @@ -6,6 +6,6 @@ define("core_course/local/activitychooser/exporter",["exports","core/str"],(func * @copyright 2025 Ferran Recio * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ -const activityCategories=["administration","assessment","collaboration","communication","content","interactivecontent"];let allStrings=null;loadNecessaryStrings();async function loadNecessaryStrings(){if(null!==allStrings)return allStrings;allStrings={};const stringToLoad=[{key:"all",component:"core"},{key:"favourites",component:"core"},{key:"recommended",component:"core"},{key:"recommended_help",component:"core_course"},...activityCategories.map((key=>({key:"mod_purpose_"+key,component:"core_course"}))),...activityCategories.map((key=>({key:"mod_purpose_"+key+"_help",component:"core_course"})))],loadedStrings=await(0,_str.getStrings)(stringToLoad);return stringToLoad.forEach(((_ref,index)=>{let{key:key}=_ref;allStrings[key]=loadedStrings[index]})),allStrings}return _exports.default=class{getTabData(tabId,filteredModules,tabLabel){let tabHelp=arguments.length>3&&void 0!==arguments[3]?arguments[3]:null;const result={tabId:tabId,active:arguments.length>4&&void 0!==arguments[4]&&arguments[4],items:filteredModules,displayed:filteredModules.length>0,tabLabel:tabLabel};return tabHelp&&(result.tabHelp=tabHelp),result}normaliseModulesData(modulesData){if(modulesData instanceof Map)modulesData=Array.from(modulesData.values());else if(!Array.isArray(modulesData))throw new Error("Invalid modules data format. Expected an array or a Map.");return modulesData}async getModChooserTemplateData(modulesData){modulesData=this.normaliseModulesData(modulesData);const allStrings=await loadNecessaryStrings(),favouriteTab=await this.getFavouriteTabData(modulesData),tabs=[{...this.getTabData("all",modulesData,allStrings.all,null,!favouriteTab.displayed),hasSearchResults:!0},favouriteTab,{...this.getTabData("recommended",modulesData.filter((mod=>!0===mod.recommended)),allStrings.recommended,allStrings.recommended_help),separator:!0}];return activityCategories.forEach((category=>{const categoryModules=modulesData.filter((mod=>mod.purpose==category));0!==categoryModules.length&&tabs.push(this.getTabData(category,categoryModules,allStrings["mod_purpose_"+category],allStrings["mod_purpose_"+category+"_help"]))})),{modules:modulesData,tabs:tabs}}async getFavouriteTabData(modulesData){modulesData=this.normaliseModulesData(modulesData);const allStrings=await loadNecessaryStrings(),favouriteModules=modulesData.filter((mod=>!0===mod.favourite));return this.getTabData("favourites",favouriteModules,allStrings.favourites,null,favouriteModules.length>0)}getSearchResultData(resultsModulesData){return{searchresultsnumber:(resultsModulesData=this.normaliseModulesData(resultsModulesData)).length,searchresults:resultsModulesData}}countTabItems(tabData){var _tabData$items$length,_tabData$items;return null!==(_tabData$items$length=null===(_tabData$items=tabData.items)||void 0===_tabData$items?void 0:_tabData$items.length)&&void 0!==_tabData$items$length?_tabData$items$length:0}},_exports.default})); +const activityCategories=["administration","assessment","collaboration","communication","content","interactivecontent"];let allStrings=null;loadNecessaryStrings();async function loadNecessaryStrings(){if(null!==allStrings)return allStrings;allStrings={};const stringToLoad=[{key:"all",component:"core"},{key:"favourites",component:"core"},{key:"recommended",component:"core"},{key:"recommended_help",component:"core_course"},...activityCategories.map((key=>({key:"mod_purpose_"+key,component:"core_course"}))),...activityCategories.map((key=>({key:"mod_purpose_"+key+"_help",component:"core_course"})))],loadedStrings=await(0,_str.getStrings)(stringToLoad);return stringToLoad.forEach(((_ref,index)=>{let{key:key}=_ref;allStrings[key]=loadedStrings[index]})),allStrings}return _exports.default=class{getTabData(tabId,filteredModules,tabLabel){let tabHelp=arguments.length>3&&void 0!==arguments[3]?arguments[3]:null;const result={tabId:tabId,active:arguments.length>4&&void 0!==arguments[4]&&arguments[4],items:filteredModules,displayed:filteredModules.length>0,tabLabel:tabLabel};return tabHelp&&(result.tabHelp=tabHelp),result}normaliseModulesData(modulesData){if(modulesData instanceof Map)modulesData=Array.from(modulesData.values());else if(!Array.isArray(modulesData))throw new Error("Invalid modules data format. Expected an array or a Map.");return modulesData}async getModChooserTemplateData(modulesData){modulesData=this.normaliseModulesData(modulesData);const allStrings=await loadNecessaryStrings(),favouriteTab=await this.getFavouriteTabData(modulesData),tabs=[{...this.getTabData("all",modulesData,allStrings.all,null,!favouriteTab.displayed),hasSearchResults:!0},favouriteTab,{...this.getTabData("recommended",modulesData.filter((mod=>!0===mod.recommended)),allStrings.recommended,allStrings.recommended_help),separator:!0}];return activityCategories.forEach((category=>{const categoryModules=modulesData.filter((mod=>mod.purpose==category));0!==categoryModules.length&&tabs.push(this.getTabData(category,categoryModules,allStrings["mod_purpose_"+category],allStrings["mod_purpose_"+category+"_help"]))})),{modules:modulesData,tabs:tabs}}async getFavouriteTabData(modulesData){modulesData=this.normaliseModulesData(modulesData);const allStrings=await loadNecessaryStrings(),favouriteModules=modulesData.filter((mod=>!0===mod.favourite));return this.getTabData("favourites",favouriteModules,allStrings.favourites,null,favouriteModules.length>0)}getSearchResultData(resultsModulesData){return{searchresultsnumber:(resultsModulesData=this.normaliseModulesData(resultsModulesData)).length,searchresults:resultsModulesData}}countTabItems(tabData){var _tabData$items$length,_tabData$items;return null!==(_tabData$items$length=null===(_tabData$items=tabData.items)||void 0===_tabData$items?void 0:_tabData$items.length)&&void 0!==_tabData$items$length?_tabData$items$length:0}getFooterData(footerData){return{activeFooter:footerData.customfootertemplate}}},_exports.default})); //# sourceMappingURL=exporter.min.js.map \ No newline at end of file diff --git a/public/course/amd/build/local/activitychooser/exporter.min.js.map b/public/course/amd/build/local/activitychooser/exporter.min.js.map index 79326021b13..5ef0bee1083 100644 --- a/public/course/amd/build/local/activitychooser/exporter.min.js.map +++ b/public/course/amd/build/local/activitychooser/exporter.min.js.map @@ -1 +1 @@ -{"version":3,"file":"exporter.min.js","sources":["../../../src/local/activitychooser/exporter.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 * Module to generate template data for the activity chooser.\n *\n * @module core_course/local/activitychooser/exporter\n * @copyright 2025 Ferran Recio \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport {getStrings} from 'core/str';\n\nconst activityCategories = [\n 'administration',\n 'assessment',\n 'collaboration',\n 'communication',\n 'content',\n 'interactivecontent',\n];\n\nlet allStrings = null;\n\nloadNecessaryStrings();\n\nexport default class {\n /**\n * A tab data structure.\n *\n * @typedef {object} TabData\n * @property {String} tabId the tab ID\n * @property {Boolean} active whether the tab is active or not\n * @property {Array} items the filtered modules to be displayed in the tab\n * @property {Boolean} displayed whether the tab is displayed or not\n * @property {String} tabLabel the tab label\n * @property {String|null} tabHelp the help text for the tab (optional)\n */\n\n /**\n * Generate a tab data object for the activity chooser.\n *\n * @private\n * @param {String} tabId Tab ID.\n * @param {Array} filteredModules Filtered modules to be displayed in the tab.\n * @param {String} tabLabel Tab label.\n * @param {String|null} tabHelp Help text for the tab (optional).\n * @param {Boolean} active Whether the tab is active or not.\n * @return {TabData} Tab data object.\n */\n getTabData(tabId, filteredModules, tabLabel, tabHelp = null, active = false) {\n const result = {\n tabId: tabId,\n active: active,\n items: filteredModules,\n displayed: filteredModules.length > 0,\n tabLabel,\n };\n if (tabHelp) {\n result.tabHelp = tabHelp;\n }\n return result;\n }\n\n /**\n * Normalise the modules data to be used in the chooser.\n *\n * The modulesData can be a plain array or a Map. This method will convert it to a\n * plain array of objects.\n *\n * @param {Array|Map} modulesData Modules data to be used in the chooser.\n * @return {Array} Normalised modules data.\n */\n normaliseModulesData(modulesData) {\n if (modulesData instanceof Map) {\n modulesData = Array.from(modulesData.values());\n } else if (!Array.isArray(modulesData)) {\n throw new Error('Invalid modules data format. Expected an array or a Map.');\n }\n return modulesData;\n }\n\n /**\n * Fetch the chooser template data for a specific section.\n *\n * @param {Array|Map} modulesData Modules data to be used in the chooser.\n * @return {Promise} Promise resolved with the template data.\n */\n async getModChooserTemplateData(modulesData) {\n modulesData = this.normaliseModulesData(modulesData);\n const allStrings = await loadNecessaryStrings();\n const favouriteTab = await this.getFavouriteTabData(modulesData);\n\n const tabs = [\n {\n ...this.getTabData(\n 'all',\n modulesData,\n allStrings.all,\n null,\n !favouriteTab.displayed,\n ),\n hasSearchResults: true, // The all tab will also show search results.\n },\n favouriteTab,\n {\n ...this.getTabData(\n 'recommended',\n modulesData.filter(mod => mod.recommended === true),\n allStrings.recommended,\n allStrings.recommended_help\n ),\n separator: true, // Add a separator before the purpose categories.\n },\n ];\n\n activityCategories.forEach((category) => {\n const categoryModules = modulesData.filter(mod => mod.purpose == category);\n if (categoryModules.length === 0) {\n return;\n }\n tabs.push(\n this.getTabData(\n category,\n categoryModules,\n allStrings['mod_purpose_' + category],\n allStrings['mod_purpose_' + category + '_help']\n )\n );\n });\n\n return {\n modules: modulesData,\n tabs,\n };\n }\n\n /**\n * Get the favourite tab data.\n *\n * @param {Array|Map} modulesData Modules data to be used in the chooser.\n * @return {Promise} Promise resolved with the template data.\n */\n async getFavouriteTabData(modulesData) {\n modulesData = this.normaliseModulesData(modulesData);\n const allStrings = await loadNecessaryStrings();\n\n // We need to deconstruct the modules data to ensure it is an array.\n const favouriteModules = modulesData.filter(\n mod => {\n return mod.favourite === true;\n }\n );\n\n return this.getTabData(\n 'favourites',\n favouriteModules,\n allStrings.favourites,\n null,\n favouriteModules.length > 0,\n );\n }\n\n /**\n * Get the search result template data.\n *\n * @param {Array|Map} resultsModulesData Modules data to be used in the chooser.\n * @return {Object} The template data.\n */\n getSearchResultData(resultsModulesData) {\n resultsModulesData = this.normaliseModulesData(resultsModulesData);\n return {\n 'searchresultsnumber': resultsModulesData.length,\n 'searchresults': resultsModulesData,\n };\n }\n\n /**\n * Get the number of items in a tab.\n *\n * @param {TabData} tabData The tab data.\n * @return {Number} The number of items in the tab.\n */\n countTabItems(tabData) {\n return tabData.items?.length ?? 0;\n }\n\n}\n\n/**\n * Load the necessary strings for the activity chooser.\n *\n * @return {Promise} Promise resolved with the loaded strings.\n */\nasync function loadNecessaryStrings() {\n if (allStrings !== null) {\n return allStrings;\n }\n allStrings = {};\n\n const stringToLoad = [\n {key: 'all', component: 'core'},\n {key: 'favourites', component: 'core'},\n {key: 'recommended', component: 'core'},\n {key: 'recommended_help', component: 'core_course'},\n ...activityCategories.map(\n (key) => ({\n key: 'mod_purpose_' + key,\n component: 'core_course',\n })\n ),\n ...activityCategories.map(\n (key) => ({\n key: 'mod_purpose_' + key + '_help',\n component: 'core_course',\n })\n ),\n ];\n\n const loadedStrings = await getStrings(stringToLoad);\n stringToLoad.forEach(({key}, index) => {\n allStrings[key] = loadedStrings[index];\n });\n return allStrings;\n}\n"],"names":["activityCategories","allStrings","loadNecessaryStrings","stringToLoad","key","component","map","loadedStrings","forEach","index","getTabData","tabId","filteredModules","tabLabel","tabHelp","result","active","items","displayed","length","normaliseModulesData","modulesData","Map","Array","from","values","isArray","Error","this","favouriteTab","getFavouriteTabData","tabs","all","hasSearchResults","filter","mod","recommended","recommended_help","separator","category","categoryModules","purpose","push","modules","favouriteModules","favourite","favourites","getSearchResultData","resultsModulesData","countTabItems","tabData","_tabData$items"],"mappings":";;;;;;;;MAyBMA,mBAAqB,CACvB,iBACA,aACA,gBACA,gBACA,UACA,0BAGAC,WAAa,KAEjBC,sCA0KeA,0BACQ,OAAfD,kBACOA,WAEXA,WAAa,SAEPE,aAAe,CACjB,CAACC,IAAK,MAAOC,UAAW,QACxB,CAACD,IAAK,aAAcC,UAAW,QAC/B,CAACD,IAAK,cAAeC,UAAW,QAChC,CAACD,IAAK,mBAAoBC,UAAW,kBAClCL,mBAAmBM,KACjBF,OACGA,IAAK,eAAiBA,IACtBC,UAAW,qBAGhBL,mBAAmBM,KACjBF,OACGA,IAAK,eAAiBA,IAAM,QAC5BC,UAAW,mBAKjBE,oBAAsB,mBAAWJ,qBACvCA,aAAaK,SAAQ,MAAQC,aAAPL,IAACA,UACnBH,WAAWG,KAAOG,cAAcE,UAE7BR,yCA7KPS,WAAWC,MAAOC,gBAAiBC,cAAUC,+DAAU,WAC7CC,OAAS,CACXJ,MAAOA,MACPK,+DACAC,MAAOL,gBACPM,UAAWN,gBAAgBO,OAAS,EACpCN,SAAAA,iBAEAC,UACAC,OAAOD,QAAUA,SAEdC,OAYXK,qBAAqBC,gBACbA,uBAAuBC,IACvBD,YAAcE,MAAMC,KAAKH,YAAYI,eAClC,IAAKF,MAAMG,QAAQL,mBAChB,IAAIM,MAAM,mEAEbN,4CASqBA,aAC5BA,YAAcO,KAAKR,qBAAqBC,mBAClCpB,iBAAmBC,uBACnB2B,mBAAqBD,KAAKE,oBAAoBT,aAE9CU,KAAO,CACT,IACOH,KAAKlB,WACJ,MACAW,YACApB,WAAW+B,IACX,MACCH,aAAaX,WAElBe,kBAAkB,GAEtBJ,aACA,IACOD,KAAKlB,WACJ,cACAW,YAAYa,QAAOC,MAA2B,IAApBA,IAAIC,cAC9BnC,WAAWmC,YACXnC,WAAWoC,kBAEfC,WAAW,WAInBtC,mBAAmBQ,SAAS+B,iBAClBC,gBAAkBnB,YAAYa,QAAOC,KAAOA,IAAIM,SAAWF,WAClC,IAA3BC,gBAAgBrB,QAGpBY,KAAKW,KACDd,KAAKlB,WACD6B,SACAC,gBACAvC,WAAW,eAAiBsC,UAC5BtC,WAAW,eAAiBsC,SAAW,cAK5C,CACHI,QAAStB,YACTU,KAAAA,gCAUkBV,aACtBA,YAAcO,KAAKR,qBAAqBC,mBAClCpB,iBAAmBC,uBAGnB0C,iBAAmBvB,YAAYa,QACjCC,MAC6B,IAAlBA,IAAIU,mBAIZjB,KAAKlB,WACR,aACAkC,iBACA3C,WAAW6C,WACX,KACAF,iBAAiBzB,OAAS,GAUlC4B,oBAAoBC,0BAET,sBADPA,mBAAqBpB,KAAKR,qBAAqB4B,qBAED7B,qBACzB6B,oBAUzBC,cAAcC,8GACHA,QAAQjC,uCAARkC,eAAehC,8DAAU"} \ No newline at end of file +{"version":3,"file":"exporter.min.js","sources":["../../../src/local/activitychooser/exporter.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 * Module to generate template data for the activity chooser.\n *\n * @module core_course/local/activitychooser/exporter\n * @copyright 2025 Ferran Recio \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport {getStrings} from 'core/str';\n\nconst activityCategories = [\n 'administration',\n 'assessment',\n 'collaboration',\n 'communication',\n 'content',\n 'interactivecontent',\n];\n\nlet allStrings = null;\n\nloadNecessaryStrings();\n\nexport default class {\n /**\n * A tab data structure.\n *\n * @typedef {object} TabData\n * @property {String} tabId the tab ID\n * @property {Boolean} active whether the tab is active or not\n * @property {Array} items the filtered modules to be displayed in the tab\n * @property {Boolean} displayed whether the tab is displayed or not\n * @property {String} tabLabel the tab label\n * @property {String|null} tabHelp the help text for the tab (optional)\n */\n\n /**\n * Generate a tab data object for the activity chooser.\n *\n * @private\n * @param {String} tabId Tab ID.\n * @param {Array} filteredModules Filtered modules to be displayed in the tab.\n * @param {String} tabLabel Tab label.\n * @param {String|null} tabHelp Help text for the tab (optional).\n * @param {Boolean} active Whether the tab is active or not.\n * @return {TabData} Tab data object.\n */\n getTabData(tabId, filteredModules, tabLabel, tabHelp = null, active = false) {\n const result = {\n tabId: tabId,\n active: active,\n items: filteredModules,\n displayed: filteredModules.length > 0,\n tabLabel,\n };\n if (tabHelp) {\n result.tabHelp = tabHelp;\n }\n return result;\n }\n\n /**\n * Normalise the modules data to be used in the chooser.\n *\n * The modulesData can be a plain array or a Map. This method will convert it to a\n * plain array of objects.\n *\n * @param {Array|Map} modulesData Modules data to be used in the chooser.\n * @return {Array} Normalised modules data.\n */\n normaliseModulesData(modulesData) {\n if (modulesData instanceof Map) {\n modulesData = Array.from(modulesData.values());\n } else if (!Array.isArray(modulesData)) {\n throw new Error('Invalid modules data format. Expected an array or a Map.');\n }\n return modulesData;\n }\n\n /**\n * Fetch the chooser template data for a specific section.\n *\n * @param {Array|Map} modulesData Modules data to be used in the chooser.\n * @return {Promise} Promise resolved with the template data.\n */\n async getModChooserTemplateData(modulesData) {\n modulesData = this.normaliseModulesData(modulesData);\n const allStrings = await loadNecessaryStrings();\n const favouriteTab = await this.getFavouriteTabData(modulesData);\n\n const tabs = [\n {\n ...this.getTabData(\n 'all',\n modulesData,\n allStrings.all,\n null,\n !favouriteTab.displayed,\n ),\n hasSearchResults: true, // The all tab will also show search results.\n },\n favouriteTab,\n {\n ...this.getTabData(\n 'recommended',\n modulesData.filter(mod => mod.recommended === true),\n allStrings.recommended,\n allStrings.recommended_help\n ),\n separator: true, // Add a separator before the purpose categories.\n },\n ];\n\n activityCategories.forEach((category) => {\n const categoryModules = modulesData.filter(mod => mod.purpose == category);\n if (categoryModules.length === 0) {\n return;\n }\n tabs.push(\n this.getTabData(\n category,\n categoryModules,\n allStrings['mod_purpose_' + category],\n allStrings['mod_purpose_' + category + '_help']\n )\n );\n });\n\n return {\n modules: modulesData,\n tabs,\n };\n }\n\n /**\n * Get the favourite tab data.\n *\n * @param {Array|Map} modulesData Modules data to be used in the chooser.\n * @return {Promise} Promise resolved with the template data.\n */\n async getFavouriteTabData(modulesData) {\n modulesData = this.normaliseModulesData(modulesData);\n const allStrings = await loadNecessaryStrings();\n\n // We need to deconstruct the modules data to ensure it is an array.\n const favouriteModules = modulesData.filter(\n mod => {\n return mod.favourite === true;\n }\n );\n\n return this.getTabData(\n 'favourites',\n favouriteModules,\n allStrings.favourites,\n null,\n favouriteModules.length > 0,\n );\n }\n\n /**\n * Get the search result template data.\n *\n * @param {Array|Map} resultsModulesData Modules data to be used in the chooser.\n * @return {Object} The template data.\n */\n getSearchResultData(resultsModulesData) {\n resultsModulesData = this.normaliseModulesData(resultsModulesData);\n return {\n 'searchresultsnumber': resultsModulesData.length,\n 'searchresults': resultsModulesData,\n };\n }\n\n /**\n * Get the number of items in a tab.\n *\n * @param {TabData} tabData The tab data.\n * @return {Number} The number of items in the tab.\n */\n countTabItems(tabData) {\n return tabData.items?.length ?? 0;\n }\n\n /**\n * Get the activity chooser footer template data.\n * @param {Object} footerData The active footer data object.\n * @return {Object} The template data.\n */\n getFooterData(footerData) {\n return {\n 'activeFooter': footerData.customfootertemplate,\n };\n }\n}\n\n/**\n * Load the necessary strings for the activity chooser.\n *\n * @return {Promise} Promise resolved with the loaded strings.\n */\nasync function loadNecessaryStrings() {\n if (allStrings !== null) {\n return allStrings;\n }\n allStrings = {};\n\n const stringToLoad = [\n {key: 'all', component: 'core'},\n {key: 'favourites', component: 'core'},\n {key: 'recommended', component: 'core'},\n {key: 'recommended_help', component: 'core_course'},\n ...activityCategories.map(\n (key) => ({\n key: 'mod_purpose_' + key,\n component: 'core_course',\n })\n ),\n ...activityCategories.map(\n (key) => ({\n key: 'mod_purpose_' + key + '_help',\n component: 'core_course',\n })\n ),\n ];\n\n const loadedStrings = await getStrings(stringToLoad);\n stringToLoad.forEach(({key}, index) => {\n allStrings[key] = loadedStrings[index];\n });\n return allStrings;\n}\n"],"names":["activityCategories","allStrings","loadNecessaryStrings","stringToLoad","key","component","map","loadedStrings","forEach","index","getTabData","tabId","filteredModules","tabLabel","tabHelp","result","active","items","displayed","length","normaliseModulesData","modulesData","Map","Array","from","values","isArray","Error","this","favouriteTab","getFavouriteTabData","tabs","all","hasSearchResults","filter","mod","recommended","recommended_help","separator","category","categoryModules","purpose","push","modules","favouriteModules","favourite","favourites","getSearchResultData","resultsModulesData","countTabItems","tabData","_tabData$items","getFooterData","footerData","customfootertemplate"],"mappings":";;;;;;;;MAyBMA,mBAAqB,CACvB,iBACA,aACA,gBACA,gBACA,UACA,0BAGAC,WAAa,KAEjBC,sCAoLeA,0BACQ,OAAfD,kBACOA,WAEXA,WAAa,SAEPE,aAAe,CACjB,CAACC,IAAK,MAAOC,UAAW,QACxB,CAACD,IAAK,aAAcC,UAAW,QAC/B,CAACD,IAAK,cAAeC,UAAW,QAChC,CAACD,IAAK,mBAAoBC,UAAW,kBAClCL,mBAAmBM,KACjBF,OACGA,IAAK,eAAiBA,IACtBC,UAAW,qBAGhBL,mBAAmBM,KACjBF,OACGA,IAAK,eAAiBA,IAAM,QAC5BC,UAAW,mBAKjBE,oBAAsB,mBAAWJ,qBACvCA,aAAaK,SAAQ,MAAQC,aAAPL,IAACA,UACnBH,WAAWG,KAAOG,cAAcE,UAE7BR,yCAvLPS,WAAWC,MAAOC,gBAAiBC,cAAUC,+DAAU,WAC7CC,OAAS,CACXJ,MAAOA,MACPK,+DACAC,MAAOL,gBACPM,UAAWN,gBAAgBO,OAAS,EACpCN,SAAAA,iBAEAC,UACAC,OAAOD,QAAUA,SAEdC,OAYXK,qBAAqBC,gBACbA,uBAAuBC,IACvBD,YAAcE,MAAMC,KAAKH,YAAYI,eAClC,IAAKF,MAAMG,QAAQL,mBAChB,IAAIM,MAAM,mEAEbN,4CASqBA,aAC5BA,YAAcO,KAAKR,qBAAqBC,mBAClCpB,iBAAmBC,uBACnB2B,mBAAqBD,KAAKE,oBAAoBT,aAE9CU,KAAO,CACT,IACOH,KAAKlB,WACJ,MACAW,YACApB,WAAW+B,IACX,MACCH,aAAaX,WAElBe,kBAAkB,GAEtBJ,aACA,IACOD,KAAKlB,WACJ,cACAW,YAAYa,QAAOC,MAA2B,IAApBA,IAAIC,cAC9BnC,WAAWmC,YACXnC,WAAWoC,kBAEfC,WAAW,WAInBtC,mBAAmBQ,SAAS+B,iBAClBC,gBAAkBnB,YAAYa,QAAOC,KAAOA,IAAIM,SAAWF,WAClC,IAA3BC,gBAAgBrB,QAGpBY,KAAKW,KACDd,KAAKlB,WACD6B,SACAC,gBACAvC,WAAW,eAAiBsC,UAC5BtC,WAAW,eAAiBsC,SAAW,cAK5C,CACHI,QAAStB,YACTU,KAAAA,gCAUkBV,aACtBA,YAAcO,KAAKR,qBAAqBC,mBAClCpB,iBAAmBC,uBAGnB0C,iBAAmBvB,YAAYa,QACjCC,MAC6B,IAAlBA,IAAIU,mBAIZjB,KAAKlB,WACR,aACAkC,iBACA3C,WAAW6C,WACX,KACAF,iBAAiBzB,OAAS,GAUlC4B,oBAAoBC,0BAET,sBADPA,mBAAqBpB,KAAKR,qBAAqB4B,qBAED7B,qBACzB6B,oBAUzBC,cAAcC,8GACHA,QAAQjC,uCAARkC,eAAehC,8DAAU,EAQpCiC,cAAcC,kBACH,cACaA,WAAWC"} \ No newline at end of file diff --git a/public/course/amd/build/local/activitychooser/selectors.min.js b/public/course/amd/build/local/activitychooser/selectors.min.js index a9386273804..11a62642cba 100644 --- a/public/course/amd/build/local/activitychooser/selectors.min.js +++ b/public/course/amd/build/local/activitychooser/selectors.min.js @@ -6,6 +6,6 @@ define("core_course/local/activitychooser/selectors",["exports"],(function(_expo * @copyright 2019 Mathew May * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ -const getDataSelector=(name,value)=>"[data-".concat(name,'="').concat(value,'"]');var _default={regions:{categoryContent:getDataSelector("region","category-content"),getSectionChooserOptions:containerid=>"".concat(containerid," ").concat(getDataSelector("region","chooser-options-container")),chooserOptions:getDataSelector("region","chooser-options-container"),chooserOption:{container:getDataSelector("region","chooser-option-container"),actions:getDataSelector("region","chooser-option-actions-container"),info:getDataSelector("region","chooser-option-info-container")},chooserSummary:{container:getDataSelector("region","chooser-option-summary-container"),content:getDataSelector("region","chooser-option-summary-content-container"),header:getDataSelector("region","summary-header"),actions:getDataSelector("region","chooser-option-summary-actions-container")},carousel:getDataSelector("region","carousel"),help:getDataSelector("region","help"),tabContent:getDataSelector("region","tabcontent"),favouriteTabNav:getDataSelector("region","favourites-tab-nav"),allTabNav:getDataSelector("region","all-tab-nav"),favouriteTab:getDataSelector("region","favourites"),getModuleSelector:modname=>'[role="menuitem"][data-modname="'.concat(modname,'"]'),searchResults:getDataSelector("region","search-results-container")},actions:{optionActions:{showSummary:getDataSelector("action","show-option-summary"),manageFavourite:getDataSelector("action","manage-module-favourite")},addChooser:getDataSelector("action","add-chooser-option"),closeOption:getDataSelector("action","close-chooser-option-summary"),hide:getDataSelector("action","hide"),search:getDataSelector("action","search"),clearSearch:getDataSelector("action","clearsearch")},elements:{section:".section",sectionmodchooser:"button.section-modchooser-link",tab:'a[data-bs-toggle="tab"]',activetab:'a[data-bs-toggle="tab"][aria-selected="true"]',moduleItem:item=>'[data-internal="'.concat(item,'"]'),favouriteIconActive:'[data-icon="favourite-active"]',favouriteIconInactive:'[data-icon="favourite-inactive"]'}};return _exports.default=_default,_exports.default})); +const getDataSelector=(name,value)=>"[data-".concat(name,'="').concat(value,'"]');var _default={regions:{activeFooter:getDataSelector("region","active-footer-container"),categoryContent:getDataSelector("region","category-content"),getSectionChooserOptions:containerid=>"".concat(containerid," ").concat(getDataSelector("region","chooser-options-container")),chooserOptions:getDataSelector("region","chooser-options-container"),chooserOption:{container:getDataSelector("region","chooser-option-container"),actions:getDataSelector("region","chooser-option-actions-container"),info:getDataSelector("region","chooser-option-info-container")},chooserSummary:{container:getDataSelector("region","chooser-option-summary-container"),content:getDataSelector("region","chooser-option-summary-content-container"),header:getDataSelector("region","summary-header"),actions:getDataSelector("region","chooser-option-summary-actions-container")},carousel:getDataSelector("region","carousel"),help:getDataSelector("region","help"),tabContent:getDataSelector("region","tabcontent"),favouriteTabNav:getDataSelector("region","favourites-tab-nav"),allTabNav:getDataSelector("region","all-tab-nav"),favouriteTab:getDataSelector("region","favourites"),getModuleSelector:modname=>'[role="menuitem"][data-modname="'.concat(modname,'"]'),searchResults:getDataSelector("region","search-results-container")},actions:{optionActions:{showSummary:getDataSelector("action","show-option-summary"),manageFavourite:getDataSelector("action","manage-module-favourite")},addChooser:getDataSelector("action","add-chooser-option"),addSelectedChooserOption:getDataSelector("action","add-selected-chooser-option"),closeOption:getDataSelector("action","close-chooser-option-summary"),hide:getDataSelector("action","hide"),search:getDataSelector("action","search"),clearSearch:getDataSelector("action","clearsearch")},elements:{section:".section",sectionmodchooser:"button.section-modchooser-link",tab:'a[data-bs-toggle="tab"]',activetab:'a[data-bs-toggle="tab"][aria-selected="true"]',moduleItem:item=>'[data-internal="'.concat(item,'"]'),favouriteIconActive:'[data-icon="favourite-active"]',favouriteIconInactive:'[data-icon="favourite-inactive"]'}};return _exports.default=_default,_exports.default})); //# sourceMappingURL=selectors.min.js.map \ No newline at end of file diff --git a/public/course/amd/build/local/activitychooser/selectors.min.js.map b/public/course/amd/build/local/activitychooser/selectors.min.js.map index d00e753e57a..e36d2157ca6 100644 --- a/public/course/amd/build/local/activitychooser/selectors.min.js.map +++ b/public/course/amd/build/local/activitychooser/selectors.min.js.map @@ -1 +1 @@ -{"version":3,"file":"selectors.min.js","sources":["../../../src/local/activitychooser/selectors.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 * Define all of the selectors we will be using on the grading interface.\n *\n * @module core_course/local/activitychooser/selectors\n * @copyright 2019 Mathew May \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\n/**\n * A small helper function to build queryable data selectors.\n * @method getDataSelector\n * @param {String} name\n * @param {String} value\n * @return {string}\n */\nconst getDataSelector = (name, value) => {\n return `[data-${name}=\"${value}\"]`;\n};\n\nexport default {\n regions: {\n categoryContent: getDataSelector('region', 'category-content'),\n getSectionChooserOptions: containerid => `${containerid} ${getDataSelector('region', 'chooser-options-container')}`,\n chooserOptions: getDataSelector('region', 'chooser-options-container'),\n chooserOption: {\n container: getDataSelector('region', 'chooser-option-container'),\n actions: getDataSelector('region', 'chooser-option-actions-container'),\n info: getDataSelector('region', 'chooser-option-info-container'),\n },\n chooserSummary: {\n container: getDataSelector('region', 'chooser-option-summary-container'),\n content: getDataSelector('region', 'chooser-option-summary-content-container'),\n header: getDataSelector('region', 'summary-header'),\n actions: getDataSelector('region', 'chooser-option-summary-actions-container'),\n },\n carousel: getDataSelector('region', 'carousel'),\n help: getDataSelector('region', 'help'),\n tabContent: getDataSelector('region', 'tabcontent'),\n favouriteTabNav: getDataSelector('region', 'favourites-tab-nav'),\n allTabNav: getDataSelector('region', 'all-tab-nav'),\n favouriteTab: getDataSelector('region', 'favourites'),\n getModuleSelector: modname => `[role=\"menuitem\"][data-modname=\"${modname}\"]`,\n searchResults: getDataSelector('region', 'search-results-container'),\n },\n actions: {\n optionActions: {\n showSummary: getDataSelector('action', 'show-option-summary'),\n manageFavourite: getDataSelector('action', 'manage-module-favourite'),\n },\n addChooser: getDataSelector('action', 'add-chooser-option'),\n closeOption: getDataSelector('action', 'close-chooser-option-summary'),\n hide: getDataSelector('action', 'hide'),\n search: getDataSelector('action', 'search'),\n clearSearch: getDataSelector('action', 'clearsearch'),\n },\n elements: {\n section: '.section',\n sectionmodchooser: 'button.section-modchooser-link',\n tab: 'a[data-bs-toggle=\"tab\"]',\n activetab: 'a[data-bs-toggle=\"tab\"][aria-selected=\"true\"]',\n moduleItem: item => `[data-internal=\"${item}\"]`,\n favouriteIconActive: '[data-icon=\"favourite-active\"]',\n favouriteIconInactive: '[data-icon=\"favourite-inactive\"]',\n },\n};\n"],"names":["getDataSelector","name","value","regions","categoryContent","getSectionChooserOptions","containerid","chooserOptions","chooserOption","container","actions","info","chooserSummary","content","header","carousel","help","tabContent","favouriteTabNav","allTabNav","favouriteTab","getModuleSelector","modname","searchResults","optionActions","showSummary","manageFavourite","addChooser","closeOption","hide","search","clearSearch","elements","section","sectionmodchooser","tab","activetab","moduleItem","item","favouriteIconActive","favouriteIconInactive"],"mappings":";;;;;;;;MA8BMA,gBAAkB,CAACC,KAAMC,wBACXD,kBAASC,yBAGd,CACXC,QAAS,CACLC,gBAAiBJ,gBAAgB,SAAU,oBAC3CK,yBAA0BC,uBAAkBA,wBAAeN,gBAAgB,SAAU,8BACrFO,eAAgBP,gBAAgB,SAAU,6BAC1CQ,cAAe,CACXC,UAAWT,gBAAgB,SAAU,4BACrCU,QAASV,gBAAgB,SAAU,oCACnCW,KAAMX,gBAAgB,SAAU,kCAEpCY,eAAgB,CACZH,UAAWT,gBAAgB,SAAU,oCACrCa,QAASb,gBAAgB,SAAU,4CACnCc,OAAQd,gBAAgB,SAAU,kBAClCU,QAASV,gBAAgB,SAAU,6CAEvCe,SAAUf,gBAAgB,SAAU,YACpCgB,KAAMhB,gBAAgB,SAAU,QAChCiB,WAAYjB,gBAAgB,SAAU,cACtCkB,gBAAiBlB,gBAAgB,SAAU,sBAC3CmB,UAAWnB,gBAAgB,SAAU,eACrCoB,aAAcpB,gBAAgB,SAAU,cACxCqB,kBAAmBC,mDAA8CA,cACjEC,cAAevB,gBAAgB,SAAU,6BAE7CU,QAAS,CACLc,cAAe,CACXC,YAAazB,gBAAgB,SAAU,uBACvC0B,gBAAiB1B,gBAAgB,SAAU,4BAE/C2B,WAAY3B,gBAAgB,SAAU,sBACtC4B,YAAa5B,gBAAgB,SAAU,gCACvC6B,KAAM7B,gBAAgB,SAAU,QAChC8B,OAAQ9B,gBAAgB,SAAU,UAClC+B,YAAa/B,gBAAgB,SAAU,gBAE3CgC,SAAU,CACNC,QAAS,WACTC,kBAAmB,iCACnBC,IAAK,0BACLC,UAAW,gDACXC,WAAYC,gCAA2BA,WACvCC,oBAAqB,iCACrBC,sBAAuB"} \ No newline at end of file +{"version":3,"file":"selectors.min.js","sources":["../../../src/local/activitychooser/selectors.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 * Define all of the selectors we will be using on the grading interface.\n *\n * @module core_course/local/activitychooser/selectors\n * @copyright 2019 Mathew May \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\n/**\n * A small helper function to build queryable data selectors.\n * @method getDataSelector\n * @param {String} name\n * @param {String} value\n * @return {string}\n */\nconst getDataSelector = (name, value) => {\n return `[data-${name}=\"${value}\"]`;\n};\n\nexport default {\n regions: {\n activeFooter: getDataSelector('region', 'active-footer-container'),\n categoryContent: getDataSelector('region', 'category-content'),\n getSectionChooserOptions: containerid => `${containerid} ${getDataSelector('region', 'chooser-options-container')}`,\n chooserOptions: getDataSelector('region', 'chooser-options-container'),\n chooserOption: {\n container: getDataSelector('region', 'chooser-option-container'),\n actions: getDataSelector('region', 'chooser-option-actions-container'),\n info: getDataSelector('region', 'chooser-option-info-container'),\n },\n chooserSummary: {\n container: getDataSelector('region', 'chooser-option-summary-container'),\n content: getDataSelector('region', 'chooser-option-summary-content-container'),\n header: getDataSelector('region', 'summary-header'),\n actions: getDataSelector('region', 'chooser-option-summary-actions-container'),\n },\n carousel: getDataSelector('region', 'carousel'),\n help: getDataSelector('region', 'help'),\n tabContent: getDataSelector('region', 'tabcontent'),\n favouriteTabNav: getDataSelector('region', 'favourites-tab-nav'),\n allTabNav: getDataSelector('region', 'all-tab-nav'),\n favouriteTab: getDataSelector('region', 'favourites'),\n getModuleSelector: modname => `[role=\"menuitem\"][data-modname=\"${modname}\"]`,\n searchResults: getDataSelector('region', 'search-results-container'),\n },\n actions: {\n optionActions: {\n showSummary: getDataSelector('action', 'show-option-summary'),\n manageFavourite: getDataSelector('action', 'manage-module-favourite'),\n },\n addChooser: getDataSelector('action', 'add-chooser-option'),\n addSelectedChooserOption: getDataSelector('action', 'add-selected-chooser-option'),\n closeOption: getDataSelector('action', 'close-chooser-option-summary'),\n hide: getDataSelector('action', 'hide'),\n search: getDataSelector('action', 'search'),\n clearSearch: getDataSelector('action', 'clearsearch'),\n },\n elements: {\n section: '.section',\n sectionmodchooser: 'button.section-modchooser-link',\n tab: 'a[data-bs-toggle=\"tab\"]',\n activetab: 'a[data-bs-toggle=\"tab\"][aria-selected=\"true\"]',\n moduleItem: item => `[data-internal=\"${item}\"]`,\n favouriteIconActive: '[data-icon=\"favourite-active\"]',\n favouriteIconInactive: '[data-icon=\"favourite-inactive\"]',\n },\n};\n"],"names":["getDataSelector","name","value","regions","activeFooter","categoryContent","getSectionChooserOptions","containerid","chooserOptions","chooserOption","container","actions","info","chooserSummary","content","header","carousel","help","tabContent","favouriteTabNav","allTabNav","favouriteTab","getModuleSelector","modname","searchResults","optionActions","showSummary","manageFavourite","addChooser","addSelectedChooserOption","closeOption","hide","search","clearSearch","elements","section","sectionmodchooser","tab","activetab","moduleItem","item","favouriteIconActive","favouriteIconInactive"],"mappings":";;;;;;;;MA8BMA,gBAAkB,CAACC,KAAMC,wBACXD,kBAASC,yBAGd,CACXC,QAAS,CACLC,aAAcJ,gBAAgB,SAAU,2BACxCK,gBAAiBL,gBAAgB,SAAU,oBAC3CM,yBAA0BC,uBAAkBA,wBAAeP,gBAAgB,SAAU,8BACrFQ,eAAgBR,gBAAgB,SAAU,6BAC1CS,cAAe,CACXC,UAAWV,gBAAgB,SAAU,4BACrCW,QAASX,gBAAgB,SAAU,oCACnCY,KAAMZ,gBAAgB,SAAU,kCAEpCa,eAAgB,CACZH,UAAWV,gBAAgB,SAAU,oCACrCc,QAASd,gBAAgB,SAAU,4CACnCe,OAAQf,gBAAgB,SAAU,kBAClCW,QAASX,gBAAgB,SAAU,6CAEvCgB,SAAUhB,gBAAgB,SAAU,YACpCiB,KAAMjB,gBAAgB,SAAU,QAChCkB,WAAYlB,gBAAgB,SAAU,cACtCmB,gBAAiBnB,gBAAgB,SAAU,sBAC3CoB,UAAWpB,gBAAgB,SAAU,eACrCqB,aAAcrB,gBAAgB,SAAU,cACxCsB,kBAAmBC,mDAA8CA,cACjEC,cAAexB,gBAAgB,SAAU,6BAE7CW,QAAS,CACLc,cAAe,CACXC,YAAa1B,gBAAgB,SAAU,uBACvC2B,gBAAiB3B,gBAAgB,SAAU,4BAE/C4B,WAAY5B,gBAAgB,SAAU,sBACtC6B,yBAA0B7B,gBAAgB,SAAU,+BACpD8B,YAAa9B,gBAAgB,SAAU,gCACvC+B,KAAM/B,gBAAgB,SAAU,QAChCgC,OAAQhC,gBAAgB,SAAU,UAClCiC,YAAajC,gBAAgB,SAAU,gBAE3CkC,SAAU,CACNC,QAAS,WACTC,kBAAmB,iCACnBC,IAAK,0BACLC,UAAW,gDACXC,WAAYC,gCAA2BA,WACvCC,oBAAqB,iCACrBC,sBAAuB"} \ No newline at end of file diff --git a/public/course/amd/src/local/activitychooser/dialogue.js b/public/course/amd/src/local/activitychooser/dialogue.js index 6b02ddef65b..f1cf1922563 100644 --- a/public/course/amd/src/local/activitychooser/dialogue.js +++ b/public/course/amd/src/local/activitychooser/dialogue.js @@ -53,12 +53,19 @@ export async function displayActivityChooserModal( bodyPromiseResolver = resolve; }); + const exporter = new Exporter(); + const footerData = await footerDataPromise; + const footerPromise = Templates.render( + 'core_course/local/activitychooser/footer', + exporter.getFooterData(footerData), + ); + const sectionModal = Modal.create({ body: bodyPromise, title: getString('addresourceoractivity'), - footer: footerData.customfootertemplate, + footer: footerPromise, large: true, scrollable: false, templateContext: { @@ -75,9 +82,9 @@ export async function displayActivityChooserModal( } const modal = await sectionModal; - const dialogue = new ActivityChooserDialogue(modal, modulesData, footerData); + new ActivityChooserDialogue(modal, modulesData, footerData); - const templateData = await dialogue.exporter.getModChooserTemplateData(modulesData); + const templateData = await exporter.getModChooserTemplateData(modulesData); bodyPromiseResolver(await Templates.render('core_course/activitychooser', templateData)); } catch (error) { const errorTemplateData = { @@ -131,6 +138,7 @@ class ActivityChooserDialogue { this.dialogueDom = null; // We cannot init until we have the modal body loaded. this.footerData = footerData; this.exporter = new Exporter(); + this.selectedModule = null; // This attribute marks when the tab content is dirty and needs to be refreshed when the user changes the tab. // We don't want the content to be updated while the user is managing their favourites. this.isFavouriteTabDirty = false; @@ -148,14 +156,17 @@ class ActivityChooserDialogue { * @return {Promise} A promise that resolves when the modal is ready. */ async init() { - const modalBody = getFirst(await this.modal.getBodyPromise()); - this.dialogueDom = new DialogueDom(this, modalBody, this.exporter); + await this.modal.getBodyPromise(); + await this.modal.getFooterPromise(); + this.dialogueDom = new DialogueDom(this, this.modal, this.exporter); this.registerModalListenerEvents(); this.setupKeyboardAccessibility(); // We want to focus on the action select when the dialog is closed. this.modal.getRoot().on(ModalEvents.hidden, () => { this.modal.destroy(); }); + // When modal is open the add button should be disabled because there is no activity selected. + this.dialogueDom.unmarkAllChooserOptionAsSelected(); } /** @@ -170,6 +181,7 @@ class ActivityChooserDialogue { modalRoot.addEventListener( 'shown.bs.tab', (event) => { + this.dialogueDom.unmarkAllChooserOptionAsSelected(); // The all tab has the search result, so we do not want to clear the search input. if (event.target.closest(selectors.regions.allTabNav)) { return; @@ -187,6 +199,10 @@ class ActivityChooserDialogue { 'click', this.handleModalClick.bind(this), ); + modalRoot.addEventListener( + 'dblclick', + this.handleModalDoubleClick.bind(this), + ); // Add a listener for an input change in the activity chooser's search bar. const searchInput = this.dialogueDom.getSearchInputElement(); @@ -227,6 +243,19 @@ class ActivityChooserDialogue { 'click', this.handleFooterClick.bind(this), ); + + // Adapt modal footer depending on the displayed carousel page. + modalRoot.addEventListener('slide.bs.carousel', (event) => { + if (event.to === undefined) { + return; + } + // The boostrap carousel event.to contains the index of the newly active item. + // The zero index is the chooser options, the first index is the module help, + // any other index are custom footer pages. + this.dialogueDom.toggleActiveFooter(event.to === 0); + this.dialogueDom.toggleBackButton(event.to !== 0); + this.dialogueDom.toggleAddButton(event.to < 2); + }); } /** @@ -236,7 +265,14 @@ class ActivityChooserDialogue { * @return {Promise} A promise that resolves when the event is handled */ async handleFooterClick(event) { - if (this.footerData.footer === true) { + if (event.target.closest(selectors.actions.addSelectedChooserOption)) { + this.submitAddSelectedModule(); + } + + if ( + event.target.closest(selectors.regions.activeFooter) + && this.footerData.footer === true + ) { const footerjs = await getPlugin(this.footerData.customfooterjs); await footerjs.footerClickListener(event, this.footerData, this.modal); } @@ -253,20 +289,41 @@ class ActivityChooserDialogue { if (target.closest(selectors.actions.optionActions.showSummary)) { this.handleShowSummary(target); + return; } if (target.closest(selectors.actions.optionActions.manageFavourite)) { await this.handleFavouriteClick(target); + return; } // From the help screen go back to the module overview. if (target.matches(selectors.actions.closeOption)) { this.dialogueDom.hideModuleHelp(target.dataset.modname); + return; } // The "clear search" button is triggered. if (target.closest(selectors.actions.clearSearch)) { this.handleClearSearch(); + return; + } + + if (target.closest(selectors.regions.chooserOption.container)) { + event.preventDefault(); + this.handleOptionSelection(target); + return; + } + } + + /** + * Handle the double click event on the modal. + * @param {Object} event The event object + */ + handleModalDoubleClick(event) { + const option = this.dialogueDom.getClosestChooserOption(event.target); + if (option !== null) { + this.submitAddSelectedModule(this.selectedModule); } } @@ -279,8 +336,11 @@ class ActivityChooserDialogue { const module = this.dialogueDom.getClosestChooserOption(target); const moduleName = module.dataset.modname; const moduleData = this.mappedModules.get(moduleName); + // We select the module now. This way the back button will keep the module selected. + this.handleOptionSelection(target); // We need to know if the overall modal has a footer so we know when to show a real / vs fake footer. moduleData.showFooter = this.modal.hasFooterContent(); + this.dialogueDom.setBackButtonModuleData(moduleData); this.dialogueDom.showModuleHelp(moduleData, this.modal); } @@ -317,6 +377,38 @@ class ActivityChooserDialogue { this.toggleSearchResultsView(searchInput.value); } + /** + * Handle the click on a chooser option. + * + * @param {HTMLElement} target The target element that triggered the event + */ + handleOptionSelection(target) { + const option = this.dialogueDom.getClosestChooserOption(target); + if (option === null) { + return; + } + this.selectedModule = option; + this.dialogueDom.markChooserOptionAsSelected(option, getFirst(this.modal.getFooter())); + } + + /** + * Submit the selected module to the chooser. + * + * This method will redirect the user to the URL of the selected module, if one is selected. + * + * @param {HTMLElement} newSelectedModule optional new selected module element. + * @return {void} + */ + submitAddSelectedModule(newSelectedModule = null) { + if (newSelectedModule) { + this.handleOptionSelection(newSelectedModule); + } + if (this.selectedModule === null) { + return; + } + window.location.href = this.dialogueDom.getChooserOptionUrl(this.selectedModule); + } + /** * Set up our tabindex information across the chooser. * @@ -333,14 +425,22 @@ class ActivityChooserDialogue { return; } - // Check for enter/ space triggers for showing the help. if (e.keyCode === enter || e.keyCode === space) { - if (e.target.matches(selectors.actions.optionActions.showSummary)) { - e.preventDefault(); - this.handleShowSummary(e.target); + // Check first if the target is an internal control button (favourite or help). + // If that is the case, the regular click will handle the event. + if (e.target.closest(selectors.regions.chooserOption.actions)) { + return; } } + if (e.keyCode === space) { + e.preventDefault(); + this.handleOptionSelection(currentOption); + } + if (e.keyCode === enter) { + e.preventDefault(); + this.submitAddSelectedModule(currentOption); + } if (e.keyCode === arrowRight) { e.preventDefault(); this.dialogueDom.focusNextChooserOption(currentOption); diff --git a/public/course/amd/src/local/activitychooser/dialoguedom.js b/public/course/amd/src/local/activitychooser/dialoguedom.js index 174dcb13f1b..0df1f44d3dd 100644 --- a/public/course/amd/src/local/activitychooser/dialoguedom.js +++ b/public/course/amd/src/local/activitychooser/dialoguedom.js @@ -15,6 +15,7 @@ import {addIconToContainer} from 'core/loadingicon'; import Carousel from 'theme_boost/bootstrap/carousel'; +import {getFirst} from 'core/normalise'; import Notification from 'core/notification'; import Pending from 'core/pending'; import selectors from 'core_course/local/activitychooser/selectors'; @@ -30,9 +31,16 @@ import * as Templates from 'core/templates'; * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ export default class ChooserDialogueDOM { - - constructor(dialogue, modalBody, exporter) { - this.modalBody = modalBody; + /** + * ChooserDialogueDOM constructor. + * + * @param {Object} dialogue The dialogue object. + * @param {Modal} modal The modal object. + * @param {Object} exporter The exporter object to generate template data. + */ + constructor(dialogue, modal, exporter) { + this.modal = modal; + this.modalBody = getFirst(this.modal.getBody()); this.exporter = exporter; // Temporal variable while migrating methods. this.dialogue = dialogue; @@ -67,6 +75,21 @@ export default class ChooserDialogueDOM { return favouriteTab && favouriteTab.classList.contains('active'); } + /** + * Get the URL of a chooser option. + * + * @param {HTMLElement} optionContainer The container of the chooser option. + * @return {String} The URL of the chooser option. + * @throws {Error} If the option container does not contain a link. + */ + getChooserOptionUrl(optionContainer) { + const optionLink = optionContainer.querySelector(selectors.actions.addChooser); + if (!optionLink) { + throw new Error('Invalid chooser option container: no link found'); + } + return optionLink.getAttribute('href'); + } + /** * Show the search results. * @@ -221,17 +244,10 @@ export default class ChooserDialogueDOM { * * @method showModuleHelp * @param {Object} moduleData Data of the module to carousel to - * @param {Modal} modal The modal object */ - showModuleHelp(moduleData, modal) { + showModuleHelp(moduleData) { const carousel = this.modalBody.querySelector(selectors.regions.carousel); - // If we have a real footer then we need to change temporarily. - if (moduleData.showFooter === true) { - modal.setFooter(Templates.render( - 'core_course/local/activitychooser/footer_partial', - moduleData - )); - } + const help = carousel.querySelector(selectors.regions.help); help.innerHTML = ''; help.classList.add('m-auto'); @@ -280,8 +296,9 @@ export default class ChooserDialogueDOM { hideModuleHelp(internal = null) { const carousel = this.modalBody.querySelector(selectors.regions.carousel); // Trigger the transition between 'pages'. - Carousel.getInstance(carousel).prev(); - if (internal === null) { + Carousel.getInstance(carousel).to(0); + // Some active footers may not provide a valid internal value. + if (!internal) { return; } carousel.addEventListener( @@ -293,6 +310,47 @@ export default class ChooserDialogueDOM { ); } + /** + * Set the module data for the back button. + * + * @param {Object} moduleData The module data to set for the back button. + */ + setBackButtonModuleData(moduleData) { + const footer = getFirst(this.modal.getFooter()); + const modnameValue = `${moduleData.componentname}_${moduleData.link}`; + footer.querySelector(selectors.actions.closeOption).dataset.modname = modnameValue; + } + + /** + * Toggle the visibility of the back button. + * + * @param {Boolean} show Whether to show or hide the back button. + */ + toggleBackButton(show) { + const footer = getFirst(this.modal.getFooter()); + footer.querySelector(selectors.actions.closeOption).classList.toggle('d-none', !show); + } + + /** + * Toggle the visibility of the "Add selected" button. + * + * @param {Boolean} show Whether to show or hide the "Add selected" button. + */ + toggleAddButton(show) { + const footer = getFirst(this.modal.getFooter()); + footer.querySelector(selectors.actions.addSelectedChooserOption).classList.toggle('d-none', !show); + } + + /** + * Toggle the visibility of the active footer. + * + * @param {Boolean} show Whether to show or hide the active footer. + */ + toggleActiveFooter(show) { + const footer = getFirst(this.modal.getFooter()); + footer.querySelector(selectors.regions.activeFooter).classList.toggle('d-none', !show); + } + /** * Focus on a specific activity inside the active tab (if present). * @@ -476,4 +534,40 @@ export default class ChooserDialogueDOM { this.toggleFocusableChooserOption(newFocusOption, true); newFocusOption.focus(); } + + /** + * Mark a chooser option as selected. + * + * @param {HTMLElement} chooserOption The chooser option element to mark as selected. + */ + markChooserOptionAsSelected(chooserOption) { + this.unmarkAllChooserOptionAsSelected(); + chooserOption.classList.add('selected'); + chooserOption.classList.add('border-primary'); + chooserOption.setAttribute('aria-selected', 'true'); + + const footer = getFirst(this.modal.getFooter()); + const addButton = footer.querySelector(selectors.actions.addSelectedChooserOption); + if (addButton) { + addButton.removeAttribute('disabled'); + } + } + + /** + * Unmark all chooser options as selected. + */ + unmarkAllChooserOptionAsSelected() { + const selectedOptions = this.modalBody.querySelectorAll(`${selectors.regions.chooserOption.container}.selected`); + selectedOptions.forEach((option) => { + option.classList.remove('selected'); + option.classList.remove('border-primary'); + option.setAttribute('aria-selected', 'false'); + }); + + const footer = getFirst(this.modal.getFooter()); + const addButton = footer.querySelector(selectors.actions.addSelectedChooserOption); + if (addButton) { + addButton.setAttribute('disabled', 'disabled'); + } + } } diff --git a/public/course/amd/src/local/activitychooser/exporter.js b/public/course/amd/src/local/activitychooser/exporter.js index 7fd378de721..0083efa8164 100644 --- a/public/course/amd/src/local/activitychooser/exporter.js +++ b/public/course/amd/src/local/activitychooser/exporter.js @@ -197,6 +197,16 @@ export default class { return tabData.items?.length ?? 0; } + /** + * Get the activity chooser footer template data. + * @param {Object} footerData The active footer data object. + * @return {Object} The template data. + */ + getFooterData(footerData) { + return { + 'activeFooter': footerData.customfootertemplate, + }; + } } /** diff --git a/public/course/amd/src/local/activitychooser/selectors.js b/public/course/amd/src/local/activitychooser/selectors.js index 9268878fff6..a0a826c11cb 100644 --- a/public/course/amd/src/local/activitychooser/selectors.js +++ b/public/course/amd/src/local/activitychooser/selectors.js @@ -34,6 +34,7 @@ const getDataSelector = (name, value) => { export default { regions: { + activeFooter: getDataSelector('region', 'active-footer-container'), categoryContent: getDataSelector('region', 'category-content'), getSectionChooserOptions: containerid => `${containerid} ${getDataSelector('region', 'chooser-options-container')}`, chooserOptions: getDataSelector('region', 'chooser-options-container'), @@ -63,6 +64,7 @@ export default { manageFavourite: getDataSelector('action', 'manage-module-favourite'), }, addChooser: getDataSelector('action', 'add-chooser-option'), + addSelectedChooserOption: getDataSelector('action', 'add-selected-chooser-option'), closeOption: getDataSelector('action', 'close-chooser-option-summary'), hide: getDataSelector('action', 'hide'), search: getDataSelector('action', 'search'), diff --git a/public/course/templates/local/activitychooser/footer_partial.mustache b/public/course/templates/local/activitychooser/footer.mustache similarity index 51% rename from public/course/templates/local/activitychooser/footer_partial.mustache rename to public/course/templates/local/activitychooser/footer.mustache index 898d119a8a1..f8e32f68b0f 100644 --- a/public/course/templates/local/activitychooser/footer_partial.mustache +++ b/public/course/templates/local/activitychooser/footer.mustache @@ -15,19 +15,37 @@ along with Moodle. If not, see . }} {{! - @template core_course/local/activitychooser/footer_partial + @template core_course/local/activitychooser/footer - Chooser favourite template partial. + This template renders the footer of the activity chooser modal. Example context (json): { + "activeFooter": "Some content" } }}
- - - {{#str}} add {{/str}} - +
+ {{{activeFooter}}} +
+
+ +
+
+ +
diff --git a/public/course/templates/local/activitychooser/help.mustache b/public/course/templates/local/activitychooser/help.mustache index 83d2be134b5..4a6da49b46d 100644 --- a/public/course/templates/local/activitychooser/help.mustache +++ b/public/course/templates/local/activitychooser/help.mustache @@ -50,9 +50,4 @@ {{{help}}} - {{^showFooter}} - - {{/showFooter}} diff --git a/public/course/tests/behat/behat_course.php b/public/course/tests/behat/behat_course.php index 443407fb27e..3c5ec0ecb30 100644 --- a/public/course/tests/behat/behat_course.php +++ b/public/course/tests/behat/behat_course.php @@ -332,6 +332,13 @@ class behat_course extends behat_base { "/parent::a"; $this->execute('behat_general::i_click_on', [$activityxpath, 'xpath']); + + $this->execute('behat_general::i_click_on_in_the', [ + get_string('addselectedactivity', 'course'), + 'button', + get_string('addresourceoractivity', 'moodle'), + 'dialogue', + ]); } /** diff --git a/public/lang/en/course.php b/public/lang/en/course.php index c3a5ec862f6..cd2f6898860 100644 --- a/public/lang/en/course.php +++ b/public/lang/en/course.php @@ -32,6 +32,7 @@ $string['activitydate:closed'] = 'Closed:'; $string['activitydate:closes'] = 'Closes:'; $string['activitydate:opened'] = 'Opened:'; $string['activitydate:opens'] = 'Opens:'; +$string['addselectedactivity'] = 'Add selected activity'; $string['aria:coursecategory'] = 'Course category'; $string['aria:courseshortname'] = 'Course short name'; $string['aria:coursename'] = 'Course name';