diff --git a/admin/settings/subsystems.php b/admin/settings/subsystems.php index 14a133126e7b3..01de61c3e363b 100644 --- a/admin/settings/subsystems.php +++ b/admin/settings/subsystems.php @@ -74,7 +74,4 @@ new lang_string('configallowemojipickerincompatible', 'admin') )); } - - $optionalsubsystems->add(new admin_setting_configcheckbox('enablemoodlenet', new lang_string('enablemoodlenet', 'admin'), - new lang_string('enablemoodlenet_desc', 'admin'), 1, 1, 0)); } diff --git a/admin/settings/users.php b/admin/settings/users.php index b9612cbe76a5b..0b7c4a4aa4713 100644 --- a/admin/settings/users.php +++ b/admin/settings/users.php @@ -186,6 +186,7 @@ 'email' => new lang_string('email'), 'city' => new lang_string('city'), 'country' => new lang_string('country'), + 'moodlenetprofile' => new lang_string('moodlenetprofile', 'user'), 'timezone' => new lang_string('timezone'), 'webpage' => new lang_string('webpage'), 'icqnumber' => new lang_string('icqnumber'), diff --git a/course/amd/build/activitychooser.min.js b/course/amd/build/activitychooser.min.js index 82e4295f945f9..27aebcf5b5842 100644 --- a/course/amd/build/activitychooser.min.js +++ b/course/amd/build/activitychooser.min.js @@ -1,2 +1,2 @@ -function _typeof(a){"@babel/helpers - typeof";if("function"==typeof Symbol&&"symbol"==typeof Symbol.iterator){_typeof=function(a){return typeof a}}else{_typeof=function(a){return a&&"function"==typeof Symbol&&a.constructor===Symbol&&a!==Symbol.prototype?"symbol":typeof a}}return _typeof(a)}define ("core_course/activitychooser",["exports","core_course/local/activitychooser/dialogue","core_course/local/activitychooser/repository","core_course/local/activitychooser/selectors","core/custom_interaction_events","core/templates","core/modal_factory","core/str","core/pending"],function(a,b,c,d,e,f,g,h,i){"use strict";Object.defineProperty(a,"__esModule",{value:!0});a.init=void 0;b=l(b);c=l(c);d=j(d);e=j(e);f=l(f);g=l(g);i=j(i);function j(a){return a&&a.__esModule?a:{default:a}}function k(){if("function"!=typeof WeakMap)return null;var a=new WeakMap;k=function(){return a};return a}function l(a){if(a&&a.__esModule){return a}if(null===a||"object"!==_typeof(a)&&"function"!=typeof a){return{default:a}}var b=k();if(b&&b.has(a)){return b.get(a)}var c={},d=Object.defineProperty&&Object.getOwnPropertyDescriptor;for(var e in a){if(Object.prototype.hasOwnProperty.call(a,e)){var f=d?Object.getOwnPropertyDescriptor(a,e):null;if(f&&(f.get||f.set)){Object.defineProperty(c,e,f)}else{c[e]=a[e]}}}c.default=a;if(b){b.set(a,c)}return c}function m(a,b,c,d,e,f,g){try{var h=a[f](g),i=h.value}catch(a){c(a);return}if(h.done){b(i)}else{Promise.resolve(i).then(d,e)}}function n(a){return function(){var b=this,c=arguments;return new Promise(function(d,e){var h=a.apply(b,c);function f(a){m(h,d,e,f,g,"next",a)}function g(a){m(h,d,e,f,g,"throw",a)}f(void 0)})}}var o=2,p=function(a,b){var c=new i.default;q(a,b);c.resolve()};a.init=p;var q=function(a,g){var h=["click",e.default.events.activate,e.default.events.keyboardActivate],i=function(){var b=null;return function(){if(!b){b=new Promise(function(b){b(c.activityModules(a))})}return b}}();e.default.define(document,h);h.forEach(function(a){document.addEventListener(a,function(){var a=n(regeneratorRuntime.mark(function a(c){var e,h,j,k,l,m,n,o;return regeneratorRuntime.wrap(function(a){while(1){switch(a.prev=a.next){case 0:if(!c.target.closest(d.default.elements.sectionmodchooser)){a.next=16;break}h=c.target.closest(d.default.elements.section);j=c.target.closest(d.default.elements.sectionmodchooser);if(null!==h&&h.hasAttribute("data-sectionid")){e=h}else{e=j}l=new Promise(function(a){k=a});m=t(l);a.next=8;return i();case 8:n=a.sent;o=r(n,e.dataset.sectionid);b.displayChooser(m,o,v(n,e.dataset.sectionid));a.t0=k;a.next=14;return f.render("core_course/activitychooser",s(o,g));case 14:a.t1=a.sent;(0,a.t0)(a.t1);case 16:case"end":return a.stop();}}},a)}));return function(){return a.apply(this,arguments)}}())})},r=function(a,b){var c=JSON.parse(JSON.stringify(a));c.content_items.forEach(function(a){a.link+="§ion="+b});return c.content_items},s=function(a,b){var c=[],d=[],e=!0,f=!1,g=!1,h=parseInt(b.tabmode),i=a.filter(function(a){return!0===a.favourite}),j=a.filter(function(a){return!0===a.recommended});if((h===0||h===o)&&h!==1){c=a.filter(function(a){return a.archetype===0});d=a.filter(function(a){return a.archetype===1});f=!0;g=!0;if(h===o){e=!1}}var k=!!i.length,l=!1===e&&!1===k,m=!0===e&&!1===k;return{default:a,showAll:e,activities:c,showActivities:f,activitiesFirst:l,resources:d,showResources:g,favourites:i,recommended:j,favouritesFirst:k,fallback:m}},t=function(a){return g.create({type:g.types.DEFAULT,title:(0,h.get_string)("addresourceoractivity"),body:a,large:!0,templateContext:{classes:"modchooser"}}).then(function(a){a.show();return a})},u=function(a,b){a.tabIndex=-1;a.classList.add("d-none");if(a.classList.contains("active")){a.classList.remove("active");a.setAttribute("aria-selected","false");var f=b.querySelector(d.default.regions.favouriteTab);f.classList.remove("active");var c=b.querySelector(d.default.regions.defaultTabNav),e=b.querySelector(d.default.regions.activityTabNav);if(!1===c.classList.contains("d-none")){c.classList.add("active");c.setAttribute("aria-selected","true");c.tabIndex=0;c.focus();var g=b.querySelector(d.default.regions.defaultTab);g.classList.add("active")}else{e.classList.add("active");e.setAttribute("aria-selected","true");e.tabIndex=0;e.focus();var h=b.querySelector(d.default.regions.activityTab);h.classList.add("active")}}},v=function(a,b){return function(){var c=n(regeneratorRuntime.mark(function c(e,g,h){var i,j,k,l,m,n,o,p,q,s,t;return regeneratorRuntime.wrap(function(c){while(1){switch(c.prev=c.next){case 0:i=h.querySelector(d.default.render.favourites);j=h.querySelectorAll("[data-internal=\"".concat(e,"\"] ").concat(d.default.actions.optionActions.manageFavourite));k=h.querySelector(d.default.regions.favouriteTabNav);l=a.content_items.find(function(a){var b=a.name;return b===e});m={};if(!l){c.next=27;break}if(!g){c.next=21;break}l.favourite=!0;m.content_items=a.content_items.filter(function(a){return!0===a.favourite});n=r(m,b);c.next=12;return f.renderForPromise("core_course/local/activitychooser/favourites",{favourites:n});case 12:o=c.sent;p=o.html;q=o.js;c.next=17;return f.replaceNodeContents(i,p,q);case 17:Array.from(j).forEach(function(a){a.classList.remove("text-muted");a.classList.add("text-primary");a.dataset.favourited="true";a.setAttribute("aria-pressed",!0);a.firstElementChild.classList.remove("fa-star-o");a.firstElementChild.classList.add("fa-star")});k.classList.remove("d-none");c.next=27;break;case 21:l.favourite=!1;s=i.querySelector("[data-internal=\"".concat(e,"\"]"));s.parentNode.removeChild(s);Array.from(j).forEach(function(a){a.classList.add("text-muted");a.classList.remove("text-primary");a.dataset.favourited="false";a.setAttribute("aria-pressed",!1);a.firstElementChild.classList.remove("fa-star");a.firstElementChild.classList.add("fa-star-o")});t=a.content_items.filter(function(a){return!0===a.favourite});if(0===t.length){u(k,h)}case 27:case"end":return c.stop();}}},c)}));return function(){return c.apply(this,arguments)}}()}}); +function _typeof(a){"@babel/helpers - typeof";if("function"==typeof Symbol&&"symbol"==typeof Symbol.iterator){_typeof=function(a){return typeof a}}else{_typeof=function(a){return a&&"function"==typeof Symbol&&a.constructor===Symbol&&a!==Symbol.prototype?"symbol":typeof a}}return _typeof(a)}define ("core_course/activitychooser",["exports","core_course/local/activitychooser/dialogue","core_course/local/activitychooser/repository","core_course/local/activitychooser/selectors","core/custom_interaction_events","core/templates","core/modal_factory","core/str","core/pending"],function(a,b,c,d,e,f,g,h,i){"use strict";Object.defineProperty(a,"__esModule",{value:!0});a.init=void 0;b=l(b);c=l(c);d=j(d);e=j(e);f=l(f);g=l(g);i=j(i);function j(a){return a&&a.__esModule?a:{default:a}}function k(){if("function"!=typeof WeakMap)return null;var a=new WeakMap;k=function(){return a};return a}function l(a){if(a&&a.__esModule){return a}if(null===a||"object"!==_typeof(a)&&"function"!=typeof a){return{default:a}}var b=k();if(b&&b.has(a)){return b.get(a)}var c={},d=Object.defineProperty&&Object.getOwnPropertyDescriptor;for(var e in a){if(Object.prototype.hasOwnProperty.call(a,e)){var f=d?Object.getOwnPropertyDescriptor(a,e):null;if(f&&(f.get||f.set)){Object.defineProperty(c,e,f)}else{c[e]=a[e]}}}c.default=a;if(b){b.set(a,c)}return c}function m(a,b,c,d,e,f,g){try{var h=a[f](g),i=h.value}catch(a){c(a);return}if(h.done){b(i)}else{Promise.resolve(i).then(d,e)}}function n(a){return function(){var b=this,c=arguments;return new Promise(function(d,e){var h=a.apply(b,c);function f(a){m(h,d,e,f,g,"next",a)}function g(a){m(h,d,e,f,g,"throw",a)}f(void 0)})}}var o=2,p=function(a,b){var c=new i.default;q(a,b);c.resolve()};a.init=p;var q=function(a,g){var h=["click",e.default.events.activate,e.default.events.keyboardActivate],i=function(){var b=null;return function(){if(!b){b=new Promise(function(b){b(c.activityModules(a))})}return b}}(),j=function(){var b=null;return function(d){if(!b){b=new Promise(function(b){b(c.fetchFooterData(a,d))})}return b}}();e.default.define(document,h);h.forEach(function(a){document.addEventListener(a,function(){var a=n(regeneratorRuntime.mark(function a(c){var e,h,k,l,m,n,o,p,q;return regeneratorRuntime.wrap(function(a){while(1){switch(a.prev=a.next){case 0:if(!c.target.closest(d.default.elements.sectionmodchooser)){a.next=19;break}h=c.target.closest(d.default.elements.section);k=c.target.closest(d.default.elements.sectionmodchooser);if(null!==h&&h.hasAttribute("data-sectionid")){e=h}else{e=k}m=new Promise(function(a){l=a});a.next=7;return j(e.dataset.sectionid);case 7:n=a.sent;o=t(m,n);a.next=11;return i();case 11:p=a.sent;q=r(p,e.dataset.sectionid);b.displayChooser(o,q,v(p,e.dataset.sectionid),n);a.t0=l;a.next=17;return f.render("core_course/activitychooser",s(q,g));case 17:a.t1=a.sent;(0,a.t0)(a.t1);case 19:case"end":return a.stop();}}},a)}));return function(){return a.apply(this,arguments)}}())})},r=function(a,b){var c=JSON.parse(JSON.stringify(a));c.content_items.forEach(function(a){a.link+="§ion="+b});return c.content_items},s=function(a,b){var c=[],d=[],e=!0,f=!1,g=!1,h=parseInt(b.tabmode),i=a.filter(function(a){return!0===a.favourite}),j=a.filter(function(a){return!0===a.recommended});if((h===0||h===o)&&h!==1){c=a.filter(function(a){return a.archetype===0});d=a.filter(function(a){return a.archetype===1});f=!0;g=!0;if(h===o){e=!1}}var k=!!i.length,l=!1===e&&!1===k,m=!0===e&&!1===k;return{default:a,showAll:e,activities:c,showActivities:f,activitiesFirst:l,resources:d,showResources:g,favourites:i,recommended:j,favouritesFirst:k,fallback:m}},t=function(a,b){return g.create({type:g.types.DEFAULT,title:(0,h.get_string)("addresourceoractivity"),body:a,footer:b.customfootertemplate,large:!0,templateContext:{classes:"modchooser"}}).then(function(a){a.show();return a})},u=function(a,b){a.tabIndex=-1;a.classList.add("d-none");if(a.classList.contains("active")){a.classList.remove("active");a.setAttribute("aria-selected","false");var f=b.querySelector(d.default.regions.favouriteTab);f.classList.remove("active");var c=b.querySelector(d.default.regions.defaultTabNav),e=b.querySelector(d.default.regions.activityTabNav);if(!1===c.classList.contains("d-none")){c.classList.add("active");c.setAttribute("aria-selected","true");c.tabIndex=0;c.focus();var g=b.querySelector(d.default.regions.defaultTab);g.classList.add("active")}else{e.classList.add("active");e.setAttribute("aria-selected","true");e.tabIndex=0;e.focus();var h=b.querySelector(d.default.regions.activityTab);h.classList.add("active")}}},v=function(a,b){return function(){var c=n(regeneratorRuntime.mark(function c(e,g,h){var i,j,k,l,m,n,o,p,q,s,t;return regeneratorRuntime.wrap(function(c){while(1){switch(c.prev=c.next){case 0:i=h.querySelector(d.default.render.favourites);j=h.querySelectorAll("[data-internal=\"".concat(e,"\"] ").concat(d.default.actions.optionActions.manageFavourite));k=h.querySelector(d.default.regions.favouriteTabNav);l=a.content_items.find(function(a){var b=a.name;return b===e});m={};if(!l){c.next=27;break}if(!g){c.next=21;break}l.favourite=!0;m.content_items=a.content_items.filter(function(a){return!0===a.favourite});n=r(m,b);c.next=12;return f.renderForPromise("core_course/local/activitychooser/favourites",{favourites:n});case 12:o=c.sent;p=o.html;q=o.js;c.next=17;return f.replaceNodeContents(i,p,q);case 17:Array.from(j).forEach(function(a){a.classList.remove("text-muted");a.classList.add("text-primary");a.dataset.favourited="true";a.setAttribute("aria-pressed",!0);a.firstElementChild.classList.remove("fa-star-o");a.firstElementChild.classList.add("fa-star")});k.classList.remove("d-none");c.next=27;break;case 21:l.favourite=!1;s=i.querySelector("[data-internal=\"".concat(e,"\"]"));s.parentNode.removeChild(s);Array.from(j).forEach(function(a){a.classList.add("text-muted");a.classList.remove("text-primary");a.dataset.favourited="false";a.setAttribute("aria-pressed",!1);a.firstElementChild.classList.remove("fa-star");a.firstElementChild.classList.add("fa-star-o")});t=a.content_items.filter(function(a){return!0===a.favourite});if(0===t.length){u(k,h)}case 27:case"end":return c.stop();}}},c)}));return function(){return c.apply(this,arguments)}}()}}); //# sourceMappingURL=activitychooser.min.js.map diff --git a/course/amd/build/activitychooser.min.js.map b/course/amd/build/activitychooser.min.js.map index 288328b146a9d..055d5ac8f0300 100644 --- a/course/amd/build/activitychooser.min.js.map +++ b/course/amd/build/activitychooser.min.js.map @@ -1 +1 @@ -{"version":3,"sources":["../src/activitychooser.js"],"names":["ACTIVITIESRESOURCES","init","courseId","chooserConfig","pendingPromise","Pending","registerListenerEvents","resolve","events","CustomEvents","activate","keyboardActivate","fetchModuleData","innerPromise","Promise","Repository","activityModules","define","document","forEach","event","addEventListener","e","target","closest","selectors","elements","sectionmodchooser","sectionDiv","section","button","hasAttribute","caller","bodyPromise","bodyPromiseResolver","sectionModal","buildModal","data","builtModuleData","sectionIdMapper","dataset","sectionid","ChooserDialogue","displayChooser","partiallyAppliedFavouriteManager","Templates","render","templateDataBuilder","webServiceData","id","newData","JSON","parse","stringify","content_items","module","link","activities","resources","showAll","showActivities","showResources","tabMode","parseInt","tabmode","favourites","filter","mod","favourite","recommended","archetype","favouritesFirst","length","activitiesFirst","fallback","ModalFactory","create","type","types","DEFAULT","title","body","large","templateContext","classes","then","modal","show","nullFavouriteDomManager","favouriteTabNav","modalBody","tabIndex","classList","add","contains","remove","setAttribute","favouriteTab","querySelector","regions","defaultTabNav","activitiesTabNav","activityTabNav","focus","defaultTab","activitiesTab","activityTab","moduleData","sectionId","internal","favouriteArea","favouriteButtons","querySelectorAll","actions","optionActions","manageFavourite","result","find","name","newFaves","builtFaves","renderForPromise","html","js","replaceNodeContents","Array","from","element","favourited","firstElementChild","nodeToRemove","parentNode","removeChild"],"mappings":"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","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 modules in a course.\n *\n * @module core_course/activitychooser\n * @package core_course\n * @copyright 2020 Mathew May \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport * as ChooserDialogue from 'core_course/local/activitychooser/dialogue';\nimport * as Repository from 'core_course/local/activitychooser/repository';\nimport selectors from 'core_course/local/activitychooser/selectors';\nimport CustomEvents from 'core/custom_interaction_events';\nimport * as Templates from 'core/templates';\nimport * as ModalFactory from 'core/modal_factory';\nimport {get_string as getString} from 'core/str';\nimport Pending from 'core/pending';\n\n// Set up some JS module wide constants that can be added to in the future.\n\n// Tab config options.\nconst ALLACTIVITIESRESOURCES = 0;\nconst ONLYALL = 1;\nconst ACTIVITIESRESOURCES = 2;\n\n// Module types.\nconst ACTIVITY = 0;\nconst RESOURCE = 1;\n\n/**\n * Set up the activity chooser.\n *\n * @method init\n * @param {Number} courseId Course ID to use later on in fetchModules()\n * @param {Object} chooserConfig Any PHP config settings that we may need to reference\n */\nexport const init = (courseId, chooserConfig) => {\n const pendingPromise = new Pending();\n\n registerListenerEvents(courseId, chooserConfig);\n\n pendingPromise.resolve();\n};\n\n/**\n * Once a selection has been made make the modal & module information and pass it along\n *\n * @method registerListenerEvents\n * @param {Number} courseId\n * @param {Object} chooserConfig Any PHP config settings that we may need to reference\n */\nconst registerListenerEvents = (courseId, chooserConfig) => {\n const events = [\n 'click',\n CustomEvents.events.activate,\n CustomEvents.events.keyboardActivate\n ];\n\n const fetchModuleData = (() => {\n let innerPromise = null;\n\n return () => {\n if (!innerPromise) {\n innerPromise = new Promise((resolve) => {\n resolve(Repository.activityModules(courseId));\n });\n }\n\n return innerPromise;\n };\n })();\n\n CustomEvents.define(document, events);\n\n // Display module chooser event listeners.\n events.forEach((event) => {\n document.addEventListener(event, async(e) => {\n if (e.target.closest(selectors.elements.sectionmodchooser)) {\n let caller;\n // We need to know who called this.\n // Standard courses use the ID in the main section info.\n const sectionDiv = e.target.closest(selectors.elements.section);\n // Front page courses need some special handling.\n const button = e.target.closest(selectors.elements.sectionmodchooser);\n\n // If we don't have a section ID use the fallback ID.\n // We always want the sectionDiv caller first as it keeps track of section ID's after DnD changes.\n // The button attribute is always just a fallback for us as the section div is not always available.\n // A YUI change could be done maybe to only update the button attribute but we are going for minimal change here.\n if (sectionDiv !== null && sectionDiv.hasAttribute('data-sectionid')) {\n // We check for attributes just in case of outdated contrib course formats.\n caller = sectionDiv;\n } else {\n caller = button;\n }\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 sectionModal = buildModal(bodyPromise);\n\n // Now we have a modal we should start fetching data.\n const data = await fetchModuleData();\n\n // Apply the section id to all the module instance links.\n const builtModuleData = sectionIdMapper(data, caller.dataset.sectionid);\n\n ChooserDialogue.displayChooser(\n sectionModal,\n builtModuleData,\n partiallyAppliedFavouriteManager(data, caller.dataset.sectionid),\n );\n\n bodyPromiseResolver(await Templates.render(\n 'core_course/activitychooser',\n templateDataBuilder(builtModuleData, chooserConfig)\n ));\n }\n });\n });\n};\n\n/**\n * Given the web service data and an ID we want to make a deep copy\n * of the WS data then add on the section ID to the addoption URL\n *\n * @method sectionIdMapper\n * @param {Object} webServiceData Our original data from the Web service call\n * @param {Number} id The ID of the section we need to append to the links\n * @return {Array} [modules] with URL's built\n */\nconst sectionIdMapper = (webServiceData, id) => {\n // We need to take a fresh deep copy of the original data as an object is a reference type.\n const newData = JSON.parse(JSON.stringify(webServiceData));\n newData.content_items.forEach((module) => {\n module.link += '§ion=' + id;\n });\n return newData.content_items;\n};\n\n/**\n * Given an array of modules we want to figure out where & how to place them into our template object\n *\n * @method templateDataBuilder\n * @param {Array} data our modules to manipulate into a Templatable object\n * @param {Object} chooserConfig Any PHP config settings that we may need to reference\n * @return {Object} Our built object ready to render out\n */\nconst templateDataBuilder = (data, chooserConfig) => {\n // Setup of various bits and pieces we need to mutate before throwing it to the wolves.\n let activities = [];\n let resources = [];\n let showAll = true;\n let showActivities = false;\n let showResources = false;\n\n // Tab mode can be the following [All, Resources & Activities, All & Activities & Resources].\n const tabMode = parseInt(chooserConfig.tabmode);\n\n // Filter the incoming data to find favourite & recommended modules.\n const favourites = data.filter(mod => mod.favourite === true);\n const recommended = data.filter(mod => mod.recommended === true);\n\n // Both of these modes need Activity & Resource tabs.\n if ((tabMode === ALLACTIVITIESRESOURCES || tabMode === ACTIVITIESRESOURCES) && tabMode !== ONLYALL) {\n // Filter the incoming data to find activities then resources.\n activities = data.filter(mod => mod.archetype === ACTIVITY);\n resources = data.filter(mod => mod.archetype === RESOURCE);\n showActivities = true;\n showResources = true;\n\n // We want all of the previous information but no 'All' tab.\n if (tabMode === ACTIVITIESRESOURCES) {\n showAll = false;\n }\n }\n\n // Given the results of the above filters lets figure out what tab to set active.\n // We have some favourites.\n const favouritesFirst = !!favourites.length;\n // We are in tabMode 2 without any favourites.\n const activitiesFirst = showAll === false && favouritesFirst === false;\n // We have nothing fallback to show all modules.\n const fallback = showAll === true && favouritesFirst === false;\n\n return {\n 'default': data,\n showAll: showAll,\n activities: activities,\n showActivities: showActivities,\n activitiesFirst: activitiesFirst,\n resources: resources,\n showResources: showResources,\n favourites: favourites,\n recommended: recommended,\n favouritesFirst: favouritesFirst,\n fallback: fallback,\n };\n};\n\n/**\n * Given an object we want to build a modal ready to show\n *\n * @method buildModal\n * @param {Promise} bodyPromise\n * @return {Object} The modal ready to display immediately and render body in later.\n */\nconst buildModal = bodyPromise => {\n return ModalFactory.create({\n type: ModalFactory.types.DEFAULT,\n title: getString('addresourceoractivity'),\n body: bodyPromise,\n large: true,\n templateContext: {\n classes: 'modchooser'\n }\n })\n .then(modal => {\n modal.show();\n return modal;\n });\n};\n\n/**\n * A small helper function to handle the case where there are no more favourites\n * and we need to mess a bit with the available tabs in the chooser\n *\n * @method nullFavouriteDomManager\n * @param {HTMLElement} favouriteTabNav Dom node of the favourite tab nav\n * @param {HTMLElement} modalBody Our current modals' body\n */\nconst nullFavouriteDomManager = (favouriteTabNav, modalBody) => {\n favouriteTabNav.tabIndex = -1;\n favouriteTabNav.classList.add('d-none');\n // Need to set active to an available tab.\n if (favouriteTabNav.classList.contains('active')) {\n favouriteTabNav.classList.remove('active');\n favouriteTabNav.setAttribute('aria-selected', 'false');\n const favouriteTab = modalBody.querySelector(selectors.regions.favouriteTab);\n favouriteTab.classList.remove('active');\n const defaultTabNav = modalBody.querySelector(selectors.regions.defaultTabNav);\n const activitiesTabNav = modalBody.querySelector(selectors.regions.activityTabNav);\n if (defaultTabNav.classList.contains('d-none') === false) {\n defaultTabNav.classList.add('active');\n defaultTabNav.setAttribute('aria-selected', 'true');\n defaultTabNav.tabIndex = 0;\n defaultTabNav.focus();\n const defaultTab = modalBody.querySelector(selectors.regions.defaultTab);\n defaultTab.classList.add('active');\n } else {\n activitiesTabNav.classList.add('active');\n activitiesTabNav.setAttribute('aria-selected', 'true');\n activitiesTabNav.tabIndex = 0;\n activitiesTabNav.focus();\n const activitiesTab = modalBody.querySelector(selectors.regions.activityTab);\n activitiesTab.classList.add('active');\n }\n\n }\n};\n\n/**\n * Export a curried function where the builtModules has been applied.\n * We have our array of modules so we can rerender the favourites area and have all of the items sorted.\n *\n * @method partiallyAppliedFavouriteManager\n * @param {Array} moduleData This is our raw WS data that we need to manipulate\n * @param {Number} sectionId We need this to add the sectionID to the URL's in the faves area after rerender\n * @return {Function} partially applied function so we can manipulate DOM nodes easily & update our internal array\n */\nconst partiallyAppliedFavouriteManager = (moduleData, sectionId) => {\n /**\n * Curried function that is being returned.\n *\n * @param {String} internal Internal name of the module to manage\n * @param {Boolean} favourite Is the caller adding a favourite or removing one?\n * @param {HTMLElement} modalBody What we need to update whilst we are here\n */\n return async(internal, favourite, modalBody) => {\n const favouriteArea = modalBody.querySelector(selectors.render.favourites);\n\n // eslint-disable-next-line max-len\n const favouriteButtons = modalBody.querySelectorAll(`[data-internal=\"${internal}\"] ${selectors.actions.optionActions.manageFavourite}`);\n const favouriteTabNav = modalBody.querySelector(selectors.regions.favouriteTabNav);\n const result = moduleData.content_items.find(({name}) => name === internal);\n const newFaves = {};\n if (result) {\n if (favourite) {\n result.favourite = true;\n\n // eslint-disable-next-line camelcase\n newFaves.content_items = moduleData.content_items.filter(mod => mod.favourite === true);\n\n const builtFaves = sectionIdMapper(newFaves, sectionId);\n\n const {html, js} = await Templates.renderForPromise('core_course/local/activitychooser/favourites',\n {favourites: builtFaves});\n\n await Templates.replaceNodeContents(favouriteArea, html, js);\n\n Array.from(favouriteButtons).forEach((element) => {\n element.classList.remove('text-muted');\n element.classList.add('text-primary');\n element.dataset.favourited = 'true';\n element.setAttribute('aria-pressed', true);\n element.firstElementChild.classList.remove('fa-star-o');\n element.firstElementChild.classList.add('fa-star');\n });\n\n favouriteTabNav.classList.remove('d-none');\n } else {\n result.favourite = false;\n\n const nodeToRemove = favouriteArea.querySelector(`[data-internal=\"${internal}\"]`);\n\n nodeToRemove.parentNode.removeChild(nodeToRemove);\n\n Array.from(favouriteButtons).forEach((element) => {\n element.classList.add('text-muted');\n element.classList.remove('text-primary');\n element.dataset.favourited = 'false';\n element.setAttribute('aria-pressed', false);\n element.firstElementChild.classList.remove('fa-star');\n element.firstElementChild.classList.add('fa-star-o');\n });\n const newFaves = moduleData.content_items.filter(mod => mod.favourite === true);\n\n if (newFaves.length === 0) {\n nullFavouriteDomManager(favouriteTabNav, modalBody);\n }\n }\n }\n };\n};\n"],"file":"activitychooser.min.js"} \ No newline at end of file +{"version":3,"sources":["../src/activitychooser.js"],"names":["ACTIVITIESRESOURCES","init","courseId","chooserConfig","pendingPromise","Pending","registerListenerEvents","resolve","events","CustomEvents","activate","keyboardActivate","fetchModuleData","innerPromise","Promise","Repository","activityModules","fetchFooterData","footerInnerPromise","sectionId","define","document","forEach","event","addEventListener","e","target","closest","selectors","elements","sectionmodchooser","sectionDiv","section","button","hasAttribute","caller","bodyPromise","bodyPromiseResolver","dataset","sectionid","footerData","sectionModal","buildModal","data","builtModuleData","sectionIdMapper","ChooserDialogue","displayChooser","partiallyAppliedFavouriteManager","Templates","render","templateDataBuilder","webServiceData","id","newData","JSON","parse","stringify","content_items","module","link","activities","resources","showAll","showActivities","showResources","tabMode","parseInt","tabmode","favourites","filter","mod","favourite","recommended","archetype","favouritesFirst","length","activitiesFirst","fallback","footer","ModalFactory","create","type","types","DEFAULT","title","body","customfootertemplate","large","templateContext","classes","then","modal","show","nullFavouriteDomManager","favouriteTabNav","modalBody","tabIndex","classList","add","contains","remove","setAttribute","favouriteTab","querySelector","regions","defaultTabNav","activitiesTabNav","activityTabNav","focus","defaultTab","activitiesTab","activityTab","moduleData","internal","favouriteArea","favouriteButtons","querySelectorAll","actions","optionActions","manageFavourite","result","find","name","newFaves","builtFaves","renderForPromise","html","js","replaceNodeContents","Array","from","element","favourited","firstElementChild","nodeToRemove","parentNode","removeChild"],"mappings":"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","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 modules in a course.\n *\n * @module core_course/activitychooser\n * @package core_course\n * @copyright 2020 Mathew May \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport * as ChooserDialogue from 'core_course/local/activitychooser/dialogue';\nimport * as Repository from 'core_course/local/activitychooser/repository';\nimport selectors from 'core_course/local/activitychooser/selectors';\nimport CustomEvents from 'core/custom_interaction_events';\nimport * as Templates from 'core/templates';\nimport * as ModalFactory from 'core/modal_factory';\nimport {get_string as getString} from 'core/str';\nimport Pending from 'core/pending';\n\n// Set up some JS module wide constants that can be added to in the future.\n\n// Tab config options.\nconst ALLACTIVITIESRESOURCES = 0;\nconst ONLYALL = 1;\nconst ACTIVITIESRESOURCES = 2;\n\n// Module types.\nconst ACTIVITY = 0;\nconst RESOURCE = 1;\n\n/**\n * Set up the activity chooser.\n *\n * @method init\n * @param {Number} courseId Course ID to use later on in fetchModules()\n * @param {Object} chooserConfig Any PHP config settings that we may need to reference\n */\nexport const init = (courseId, chooserConfig) => {\n const pendingPromise = new Pending();\n\n registerListenerEvents(courseId, chooserConfig);\n\n pendingPromise.resolve();\n};\n\n/**\n * Once a selection has been made make the modal & module information and pass it along\n *\n * @method registerListenerEvents\n * @param {Number} courseId\n * @param {Object} chooserConfig Any PHP config settings that we may need to reference\n */\nconst registerListenerEvents = (courseId, chooserConfig) => {\n const events = [\n 'click',\n CustomEvents.events.activate,\n CustomEvents.events.keyboardActivate\n ];\n\n const fetchModuleData = (() => {\n let innerPromise = null;\n\n return () => {\n if (!innerPromise) {\n innerPromise = new Promise((resolve) => {\n resolve(Repository.activityModules(courseId));\n });\n }\n\n return innerPromise;\n };\n })();\n\n const fetchFooterData = (() => {\n let footerInnerPromise = null;\n\n return (sectionId) => {\n if (!footerInnerPromise) {\n footerInnerPromise = new Promise((resolve) => {\n resolve(Repository.fetchFooterData(courseId, sectionId));\n });\n }\n\n return footerInnerPromise;\n };\n })();\n\n CustomEvents.define(document, events);\n\n // Display module chooser event listeners.\n events.forEach((event) => {\n document.addEventListener(event, async(e) => {\n if (e.target.closest(selectors.elements.sectionmodchooser)) {\n let caller;\n // We need to know who called this.\n // Standard courses use the ID in the main section info.\n const sectionDiv = e.target.closest(selectors.elements.section);\n // Front page courses need some special handling.\n const button = e.target.closest(selectors.elements.sectionmodchooser);\n\n // If we don't have a section ID use the fallback ID.\n // We always want the sectionDiv caller first as it keeps track of section ID's after DnD changes.\n // The button attribute is always just a fallback for us as the section div is not always available.\n // A YUI change could be done maybe to only update the button attribute but we are going for minimal change here.\n if (sectionDiv !== null && sectionDiv.hasAttribute('data-sectionid')) {\n // We check for attributes just in case of outdated contrib course formats.\n caller = sectionDiv;\n } else {\n caller = button;\n }\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 fetchFooterData(caller.dataset.sectionid);\n const sectionModal = buildModal(bodyPromise, footerData);\n\n // Now we have a modal we should start fetching data.\n const data = await fetchModuleData();\n\n // Apply the section id to all the module instance links.\n const builtModuleData = sectionIdMapper(data, caller.dataset.sectionid);\n\n ChooserDialogue.displayChooser(\n sectionModal,\n builtModuleData,\n partiallyAppliedFavouriteManager(data, caller.dataset.sectionid),\n footerData,\n );\n\n bodyPromiseResolver(await Templates.render(\n 'core_course/activitychooser',\n templateDataBuilder(builtModuleData, chooserConfig)\n ));\n }\n });\n });\n};\n\n/**\n * Given the web service data and an ID we want to make a deep copy\n * of the WS data then add on the section ID to the addoption URL\n *\n * @method sectionIdMapper\n * @param {Object} webServiceData Our original data from the Web service call\n * @param {Number} id The ID of the section we need to append to the links\n * @return {Array} [modules] with URL's built\n */\nconst sectionIdMapper = (webServiceData, id) => {\n // We need to take a fresh deep copy of the original data as an object is a reference type.\n const newData = JSON.parse(JSON.stringify(webServiceData));\n newData.content_items.forEach((module) => {\n module.link += '§ion=' + id;\n });\n return newData.content_items;\n};\n\n/**\n * Given an array of modules we want to figure out where & how to place them into our template object\n *\n * @method templateDataBuilder\n * @param {Array} data our modules to manipulate into a Templatable object\n * @param {Object} chooserConfig Any PHP config settings that we may need to reference\n * @return {Object} Our built object ready to render out\n */\nconst templateDataBuilder = (data, chooserConfig) => {\n // Setup of various bits and pieces we need to mutate before throwing it to the wolves.\n let activities = [];\n let resources = [];\n let showAll = true;\n let showActivities = false;\n let showResources = false;\n\n // Tab mode can be the following [All, Resources & Activities, All & Activities & Resources].\n const tabMode = parseInt(chooserConfig.tabmode);\n\n // Filter the incoming data to find favourite & recommended modules.\n const favourites = data.filter(mod => mod.favourite === true);\n const recommended = data.filter(mod => mod.recommended === true);\n\n // Both of these modes need Activity & Resource tabs.\n if ((tabMode === ALLACTIVITIESRESOURCES || tabMode === ACTIVITIESRESOURCES) && tabMode !== ONLYALL) {\n // Filter the incoming data to find activities then resources.\n activities = data.filter(mod => mod.archetype === ACTIVITY);\n resources = data.filter(mod => mod.archetype === RESOURCE);\n showActivities = true;\n showResources = true;\n\n // We want all of the previous information but no 'All' tab.\n if (tabMode === ACTIVITIESRESOURCES) {\n showAll = false;\n }\n }\n\n // Given the results of the above filters lets figure out what tab to set active.\n // We have some favourites.\n const favouritesFirst = !!favourites.length;\n // We are in tabMode 2 without any favourites.\n const activitiesFirst = showAll === false && favouritesFirst === false;\n // We have nothing fallback to show all modules.\n const fallback = showAll === true && favouritesFirst === false;\n\n return {\n 'default': data,\n showAll: showAll,\n activities: activities,\n showActivities: showActivities,\n activitiesFirst: activitiesFirst,\n resources: resources,\n showResources: showResources,\n favourites: favourites,\n recommended: recommended,\n favouritesFirst: favouritesFirst,\n fallback: fallback,\n };\n};\n\n/**\n * Given an object we want to build a modal ready to show\n *\n * @method buildModal\n * @param {Promise} bodyPromise\n * @param {String|Boolean} footer Either a footer to add or nothing\n * @return {Object} The modal ready to display immediately and render body in later.\n */\nconst buildModal = (bodyPromise, footer) => {\n return ModalFactory.create({\n type: ModalFactory.types.DEFAULT,\n title: getString('addresourceoractivity'),\n body: bodyPromise,\n footer: footer.customfootertemplate,\n large: true,\n templateContext: {\n classes: 'modchooser'\n }\n })\n .then(modal => {\n modal.show();\n return modal;\n });\n};\n\n/**\n * A small helper function to handle the case where there are no more favourites\n * and we need to mess a bit with the available tabs in the chooser\n *\n * @method nullFavouriteDomManager\n * @param {HTMLElement} favouriteTabNav Dom node of the favourite tab nav\n * @param {HTMLElement} modalBody Our current modals' body\n */\nconst nullFavouriteDomManager = (favouriteTabNav, modalBody) => {\n favouriteTabNav.tabIndex = -1;\n favouriteTabNav.classList.add('d-none');\n // Need to set active to an available tab.\n if (favouriteTabNav.classList.contains('active')) {\n favouriteTabNav.classList.remove('active');\n favouriteTabNav.setAttribute('aria-selected', 'false');\n const favouriteTab = modalBody.querySelector(selectors.regions.favouriteTab);\n favouriteTab.classList.remove('active');\n const defaultTabNav = modalBody.querySelector(selectors.regions.defaultTabNav);\n const activitiesTabNav = modalBody.querySelector(selectors.regions.activityTabNav);\n if (defaultTabNav.classList.contains('d-none') === false) {\n defaultTabNav.classList.add('active');\n defaultTabNav.setAttribute('aria-selected', 'true');\n defaultTabNav.tabIndex = 0;\n defaultTabNav.focus();\n const defaultTab = modalBody.querySelector(selectors.regions.defaultTab);\n defaultTab.classList.add('active');\n } else {\n activitiesTabNav.classList.add('active');\n activitiesTabNav.setAttribute('aria-selected', 'true');\n activitiesTabNav.tabIndex = 0;\n activitiesTabNav.focus();\n const activitiesTab = modalBody.querySelector(selectors.regions.activityTab);\n activitiesTab.classList.add('active');\n }\n\n }\n};\n\n/**\n * Export a curried function where the builtModules has been applied.\n * We have our array of modules so we can rerender the favourites area and have all of the items sorted.\n *\n * @method partiallyAppliedFavouriteManager\n * @param {Array} moduleData This is our raw WS data that we need to manipulate\n * @param {Number} sectionId We need this to add the sectionID to the URL's in the faves area after rerender\n * @return {Function} partially applied function so we can manipulate DOM nodes easily & update our internal array\n */\nconst partiallyAppliedFavouriteManager = (moduleData, sectionId) => {\n /**\n * Curried function that is being returned.\n *\n * @param {String} internal Internal name of the module to manage\n * @param {Boolean} favourite Is the caller adding a favourite or removing one?\n * @param {HTMLElement} modalBody What we need to update whilst we are here\n */\n return async(internal, favourite, modalBody) => {\n const favouriteArea = modalBody.querySelector(selectors.render.favourites);\n\n // eslint-disable-next-line max-len\n const favouriteButtons = modalBody.querySelectorAll(`[data-internal=\"${internal}\"] ${selectors.actions.optionActions.manageFavourite}`);\n const favouriteTabNav = modalBody.querySelector(selectors.regions.favouriteTabNav);\n const result = moduleData.content_items.find(({name}) => name === internal);\n const newFaves = {};\n if (result) {\n if (favourite) {\n result.favourite = true;\n\n // eslint-disable-next-line camelcase\n newFaves.content_items = moduleData.content_items.filter(mod => mod.favourite === true);\n\n const builtFaves = sectionIdMapper(newFaves, sectionId);\n\n const {html, js} = await Templates.renderForPromise('core_course/local/activitychooser/favourites',\n {favourites: builtFaves});\n\n await Templates.replaceNodeContents(favouriteArea, html, js);\n\n Array.from(favouriteButtons).forEach((element) => {\n element.classList.remove('text-muted');\n element.classList.add('text-primary');\n element.dataset.favourited = 'true';\n element.setAttribute('aria-pressed', true);\n element.firstElementChild.classList.remove('fa-star-o');\n element.firstElementChild.classList.add('fa-star');\n });\n\n favouriteTabNav.classList.remove('d-none');\n } else {\n result.favourite = false;\n\n const nodeToRemove = favouriteArea.querySelector(`[data-internal=\"${internal}\"]`);\n\n nodeToRemove.parentNode.removeChild(nodeToRemove);\n\n Array.from(favouriteButtons).forEach((element) => {\n element.classList.add('text-muted');\n element.classList.remove('text-primary');\n element.dataset.favourited = 'false';\n element.setAttribute('aria-pressed', false);\n element.firstElementChild.classList.remove('fa-star');\n element.firstElementChild.classList.add('fa-star-o');\n });\n const newFaves = moduleData.content_items.filter(mod => mod.favourite === true);\n\n if (newFaves.length === 0) {\n nullFavouriteDomManager(favouriteTabNav, modalBody);\n }\n }\n }\n };\n};\n"],"file":"activitychooser.min.js"} \ No newline at end of file diff --git a/course/amd/build/local/activitychooser/dialogue.min.js b/course/amd/build/local/activitychooser/dialogue.min.js index dde9eec09ea7f..dbb33f45fbe7f 100644 --- a/course/amd/build/local/activitychooser/dialogue.min.js +++ b/course/amd/build/local/activitychooser/dialogue.min.js @@ -1,2 +1,2 @@ -function _typeof(a){"@babel/helpers - typeof";if("function"==typeof Symbol&&"symbol"==typeof Symbol.iterator){_typeof=function(a){return typeof a}}else{_typeof=function(a){return a&&"function"==typeof Symbol&&a.constructor===Symbol&&a!==Symbol.prototype?"symbol":typeof a}}return _typeof(a)}define ("core_course/local/activitychooser/dialogue",["exports","jquery","core/modal_events","core_course/local/activitychooser/selectors","core/templates","core/key_codes","core/loadingicon","core_course/local/activitychooser/repository","core/notification","core/utils"],function(a,b,c,d,e,f,g,h,i,j){"use strict";Object.defineProperty(a,"__esModule",{value:!0});a.displayChooser=void 0;b=m(b);c=l(c);d=m(d);e=l(e);h=l(h);i=m(i);function k(){if("function"!=typeof WeakMap)return null;var a=new WeakMap;k=function(){return a};return a}function l(a){if(a&&a.__esModule){return a}if(null===a||"object"!==_typeof(a)&&"function"!=typeof a){return{default:a}}var b=k();if(b&&b.has(a)){return b.get(a)}var c={},d=Object.defineProperty&&Object.getOwnPropertyDescriptor;for(var e in a){if(Object.prototype.hasOwnProperty.call(a,e)){var f=d?Object.getOwnPropertyDescriptor(a,e):null;if(f&&(f.get||f.set)){Object.defineProperty(c,e,f)}else{c[e]=a[e]}}}c.default=a;if(b){b.set(a,c)}return c}function m(a){return a&&a.__esModule?a:{default:a}}function n(a,b,c,d,e,f,g){try{var h=a[f](g),i=h.value}catch(a){c(a);return}if(h.done){b(i)}else{Promise.resolve(i).then(d,e)}}function o(a){return function(){var b=this,c=arguments;return new Promise(function(d,e){var h=a.apply(b,c);function f(a){n(h,d,e,f,g,"next",a)}function g(a){n(h,d,e,f,g,"throw",a)}f(void 0)})}}function p(a,b){return u(a)||t(a,b)||r(a,b)||q()}function q(){throw new TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}function r(a,b){if(!a)return;if("string"==typeof a)return s(a,b);var c=Object.prototype.toString.call(a).slice(8,-1);if("Object"===c&&a.constructor)c=a.constructor.name;if("Map"===c||"Set"===c)return Array.from(c);if("Arguments"===c||/^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(c))return s(a,b)}function s(a,b){if(null==b||b>a.length)b=a.length;for(var c=0,d=Array(b);ca.length)b=a.length;for(var c=0,d=Array(b);c.\n\n/**\n * A type of dialogue used as for choosing options.\n *\n * @module core_course/local/chooser/dialogue\n * @package core\n * @copyright 2019 Mihail Geshoski \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport $ from 'jquery';\nimport * as ModalEvents from 'core/modal_events';\nimport selectors from 'core_course/local/activitychooser/selectors';\nimport * as Templates from 'core/templates';\nimport {end, arrowLeft, arrowRight, home, enter, space} from 'core/key_codes';\nimport {addIconToContainer} from 'core/loadingicon';\nimport * as Repository from 'core_course/local/activitychooser/repository';\nimport Notification from 'core/notification';\nimport {debounce} from 'core/utils';\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 {jQuery} carousel Our initialized carousel to manipulate\n * @param {Object} moduleData Data of the module to carousel to\n */\nconst showModuleHelp = (carousel, moduleData) => {\n const help = carousel.find(selectors.regions.help)[0];\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('core_course/local/activitychooser/help', moduleData);\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.one('slid.bs.carousel', () => {\n transitionPromiseResolver();\n });\n // Trigger the transition between 'pages'.\n carousel.carousel('next');\n};\n\n/**\n * Given a user wants to change the favourite state of a module we either add or remove the status.\n * We also propergate this change across our map of modals.\n *\n * @method manageFavouriteState\n * @param {HTMLElement} modalBody The DOM node of the modal to manipulate\n * @param {HTMLElement} caller\n * @param {Function} partialFavourite Partially applied function we need to manage favourite status\n */\nconst manageFavouriteState = async(modalBody, caller, partialFavourite) => {\n const isFavourite = caller.dataset.favourited;\n const id = caller.dataset.id;\n const name = caller.dataset.name;\n const internal = caller.dataset.internal;\n // Switch on fave or not.\n if (isFavourite === 'true') {\n await Repository.unfavouriteModule(name, id);\n\n partialFavourite(internal, false, modalBody);\n } else {\n await Repository.favouriteModule(name, id);\n\n partialFavourite(internal, true, modalBody);\n }\n\n};\n\n/**\n * Register chooser related event listeners.\n *\n * @method registerListenerEvents\n * @param {Promise} modal Our modal that we are working with\n * @param {Map} mappedModules A map of all of the modules we are working with with K: mod_name V: {Object}\n * @param {Function} partialFavourite Partially applied function we need to manage favourite status\n */\nconst registerListenerEvents = (modal, mappedModules, partialFavourite) => {\n const bodyClickListener = async(e) => {\n if (e.target.closest(selectors.actions.optionActions.showSummary)) {\n const carousel = $(modal.getBody()[0].querySelector(selectors.regions.carousel));\n\n const module = e.target.closest(selectors.regions.chooserOption.container);\n const moduleName = module.dataset.modname;\n const moduleData = mappedModules.get(moduleName);\n showModuleHelp(carousel, moduleData);\n }\n\n if (e.target.closest(selectors.actions.optionActions.manageFavourite)) {\n const caller = e.target.closest(selectors.actions.optionActions.manageFavourite);\n await manageFavouriteState(modal.getBody()[0], caller, partialFavourite);\n const activeSectionId = modal.getBody()[0].querySelector(selectors.elements.activetab).getAttribute(\"href\");\n const sectionChooserOptions = modal.getBody()[0]\n .querySelector(selectors.regions.getSectionChooserOptions(activeSectionId));\n const firstChooserOption = sectionChooserOptions\n .querySelector(selectors.regions.chooserOption.container);\n toggleFocusableChooserOption(firstChooserOption, true);\n initChooserOptionsKeyboardNavigation(modal.getBody()[0], mappedModules, sectionChooserOptions);\n }\n\n // From the help screen go back to the module overview.\n if (e.target.matches(selectors.actions.closeOption)) {\n const carousel = $(modal.getBody()[0].querySelector(selectors.regions.carousel));\n\n // Trigger the transition between 'pages'.\n carousel.carousel('prev');\n carousel.on('slid.bs.carousel', () => {\n const allModules = modal.getBody()[0].querySelector(selectors.regions.modules);\n const caller = allModules.querySelector(selectors.regions.getModuleSelector(e.target.dataset.modname));\n caller.focus();\n });\n }\n\n // The \"clear search\" button is triggered.\n if (e.target.closest(selectors.actions.clearSearch)) {\n // Clear the entered search query in the search bar and hide the search results container.\n const searchInput = modal.getBody()[0].querySelector(selectors.actions.search);\n searchInput.value = \"\";\n searchInput.focus();\n toggleSearchResultsView(modal.getBody()[0], mappedModules, searchInput.value);\n }\n };\n\n modal.getBodyPromise()\n\n // The return value of getBodyPromise is a jquery object containing the body NodeElement.\n .then(body => body[0])\n\n // Set up the carousel.\n .then(body => {\n $(body.querySelector(selectors.regions.carousel))\n .carousel({\n interval: false,\n pause: true,\n keyboard: false\n });\n\n return body;\n })\n\n // Add the listener for clicks on the body.\n .then(body => {\n body.addEventListener('click', bodyClickListener);\n return body;\n })\n\n // Add a listener for an input change in the activity chooser's search bar.\n .then(body => {\n const searchInput = body.querySelector(selectors.actions.search);\n // The search input is triggered.\n searchInput.addEventListener('input', debounce(() => {\n // Display the search results.\n toggleSearchResultsView(body, mappedModules, searchInput.value);\n }, 300));\n return body;\n })\n\n // Register event listeners related to the keyboard navigation controls.\n .then(body => {\n // Get the active chooser options section.\n const activeSectionId = body.querySelector(selectors.elements.activetab).getAttribute(\"href\");\n const sectionChooserOptions = body.querySelector(selectors.regions.getSectionChooserOptions(activeSectionId));\n const firstChooserOption = sectionChooserOptions.querySelector(selectors.regions.chooserOption.container);\n\n toggleFocusableChooserOption(firstChooserOption, true);\n initTabsKeyboardNavigation(body);\n initChooserOptionsKeyboardNavigation(body, mappedModules, sectionChooserOptions);\n\n return body;\n })\n .catch();\n\n};\n\n/**\n * Initialise the keyboard navigation controls for the tab list items.\n *\n * @method initTabsKeyboardNavigation\n * @param {HTMLElement} body Our modal that we are working with\n */\nconst initTabsKeyboardNavigation = (body) => {\n // Set up the tab handlers.\n const favTabNav = body.querySelector(selectors.regions.favouriteTabNav);\n const recommendedTabNav = body.querySelector(selectors.regions.recommendedTabNav);\n const defaultTabNav = body.querySelector(selectors.regions.defaultTabNav);\n const activityTabNav = body.querySelector(selectors.regions.activityTabNav);\n const resourceTabNav = body.querySelector(selectors.regions.resourceTabNav);\n const tabNavArray = [favTabNav, recommendedTabNav, defaultTabNav, activityTabNav, resourceTabNav];\n tabNavArray.forEach((element) => {\n return element.addEventListener('keydown', (e) => {\n // The first visible navigation tab link.\n const firstLink = e.target.parentElement.querySelector(selectors.elements.visibletabs);\n // The last navigation tab link. It would always be the default activities tab link.\n const lastLink = e.target.parentElement.lastElementChild;\n\n if (e.keyCode === arrowRight) {\n const nextLink = e.target.nextElementSibling;\n if (nextLink === null) {\n e.target.tabIndex = -1;\n firstLink.tabIndex = 0;\n firstLink.focus();\n } else if (nextLink.classList.contains('d-none')) {\n e.target.tabIndex = -1;\n lastLink.tabIndex = 0;\n lastLink.focus();\n } else {\n e.target.tabIndex = -1;\n nextLink.tabIndex = 0;\n nextLink.focus();\n }\n }\n if (e.keyCode === arrowLeft) {\n const previousLink = e.target.previousElementSibling;\n if (previousLink === null) {\n e.target.tabIndex = -1;\n lastLink.tabIndex = 0;\n lastLink.focus();\n } else if (previousLink.classList.contains('d-none')) {\n e.target.tabIndex = -1;\n firstLink.tabIndex = 0;\n firstLink.focus();\n } else {\n e.target.tabIndex = -1;\n previousLink.tabIndex = 0;\n previousLink.focus();\n }\n }\n if (e.keyCode === home) {\n e.target.tabIndex = -1;\n firstLink.tabIndex = 0;\n firstLink.focus();\n }\n if (e.keyCode === end) {\n e.target.tabIndex = -1;\n lastLink.tabIndex = 0;\n lastLink.focus();\n }\n if (e.keyCode === space) {\n e.preventDefault();\n e.target.click();\n }\n });\n });\n};\n\n/**\n * Initialise the keyboard navigation controls for the chooser options.\n *\n * @method initChooserOptionsKeyboardNavigation\n * @param {HTMLElement} body Our modal that we are working with\n * @param {Map} mappedModules A map of all of the modules we are working with with K: mod_name V: {Object}\n * @param {HTMLElement} chooserOptionsContainer The section that contains the chooser items\n */\nconst initChooserOptionsKeyboardNavigation = (body, mappedModules, chooserOptionsContainer) => {\n const chooserOptions = chooserOptionsContainer.querySelectorAll(selectors.regions.chooserOption.container);\n\n Array.from(chooserOptions).forEach((element) => {\n return element.addEventListener('keydown', (e) => {\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 const module = e.target.closest(selectors.regions.chooserOption.container);\n const moduleName = module.dataset.modname;\n const moduleData = mappedModules.get(moduleName);\n const carousel = $(body.querySelector(selectors.regions.carousel));\n carousel.carousel({\n interval: false,\n pause: true,\n keyboard: false\n });\n showModuleHelp(carousel, moduleData);\n }\n }\n\n // Next.\n if (e.keyCode === arrowRight) {\n e.preventDefault();\n const currentOption = e.target.closest(selectors.regions.chooserOption.container);\n const nextOption = currentOption.nextElementSibling;\n const firstOption = chooserOptionsContainer.firstElementChild;\n const toFocusOption = clickErrorHandler(nextOption, firstOption);\n focusChooserOption(toFocusOption, currentOption);\n }\n\n // Previous.\n if (e.keyCode === arrowLeft) {\n e.preventDefault();\n const currentOption = e.target.closest(selectors.regions.chooserOption.container);\n const previousOption = currentOption.previousElementSibling;\n const lastOption = chooserOptionsContainer.lastElementChild;\n const toFocusOption = clickErrorHandler(previousOption, lastOption);\n focusChooserOption(toFocusOption, currentOption);\n }\n\n if (e.keyCode === home) {\n e.preventDefault();\n const currentOption = e.target.closest(selectors.regions.chooserOption.container);\n const firstOption = chooserOptionsContainer.firstElementChild;\n focusChooserOption(firstOption, currentOption);\n }\n\n if (e.keyCode === end) {\n e.preventDefault();\n const currentOption = e.target.closest(selectors.regions.chooserOption.container);\n const lastOption = chooserOptionsContainer.lastElementChild;\n focusChooserOption(lastOption, currentOption);\n }\n });\n });\n};\n\n/**\n * Focus on a chooser option element and remove the previous chooser element from the focus order\n *\n * @method focusChooserOption\n * @param {HTMLElement} currentChooserOption The current chooser option element that we want to focus\n * @param {HTMLElement|null} previousChooserOption The previous focused option element\n */\nconst focusChooserOption = (currentChooserOption, previousChooserOption = null) => {\n if (previousChooserOption !== null) {\n toggleFocusableChooserOption(previousChooserOption, false);\n }\n\n toggleFocusableChooserOption(currentChooserOption, true);\n currentChooserOption.focus();\n};\n\n/**\n * Add or remove a chooser option from the focus order.\n *\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 */\nconst 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 * Small error handling function to make sure the navigated to object exists\n *\n * @method clickErrorHandler\n * @param {HTMLElement} item What we want to check exists\n * @param {HTMLElement} fallback If we dont match anything fallback the focus\n * @return {HTMLElement}\n */\nconst clickErrorHandler = (item, fallback) => {\n if (item !== null) {\n return item;\n } else {\n return fallback;\n }\n};\n\n/**\n * Render the search results in a defined container\n *\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 */\nconst renderSearchResults = async(searchResultsContainer, searchResultsData) => {\n const templateData = {\n 'searchresultsnumber': searchResultsData.length,\n 'searchresults': searchResultsData\n };\n // Build up the html & js ready to place into the help section.\n const {html, js} = await Templates.renderForPromise('core_course/local/activitychooser/search_results', templateData);\n await Templates.replaceNodeContents(searchResultsContainer, html, js);\n};\n\n/**\n * Toggle (display/hide) the search results depending on the value of the search query\n *\n * @method toggleSearchResultsView\n * @param {HTMLElement} modalBody The body of the created modal for the section\n * @param {Map} mappedModules A map of all of the modules we are working with with K: mod_name V: {Object}\n * @param {String} searchQuery The search query\n */\nconst toggleSearchResultsView = async(modalBody, mappedModules, searchQuery) => {\n const searchResultsContainer = modalBody.querySelector(selectors.regions.searchResults);\n const chooserContainer = modalBody.querySelector(selectors.regions.chooser);\n const clearSearchButton = modalBody.querySelector(selectors.elements.clearsearch);\n const searchIcon = modalBody.querySelector(selectors.elements.searchicon);\n\n if (searchQuery.length > 0) { // Search query is present.\n const searchResultsData = searchModules(mappedModules, searchQuery);\n await renderSearchResults(searchResultsContainer, searchResultsData);\n const searchResultItemsContainer = searchResultsContainer.querySelector(selectors.regions.searchResultItems);\n const firstSearchResultItem = searchResultItemsContainer.querySelector(selectors.regions.chooserOption.container);\n if (firstSearchResultItem) {\n // Set the first result item to be focusable.\n toggleFocusableChooserOption(firstSearchResultItem, true);\n // Register keyboard events on the created search result items.\n initChooserOptionsKeyboardNavigation(modalBody, mappedModules, searchResultItemsContainer);\n }\n // Display the \"clear\" search button in the activity chooser search bar.\n searchIcon.classList.add('d-none');\n clearSearchButton.classList.remove('d-none');\n // Hide the default chooser options container.\n chooserContainer.setAttribute('hidden', 'hidden');\n // Display the search results container.\n searchResultsContainer.removeAttribute('hidden');\n } else { // Search query is not present.\n // Hide the \"clear\" search button in the activity chooser search bar.\n clearSearchButton.classList.add('d-none');\n searchIcon.classList.remove('d-none');\n // Hide the search results container.\n searchResultsContainer.setAttribute('hidden', 'hidden');\n // Display the default chooser options container.\n chooserContainer.removeAttribute('hidden');\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 {Array} modules List of available modules\n * @param {String} searchTerm The search term to match\n * @return {Array}\n */\nconst searchModules = (modules, searchTerm) => {\n if (searchTerm === '') {\n return modules;\n }\n searchTerm = searchTerm.toLowerCase();\n const searchResults = [];\n modules.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 * Set up our tabindex information across the chooser.\n *\n * @method setupKeyboardAccessibility\n * @param {Promise} modal Our created modal for the section\n * @param {Map} mappedModules A map of all of the built module information\n */\nconst setupKeyboardAccessibility = (modal, mappedModules) => {\n modal.getModal()[0].tabIndex = -1;\n\n modal.getBodyPromise().then(body => {\n $(selectors.elements.tab).on('shown.bs.tab', (e) => {\n const activeSectionId = e.target.getAttribute(\"href\");\n const activeSectionChooserOptions = body[0]\n .querySelector(selectors.regions.getSectionChooserOptions(activeSectionId));\n const firstChooserOption = activeSectionChooserOptions\n .querySelector(selectors.regions.chooserOption.container);\n const prevActiveSectionId = e.relatedTarget.getAttribute(\"href\");\n const prevActiveSectionChooserOptions = body[0]\n .querySelector(selectors.regions.getSectionChooserOptions(prevActiveSectionId));\n\n // Disable the focus of every chooser option in the previous active section.\n disableFocusAllChooserOptions(prevActiveSectionChooserOptions);\n // Enable the focus of the first chooser option in the current active section.\n toggleFocusableChooserOption(firstChooserOption, true);\n initChooserOptionsKeyboardNavigation(body[0], mappedModules, activeSectionChooserOptions);\n });\n return;\n }).catch(Notification.exception);\n};\n\n/**\n * Disable the focus of all chooser options in a specific container (section).\n *\n * @method disableFocusAllChooserOptions\n * @param {HTMLElement} sectionChooserOptions The section that contains the chooser items\n */\nconst disableFocusAllChooserOptions = (sectionChooserOptions) => {\n const allChooserOptions = sectionChooserOptions.querySelectorAll(selectors.regions.chooserOption.container);\n allChooserOptions.forEach((chooserOption) => {\n toggleFocusableChooserOption(chooserOption, false);\n });\n};\n\n/**\n * Display the module chooser.\n *\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 */\nexport const displayChooser = (modalPromise, sectionModules, partialFavourite) => {\n // Make a map so we can quickly fetch a specific module's object for either rendering or searching.\n const mappedModules = new Map();\n sectionModules.forEach((module) => {\n mappedModules.set(module.componentname + '_' + module.link, module);\n });\n\n // Register event listeners.\n modalPromise.then(modal => {\n registerListenerEvents(modal, mappedModules, partialFavourite);\n\n // We want to focus on the first chooser option element as soon as the modal is opened.\n setupKeyboardAccessibility(modal, mappedModules);\n\n // We want to focus on the action select when the dialog is closed.\n modal.getRoot().on(ModalEvents.hidden, () => {\n modal.destroy();\n });\n\n return modal;\n }).catch();\n};\n"],"file":"dialogue.min.js"} \ No newline at end of file +{"version":3,"sources":["../../../src/local/activitychooser/dialogue.js"],"names":["getPlugin","pluginName","showModuleHelp","carousel","moduleData","modal","showFooter","setFooter","Templates","render","help","find","selectors","regions","innerHTML","classList","add","spinnerPromise","transitionPromiseResolver","transitionPromise","Promise","resolve","contentPromise","renderForPromise","all","then","html","js","replaceNodeContents","querySelector","chooserSummary","header","focus","catch","Notification","exception","one","manageFavouriteState","modalBody","caller","partialFavourite","isFavourite","dataset","favourited","id","name","internal","Repository","unfavouriteModule","favouriteModule","registerListenerEvents","mappedModules","footerData","bodyClickListener","e","target","closest","actions","optionActions","showSummary","getBody","module","chooserOption","container","moduleName","modname","get","hasFooterContent","manageFavourite","activeSectionId","elements","activetab","getAttribute","sectionChooserOptions","getSectionChooserOptions","firstChooserOption","toggleFocusableChooserOption","initChooserOptionsKeyboardNavigation","matches","closeOption","on","allModules","modules","getModuleSelector","clearSearch","searchInput","search","value","toggleSearchResultsView","footerClickListener","footer","customfooterjs","footerjs","getBodyPromise","body","interval","pause","keyboard","addEventListener","initTabsKeyboardNavigation","getFooterPromise","favTabNav","favouriteTabNav","recommendedTabNav","defaultTabNav","activityTabNav","resourceTabNav","forEach","element","firstLink","parentElement","visibletabs","lastLink","lastElementChild","keyCode","arrowRight","nextLink","nextElementSibling","tabIndex","contains","arrowLeft","previousLink","previousElementSibling","home","end","space","preventDefault","click","chooserOptionsContainer","chooserOptions","querySelectorAll","Array","from","enter","currentOption","nextOption","firstOption","firstElementChild","toFocusOption","clickErrorHandler","focusChooserOption","previousOption","lastOption","currentChooserOption","previousChooserOption","isFocusable","chooserOptionLink","addChooser","chooserOptionHelp","chooserOptionFavourite","item","fallback","renderSearchResults","searchResultsContainer","searchResultsData","templateData","length","searchQuery","searchResults","chooserContainer","chooser","clearSearchButton","clearsearch","searchIcon","searchicon","searchModules","searchResultItemsContainer","searchResultItems","firstSearchResultItem","remove","setAttribute","removeAttribute","searchTerm","toLowerCase","activity","activityName","title","activityDesc","includes","push","setupKeyboardAccessibility","getModal","tab","activeSectionChooserOptions","prevActiveSectionId","relatedTarget","prevActiveSectionChooserOptions","disableFocusAllChooserOptions","allChooserOptions","displayChooser","modalPromise","sectionModules","Map","set","componentname","link","getRoot","ModalEvents","hidden","destroy"],"mappings":"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","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/chooser/dialogue\n * @package core\n * @copyright 2019 Mihail Geshoski \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport $ from 'jquery';\nimport * as ModalEvents from 'core/modal_events';\nimport selectors from 'core_course/local/activitychooser/selectors';\nimport * as Templates from 'core/templates';\nimport {end, arrowLeft, arrowRight, home, enter, space} from 'core/key_codes';\nimport {addIconToContainer} from 'core/loadingicon';\nimport * as Repository from 'core_course/local/activitychooser/repository';\nimport Notification from 'core/notification';\nimport {debounce} from 'core/utils';\nconst getPlugin = pluginName => import(pluginName);\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 {jQuery} carousel Our initialized carousel to manipulate\n * @param {Object} moduleData Data of the module to carousel to\n * @param {jQuery} modal We need to figure out if the current modal has a footer.\n */\nconst showModuleHelp = (carousel, moduleData, modal = null) => {\n // If we have a real footer then we need to change temporarily.\n if (modal !== null && moduleData.showFooter === true) {\n modal.setFooter(Templates.render('core_course/local/activitychooser/footer_partial', moduleData));\n }\n const help = carousel.find(selectors.regions.help)[0];\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('core_course/local/activitychooser/help', moduleData);\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.one('slid.bs.carousel', () => {\n transitionPromiseResolver();\n });\n // Trigger the transition between 'pages'.\n carousel.carousel('next');\n};\n\n/**\n * Given a user wants to change the favourite state of a module we either add or remove the status.\n * We also propergate this change across our map of modals.\n *\n * @method manageFavouriteState\n * @param {HTMLElement} modalBody The DOM node of the modal to manipulate\n * @param {HTMLElement} caller\n * @param {Function} partialFavourite Partially applied function we need to manage favourite status\n */\nconst manageFavouriteState = async(modalBody, caller, partialFavourite) => {\n const isFavourite = caller.dataset.favourited;\n const id = caller.dataset.id;\n const name = caller.dataset.name;\n const internal = caller.dataset.internal;\n // Switch on fave or not.\n if (isFavourite === 'true') {\n await Repository.unfavouriteModule(name, id);\n\n partialFavourite(internal, false, modalBody);\n } else {\n await Repository.favouriteModule(name, id);\n\n partialFavourite(internal, true, modalBody);\n }\n\n};\n\n/**\n * Register chooser related event listeners.\n *\n * @method registerListenerEvents\n * @param {Promise} modal Our modal that we are working with\n * @param {Map} mappedModules A map of all of the modules we are working with with K: mod_name V: {Object}\n * @param {Function} partialFavourite Partially applied function we need to manage favourite status\n * @param {Object} footerData Our base footer object.\n */\nconst registerListenerEvents = (modal, mappedModules, partialFavourite, footerData) => {\n const bodyClickListener = async(e) => {\n if (e.target.closest(selectors.actions.optionActions.showSummary)) {\n const carousel = $(modal.getBody()[0].querySelector(selectors.regions.carousel));\n\n const module = e.target.closest(selectors.regions.chooserOption.container);\n const moduleName = module.dataset.modname;\n const moduleData = 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 = modal.hasFooterContent();\n showModuleHelp(carousel, moduleData, modal);\n }\n\n if (e.target.closest(selectors.actions.optionActions.manageFavourite)) {\n const caller = e.target.closest(selectors.actions.optionActions.manageFavourite);\n await manageFavouriteState(modal.getBody()[0], caller, partialFavourite);\n const activeSectionId = modal.getBody()[0].querySelector(selectors.elements.activetab).getAttribute(\"href\");\n const sectionChooserOptions = modal.getBody()[0]\n .querySelector(selectors.regions.getSectionChooserOptions(activeSectionId));\n const firstChooserOption = sectionChooserOptions\n .querySelector(selectors.regions.chooserOption.container);\n toggleFocusableChooserOption(firstChooserOption, true);\n initChooserOptionsKeyboardNavigation(modal.getBody()[0], mappedModules, sectionChooserOptions, modal);\n }\n\n // From the help screen go back to the module overview.\n if (e.target.matches(selectors.actions.closeOption)) {\n const carousel = $(modal.getBody()[0].querySelector(selectors.regions.carousel));\n\n // Trigger the transition between 'pages'.\n carousel.carousel('prev');\n carousel.on('slid.bs.carousel', () => {\n const allModules = modal.getBody()[0].querySelector(selectors.regions.modules);\n const caller = allModules.querySelector(selectors.regions.getModuleSelector(e.target.dataset.modname));\n caller.focus();\n });\n }\n\n // The \"clear search\" button is triggered.\n if (e.target.closest(selectors.actions.clearSearch)) {\n // Clear the entered search query in the search bar and hide the search results container.\n const searchInput = modal.getBody()[0].querySelector(selectors.actions.search);\n searchInput.value = \"\";\n searchInput.focus();\n toggleSearchResultsView(modal, mappedModules, searchInput.value);\n }\n };\n\n // We essentially have two types of footer.\n // A fake one that is handled within the template for chooser_help and then all of the stuff for\n // modal.footer. We need to ensure we know exactly what type of footer we are using so we know what we\n // need to manage. The below code handles a real footer going to a mnet carousel item.\n const footerClickListener = async(e) => {\n if (footerData.footer === true) {\n const footerjs = await getPlugin(footerData.customfooterjs);\n await footerjs.footerClickListener(e, footerData, modal);\n }\n };\n\n modal.getBodyPromise()\n\n // The return value of getBodyPromise is a jquery object containing the body NodeElement.\n .then(body => body[0])\n\n // Set up the carousel.\n .then(body => {\n $(body.querySelector(selectors.regions.carousel))\n .carousel({\n interval: false,\n pause: true,\n keyboard: false\n });\n\n return body;\n })\n\n // Add the listener for clicks on the body.\n .then(body => {\n body.addEventListener('click', bodyClickListener);\n return body;\n })\n\n // Add a listener for an input change in the activity chooser's search bar.\n .then(body => {\n const searchInput = body.querySelector(selectors.actions.search);\n // The search input is triggered.\n searchInput.addEventListener('input', debounce(() => {\n // Display the search results.\n toggleSearchResultsView(modal, mappedModules, searchInput.value);\n }, 300));\n return body;\n })\n\n // Register event listeners related to the keyboard navigation controls.\n .then(body => {\n // Get the active chooser options section.\n const activeSectionId = body.querySelector(selectors.elements.activetab).getAttribute(\"href\");\n const sectionChooserOptions = body.querySelector(selectors.regions.getSectionChooserOptions(activeSectionId));\n const firstChooserOption = sectionChooserOptions.querySelector(selectors.regions.chooserOption.container);\n\n toggleFocusableChooserOption(firstChooserOption, true);\n initTabsKeyboardNavigation(body);\n initChooserOptionsKeyboardNavigation(body, mappedModules, sectionChooserOptions, modal);\n\n return body;\n })\n .catch();\n\n modal.getFooterPromise()\n\n // The return value of getBodyPromise is a jquery object containing the body NodeElement.\n .then(footer => footer[0])\n // Add the listener for clicks on the footer.\n .then(footer => {\n footer.addEventListener('click', footerClickListener);\n return footer;\n })\n .catch();\n};\n\n/**\n * Initialise the keyboard navigation controls for the tab list items.\n *\n * @method initTabsKeyboardNavigation\n * @param {HTMLElement} body Our modal that we are working with\n */\nconst initTabsKeyboardNavigation = (body) => {\n // Set up the tab handlers.\n const favTabNav = body.querySelector(selectors.regions.favouriteTabNav);\n const recommendedTabNav = body.querySelector(selectors.regions.recommendedTabNav);\n const defaultTabNav = body.querySelector(selectors.regions.defaultTabNav);\n const activityTabNav = body.querySelector(selectors.regions.activityTabNav);\n const resourceTabNav = body.querySelector(selectors.regions.resourceTabNav);\n const tabNavArray = [favTabNav, recommendedTabNav, defaultTabNav, activityTabNav, resourceTabNav];\n tabNavArray.forEach((element) => {\n return element.addEventListener('keydown', (e) => {\n // The first visible navigation tab link.\n const firstLink = e.target.parentElement.querySelector(selectors.elements.visibletabs);\n // The last navigation tab link. It would always be the default activities tab link.\n const lastLink = e.target.parentElement.lastElementChild;\n\n if (e.keyCode === arrowRight) {\n const nextLink = e.target.nextElementSibling;\n if (nextLink === null) {\n e.target.tabIndex = -1;\n firstLink.tabIndex = 0;\n firstLink.focus();\n } else if (nextLink.classList.contains('d-none')) {\n e.target.tabIndex = -1;\n lastLink.tabIndex = 0;\n lastLink.focus();\n } else {\n e.target.tabIndex = -1;\n nextLink.tabIndex = 0;\n nextLink.focus();\n }\n }\n if (e.keyCode === arrowLeft) {\n const previousLink = e.target.previousElementSibling;\n if (previousLink === null) {\n e.target.tabIndex = -1;\n lastLink.tabIndex = 0;\n lastLink.focus();\n } else if (previousLink.classList.contains('d-none')) {\n e.target.tabIndex = -1;\n firstLink.tabIndex = 0;\n firstLink.focus();\n } else {\n e.target.tabIndex = -1;\n previousLink.tabIndex = 0;\n previousLink.focus();\n }\n }\n if (e.keyCode === home) {\n e.target.tabIndex = -1;\n firstLink.tabIndex = 0;\n firstLink.focus();\n }\n if (e.keyCode === end) {\n e.target.tabIndex = -1;\n lastLink.tabIndex = 0;\n lastLink.focus();\n }\n if (e.keyCode === space) {\n e.preventDefault();\n e.target.click();\n }\n });\n });\n};\n\n/**\n * Initialise the keyboard navigation controls for the chooser options.\n *\n * @method initChooserOptionsKeyboardNavigation\n * @param {HTMLElement} body Our modal that we are working with\n * @param {Map} mappedModules A map of all of the modules we are working with with K: mod_name V: {Object}\n * @param {HTMLElement} chooserOptionsContainer The section that contains the chooser items\n * @param {Object} modal Our created modal for the section\n */\nconst initChooserOptionsKeyboardNavigation = (body, mappedModules, chooserOptionsContainer, modal = null) => {\n const chooserOptions = chooserOptionsContainer.querySelectorAll(selectors.regions.chooserOption.container);\n\n Array.from(chooserOptions).forEach((element) => {\n return element.addEventListener('keydown', (e) => {\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 const module = e.target.closest(selectors.regions.chooserOption.container);\n const moduleName = module.dataset.modname;\n const moduleData = mappedModules.get(moduleName);\n const carousel = $(body.querySelector(selectors.regions.carousel));\n carousel.carousel({\n interval: false,\n pause: true,\n keyboard: false\n });\n\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 = modal.hasFooterContent();\n showModuleHelp(carousel, moduleData, modal);\n }\n }\n\n // Next.\n if (e.keyCode === arrowRight) {\n e.preventDefault();\n const currentOption = e.target.closest(selectors.regions.chooserOption.container);\n const nextOption = currentOption.nextElementSibling;\n const firstOption = chooserOptionsContainer.firstElementChild;\n const toFocusOption = clickErrorHandler(nextOption, firstOption);\n focusChooserOption(toFocusOption, currentOption);\n }\n\n // Previous.\n if (e.keyCode === arrowLeft) {\n e.preventDefault();\n const currentOption = e.target.closest(selectors.regions.chooserOption.container);\n const previousOption = currentOption.previousElementSibling;\n const lastOption = chooserOptionsContainer.lastElementChild;\n const toFocusOption = clickErrorHandler(previousOption, lastOption);\n focusChooserOption(toFocusOption, currentOption);\n }\n\n if (e.keyCode === home) {\n e.preventDefault();\n const currentOption = e.target.closest(selectors.regions.chooserOption.container);\n const firstOption = chooserOptionsContainer.firstElementChild;\n focusChooserOption(firstOption, currentOption);\n }\n\n if (e.keyCode === end) {\n e.preventDefault();\n const currentOption = e.target.closest(selectors.regions.chooserOption.container);\n const lastOption = chooserOptionsContainer.lastElementChild;\n focusChooserOption(lastOption, currentOption);\n }\n });\n });\n};\n\n/**\n * Focus on a chooser option element and remove the previous chooser element from the focus order\n *\n * @method focusChooserOption\n * @param {HTMLElement} currentChooserOption The current chooser option element that we want to focus\n * @param {HTMLElement|null} previousChooserOption The previous focused option element\n */\nconst focusChooserOption = (currentChooserOption, previousChooserOption = null) => {\n if (previousChooserOption !== null) {\n toggleFocusableChooserOption(previousChooserOption, false);\n }\n\n toggleFocusableChooserOption(currentChooserOption, true);\n currentChooserOption.focus();\n};\n\n/**\n * Add or remove a chooser option from the focus order.\n *\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 */\nconst 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 * Small error handling function to make sure the navigated to object exists\n *\n * @method clickErrorHandler\n * @param {HTMLElement} item What we want to check exists\n * @param {HTMLElement} fallback If we dont match anything fallback the focus\n * @return {HTMLElement}\n */\nconst clickErrorHandler = (item, fallback) => {\n if (item !== null) {\n return item;\n } else {\n return fallback;\n }\n};\n\n/**\n * Render the search results in a defined container\n *\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 */\nconst renderSearchResults = async(searchResultsContainer, searchResultsData) => {\n const templateData = {\n 'searchresultsnumber': searchResultsData.length,\n 'searchresults': searchResultsData\n };\n // Build up the html & js ready to place into the help section.\n const {html, js} = await Templates.renderForPromise('core_course/local/activitychooser/search_results', templateData);\n await Templates.replaceNodeContents(searchResultsContainer, html, js);\n};\n\n/**\n * Toggle (display/hide) the search results depending on the value of the search query\n *\n * @method toggleSearchResultsView\n * @param {Object} modal Our created modal for the section\n * @param {Map} mappedModules A map of all of the modules we are working with with K: mod_name V: {Object}\n * @param {String} searchQuery The search query\n */\nconst toggleSearchResultsView = async(modal, mappedModules, searchQuery) => {\n const modalBody = modal.getBody()[0];\n const searchResultsContainer = modalBody.querySelector(selectors.regions.searchResults);\n const chooserContainer = modalBody.querySelector(selectors.regions.chooser);\n const clearSearchButton = modalBody.querySelector(selectors.elements.clearsearch);\n const searchIcon = modalBody.querySelector(selectors.elements.searchicon);\n\n if (searchQuery.length > 0) { // Search query is present.\n const searchResultsData = searchModules(mappedModules, searchQuery);\n await renderSearchResults(searchResultsContainer, searchResultsData);\n const searchResultItemsContainer = searchResultsContainer.querySelector(selectors.regions.searchResultItems);\n const firstSearchResultItem = searchResultItemsContainer.querySelector(selectors.regions.chooserOption.container);\n if (firstSearchResultItem) {\n // Set the first result item to be focusable.\n toggleFocusableChooserOption(firstSearchResultItem, true);\n // Register keyboard events on the created search result items.\n initChooserOptionsKeyboardNavigation(modalBody, mappedModules, searchResultItemsContainer, modal);\n }\n // Display the \"clear\" search button in the activity chooser search bar.\n searchIcon.classList.add('d-none');\n clearSearchButton.classList.remove('d-none');\n // Hide the default chooser options container.\n chooserContainer.setAttribute('hidden', 'hidden');\n // Display the search results container.\n searchResultsContainer.removeAttribute('hidden');\n } else { // Search query is not present.\n // Hide the \"clear\" search button in the activity chooser search bar.\n clearSearchButton.classList.add('d-none');\n searchIcon.classList.remove('d-none');\n // Hide the search results container.\n searchResultsContainer.setAttribute('hidden', 'hidden');\n // Display the default chooser options container.\n chooserContainer.removeAttribute('hidden');\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 {Array} modules List of available modules\n * @param {String} searchTerm The search term to match\n * @return {Array}\n */\nconst searchModules = (modules, searchTerm) => {\n if (searchTerm === '') {\n return modules;\n }\n searchTerm = searchTerm.toLowerCase();\n const searchResults = [];\n modules.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 * Set up our tabindex information across the chooser.\n *\n * @method setupKeyboardAccessibility\n * @param {Promise} modal Our created modal for the section\n * @param {Map} mappedModules A map of all of the built module information\n */\nconst setupKeyboardAccessibility = (modal, mappedModules) => {\n modal.getModal()[0].tabIndex = -1;\n\n modal.getBodyPromise().then(body => {\n $(selectors.elements.tab).on('shown.bs.tab', (e) => {\n const activeSectionId = e.target.getAttribute(\"href\");\n const activeSectionChooserOptions = body[0]\n .querySelector(selectors.regions.getSectionChooserOptions(activeSectionId));\n const firstChooserOption = activeSectionChooserOptions\n .querySelector(selectors.regions.chooserOption.container);\n const prevActiveSectionId = e.relatedTarget.getAttribute(\"href\");\n const prevActiveSectionChooserOptions = body[0]\n .querySelector(selectors.regions.getSectionChooserOptions(prevActiveSectionId));\n\n // Disable the focus of every chooser option in the previous active section.\n disableFocusAllChooserOptions(prevActiveSectionChooserOptions);\n // Enable the focus of the first chooser option in the current active section.\n toggleFocusableChooserOption(firstChooserOption, true);\n initChooserOptionsKeyboardNavigation(body[0], mappedModules, activeSectionChooserOptions, modal);\n });\n return;\n }).catch(Notification.exception);\n};\n\n/**\n * Disable the focus of all chooser options in a specific container (section).\n *\n * @method disableFocusAllChooserOptions\n * @param {HTMLElement} sectionChooserOptions The section that contains the chooser items\n */\nconst disableFocusAllChooserOptions = (sectionChooserOptions) => {\n const allChooserOptions = sectionChooserOptions.querySelectorAll(selectors.regions.chooserOption.container);\n allChooserOptions.forEach((chooserOption) => {\n toggleFocusableChooserOption(chooserOption, false);\n });\n};\n\n/**\n * Display the module chooser.\n *\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 // Make a map so we can quickly fetch a specific module's object for either rendering or searching.\n const mappedModules = new Map();\n sectionModules.forEach((module) => {\n mappedModules.set(module.componentname + '_' + module.link, module);\n });\n\n // Register event listeners.\n modalPromise.then(modal => {\n registerListenerEvents(modal, mappedModules, partialFavourite, footerData);\n\n // We want to focus on the first chooser option element as soon as the modal is opened.\n setupKeyboardAccessibility(modal, mappedModules);\n\n // We want to focus on the action select when the dialog is closed.\n modal.getRoot().on(ModalEvents.hidden, () => {\n modal.destroy();\n });\n\n return modal;\n }).catch();\n};\n"],"file":"dialogue.min.js"} \ No newline at end of file diff --git a/course/amd/build/local/activitychooser/repository.min.js b/course/amd/build/local/activitychooser/repository.min.js index e7e7505e824e7..44e5ee1f5b911 100644 --- a/course/amd/build/local/activitychooser/repository.min.js +++ b/course/amd/build/local/activitychooser/repository.min.js @@ -1,2 +1,2 @@ -define ("core_course/local/activitychooser/repository",["exports","core/ajax"],function(a,b){"use strict";Object.defineProperty(a,"__esModule",{value:!0});a.unfavouriteModule=a.favouriteModule=a.activityModules=void 0;b=function(a){return a&&a.__esModule?a:{default:a}}(b);var c=function(a){return b.default.call([{methodname:"core_course_get_course_content_items",args:{courseid:a}}])[0]};a.activityModules=c;var d=function(a,c){return b.default.call([{methodname:"core_course_add_content_item_to_user_favourites",args:{componentname:a,contentitemid:c}}])[0]};a.favouriteModule=d;var e=function(a,c){return b.default.call([{methodname:"core_course_remove_content_item_from_user_favourites",args:{componentname:a,contentitemid:c}}])[0]};a.unfavouriteModule=e}); +define ("core_course/local/activitychooser/repository",["exports","core/ajax"],function(a,b){"use strict";Object.defineProperty(a,"__esModule",{value:!0});a.fetchFooterData=a.unfavouriteModule=a.favouriteModule=a.activityModules=void 0;b=function(a){return a&&a.__esModule?a:{default:a}}(b);var c=function(a){return b.default.call([{methodname:"core_course_get_course_content_items",args:{courseid:a}}])[0]};a.activityModules=c;var d=function(a,c){return b.default.call([{methodname:"core_course_add_content_item_to_user_favourites",args:{componentname:a,contentitemid:c}}])[0]};a.favouriteModule=d;var e=function(a,c){return b.default.call([{methodname:"core_course_remove_content_item_from_user_favourites",args:{componentname:a,contentitemid:c}}])[0]};a.unfavouriteModule=e;var f=function(a,c){return b.default.call([{methodname:"core_course_get_activity_chooser_footer",args:{courseid:a,sectionid:c}}])[0]};a.fetchFooterData=f}); //# sourceMappingURL=repository.min.js.map diff --git a/course/amd/build/local/activitychooser/repository.min.js.map b/course/amd/build/local/activitychooser/repository.min.js.map index e7bb504895339..0469db64b55f9 100644 --- a/course/amd/build/local/activitychooser/repository.min.js.map +++ b/course/amd/build/local/activitychooser/repository.min.js.map @@ -1 +1 @@ -{"version":3,"sources":["../../../src/local/activitychooser/repository.js"],"names":["activityModules","courseid","ajax","call","methodname","args","favouriteModule","modName","modID","componentname","contentitemid","unfavouriteModule"],"mappings":"0NAsBA,uDASO,GAAMA,CAAAA,CAAe,CAAG,SAACC,CAAD,CAAc,CAOzC,MAAOC,WAAKC,IAAL,CAAU,CAND,CACZC,UAAU,CAAE,sCADA,CAEZC,IAAI,CAAE,CACFJ,QAAQ,CAAEA,CADR,CAFM,CAMC,CAAV,EAAqB,CAArB,CACV,CARM,C,oBAmBA,GAAMK,CAAAA,CAAe,CAAG,SAACC,CAAD,CAAUC,CAAV,CAAoB,CAQ/C,MAAON,WAAKC,IAAL,CAAU,CAPD,CACZC,UAAU,CAAE,iDADA,CAEZC,IAAI,CAAE,CACFI,aAAa,CAAEF,CADb,CAEFG,aAAa,CAAEF,CAFb,CAFM,CAOC,CAAV,EAAqB,CAArB,CACV,CATM,C,oBAoBA,GAAMG,CAAAA,CAAiB,CAAG,SAACJ,CAAD,CAAUC,CAAV,CAAoB,CAQjD,MAAON,WAAKC,IAAL,CAAU,CAPD,CACZC,UAAU,CAAE,sDADA,CAEZC,IAAI,CAAE,CACFI,aAAa,CAAEF,CADb,CAEFG,aAAa,CAAEF,CAFb,CAFM,CAOC,CAAV,EAAqB,CAArB,CACV,CATM,C","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 *\n * @module core_course/repository\n * @package core_course\n * @copyright 2019 Mathew May \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\nimport ajax from 'core/ajax';\n\n/**\n * Fetch all the information on modules we'll need in the activity chooser.\n *\n * @method activityModules\n * @param {Number} courseid What course to fetch the modules for\n * @return {object} jQuery promise\n */\nexport const activityModules = (courseid) => {\n const request = {\n methodname: 'core_course_get_course_content_items',\n args: {\n courseid: courseid,\n },\n };\n return ajax.call([request])[0];\n};\n\n/**\n * Given a module name, module ID & the current course we want to specify that the module\n * is a users' favourite.\n *\n * @method favouriteModule\n * @param {String} modName Frankenstyle name of the component to add favourite\n * @param {int} modID ID of the module. Mainly for LTI cases where they have same / similar names\n * @return {object} jQuery promise\n */\nexport const favouriteModule = (modName, modID) => {\n const request = {\n methodname: 'core_course_add_content_item_to_user_favourites',\n args: {\n componentname: modName,\n contentitemid: modID,\n },\n };\n return ajax.call([request])[0];\n};\n\n/**\n * Given a module name, module ID & the current course we want to specify that the module\n * is no longer a users' favourite.\n *\n * @method unfavouriteModule\n * @param {String} modName Frankenstyle name of the component to add favourite\n * @param {int} modID ID of the module. Mainly for LTI cases where they have same / similar names\n * @return {object} jQuery promise\n */\nexport const unfavouriteModule = (modName, modID) => {\n const request = {\n methodname: 'core_course_remove_content_item_from_user_favourites',\n args: {\n componentname: modName,\n contentitemid: modID,\n },\n };\n return ajax.call([request])[0];\n};\n"],"file":"repository.min.js"} \ No newline at end of file +{"version":3,"sources":["../../../src/local/activitychooser/repository.js"],"names":["activityModules","courseid","ajax","call","methodname","args","favouriteModule","modName","modID","componentname","contentitemid","unfavouriteModule","fetchFooterData","sectionid"],"mappings":"4OAsBA,uDASO,GAAMA,CAAAA,CAAe,CAAG,SAACC,CAAD,CAAc,CAOzC,MAAOC,WAAKC,IAAL,CAAU,CAND,CACZC,UAAU,CAAE,sCADA,CAEZC,IAAI,CAAE,CACFJ,QAAQ,CAAEA,CADR,CAFM,CAMC,CAAV,EAAqB,CAArB,CACV,CARM,C,oBAmBA,GAAMK,CAAAA,CAAe,CAAG,SAACC,CAAD,CAAUC,CAAV,CAAoB,CAQ/C,MAAON,WAAKC,IAAL,CAAU,CAPD,CACZC,UAAU,CAAE,iDADA,CAEZC,IAAI,CAAE,CACFI,aAAa,CAAEF,CADb,CAEFG,aAAa,CAAEF,CAFb,CAFM,CAOC,CAAV,EAAqB,CAArB,CACV,CATM,C,oBAoBA,GAAMG,CAAAA,CAAiB,CAAG,SAACJ,CAAD,CAAUC,CAAV,CAAoB,CAQjD,MAAON,WAAKC,IAAL,CAAU,CAPD,CACZC,UAAU,CAAE,sDADA,CAEZC,IAAI,CAAE,CACFI,aAAa,CAAEF,CADb,CAEFG,aAAa,CAAEF,CAFb,CAFM,CAOC,CAAV,EAAqB,CAArB,CACV,CATM,C,sBAmBA,GAAMI,CAAAA,CAAe,CAAG,SAACX,CAAD,CAAWY,CAAX,CAAyB,CAQpD,MAAOX,WAAKC,IAAL,CAAU,CAPD,CACZC,UAAU,CAAE,yCADA,CAEZC,IAAI,CAAE,CACFJ,QAAQ,CAAEA,CADR,CAEFY,SAAS,CAAEA,CAFT,CAFM,CAOC,CAAV,EAAqB,CAArB,CACV,CATM,C","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 *\n * @module core_course/repository\n * @package core_course\n * @copyright 2019 Mathew May \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\nimport ajax from 'core/ajax';\n\n/**\n * Fetch all the information on modules we'll need in the activity chooser.\n *\n * @method activityModules\n * @param {Number} courseid What course to fetch the modules for\n * @return {object} jQuery promise\n */\nexport const activityModules = (courseid) => {\n const request = {\n methodname: 'core_course_get_course_content_items',\n args: {\n courseid: courseid,\n },\n };\n return ajax.call([request])[0];\n};\n\n/**\n * Given a module name, module ID & the current course we want to specify that the module\n * is a users' favourite.\n *\n * @method favouriteModule\n * @param {String} modName Frankenstyle name of the component to add favourite\n * @param {int} modID ID of the module. Mainly for LTI cases where they have same / similar names\n * @return {object} jQuery promise\n */\nexport const favouriteModule = (modName, modID) => {\n const request = {\n methodname: 'core_course_add_content_item_to_user_favourites',\n args: {\n componentname: modName,\n contentitemid: modID,\n },\n };\n return ajax.call([request])[0];\n};\n\n/**\n * Given a module name, module ID & the current course we want to specify that the module\n * is no longer a users' favourite.\n *\n * @method unfavouriteModule\n * @param {String} modName Frankenstyle name of the component to add favourite\n * @param {int} modID ID of the module. Mainly for LTI cases where they have same / similar names\n * @return {object} jQuery promise\n */\nexport const unfavouriteModule = (modName, modID) => {\n const request = {\n methodname: 'core_course_remove_content_item_from_user_favourites',\n args: {\n componentname: modName,\n contentitemid: modID,\n },\n };\n return ajax.call([request])[0];\n};\n\n/**\n * Fetch all the information on modules we'll need in the activity chooser.\n *\n * @method fetchFooterData\n * @param {Number} courseid What course to fetch the data for\n * @param {Number} sectionid What section to fetch the data for\n * @return {object} jQuery promise\n */\nexport const fetchFooterData = (courseid, sectionid) => {\n const request = {\n methodname: 'core_course_get_activity_chooser_footer',\n args: {\n courseid: courseid,\n sectionid: sectionid,\n },\n };\n return ajax.call([request])[0];\n};\n"],"file":"repository.min.js"} \ No newline at end of file diff --git a/course/amd/src/activitychooser.js b/course/amd/src/activitychooser.js index 3704632d84df9..176ed9901e351 100644 --- a/course/amd/src/activitychooser.js +++ b/course/amd/src/activitychooser.js @@ -85,6 +85,20 @@ const registerListenerEvents = (courseId, chooserConfig) => { }; })(); + const fetchFooterData = (() => { + let footerInnerPromise = null; + + return (sectionId) => { + if (!footerInnerPromise) { + footerInnerPromise = new Promise((resolve) => { + resolve(Repository.fetchFooterData(courseId, sectionId)); + }); + } + + return footerInnerPromise; + }; + })(); + CustomEvents.define(document, events); // Display module chooser event listeners. @@ -115,7 +129,8 @@ const registerListenerEvents = (courseId, chooserConfig) => { bodyPromiseResolver = resolve; }); - const sectionModal = buildModal(bodyPromise); + const footerData = await fetchFooterData(caller.dataset.sectionid); + const sectionModal = buildModal(bodyPromise, footerData); // Now we have a modal we should start fetching data. const data = await fetchModuleData(); @@ -127,6 +142,7 @@ const registerListenerEvents = (courseId, chooserConfig) => { sectionModal, builtModuleData, partiallyAppliedFavouriteManager(data, caller.dataset.sectionid), + footerData, ); bodyPromiseResolver(await Templates.render( @@ -221,13 +237,15 @@ const templateDataBuilder = (data, chooserConfig) => { * * @method buildModal * @param {Promise} bodyPromise + * @param {String|Boolean} footer Either a footer to add or nothing * @return {Object} The modal ready to display immediately and render body in later. */ -const buildModal = bodyPromise => { +const buildModal = (bodyPromise, footer) => { return ModalFactory.create({ type: ModalFactory.types.DEFAULT, title: getString('addresourceoractivity'), body: bodyPromise, + footer: footer.customfootertemplate, large: true, templateContext: { classes: 'modchooser' diff --git a/course/amd/src/local/activitychooser/dialogue.js b/course/amd/src/local/activitychooser/dialogue.js index 3cbc5e865836c..cd045a3de0d7f 100644 --- a/course/amd/src/local/activitychooser/dialogue.js +++ b/course/amd/src/local/activitychooser/dialogue.js @@ -31,6 +31,7 @@ import {addIconToContainer} from 'core/loadingicon'; import * as Repository from 'core_course/local/activitychooser/repository'; import Notification from 'core/notification'; import {debounce} from 'core/utils'; +const getPlugin = pluginName => import(pluginName); /** * Given an event from the main module 'page' navigate to it's help section via a carousel. @@ -38,8 +39,13 @@ import {debounce} from 'core/utils'; * @method showModuleHelp * @param {jQuery} carousel Our initialized carousel to manipulate * @param {Object} moduleData Data of the module to carousel to + * @param {jQuery} modal We need to figure out if the current modal has a footer. */ -const showModuleHelp = (carousel, moduleData) => { +const showModuleHelp = (carousel, moduleData, modal = null) => { + // If we have a real footer then we need to change temporarily. + if (modal !== null && moduleData.showFooter === true) { + modal.setFooter(Templates.render('core_course/local/activitychooser/footer_partial', moduleData)); + } const help = carousel.find(selectors.regions.help)[0]; help.innerHTML = ''; help.classList.add('m-auto'); @@ -107,8 +113,9 @@ const manageFavouriteState = async(modalBody, caller, partialFavourite) => { * @param {Promise} modal Our modal that we are working with * @param {Map} mappedModules A map of all of the modules we are working with with K: mod_name V: {Object} * @param {Function} partialFavourite Partially applied function we need to manage favourite status + * @param {Object} footerData Our base footer object. */ -const registerListenerEvents = (modal, mappedModules, partialFavourite) => { +const registerListenerEvents = (modal, mappedModules, partialFavourite, footerData) => { const bodyClickListener = async(e) => { if (e.target.closest(selectors.actions.optionActions.showSummary)) { const carousel = $(modal.getBody()[0].querySelector(selectors.regions.carousel)); @@ -116,7 +123,9 @@ const registerListenerEvents = (modal, mappedModules, partialFavourite) => { const module = e.target.closest(selectors.regions.chooserOption.container); const moduleName = module.dataset.modname; const moduleData = mappedModules.get(moduleName); - showModuleHelp(carousel, moduleData); + // We need to know if the overall modal has a footer so we know when to show a real / vs fake footer. + moduleData.showFooter = modal.hasFooterContent(); + showModuleHelp(carousel, moduleData, modal); } if (e.target.closest(selectors.actions.optionActions.manageFavourite)) { @@ -128,7 +137,7 @@ const registerListenerEvents = (modal, mappedModules, partialFavourite) => { const firstChooserOption = sectionChooserOptions .querySelector(selectors.regions.chooserOption.container); toggleFocusableChooserOption(firstChooserOption, true); - initChooserOptionsKeyboardNavigation(modal.getBody()[0], mappedModules, sectionChooserOptions); + initChooserOptionsKeyboardNavigation(modal.getBody()[0], mappedModules, sectionChooserOptions, modal); } // From the help screen go back to the module overview. @@ -150,7 +159,18 @@ const registerListenerEvents = (modal, mappedModules, partialFavourite) => { const searchInput = modal.getBody()[0].querySelector(selectors.actions.search); searchInput.value = ""; searchInput.focus(); - toggleSearchResultsView(modal.getBody()[0], mappedModules, searchInput.value); + toggleSearchResultsView(modal, mappedModules, searchInput.value); + } + }; + + // We essentially have two types of footer. + // A fake one that is handled within the template for chooser_help and then all of the stuff for + // modal.footer. We need to ensure we know exactly what type of footer we are using so we know what we + // need to manage. The below code handles a real footer going to a mnet carousel item. + const footerClickListener = async(e) => { + if (footerData.footer === true) { + const footerjs = await getPlugin(footerData.customfooterjs); + await footerjs.footerClickListener(e, footerData, modal); } }; @@ -183,7 +203,7 @@ const registerListenerEvents = (modal, mappedModules, partialFavourite) => { // The search input is triggered. searchInput.addEventListener('input', debounce(() => { // Display the search results. - toggleSearchResultsView(body, mappedModules, searchInput.value); + toggleSearchResultsView(modal, mappedModules, searchInput.value); }, 300)); return body; }) @@ -197,12 +217,22 @@ const registerListenerEvents = (modal, mappedModules, partialFavourite) => { toggleFocusableChooserOption(firstChooserOption, true); initTabsKeyboardNavigation(body); - initChooserOptionsKeyboardNavigation(body, mappedModules, sectionChooserOptions); + initChooserOptionsKeyboardNavigation(body, mappedModules, sectionChooserOptions, modal); return body; }) .catch(); + modal.getFooterPromise() + + // The return value of getBodyPromise is a jquery object containing the body NodeElement. + .then(footer => footer[0]) + // Add the listener for clicks on the footer. + .then(footer => { + footer.addEventListener('click', footerClickListener); + return footer; + }) + .catch(); }; /** @@ -283,8 +313,9 @@ const initTabsKeyboardNavigation = (body) => { * @param {HTMLElement} body Our modal that we are working with * @param {Map} mappedModules A map of all of the modules we are working with with K: mod_name V: {Object} * @param {HTMLElement} chooserOptionsContainer The section that contains the chooser items + * @param {Object} modal Our created modal for the section */ -const initChooserOptionsKeyboardNavigation = (body, mappedModules, chooserOptionsContainer) => { +const initChooserOptionsKeyboardNavigation = (body, mappedModules, chooserOptionsContainer, modal = null) => { const chooserOptions = chooserOptionsContainer.querySelectorAll(selectors.regions.chooserOption.container); Array.from(chooserOptions).forEach((element) => { @@ -303,7 +334,10 @@ const initChooserOptionsKeyboardNavigation = (body, mappedModules, chooserOption pause: true, keyboard: false }); - showModuleHelp(carousel, moduleData); + + // We need to know if the overall modal has a footer so we know when to show a real / vs fake footer. + moduleData.showFooter = modal.hasFooterContent(); + showModuleHelp(carousel, moduleData, modal); } } @@ -424,11 +458,12 @@ const renderSearchResults = async(searchResultsContainer, searchResultsData) => * Toggle (display/hide) the search results depending on the value of the search query * * @method toggleSearchResultsView - * @param {HTMLElement} modalBody The body of the created modal for the section + * @param {Object} modal Our created modal for the section * @param {Map} mappedModules A map of all of the modules we are working with with K: mod_name V: {Object} * @param {String} searchQuery The search query */ -const toggleSearchResultsView = async(modalBody, mappedModules, searchQuery) => { +const toggleSearchResultsView = async(modal, mappedModules, searchQuery) => { + const modalBody = modal.getBody()[0]; const searchResultsContainer = modalBody.querySelector(selectors.regions.searchResults); const chooserContainer = modalBody.querySelector(selectors.regions.chooser); const clearSearchButton = modalBody.querySelector(selectors.elements.clearsearch); @@ -443,7 +478,7 @@ const toggleSearchResultsView = async(modalBody, mappedModules, searchQuery) => // Set the first result item to be focusable. toggleFocusableChooserOption(firstSearchResultItem, true); // Register keyboard events on the created search result items. - initChooserOptionsKeyboardNavigation(modalBody, mappedModules, searchResultItemsContainer); + initChooserOptionsKeyboardNavigation(modalBody, mappedModules, searchResultItemsContainer, modal); } // Display the "clear" search button in the activity chooser search bar. searchIcon.classList.add('d-none'); @@ -513,7 +548,7 @@ const setupKeyboardAccessibility = (modal, mappedModules) => { disableFocusAllChooserOptions(prevActiveSectionChooserOptions); // Enable the focus of the first chooser option in the current active section. toggleFocusableChooserOption(firstChooserOption, true); - initChooserOptionsKeyboardNavigation(body[0], mappedModules, activeSectionChooserOptions); + initChooserOptionsKeyboardNavigation(body[0], mappedModules, activeSectionChooserOptions, modal); }); return; }).catch(Notification.exception); @@ -539,8 +574,9 @@ const disableFocusAllChooserOptions = (sectionChooserOptions) => { * @param {Promise} modalPromise Our created modal for the section * @param {Array} sectionModules An array of all of the built module information * @param {Function} partialFavourite Partially applied function we need to manage favourite status + * @param {Object} footerData Our base footer object. */ -export const displayChooser = (modalPromise, sectionModules, partialFavourite) => { +export const displayChooser = (modalPromise, sectionModules, partialFavourite, footerData) => { // Make a map so we can quickly fetch a specific module's object for either rendering or searching. const mappedModules = new Map(); sectionModules.forEach((module) => { @@ -549,7 +585,7 @@ export const displayChooser = (modalPromise, sectionModules, partialFavourite) = // Register event listeners. modalPromise.then(modal => { - registerListenerEvents(modal, mappedModules, partialFavourite); + registerListenerEvents(modal, mappedModules, partialFavourite, footerData); // We want to focus on the first chooser option element as soon as the modal is opened. setupKeyboardAccessibility(modal, mappedModules); diff --git a/course/amd/src/local/activitychooser/repository.js b/course/amd/src/local/activitychooser/repository.js index 52c544440a019..43de8c196813a 100644 --- a/course/amd/src/local/activitychooser/repository.js +++ b/course/amd/src/local/activitychooser/repository.js @@ -78,3 +78,22 @@ export const unfavouriteModule = (modName, modID) => { }; return ajax.call([request])[0]; }; + +/** + * Fetch all the information on modules we'll need in the activity chooser. + * + * @method fetchFooterData + * @param {Number} courseid What course to fetch the data for + * @param {Number} sectionid What section to fetch the data for + * @return {object} jQuery promise + */ +export const fetchFooterData = (courseid, sectionid) => { + const request = { + methodname: 'core_course_get_activity_chooser_footer', + args: { + courseid: courseid, + sectionid: sectionid, + }, + }; + return ajax.call([request])[0]; +}; diff --git a/course/classes/local/entity/activity_chooser_footer.php b/course/classes/local/entity/activity_chooser_footer.php new file mode 100644 index 0000000000000..2524d24182f1f --- /dev/null +++ b/course/classes/local/entity/activity_chooser_footer.php @@ -0,0 +1,86 @@ +. + +/** + * Activity Chooser footer data class. + * + * @package core + * @subpackage course + * @copyright 2020 Mathew May + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +namespace core_course\local\entity; + +/** + * A class to represent the Activity Chooser footer data. + * + * @package core + * @subpackage course + * @copyright 2020 Mathew May + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class activity_chooser_footer { + + /** @var string $footerjspath The path to the plugin JS file to dynamically import later. */ + protected $footerjspath; + + /** @var string $footertemplate The rendered template for the footer. */ + protected $footertemplate; + + /** @var string $carouseltemplate The rendered template for the footer. */ + protected $carouseltemplate; + + /** + * Constructor method. + * + * @param string $footerjspath JS file to dynamically import later. + * @param string $footertemplate Footer template that has been rendered. + * @param string|null $carouseltemplate Carousel template that may have been rendered. + */ + public function __construct(string $footerjspath, string $footertemplate, ?string $carouseltemplate = '') { + $this->footerjspath = $footerjspath; + $this->footertemplate = $footertemplate; + $this->carouseltemplate = $carouseltemplate; + } + + /** + * Get the footer JS file path for this plugin. + * + * @return string The JS file to call functions from. + */ + public function get_footer_js_file(): string { + return $this->footerjspath; + } + + /** + * Get the footer rendered template for this plugin. + * + * @return string The template that has been rendered for the chooser footer. + */ + public function get_footer_template(): string { + return $this->footertemplate; + } + + /** + * Get the carousel rendered template for this plugin. + * + * @return string The template that has been rendered for the chooser carousel. + */ + public function get_carousel_template(): string { + return $this->carouseltemplate; + } +} diff --git a/course/externallib.php b/course/externallib.php index 5ddf89e5e3087..fe3e80b290d06 100644 --- a/course/externallib.php +++ b/course/externallib.php @@ -4336,4 +4336,74 @@ public static function toggle_activity_recommendation_returns() { ] ); } + + /** + * Returns description of method parameters + * + * @return external_function_parameters + */ + public static function get_activity_chooser_footer_parameters() { + return new external_function_parameters([ + 'courseid' => new external_value(PARAM_INT, 'ID of the course', VALUE_REQUIRED), + 'sectionid' => new external_value(PARAM_INT, 'ID of the section', VALUE_REQUIRED), + ]); + } + + /** + * Given a course ID we need to build up a footre for the chooser. + * + * @param int $courseid The course we want to fetch the modules for + * @param int $sectionid The section we want to fetch the modules for + * @return array + */ + public static function get_activity_chooser_footer(int $courseid, int $sectionid) { + [ + 'courseid' => $courseid, + 'sectionid' => $sectionid, + ] = self::validate_parameters(self::get_activity_chooser_footer_parameters(), [ + 'courseid' => $courseid, + 'sectionid' => $sectionid, + ]); + + $coursecontext = context_course::instance($courseid); + self::validate_context($coursecontext); + + $pluginswithfunction = get_plugins_with_function('custom_chooser_footer', 'lib.php'); + if ($pluginswithfunction) { + foreach ($pluginswithfunction as $plugintype => $plugins) { + foreach ($plugins as $pluginfunction) { + $footerdata = $pluginfunction($courseid, $sectionid); + break; // Only a single plugin can modify the footer. + } + break; // Only a single plugin can modify the footer. + } + return [ + 'footer' => true, + 'customfooterjs' => $footerdata->get_footer_js_file(), + 'customfootertemplate' => $footerdata->get_footer_template(), + 'customcarouseltemplate' => $footerdata->get_carousel_template(), + ]; + } else { + return [ + 'footer' => false, + ]; + } + } + + /** + * Returns description of method result value + * + * @return external_description + */ + public static function get_activity_chooser_footer_returns() { + return new external_single_structure( + [ + 'footer' => new external_value(PARAM_BOOL, 'Is a footer being return by this request?', VALUE_REQUIRED), + 'customfooterjs' => new external_value(PARAM_RAW, 'The path to the plugin JS file', VALUE_OPTIONAL), + 'customfootertemplate' => new external_value(PARAM_RAW, 'The prerendered footer', VALUE_OPTIONAL), + 'customcarouseltemplate' => new external_value(PARAM_RAW, 'Either "" or the prerendered carousel page', + VALUE_OPTIONAL), + ] + ); + } } diff --git a/course/templates/activitychooser.mustache b/course/templates/activitychooser.mustache index 4511df99bd4f4..bf6ba5ec43cf6 100644 --- a/course/templates/activitychooser.mustache +++ b/course/templates/activitychooser.mustache @@ -147,5 +147,7 @@ + + diff --git a/course/templates/local/activitychooser/footer_partial.mustache b/course/templates/local/activitychooser/footer_partial.mustache new file mode 100644 index 0000000000000..898d119a8a19b --- /dev/null +++ b/course/templates/local/activitychooser/footer_partial.mustache @@ -0,0 +1,33 @@ +{{! + This file is part of Moodle - http://moodle.org/ + + Moodle is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Moodle is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Moodle. If not, see . +}} +{{! + @template core_course/local/activitychooser/footer_partial + + Chooser favourite template partial. + + Example context (json): + { + } +}} +
+ + + {{#str}} add {{/str}} + +
diff --git a/course/templates/local/activitychooser/help.mustache b/course/templates/local/activitychooser/help.mustache index 68798bcc563a3..9ddacd4449636 100644 --- a/course/templates/local/activitychooser/help.mustache +++ b/course/templates/local/activitychooser/help.mustache @@ -44,12 +44,9 @@ {{{help}}} -
- - - {{#str}} add {{/str}} - -
+ {{^showFooter}} +
+ {{>core_course/local/activitychooser/footer_partial}} +
+ {{/showFooter}} diff --git a/course/tests/behat/activity_chooser.feature b/course/tests/behat/activity_chooser.feature index b71e1e5ac3d01..3748d0fe3fa17 100644 --- a/course/tests/behat/activity_chooser.feature +++ b/course/tests/behat/activity_chooser.feature @@ -14,6 +14,8 @@ Feature: Display and choose from the available activities in course And the following "course enrolments" exist: | user | course | role | | teacher | C | editingteacher | + And the following config values are set as admin: + | enablemoodlenet | 0 | tool_moodlenet | And I log in as "teacher" And I am on "Course" course homepage with editing mode on diff --git a/lang/en/admin.php b/lang/en/admin.php index d47cd39a47f4d..b21142e5fbc57 100644 --- a/lang/en/admin.php +++ b/lang/en/admin.php @@ -539,8 +539,6 @@ $string['enablegravatar'] = 'Enable Gravatar'; $string['enablegravatar_help'] = 'When enabled Moodle will attempt to fetch a user profile picture from Gravatar if the user has not uploaded an image.'; $string['enablemobilewebservice'] = 'Enable web services for mobile devices'; -$string['enablemoodlenet'] = 'Enable integration with MoodleNet instances'; -$string['enablemoodlenet_desc'] = 'If enabled, and provided the MoodleNet plugin is installed, users can import content from MoodleNet into this site.'; $string['enablerecordcache'] = 'Enable record cache'; $string['enablerssfeeds'] = 'Enable RSS feeds'; $string['enablesearchareas'] = 'Enable search areas'; diff --git a/lang/en/user.php b/lang/en/user.php index 5eee2cb2b4305..ee6a9a81a9a8a 100644 --- a/lang/en/user.php +++ b/lang/en/user.php @@ -33,6 +33,7 @@ $string['filtersetmatchdescription'] = 'How multiple filters should be combined'; $string['match'] = 'Match'; $string['matchofthefollowing'] = 'of the following:'; +$string['moodlenetprofile'] = 'MoodleNet profile'; $string['placeholdertypeorselect'] = 'Type or select...'; $string['placeholdertype'] = 'Type...'; $string['privacy:courserequestpath'] = 'Requested courses'; @@ -87,6 +88,7 @@ $string['privacy:metadata:middlename'] = 'The middle name of the user'; $string['privacy:metadata:mnethostid'] = 'An identifier for the MNet host if used'; $string['privacy:metadata:model'] = 'The device name, occam or iPhone etc..'; +$string['privacy:metadata:moodlenetprofile'] = 'The MoodleNet profile for the user'; $string['privacy:metadata:msn'] = 'The MSN identifier of the user'; $string['privacy:metadata:my_pages'] = 'User pages - dashboard and profile. This table does not contain personal data and only used to link dashboard blocks to users'; $string['privacy:metadata:my_pages:name'] = 'Page name'; diff --git a/lib/db/install.xml b/lib/db/install.xml index be3ac0058bad2..8f91417fa6be4 100644 --- a/lib/db/install.xml +++ b/lib/db/install.xml @@ -872,6 +872,7 @@ + diff --git a/lib/db/services.php b/lib/db/services.php index 1a9ae7fa9e1c1..b900c1ae3295a 100644 --- a/lib/db/services.php +++ b/lib/db/services.php @@ -671,6 +671,14 @@ 'type' => 'read', 'ajax' => true, ), + 'core_course_get_activity_chooser_footer' => array( + 'classname' => 'core_course_external', + 'methodname' => 'get_activity_chooser_footer', + 'classpath' => 'course/externallib.php', + 'description' => 'Fetch the data for the activity chooser footer.', + 'type' => 'read', + 'ajax' => true, + ), 'core_course_toggle_activity_recommendation' => array( 'classname' => 'core_course_external', 'methodname' => 'toggle_activity_recommendation', diff --git a/lib/db/upgrade.php b/lib/db/upgrade.php index d0d822d82b6fa..b6348d1cca448 100644 --- a/lib/db/upgrade.php +++ b/lib/db/upgrade.php @@ -2427,5 +2427,19 @@ function xmldb_main_upgrade($oldversion) { upgrade_main_savepoint(true, 2020052200.01); } + if ($oldversion < 2020060500.01) { + // Define field moodlenetprofile to be added to user. + $table = new xmldb_table('user'); + $field = new xmldb_field('moodlenetprofile', XMLDB_TYPE_CHAR, '255', null, null, null, null, 'alternatename'); + + // Conditionally launch add field moodlenetprofile. + if (!$dbman->field_exists($table, $field)) { + $dbman->add_field($table, $field); + } + + // Main savepoint reached. + upgrade_main_savepoint(true, 2020060500.01); + } + return true; } diff --git a/lib/myprofilelib.php b/lib/myprofilelib.php index 87c792dbcd160..730ef6e352cf9 100644 --- a/lib/myprofilelib.php +++ b/lib/myprofilelib.php @@ -165,6 +165,12 @@ function core_myprofile_navigation(core_user\output\myprofile\tree $tree, $user, $tree->add_node($node); } + if (!isset($hiddenfields['moodlenetprofile']) && $user->moodlenetprofile) { + $node = new core_user\output\myprofile\node('contact', 'moodlenetprofile', get_string('moodlenetprofile', 'user'), null, + null, $user->moodlenetprofile); + $tree->add_node($node); + } + if (!isset($hiddenfields['country']) && $user->country) { $node = new core_user\output\myprofile\node('contact', 'country', get_string('country'), null, null, get_string($user->country, 'countries')); diff --git a/mod/url/lib.php b/mod/url/lib.php index 23d11a0f4a795..815360d3ae224 100644 --- a/mod/url/lib.php +++ b/mod/url/lib.php @@ -318,6 +318,7 @@ function url_dndupload_handle($uploadinfo) { $data->introformat = FORMAT_HTML; $data->externalurl = clean_param($uploadinfo->content, PARAM_URL); $data->timemodified = time(); + $data->coursemodule = $uploadinfo->coursemodule; // Set the display options to the site defaults. $config = get_config('url'); diff --git a/pix/MoodleNet.png b/pix/MoodleNet.png new file mode 100644 index 0000000000000..872756bda88d1 Binary files /dev/null and b/pix/MoodleNet.png differ diff --git a/pix/MoodleNet.svg b/pix/MoodleNet.svg new file mode 100644 index 0000000000000..8f191bf0ea977 --- /dev/null +++ b/pix/MoodleNet.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/theme/boost/scss/moodle/core.scss b/theme/boost/scss/moodle/core.scss index 2bfa166876b95..411f762413e46 100644 --- a/theme/boost/scss/moodle/core.scss +++ b/theme/boost/scss/moodle/core.scss @@ -1637,6 +1637,17 @@ body#page-lib-editor-tinymce-plugins-moodlemedia-preview { } } +.modchooser .modal-footer { + height: 70px; + .moodlenet-logo { + .icon { + height: 2.5rem; + width: 6rem; + margin-bottom: .6rem; + } + } +} + .modchoosercontainer.noscroll { overflow-y: hidden; } @@ -1684,7 +1695,7 @@ body#page-lib-editor-tinymce-plugins-moodlemedia-preview { background-color: $white; overflow-x: hidden; overflow-y: auto; - min-height: 640px; + height: 640px; .content { overflow-y: auto; @@ -2447,6 +2458,10 @@ body.h5p-embed { overflow-wrap: break-word !important; /* stylelint-disable-line declaration-no-important */ } +.z-index-0 { + z-index: 0 !important; /* stylelint-disable-line declaration-no-important */ +} + .z-index-1 { z-index: 1 !important; /* stylelint-disable-line declaration-no-important */ } @@ -2653,4 +2668,4 @@ $picker-emojis-per-row: 7 !default; position: relative; z-index: inherit; } -} \ No newline at end of file +} diff --git a/theme/boost/style/moodle.css b/theme/boost/style/moodle.css index 5dda75a93fa62..b2d48fe4e6643 100644 --- a/theme/boost/style/moodle.css +++ b/theme/boost/style/moodle.css @@ -11001,6 +11001,13 @@ body#page-lib-editor-tinymce-plugins-moodlemedia-preview { .modchooser .modal-body .carousel-item .loading-icon .icon { margin: 1em auto; } +.modchooser .modal-footer { + height: 70px; } + .modchooser .modal-footer .moodlenet-logo .icon { + height: 2.5rem; + width: 6rem; + margin-bottom: .6rem; } + .modchoosercontainer.noscroll { overflow-y: hidden; } @@ -11038,7 +11045,7 @@ body#page-lib-editor-tinymce-plugins-moodlemedia-preview { background-color: #fff; overflow-x: hidden; overflow-y: auto; - min-height: 640px; } + height: 640px; } .modchooser .modal-body .optionsummary .content { overflow-y: auto; } .modchooser .modal-body .optionsummary .content .heading .icon { @@ -11631,6 +11638,10 @@ body.h5p-embed .h5pmessages { overflow-wrap: break-word !important; /* stylelint-disable-line declaration-no-important */ } +.z-index-0 { + z-index: 0 !important; + /* stylelint-disable-line declaration-no-important */ } + .z-index-1 { z-index: 1 !important; /* stylelint-disable-line declaration-no-important */ } diff --git a/theme/classic/style/moodle.css b/theme/classic/style/moodle.css index ab07c5f1a5dcc..e2c098d5e91ae 100644 --- a/theme/classic/style/moodle.css +++ b/theme/classic/style/moodle.css @@ -11208,6 +11208,13 @@ body#page-lib-editor-tinymce-plugins-moodlemedia-preview { .modchooser .modal-body .carousel-item .loading-icon .icon { margin: 1em auto; } +.modchooser .modal-footer { + height: 70px; } + .modchooser .modal-footer .moodlenet-logo .icon { + height: 2.5rem; + width: 6rem; + margin-bottom: .6rem; } + .modchoosercontainer.noscroll { overflow-y: hidden; } @@ -11245,7 +11252,7 @@ body#page-lib-editor-tinymce-plugins-moodlemedia-preview { background-color: #fff; overflow-x: hidden; overflow-y: auto; - min-height: 640px; } + height: 640px; } .modchooser .modal-body .optionsummary .content { overflow-y: auto; } .modchooser .modal-body .optionsummary .content .heading .icon { @@ -11842,6 +11849,10 @@ body.h5p-embed .h5pmessages { overflow-wrap: break-word !important; /* stylelint-disable-line declaration-no-important */ } +.z-index-0 { + z-index: 0 !important; + /* stylelint-disable-line declaration-no-important */ } + .z-index-1 { z-index: 1 !important; /* stylelint-disable-line declaration-no-important */ } diff --git a/user/classes/privacy/provider.php b/user/classes/privacy/provider.php index 839026e044ad4..74b28a8bc3aef 100644 --- a/user/classes/privacy/provider.php +++ b/user/classes/privacy/provider.php @@ -104,7 +104,8 @@ public static function get_metadata(collection $collection) : collection { 'lastnamephonetic' => 'privacy:metadata:lastnamephonetic', 'firstnamephonetic' => 'privacy:metadata:firstnamephonetic', 'middlename' => 'privacy:metadata:middlename', - 'alternatename' => 'privacy:metadata:alternatename' + 'alternatename' => 'privacy:metadata:alternatename', + 'moodlenetprofile' => 'privacy:metadata:moodlenetprofile' ]; $passwordhistory = [ diff --git a/user/editlib.php b/user/editlib.php index c8d2f68ae0a3c..b3ec83811a992 100644 --- a/user/editlib.php +++ b/user/editlib.php @@ -300,6 +300,9 @@ function useredit_shared_definition(&$mform, $editoroptions, $filemanageroptions $mform->setDefault('maildisplay', core_user::get_property_default('maildisplay')); $mform->addHelpButton('maildisplay', 'emaildisplay'); + $mform->addElement('text', 'moodlenetprofile', get_string('moodlenetprofile', 'user')); + $mform->setType('moodlenetprofile', PARAM_RAW_TRIMMED); + $mform->addElement('text', 'city', get_string('city'), 'maxlength="120" size="21"'); $mform->setType('city', PARAM_TEXT); if (!empty($CFG->defaultcity)) { diff --git a/version.php b/version.php index f506718cd43a1..797c89131e99b 100644 --- a/version.php +++ b/version.php @@ -29,7 +29,7 @@ defined('MOODLE_INTERNAL') || die(); -$version = 2020060200.01; // YYYYMMDD = weekly release date of this DEV branch. +$version = 2020060500.01; // YYYYMMDD = weekly release date of this DEV branch. // RR = release increments - 00 in DEV branches. // .XX = incremental changes. $release = '3.9dev+ (Build: 20200602)'; // Human-friendly version name