Merge branch 'MDL-67584-integration-2' of https://github.com/Chocolate-lightning/moodle

This commit is contained in:
Jun Pataleta 2020-02-27 12:36:33 +08:00
commit 9585a6fdf7
18 changed files with 294 additions and 19 deletions

View File

@ -1,2 +1,2 @@
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=k(b);c=k(c);d=j(d);e=j(e);f=k(f);g=k(g);i=j(i);function j(a){return a&&a.__esModule?a:{default:a}}function k(a){if(a&&a.__esModule){return a}else{var b={};if(null!=a){for(var c in a){if(Object.prototype.hasOwnProperty.call(a,c)){var d=Object.defineProperty&&Object.getOwnPropertyDescriptor?Object.getOwnPropertyDescriptor(a,c):{};if(d.get||d.set){Object.defineProperty(b,c,d)}else{b[c]=a[c]}}}}b.default=a;return b}}function l(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 m(a){return function(){var b=this,c=arguments;return new Promise(function(d,e){var h=a.apply(b,c);function f(a){l(h,d,e,f,g,"next",a)}function g(a){l(h,d,e,f,g,"throw",a)}f(void 0)})}}var n=function(a){var b=new i.default;o(a);b.resolve()};a.init=n;var o=function(a){var f=["click",e.default.events.activate,e.default.events.keyboardActivate],g=function(){var b=null;return function(){if(!b){b=new Promise(function(b){b(c.activityModules(a))})}return b}}();e.default.define(document,f);f.forEach(function(a){document.addEventListener(a,function(){var a=m(regeneratorRuntime.mark(function a(c){var e,f,h;return regeneratorRuntime.wrap(function(a){while(1){switch(a.prev=a.next){case 0:if(!c.target.closest(d.default.elements.sectionmodchooser)){a.next=12;break}e=c.target.closest(d.default.elements.sectionmodchooser);a.t0=p;a.next=5;return g();case 5:a.t1=a.sent;a.t2=e.dataset.sectionid;f=(0,a.t0)(a.t1,a.t2);a.next=10;return q(f);case 10:h=a.sent;b.displayChooser(e,h,f);case 12:case"end":return a.stop();}}},a)}));return function(){return a.apply(this,arguments)}}())})},p=function(a,b){var c=JSON.parse(JSON.stringify(a));c.content_items.forEach(function(a){a.link+="&section="+b});return c.content_items},q=function(a){return s(r(a))},r=function(a){var b=[],c=a.filter(function(a){return!0===a.recommended}),d=!!b.length,e=!!(c.length&&!1===d);return{default:a,favourites:b,recommended:c,favouritesFirst:d,recommendedFirst:e,fallback:!1===d&&!1===e}},s=function(a){return g.create({type:g.types.DEFAULT,title:(0,h.get_string)("addresourceoractivity"),body:f.render("core_course/chooser",a),large:!0,templateContext:{classes:"modchooser"}})}});
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=k(b);c=k(c);d=j(d);e=j(e);f=k(f);g=k(g);i=j(i);function j(a){return a&&a.__esModule?a:{default:a}}function k(a){if(a&&a.__esModule){return a}else{var b={};if(null!=a){for(var c in a){if(Object.prototype.hasOwnProperty.call(a,c)){var d=Object.defineProperty&&Object.getOwnPropertyDescriptor?Object.getOwnPropertyDescriptor(a,c):{};if(d.get||d.set){Object.defineProperty(b,c,d)}else{b[c]=a[c]}}}}b.default=a;return b}}function l(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 m(a){return function(){var b=this,c=arguments;return new Promise(function(d,e){var h=a.apply(b,c);function f(a){l(h,d,e,f,g,"next",a)}function g(a){l(h,d,e,f,g,"throw",a)}f(void 0)})}}var n=function(a){var b=new i.default;o(a);b.resolve()};a.init=n;var o=function(a){var f=["click",e.default.events.activate,e.default.events.keyboardActivate],g=function(){var b=null;return function(){if(!b){b=new Promise(function(b){b(c.activityModules(a))})}return b}}();e.default.define(document,f);f.forEach(function(a){document.addEventListener(a,function(){var a=m(regeneratorRuntime.mark(function a(c){var e,f,h,i,j;return regeneratorRuntime.wrap(function(a){while(1){switch(a.prev=a.next){case 0:if(!c.target.closest(d.default.elements.sectionmodchooser)){a.next=11;break}a.next=3;return g();case 3:e=a.sent;f=c.target.closest(d.default.elements.sectionmodchooser);h=u(e,f.dataset.sectionid);i=p(e,f.dataset.sectionid);a.next=9;return q(i);case 9:j=a.sent;b.displayChooser(f,j,i,h);case 11:case"end":return a.stop();}}},a)}));return function(){return a.apply(this,arguments)}}())})},p=function(a,b){var c=JSON.parse(JSON.stringify(a));c.content_items.forEach(function(a){a.link+="&section="+b});return c.content_items},q=function(a){return s(r(a))},r=function(a){var b=a.filter(function(a){return!0===a.favourite}),c=a.filter(function(a){return!0===a.recommended}),d=!!b.length,e=!!(c.length&&!1===d);return{default:a,favourites:b,recommended:c,favouritesFirst:d,recommendedFirst:e,fallback:!1===d&&!1===e}},s=function(a){return g.create({type:g.types.DEFAULT,title:(0,h.get_string)("addresourceoractivity"),body:f.render("core_course/chooser",a),large:!0,templateContext:{classes:"modchooser"}})},t=function(a,b){a.classList.add("d-none");if(a.classList.contains("active")){a.classList.remove("active");var f=b.querySelector(d.default.regions.favouriteTab);f.classList.remove("active");var c=b.querySelector(d.default.regions.recommendedTabNav),e=b.querySelector(d.default.regions.defaultTabNav);if(!1===c.classList.contains("d-none")){c.classList.add("active");var g=b.querySelector(d.default.regions.recommendedTab);g.classList.add("active")}else{e.classList.add("active");var h=b.querySelector(d.default.regions.defaultTab);h.classList.add("active")}}},u=function(a,b){return function(){var c=m(regeneratorRuntime.mark(function c(e,g,h){var i,j,k,l,m,n,o,q,r,s,u;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=p(m,b);c.next=12;return f.renderForPromise("core_course/chooser_favourites",{favourites:n});case 12:o=c.sent;q=o.html;r=o.js;c.next=17;return f.replaceNodeContents(i,q,r);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")});u=a.content_items.filter(function(a){return!0===a.favourite});if(0===u.length){t(k,h)}case 27:case"end":return c.stop();}}},c)}));return function(){return c.apply(this,arguments)}}()}});
//# sourceMappingURL=activitychooser.min.js.map

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -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.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});
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});
//# sourceMappingURL=repository.min.js.map

View File

@ -1 +1 @@
{"version":3,"sources":["../../../src/local/activitychooser/repository.js"],"names":["activityModules","courseid","ajax","call","methodname","args"],"mappings":"oLAsBA,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","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 <http://www.gnu.org/licenses/>.\n\n/**\n *\n * @module core_course/repository\n * @package core_course\n * @copyright 2019 Mathew May <mathew.solutions>\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"],"file":"repository.min.js"}
{"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 <http://www.gnu.org/licenses/>.\n\n/**\n *\n * @module core_course/repository\n * @package core_course\n * @copyright 2019 Mathew May <mathew.solutions>\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"}

View File

@ -1,2 +1,2 @@
define ("core_course/local/activitychooser/selectors",["exports"],function(a){"use strict";Object.defineProperty(a,"__esModule",{value:!0});a.default=void 0;var b=function(a,b){return"[data-".concat(a,"=\"").concat(b,"\"]")},c={regions:{chooser:b("region","chooser-container"),chooserOptions:b("region","chooser-options-container"),chooserOption:{container:b("region","chooser-option-container"),actions:b("region","chooser-option-actions-container"),info:b("region","chooser-option-info-container")},chooserSummary:{container:b("region","chooser-option-summary-container"),content:b("region","chooser-option-summary-content-container"),description:b("region","summary-description"),actions:b("region","chooser-option-summary-actions-container")},carousel:b("region","carousel"),help:b("region","help"),modules:b("region","modules"),favouriteTabNav:b("region","favourite-tab-nav"),recommendedTabNav:b("region","recommended-tab-nav"),defaultTabNav:b("region","default-tab-nav"),favouriteTab:b("region","favourites"),recommendedTab:b("region","recommended"),defaultTab:b("region","default"),getModuleSelector:function getModuleSelector(a){return"[role=\"menuitem\"][data-modname=\"".concat(a,"\"]")}},actions:{optionActions:{showSummary:b("action","show-option-summary")},addChooser:b("action","add-chooser-option"),closeOption:b("action","close-chooser-option-summary"),hide:b("action","hide")},elements:{section:".section",sectionmodchooser:"button.section-modchooser-link",sitemenu:".block_site_main_menu",sitetopic:"div.sitetopic"}};a.default=c;return a.default});
define ("core_course/local/activitychooser/selectors",["exports"],function(a){"use strict";Object.defineProperty(a,"__esModule",{value:!0});a.default=void 0;var b=function(a,b){return"[data-".concat(a,"=\"").concat(b,"\"]")},c={regions:{chooser:b("region","chooser-container"),chooserOptions:b("region","chooser-options-container"),chooserOption:{container:b("region","chooser-option-container"),actions:b("region","chooser-option-actions-container"),info:b("region","chooser-option-info-container")},chooserSummary:{container:b("region","chooser-option-summary-container"),content:b("region","chooser-option-summary-content-container"),description:b("region","summary-description"),actions:b("region","chooser-option-summary-actions-container")},carousel:b("region","carousel"),help:b("region","help"),modules:b("region","modules"),favouriteTabNav:b("region","favourite-tab-nav"),recommendedTabNav:b("region","recommended-tab-nav"),defaultTabNav:b("region","default-tab-nav"),favouriteTab:b("region","favourites"),recommendedTab:b("region","recommended"),defaultTab:b("region","default"),getModuleSelector:function getModuleSelector(a){return"[role=\"menuitem\"][data-modname=\"".concat(a,"\"]")}},actions:{optionActions:{showSummary:b("action","show-option-summary"),manageFavourite:b("action","manage-module-favourite")},addChooser:b("action","add-chooser-option"),closeOption:b("action","close-chooser-option-summary"),hide:b("action","hide")},render:{favourites:b("render","favourites-area")},elements:{section:".section",sectionmodchooser:"button.section-modchooser-link",sitemenu:".block_site_main_menu",sitetopic:"div.sitetopic"}};a.default=c;return a.default});
//# sourceMappingURL=selectors.min.js.map

File diff suppressed because one or more lines are too long

View File

@ -78,11 +78,13 @@ const registerListenerEvents = (courseId) => {
events.forEach((event) => {
document.addEventListener(event, async(e) => {
if (e.target.closest(selectors.elements.sectionmodchooser)) {
const data = await fetchModuleData();
const caller = e.target.closest(selectors.elements.sectionmodchooser);
const builtModuleData = sectionIdMapper(await fetchModuleData(), caller.dataset.sectionid);
const favouriteFunction = partiallyAppliedFavouriteManager(data, caller.dataset.sectionid);
const builtModuleData = sectionIdMapper(data, caller.dataset.sectionid);
const sectionModal = await modalBuilder(builtModuleData);
ChooserDialogue.displayChooser(caller, sectionModal, builtModuleData);
ChooserDialogue.displayChooser(caller, sectionModal, builtModuleData, favouriteFunction);
}
});
});
@ -111,7 +113,7 @@ const sectionIdMapper = (webServiceData, id) => {
*
* @method modalBuilder
* @param {Map} data our map of section ID's & modules to generate modals for
* @return {Object} TODO
* @return {Object} Our modal that we are going to show the user
*/
const modalBuilder = data => buildModal(templateDataBuilder(data));
@ -124,7 +126,7 @@ const modalBuilder = data => buildModal(templateDataBuilder(data));
*/
const templateDataBuilder = (data) => {
// Filter the incoming data to find favourite & recommended modules.
const favourites = [];
const favourites = data.filter(mod => mod.favourite === true);
const recommended = data.filter(mod => mod.recommended === true);
// Given the results of the above filters lets figure out what tab to set active.
@ -164,3 +166,105 @@ const buildModal = data => {
}
});
};
/**
* A small helper function to handle the case where there are no more favourites
* and we need to mess a bit with the available tabs in the chooser
*
* @method nullFavouriteDomManager
* @param {HTMLElement} favouriteTabNav Dom node of the favourite tab nav
* @param {HTMLElement} modalBody Our current modals' body
*/
const nullFavouriteDomManager = (favouriteTabNav, modalBody) => {
favouriteTabNav.classList.add('d-none');
// Need to set active to an available tab.
if (favouriteTabNav.classList.contains('active')) {
favouriteTabNav.classList.remove('active');
const favouriteTab = modalBody.querySelector(selectors.regions.favouriteTab);
favouriteTab.classList.remove('active');
const recommendedTabNav = modalBody.querySelector(selectors.regions.recommendedTabNav);
const defaultTabNav = modalBody.querySelector(selectors.regions.defaultTabNav);
if (recommendedTabNav.classList.contains('d-none') === false) {
recommendedTabNav.classList.add('active');
const recommendedTab = modalBody.querySelector(selectors.regions.recommendedTab);
recommendedTab.classList.add('active');
} else {
defaultTabNav.classList.add('active');
const defaultTab = modalBody.querySelector(selectors.regions.defaultTab);
defaultTab.classList.add('active');
}
}
};
/**
* Export a curried function where the builtModules has been applied.
* We have our array of modules so we can rerender the favourites area and have all of the items sorted.
*
* @method partiallyAppliedFavouriteManager
* @param {Array} moduleData This is our raw WS data that we need to manipulate
* @param {Number} sectionId We need this to add the sectionID to the URL's in the faves area after rerender
* @return {Function} partially applied function so we can manipulate DOM nodes easily & update our internal array
*/
const partiallyAppliedFavouriteManager = (moduleData, sectionId) => {
/**
* Curried function that is being returned.
*
* @param {String} internal Internal name of the module to manage
* @param {Boolean} favourite Is the caller adding a favourite or removing one?
* @param {HTMLElement} modalBody What we need to update whilst we are here
*/
return async(internal, favourite, modalBody) => {
const favouriteArea = modalBody.querySelector(selectors.render.favourites);
// eslint-disable-next-line max-len
const favouriteButtons = modalBody.querySelectorAll(`[data-internal="${internal}"] ${selectors.actions.optionActions.manageFavourite}`);
const favouriteTabNav = modalBody.querySelector(selectors.regions.favouriteTabNav);
const result = moduleData.content_items.find(({name}) => name === internal);
const newFaves = {};
if (result) {
if (favourite) {
result.favourite = true;
newFaves.content_items = moduleData.content_items.filter(mod => mod.favourite === true);
const builtFaves = sectionIdMapper(newFaves, sectionId);
const {html, js} = await Templates.renderForPromise('core_course/chooser_favourites', {favourites: builtFaves});
await Templates.replaceNodeContents(favouriteArea, html, js);
Array.from(favouriteButtons).forEach((element) => {
element.classList.remove('text-muted');
element.classList.add('text-primary');
element.dataset.favourited = 'true';
element.setAttribute('aria-pressed', true);
element.firstElementChild.classList.remove('fa-star-o');
element.firstElementChild.classList.add('fa-star');
});
favouriteTabNav.classList.remove('d-none');
} else {
result.favourite = false;
const nodeToRemove = favouriteArea.querySelector(`[data-internal="${internal}"]`);
nodeToRemove.parentNode.removeChild(nodeToRemove);
Array.from(favouriteButtons).forEach((element) => {
element.classList.add('text-muted');
element.classList.remove('text-primary');
element.dataset.favourited = 'false';
element.setAttribute('aria-pressed', false);
element.firstElementChild.classList.remove('fa-star');
element.firstElementChild.classList.add('fa-star-o');
});
const newFaves = moduleData.content_items.filter(mod => mod.favourite === true);
if (newFaves.length === 0) {
nullFavouriteDomManager(favouriteTabNav, modalBody);
}
}
}
};
};

View File

@ -28,6 +28,8 @@ import selectors from 'core_course/local/activitychooser/selectors';
import * as Templates from 'core/templates';
import {end, arrowLeft, arrowRight, home, enter, space} from 'core/key_codes';
import {addIconToContainer} from 'core/loadingicon';
import * as Repository from 'core_course/local/activitychooser/repository';
import Notification from 'core/notification';
/**
* Given an event from the main module 'page' navigate to it's help section via a carousel.
@ -69,14 +71,42 @@ const showModuleHelp = (carousel, moduleData) => {
carousel.carousel('next');
};
/**
* Given a user wants to change the favourite state of a module we either add or remove the status.
* We also propergate this change across our map of modals.
*
* @method manageFavouriteState
* @param {HTMLElement} modalBody The DOM node of the modal to manipulate
* @param {HTMLElement} caller
* @param {Function} partialFavourite Partially applied function we need to manage favourite status
*/
const manageFavouriteState = async(modalBody, caller, partialFavourite) => {
const isFavourite = caller.dataset.favourited;
const id = caller.dataset.id;
const name = caller.dataset.name;
const internal = caller.dataset.internal;
// Switch on fave or not.
if (isFavourite === 'true') {
await Repository.unfavouriteModule(name, id);
partialFavourite(internal, false, modalBody);
} else {
await Repository.favouriteModule(name, id);
partialFavourite(internal, true, modalBody);
}
};
/**
* Register chooser related event listeners.
*
* @method registerListenerEvents
* @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
*/
const registerListenerEvents = (modal, mappedModules) => {
const registerListenerEvents = (modal, mappedModules, partialFavourite) => {
const bodyClickListener = e => {
if (e.target.closest(selectors.actions.optionActions.showSummary)) {
const carousel = $(modal.getBody()[0].querySelector(selectors.regions.carousel));
@ -87,6 +117,11 @@ const registerListenerEvents = (modal, mappedModules) => {
showModuleHelp(carousel, moduleData);
}
if (e.target.closest(selectors.actions.optionActions.manageFavourite)) {
const caller = e.target.closest(selectors.actions.optionActions.manageFavourite);
manageFavouriteState(modal.getBody()[0], caller, partialFavourite);
}
// From the help screen go back to the module overview.
if (e.target.matches(selectors.actions.closeOption)) {
const carousel = $(modal.getBody()[0].querySelector(selectors.regions.carousel));
@ -273,18 +308,22 @@ const focusChooserOption = (currentChooserOption, previousChooserOption = false)
if (previousChooserOption !== false) {
const previousChooserOptionLink = previousChooserOption.querySelector(selectors.actions.addChooser);
const previousChooserOptionHelp = previousChooserOption.querySelector(selectors.actions.optionActions.showSummary);
const previousChooserOptionFavourite = previousChooserOption.querySelector(selectors.actions.optionActions.manageFavourite);
// Set tabindex to -1 to remove the previous chooser option element from the focus order.
previousChooserOption.tabIndex = -1;
previousChooserOptionLink.tabIndex = -1;
previousChooserOptionHelp.tabIndex = -1;
previousChooserOptionFavourite.tabIndex = -1;
}
const currentChooserOptionLink = currentChooserOption.querySelector(selectors.actions.addChooser);
const currentChooserOptionHelp = currentChooserOption.querySelector(selectors.actions.optionActions.showSummary);
const currentChooserOptionFavourite = currentChooserOption.querySelector(selectors.actions.optionActions.manageFavourite);
// Set tabindex to 0 to add current chooser option element to the focus order.
currentChooserOption.tabIndex = 0;
currentChooserOptionLink.tabIndex = 0;
currentChooserOptionHelp.tabIndex = 0;
currentChooserOptionFavourite.tabIndex = 0;
// Focus the current chooser option element.
currentChooserOption.focus();
};
@ -312,8 +351,9 @@ const clickErrorHandler = (item, fallback) => {
* @param {HTMLElement} origin The calling button
* @param {Object} modal 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
*/
export const displayChooser = (origin, modal, sectionModules) => {
export const displayChooser = (origin, modal, sectionModules, partialFavourite) => {
// Make a map so we can quickly fetch a specific module's object for either rendering or searching.
const mappedModules = new Map();
@ -322,7 +362,7 @@ export const displayChooser = (origin, modal, sectionModules) => {
});
// Register event listeners.
registerListenerEvents(modal, mappedModules);
registerListenerEvents(modal, mappedModules, partialFavourite);
// We want to focus on the action select when the dialog is closed.
modal.getRoot().on(ModalEvents.hidden, () => {

View File

@ -38,3 +38,43 @@ export const activityModules = (courseid) => {
};
return ajax.call([request])[0];
};
/**
* Given a module name, module ID & the current course we want to specify that the module
* is a users' favourite.
*
* @method favouriteModule
* @param {String} modName Frankenstyle name of the component to add favourite
* @param {int} modID ID of the module. Mainly for LTI cases where they have same / similar names
* @return {object} jQuery promise
*/
export const favouriteModule = (modName, modID) => {
const request = {
methodname: 'core_course_add_content_item_to_user_favourites',
args: {
componentname: modName,
contentitemid: modID,
},
};
return ajax.call([request])[0];
};
/**
* Given a module name, module ID & the current course we want to specify that the module
* is no longer a users' favourite.
*
* @method unfavouriteModule
* @param {String} modName Frankenstyle name of the component to add favourite
* @param {int} modID ID of the module. Mainly for LTI cases where they have same / similar names
* @return {object} jQuery promise
*/
export const unfavouriteModule = (modName, modID) => {
const request = {
methodname: 'core_course_remove_content_item_from_user_favourites',
args: {
componentname: modName,
contentitemid: modID,
},
};
return ajax.call([request])[0];
};

View File

@ -62,11 +62,15 @@ export default {
actions: {
optionActions: {
showSummary: getDataSelector('action', 'show-option-summary'),
manageFavourite: getDataSelector('action', 'manage-module-favourite'),
},
addChooser: getDataSelector('action', 'add-chooser-option'),
closeOption: getDataSelector('action', 'close-chooser-option-summary'),
hide: getDataSelector('action', 'hide')
},
render: {
favourites: getDataSelector('render', 'favourites-area'),
},
elements: {
section: '.section',
sectionmodchooser: 'button.section-modchooser-link',

View File

@ -85,9 +85,7 @@
<div class="tab-pane {{#favouritesFirst}}active{{/favouritesFirst}}" id="starred-{{uniqid}}" data-region="favourites" role="tabpanel" aria-labelledby="starred-tab-{{uniqid}}">
<div class="modchoosercontainer" data-region="chooser-container" aria-label="{{#str}} activitymodules {{/str}}">
<div class="optionscontainer d-flex flex-wrap mw-100 p-3 position-relative" role="menubar" data-region="chooser-options-container" data-render="favourites-area">
{{#favourites}}
{{>core_course/chooser_item}}
{{/favourites}}
{{>core_course/chooser_favourites}}
</div>
</div>
</div>

View File

@ -0,0 +1,36 @@
{{!
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 <http://www.gnu.org/licenses/>.
}}
{{!
@template core_course/chooser_favourites
Chooser favourite template partial.
Example context (json):
{
"favourites": {
"label": "Option name",
"description": "Option description",
"urls": {
"addoption": "http://addoptionurl.com"
},
"icon": "<img class='icon' src='http://urltooptionicon'>"
}
}
}}
{{#favourites}}
{{>core_course/chooser_item}}
{{/favourites}}

View File

@ -29,7 +29,7 @@
"icon": "<img class='icon' src='http://urltooptionicon'>"
}
}}
<div role="menuitem" tabindex="-1" aria-label="{{label}}" class="option d-block text-center py-3 px-2" data-region="chooser-option-container" data-modname="{{componentname}}_{{link}}">
<div role="menuitem" tabindex="-1" aria-label="{{title}}" class="option d-block text-center py-3 px-2" data-region="chooser-option-container" data-internal="{{name}}" data-modname="{{componentname}}_{{link}}">
<div class="optioninfo w-100" data-region="chooser-option-info-container">
<a class="d-block" href="{{link}}" title="{{#str}} addnew, moodle, {{title}} {{/str}}" tabindex="-1" data-action="add-chooser-option">
<span class="optionicon d-block">
@ -38,6 +38,30 @@
<span class="optionname d-block">{{title}}</span>
</a>
<div class="optionactions btn-group" role="group" data-region="chooser-option-actions-container">
{{^legacyitem}}
<button class="btn btn-icon icon-no-margin icon-size-3 m-0 optionaction {{#favourite}}text-primary{{/favourite}}{{^favourite}}text-muted{{/favourite}}"
data-action="manage-module-favourite"
data-favourited="{{favourite}}"
data-id="{{id}}"
data-name="{{componentname}}"
data-internal="{{name}}"
{{^favourite}}
aria-pressed="false"
{{/favourite}}
{{#favourite}}
aria-pressed="true"
{{/favourite}}
aria-label="{{#str}} aria:modulefavourite, core_course, {{title}} {{/str}}"
tabindex="-1"
>
{{#favourite}}
{{#pix}} i/star, core {{/pix}}
{{/favourite}}
{{^favourite}}
{{#pix}} i/star-o, core {{/pix}}
{{/favourite}}
</button>
{{/legacyitem}}
<button class="btn btn-icon icon-no-margin icon-size-3 m-0 optionaction" data-action="show-option-summary" tabindex="-1">
<span aria-hidden="true">{{#pix}} docs, core {{/pix}}</span>
<span class="sr-only">{{#str}} informationformodule, core_course, {{title}} {{/str}}</span>

View File

@ -68,3 +68,30 @@ Feature: Display and choose from the available activities in course
Then I should see "Recommended" in the "Add an activity or resource" "dialogue"
And I click on "Recommended" "link" in the "Add an activity or resource" "dialogue"
And I should see "Book" in the "recommended" "core_course > Activity chooser tab"
Scenario: Favourite a module in the activity chooser
Given I open the activity chooser
And I should not see "Starred" in the "Add an activity or resource" "dialogue"
And I click on "Star Assignment module" "button" in the "Add an activity or resource" "dialogue"
And I should see "Starred" in the "Add an activity or resource" "dialogue"
When I click on "Starred" "link" in the "Add an activity or resource" "dialogue"
Then I should see "Assignment" in the "favourites" "core_course > Activity chooser tab"
And I click on "Information about the Assignment activity" "button" in the "favourites" "core_course > Activity chooser tab"
And I should see "The assignment activity module enables a teacher to communicate tasks, collect work and provide grades and feedback."
Scenario: Add a favourite module and check it exists when reopening the chooser
Given I open the activity chooser
And I click on "Star Assignment module" "button" in the "Add an activity or resource" "dialogue"
And I click on "Star Forum module" "button" in the "Add an activity or resource" "dialogue"
And I should see "Starred" in the "Add an activity or resource" "dialogue"
And I click on "Close" "button" in the "Add an activity or resource" "dialogue"
When I click on "Add an activity or resource" "button" in the "Topic 3" "section"
And I click on "Starred" "link" in the "Add an activity or resource" "dialogue"
Then I should see "Forum" in the "favourites" "core_course > Activity chooser tab"
Scenario: Add a favourite and then remove it whilst checking the tabs work as expected
Given I open the activity chooser
And I click on "Star Assignment module" "button" in the "Add an activity or resource" "dialogue"
And I click on "Starred" "link" in the "Add an activity or resource" "dialogue"
And I click on "Star Assignment module" "button" in the "Add an activity or resource" "dialogue"
Then I should not see "Starred" in the "Add an activity or resource" "dialogue"

View File

@ -31,6 +31,7 @@ $string['aria:defaulttab'] = 'The default modules';
$string['aria:favourite'] = 'Course is starred';
$string['aria:favouritestab'] = 'Your starred modules';
$string['aria:recommendedtab'] = 'The recommended modules';
$string['aria:modulefavourite'] = 'Star {$a} module';
$string['coursealreadyfinished'] = 'Course already finished';
$string['coursenotyetstarted'] = 'The course has not yet started';
$string['coursenotyetfinished'] = 'The course has not yet finished';

View File

@ -320,6 +320,7 @@ class icon_system_fontawesome extends icon_system_font {
'core:i/show' => 'fa-eye-slash',
'core:i/siteevent' => 'fa-globe',
'core:i/star' => 'fa-star',
'core:i/star-o' => 'fa-star-o',
'core:i/star-rating' => 'fa-star',
'core:i/stats' => 'fa-line-chart',
'core:i/switch' => 'fa-exchange',