From 1baacc2be65d25eee51657dc519fcaf5315def5b Mon Sep 17 00:00:00 2001 From: Sara Arjona Date: Thu, 27 Nov 2025 14:52:08 +0100 Subject: [PATCH] MDL-87117 course: Add support in activity chooser to summary and tip --- .upgradenotes/MDL-87117-2025112701504315.yml | 8 ++++++ .../classes/local/entity/content_item.php | 12 +++++++++ .../course_content_item_exporter.php | 4 ++- .../content_item_readonly_repository.php | 27 ++++++++++++++++++- .../local/activitychooser/dialogue.min.js | 2 +- .../local/activitychooser/dialogue.min.js.map | 2 +- .../amd/src/local/activitychooser/dialogue.js | 3 ++- .../local/activitychooser/help.mustache | 12 ++++++--- public/lang/en/moodle.php | 1 + .../theme/boost/scss/moodle/modchooser.scss | 3 +++ public/theme/boost/style/moodle.css | 3 +++ public/theme/classic/style/moodle.css | 3 +++ 12 files changed, 72 insertions(+), 8 deletions(-) create mode 100644 .upgradenotes/MDL-87117-2025112701504315.yml diff --git a/.upgradenotes/MDL-87117-2025112701504315.yml b/.upgradenotes/MDL-87117-2025112701504315.yml new file mode 100644 index 00000000000..6ef6d5dacd6 --- /dev/null +++ b/.upgradenotes/MDL-87117-2025112701504315.yml @@ -0,0 +1,8 @@ +issueNumber: MDL-87117 +notes: + core_course: + - message: >- + Two optional new strings, `modulename_summary` and `modulename_tip`, + have been added to modules and will be displayed in the activity chooser + interface when defined. + type: improved diff --git a/public/course/classes/local/entity/content_item.php b/public/course/classes/local/entity/content_item.php index d34c43a9059..5ff402e7414 100644 --- a/public/course/classes/local/entity/content_item.php +++ b/public/course/classes/local/entity/content_item.php @@ -48,6 +48,7 @@ class content_item { * @param bool $branded whether or not this item is branded. * @param bool $gradable whether or not this item is gradable. * @param string|null $otherpurpose the alternative purpose type of this component. + * @param string|null $summary a brief summary of this content item. */ public function __construct( /** @var int $id the id. */ @@ -74,6 +75,8 @@ class content_item { private bool $gradable = false, /** @var string|null $otherpurpose the alternative purpose type of this component. */ private ?string $otherpurpose = null, + /** @var string|null $summary a brief summary of this content item. */ + private ?string $summary = null, ) { } @@ -183,4 +186,13 @@ class content_item { public function is_gradable(): bool { return $this->gradable; } + + /** + * Get a brief summary for this content item. + * + * @return string|null + */ + public function get_summary(): ?string { + return $this->summary; + } } diff --git a/public/course/classes/local/exporters/course_content_item_exporter.php b/public/course/classes/local/exporters/course_content_item_exporter.php index d813678ac4a..b56d74c099f 100644 --- a/public/course/classes/local/exporters/course_content_item_exporter.php +++ b/public/course/classes/local/exporters/course_content_item_exporter.php @@ -66,6 +66,7 @@ class course_content_item_exporter extends exporter { 'link' => ['type' => PARAM_URL, 'description' => 'The link to the content item creation page'], 'icon' => ['type' => PARAM_RAW, 'description' => 'Html containing the icon for the content item'], 'help' => ['type' => PARAM_RAW, 'description' => 'Html description / help for the content item'], + 'summary' => ['type' => PARAM_RAW, 'description' => 'Html summary for the content item'], 'archetype' => ['type' => PARAM_RAW, 'description' => 'The archetype of the module exposing the content item'], 'componentname' => ['type' => PARAM_TEXT, 'description' => 'The name of the component exposing the content item'], 'purpose' => ['type' => PARAM_TEXT, 'description' => 'The purpose of the component exposing the content item'], @@ -140,8 +141,9 @@ class course_content_item_exporter extends exporter { 'title' => $this->contentitem->get_title()->get_value(), 'link' => $this->contentitem->get_link()->out(false), 'icon' => $this->contentitem->get_icon(), - // Help text should not be parsed using course filters. + // Help text, summary and tip should not be parsed using course filters. 'help' => format_text($this->contentitem->get_help(), FORMAT_MARKDOWN, ['filter' => false]), + 'summary' => format_text($this->contentitem->get_summary(), FORMAT_MARKDOWN, ['filter' => false]), 'archetype' => $this->contentitem->get_archetype(), 'componentname' => $this->contentitem->get_component_name(), 'favourite' => $favourite, diff --git a/public/course/classes/local/repository/content_item_readonly_repository.php b/public/course/classes/local/repository/content_item_readonly_repository.php index e613e8a560e..cfd752fdacc 100644 --- a/public/course/classes/local/repository/content_item_readonly_repository.php +++ b/public/course/classes/local/repository/content_item_readonly_repository.php @@ -50,6 +50,12 @@ class content_item_readonly_repository implements content_item_readonly_reposito $sm = get_string_manager(); if ($sm->string_exists('modulename_help', $modname)) { $help = get_string('modulename_help', $modname); + if ($sm->string_exists('modulename_tip', $modname)) { + $tipcontent = get_string('modulename_tip', $modname); + $tipicon = $OUTPUT->pix_icon('i/tip', ''); + $tip = $tipicon . get_string('tip', '', $tipcontent); + $help .= \html_writer::tag('div', $tip, ['class' => 'helpdoctip alert alert-secondary']); + } if ($sm->string_exists('modulename_link', $modname)) { // Link to further info in Moodle docs. // The link is stored in a language file but should not be translated, use value for English. $link = $sm->get_string('modulename_link', $modname, null, 'en'); @@ -57,12 +63,27 @@ class content_item_readonly_repository implements content_item_readonly_reposito $linktext = get_string('morehelp'); $arialabel = get_string('morehelpaboutmodule', '', get_string('modulename', $modname)); $doclink = $OUTPUT->doc_link($link, $linktext, true, ['aria-label' => $arialabel]); - $help .= \html_writer::tag('div', $doclink, ['class' => 'helpdoclink']); + $help .= \html_writer::tag('div', $doclink, ['class' => 'helpdoclink opacity-75 pt-3']); } } return $help; } + /** + * Get the summary string for content items representing core modules. + * + * @param string $modname the module name. + * @return string the summary string or empty string if none exists. + */ + private function get_core_module_summary_string(string $modname): string { + $sm = get_string_manager(); + if (!$sm->string_exists('modulename_summary', $modname)) { + return ''; + } + + return get_string('modulename_summary', $modname); + } + /** * Helper to get the contentitems from all subplugin hooks for a given module plugin. * @@ -145,6 +166,7 @@ class content_item_readonly_repository implements content_item_readonly_reposito // Create the content item for the module itself. // If the module chooses to implement the hook, this may be thrown away. $help = $this->get_core_module_help_string($mod->name); + $summary = $this->get_core_module_summary_string($mod->name); $archetype = plugin_supports('mod', $mod->name, FEATURE_MOD_ARCHETYPE, MOD_ARCHETYPE_OTHER); $purpose = plugin_supports('mod', $mod->name, FEATURE_MOD_PURPOSE, MOD_PURPOSE_OTHER); $otherpurpose = plugin_supports('mod', $mod->name, FEATURE_MOD_OTHERPURPOSE); @@ -164,6 +186,7 @@ class content_item_readonly_repository implements content_item_readonly_reposito branded: $isbranded, gradable: $gradable, otherpurpose: $otherpurpose, + summary: $summary, ); $modcontentitemreference = clone($contentitem); @@ -215,6 +238,7 @@ class content_item_readonly_repository implements content_item_readonly_reposito // Create the content item for the module itself. // If the module chooses to implement the hook, this may be thrown away. $help = $this->get_core_module_help_string($mod->name); + $summary = $this->get_core_module_summary_string($mod->name); $archetype = plugin_supports('mod', $mod->name, FEATURE_MOD_ARCHETYPE, MOD_ARCHETYPE_OTHER); $purpose = plugin_supports('mod', $mod->name, FEATURE_MOD_PURPOSE, MOD_PURPOSE_OTHER); $otherpurpose = plugin_supports('mod', $mod->name, FEATURE_MOD_OTHERPURPOSE); @@ -242,6 +266,7 @@ class content_item_readonly_repository implements content_item_readonly_reposito branded: $isbranded, gradable: $gradable, otherpurpose: $otherpurpose, + summary: $summary, ); $modcontentitemreference = clone($contentitem); diff --git a/public/course/format/amd/build/local/activitychooser/dialogue.min.js b/public/course/format/amd/build/local/activitychooser/dialogue.min.js index e0898997e38..b148d175128 100644 --- a/public/course/format/amd/build/local/activitychooser/dialogue.min.js +++ b/public/course/format/amd/build/local/activitychooser/dialogue.min.js @@ -1,3 +1,3 @@ -define("core_courseformat/local/activitychooser/dialogue",["exports","core/utils","core_courseformat/local/activitychooser/dialoguedom","core/key_codes","core_courseformat/local/activitychooser/exporter","core/normalise","core/str","core/modal","core/modal_events","core/notification","core_courseformat/local/activitychooser/repository","core_courseformat/local/activitychooser/selectors","core/templates"],(function(_exports,_utils,_dialoguedom,_key_codes,_exporter,_normalise,_str,_modal,ModalEvents,_notification,Repository,_selectors,Templates){Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.displayActivityChooserModal=async function(footerDataPromise,modulesDataPromise){let bodyPromiseResolver;const bodyPromise=new Promise((resolve=>{bodyPromiseResolver=resolve})),exporter=new _exporter.default,footerData=await footerDataPromise,footerPromise=Templates.render("core_courseformat/local/activitychooser/footer",exporter.getFooterData(footerData)),sectionModal=_modal.default.create({body:bodyPromise,title:(0,_str.getString)("addresourceoractivity"),footer:footerPromise,large:!0,scrollable:!1,templateContext:{classes:"modchooser"},show:!0});try{const modulesData=await modulesDataPromise;if(!modulesData)return;const modal=await sectionModal;new ActivityChooserDialogue(modal,modulesData,footerData);const templateData=await exporter.getModChooserTemplateData(modulesData);bodyPromiseResolver(await Templates.render("core_courseformat/activitychooser",templateData))}catch(error){const errorTemplateData={errormessage:error.message};return void bodyPromiseResolver(await Templates.render("core_courseformat/local/activitychooser/error",errorTemplateData))}},_exports.displayChooser=void 0,_dialoguedom=_interopRequireDefault(_dialoguedom),_exporter=_interopRequireDefault(_exporter),_modal=_interopRequireDefault(_modal),ModalEvents=_interopRequireWildcard(ModalEvents),_notification=_interopRequireDefault(_notification),Repository=_interopRequireWildcard(Repository),_selectors=_interopRequireDefault(_selectors),Templates=_interopRequireWildcard(Templates);var _systemImportTransformerGlobalIdentifier="undefined"!=typeof window?window:"undefined"!=typeof self?self:"undefined"!=typeof global?global:{};function _getRequireWildcardCache(nodeInterop){if("function"!=typeof WeakMap)return null;var cacheBabelInterop=new WeakMap,cacheNodeInterop=new WeakMap;return(_getRequireWildcardCache=function(nodeInterop){return nodeInterop?cacheNodeInterop:cacheBabelInterop})(nodeInterop)}function _interopRequireWildcard(obj,nodeInterop){if(!nodeInterop&&obj&&obj.__esModule)return obj;if(null===obj||"object"!=typeof obj&&"function"!=typeof obj)return{default:obj};var cache=_getRequireWildcardCache(nodeInterop);if(cache&&cache.has(obj))return cache.get(obj);var newObj={},hasPropertyDescriptor=Object.defineProperty&&Object.getOwnPropertyDescriptor;for(var key in obj)if("default"!==key&&Object.prototype.hasOwnProperty.call(obj,key)){var desc=hasPropertyDescriptor?Object.getOwnPropertyDescriptor(obj,key):null;desc&&(desc.get||desc.set)?Object.defineProperty(newObj,key,desc):newObj[key]=obj[key]}return newObj.default=obj,cache&&cache.set(obj,newObj),newObj}function _interopRequireDefault(obj){return obj&&obj.__esModule?obj:{default:obj}}_exports.displayChooser=(modalPromise,sectionModules,partialFavourite,footerData)=>{window.console.warn("The displayChooser function is deprecated. Please displayActivityChooserModal instead."),modalPromise.then((modal=>(new ActivityChooserDialogue(modal,sectionModules,footerData),modal))).catch(_notification.default.exception)};class ActivityChooserDialogue{constructor(modal,modulesData,footerData){this.modal=modal,this.dialogueDom=null,this.footerData=footerData,this.exporter=new _exporter.default,this.selectedModule=null,this.isFavouriteTabDirty=!1,this.mappedModules=new Map,modulesData.forEach((module=>{this.mappedModules.set(module.componentname+"_"+module.link,module)})),this.init()}async init(){await this.modal.getBodyPromise(),await this.modal.getFooterPromise(),this.dialogueDom=new _dialoguedom.default(this,this.modal,this.exporter),this.registerModalListenerEvents(),this.setupKeyboardAccessibility(),this.modal.getRoot().on(ModalEvents.hidden,(()=>{this.modal.destroy()})),this.dialogueDom.unmarkAllChooserOptionAsSelected()}async registerModalListenerEvents(){const modalRoot=(0,_normalise.getFirst)(this.modal.getRoot());modalRoot.addEventListener("shown.bs.tab",(event=>{if(this.dialogueDom.unmarkAllChooserOptionAsSelected(),event.target.closest(_selectors.default.regions.allTabNav))return;const searchInput=this.dialogueDom.getSearchInputElement();searchInput.value.length>0&&(searchInput.value="",this.toggleSearchResultsView(searchInput.value))})),modalRoot.addEventListener("click",this.handleModalClick.bind(this)),modalRoot.addEventListener("dblclick",this.handleModalDoubleClick.bind(this));const searchInput=this.dialogueDom.getSearchInputElement();searchInput.addEventListener("input",(0,_utils.debounce)((()=>{this.toggleSearchResultsView(searchInput.value)}),300,{pending:!0})),this.dialogueDom.initBootstrapComponents(),modalRoot.addEventListener("shown.bs.tab",(event=>{event.relatedTarget&&this.dialogueDom.disableFocusAllChooserOptions(event.relatedTarget),this.dialogueDom.initActiveTabNavigation()})),modalRoot.addEventListener("shown.bs.tab",(()=>{this.isFavouriteTabDirty&&!this.dialogueDom.isFavoutiteTabActive()&&this.refreshFavouritesTabContent()})),this.dialogueDom.initActiveTabNavigation();(0,_normalise.getFirst)(await this.modal.getFooterPromise()).addEventListener("click",this.handleFooterClick.bind(this)),modalRoot.addEventListener("slide.bs.carousel",(event=>{void 0!==event.to&&(this.dialogueDom.toggleActiveFooter(0===event.to),this.dialogueDom.toggleBackButton(0!==event.to),this.dialogueDom.toggleAddButton(event.to<2))}))}async handleFooterClick(event){if(event.target.closest(_selectors.default.actions.addSelectedChooserOption)&&this.submitAddSelectedModule(),event.target.closest(_selectors.default.regions.activeFooter)&&!0===this.footerData.footer){const footerjs=await(pluginName=this.footerData.customfooterjs,"function"==typeof _systemImportTransformerGlobalIdentifier.define&&_systemImportTransformerGlobalIdentifier.define.amd?new Promise((function(resolve,reject){_systemImportTransformerGlobalIdentifier.require([pluginName],resolve,reject)})):"undefined"!=typeof module&&module.exports&&"undefined"!=typeof require||"undefined"!=typeof module&&module.component&&_systemImportTransformerGlobalIdentifier.require&&"component"===_systemImportTransformerGlobalIdentifier.require.loader?Promise.resolve(require(pluginName)):Promise.resolve(_systemImportTransformerGlobalIdentifier[pluginName]));await footerjs.footerClickListener(event,this.footerData,this.modal)}var pluginName}async handleModalClick(event){const target=event.target;return target.closest(_selectors.default.actions.optionActions.showSummary)?(event.preventDefault(),void this.handleShowSummary(target)):target.closest(_selectors.default.actions.displayCategory)?(event.preventDefault(),void this.handleDisplayCategory(target)):target.closest(_selectors.default.actions.optionActions.manageFavourite)?(event.preventDefault(),void await this.handleFavouriteClick(target)):target.matches(_selectors.default.actions.closeOption)?(event.preventDefault(),void this.dialogueDom.hideModuleHelp(target.dataset.modname)):target.closest(_selectors.default.actions.clearSearch)?void this.handleClearSearch():target.closest(_selectors.default.regions.chooserOption.info)?(event.preventDefault(),void this.handleOptionSelection(target)):void 0}handleModalDoubleClick(event){null!==this.dialogueDom.getClosestChooserOption(event.target)&&this.submitAddSelectedModule(this.selectedModule)}handleShowSummary(target){const moduleName=this.dialogueDom.getClosestChooserOption(target).dataset.modname,moduleData=this.mappedModules.get(moduleName);this.handleOptionSelection(target),moduleData.showFooter=this.modal.hasFooterContent(),this.dialogueDom.setBackButtonModuleData(moduleData),this.dialogueDom.showModuleHelp(moduleData,this.modal)}handleDisplayCategory(target){const category=target.dataset.category;if(!category)return;this.dialogueDom.hideModuleHelp();const tabNav=this.dialogueDom.showCategoryTab(category);tabNav.focus(),tabNav.scrollIntoView()}async handleFavouriteClick(target){const caller=target.closest(_selectors.default.actions.optionActions.manageFavourite),id=caller.dataset.id,name=caller.dataset.name,internal=caller.dataset.internal;"true"===caller.dataset.favourited?(await Repository.unfavouriteModule(name,id),this.updateFavouriteItemValue(internal,!1)):(await Repository.favouriteModule(name,id),this.updateFavouriteItemValue(internal,!0))}handleClearSearch(){const searchInput=this.dialogueDom.getSearchInputElement();searchInput.value="",searchInput.focus(),this.toggleSearchResultsView(searchInput.value)}handleOptionSelection(target){const option=this.dialogueDom.getClosestChooserOption(target);null!==option&&(this.selectedModule=option,this.dialogueDom.markChooserOptionAsSelected(option,(0,_normalise.getFirst)(this.modal.getFooter())))}submitAddSelectedModule(){let newSelectedModule=arguments.length>0&&void 0!==arguments[0]?arguments[0]:null;newSelectedModule&&this.handleOptionSelection(newSelectedModule),null!==this.selectedModule&&(window.location.href=this.dialogueDom.getChooserOptionUrl(this.selectedModule))}setupKeyboardAccessibility(){const mainElement=(0,_normalise.getFirst)(this.modal.getModal());mainElement.tabIndex=-1,mainElement.addEventListener("keydown",(e=>{const currentOption=this.dialogueDom.getClosestChooserOption(e.target);null!==currentOption&&(e.keyCode!==_key_codes.enter&&e.keyCode!==_key_codes.space||!e.target.closest(_selectors.default.regions.chooserOption.actions))&&(e.keyCode===_key_codes.space&&(e.preventDefault(),this.handleOptionSelection(currentOption)),e.keyCode===_key_codes.enter&&(e.preventDefault(),this.submitAddSelectedModule(currentOption)),e.keyCode===_key_codes.arrowRight&&(e.preventDefault(),this.dialogueDom.focusNextChooserOption(currentOption)),e.keyCode===_key_codes.arrowLeft&&(e.preventDefault(),this.dialogueDom.focusPreviousChooserOption(currentOption)),e.keyCode===_key_codes.home&&(e.preventDefault(),this.dialogueDom.focusFirstChooserOption(currentOption)),e.keyCode===_key_codes.end&&(e.preventDefault(),this.dialogueDom.focusLastChooserOption(currentOption)))}))}async toggleSearchResultsView(searchQuery){const searchResultsData=this.searchModules(searchQuery);searchQuery.length>0?(await this.dialogueDom.refreshSearchResults(searchQuery,searchResultsData),this.dialogueDom.showAllActivitiesTab(!0)):this.dialogueDom.cleanSearchResults()}searchModules(searchTerm){if(""===searchTerm)return this.mappedModules;searchTerm=searchTerm.toLowerCase();const searchResults=[];return this.mappedModules.forEach((activity=>{const activityName=activity.title.toLowerCase(),activityDesc=activity.help.toLowerCase();(activityName.includes(searchTerm)||activityDesc.includes(searchTerm))&&searchResults.push(activity)})),searchResults}async updateFavouriteItemValue(internal,favourite){const moduleItem=this.mappedModules.find((_ref=>{let{name:name}=_ref;return name===internal}));moduleItem&&(moduleItem.favourite=favourite,this.dialogueDom.updateItemStarredIcons(internal,favourite),this.dialogueDom.isFavoutiteTabActive()?this.isFavouriteTabDirty=!0:this.refreshFavouritesTabContent())}async refreshFavouritesTabContent(){this.isFavouriteTabDirty=!1;const favouriteCount=this.mappedModules.filter((mod=>!0===mod.favourite)).size;this.dialogueDom.toggleFavouriteTabDisplay(favouriteCount>0),await this.dialogueDom.refreshFavouritesTabContent(this.mappedModules)}}})); +define("core_courseformat/local/activitychooser/dialogue",["exports","core/utils","core_courseformat/local/activitychooser/dialoguedom","core/key_codes","core_courseformat/local/activitychooser/exporter","core/normalise","core/str","core/modal","core/modal_events","core/notification","core_courseformat/local/activitychooser/repository","core_courseformat/local/activitychooser/selectors","core/templates"],(function(_exports,_utils,_dialoguedom,_key_codes,_exporter,_normalise,_str,_modal,ModalEvents,_notification,Repository,_selectors,Templates){Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.displayActivityChooserModal=async function(footerDataPromise,modulesDataPromise){let bodyPromiseResolver;const bodyPromise=new Promise((resolve=>{bodyPromiseResolver=resolve})),exporter=new _exporter.default,footerData=await footerDataPromise,footerPromise=Templates.render("core_courseformat/local/activitychooser/footer",exporter.getFooterData(footerData)),sectionModal=_modal.default.create({body:bodyPromise,title:(0,_str.getString)("addresourceoractivity"),footer:footerPromise,large:!0,scrollable:!1,templateContext:{classes:"modchooser"},show:!0});try{const modulesData=await modulesDataPromise;if(!modulesData)return;const modal=await sectionModal;new ActivityChooserDialogue(modal,modulesData,footerData);const templateData=await exporter.getModChooserTemplateData(modulesData);bodyPromiseResolver(await Templates.render("core_courseformat/activitychooser",templateData))}catch(error){const errorTemplateData={errormessage:error.message};return void bodyPromiseResolver(await Templates.render("core_courseformat/local/activitychooser/error",errorTemplateData))}},_exports.displayChooser=void 0,_dialoguedom=_interopRequireDefault(_dialoguedom),_exporter=_interopRequireDefault(_exporter),_modal=_interopRequireDefault(_modal),ModalEvents=_interopRequireWildcard(ModalEvents),_notification=_interopRequireDefault(_notification),Repository=_interopRequireWildcard(Repository),_selectors=_interopRequireDefault(_selectors),Templates=_interopRequireWildcard(Templates);var _systemImportTransformerGlobalIdentifier="undefined"!=typeof window?window:"undefined"!=typeof self?self:"undefined"!=typeof global?global:{};function _getRequireWildcardCache(nodeInterop){if("function"!=typeof WeakMap)return null;var cacheBabelInterop=new WeakMap,cacheNodeInterop=new WeakMap;return(_getRequireWildcardCache=function(nodeInterop){return nodeInterop?cacheNodeInterop:cacheBabelInterop})(nodeInterop)}function _interopRequireWildcard(obj,nodeInterop){if(!nodeInterop&&obj&&obj.__esModule)return obj;if(null===obj||"object"!=typeof obj&&"function"!=typeof obj)return{default:obj};var cache=_getRequireWildcardCache(nodeInterop);if(cache&&cache.has(obj))return cache.get(obj);var newObj={},hasPropertyDescriptor=Object.defineProperty&&Object.getOwnPropertyDescriptor;for(var key in obj)if("default"!==key&&Object.prototype.hasOwnProperty.call(obj,key)){var desc=hasPropertyDescriptor?Object.getOwnPropertyDescriptor(obj,key):null;desc&&(desc.get||desc.set)?Object.defineProperty(newObj,key,desc):newObj[key]=obj[key]}return newObj.default=obj,cache&&cache.set(obj,newObj),newObj}function _interopRequireDefault(obj){return obj&&obj.__esModule?obj:{default:obj}}_exports.displayChooser=(modalPromise,sectionModules,partialFavourite,footerData)=>{window.console.warn("The displayChooser function is deprecated. Please displayActivityChooserModal instead."),modalPromise.then((modal=>(new ActivityChooserDialogue(modal,sectionModules,footerData),modal))).catch(_notification.default.exception)};class ActivityChooserDialogue{constructor(modal,modulesData,footerData){this.modal=modal,this.dialogueDom=null,this.footerData=footerData,this.exporter=new _exporter.default,this.selectedModule=null,this.isFavouriteTabDirty=!1,this.mappedModules=new Map,modulesData.forEach((module=>{this.mappedModules.set(module.componentname+"_"+module.link,module)})),this.init()}async init(){await this.modal.getBodyPromise(),await this.modal.getFooterPromise(),this.dialogueDom=new _dialoguedom.default(this,this.modal,this.exporter),this.registerModalListenerEvents(),this.setupKeyboardAccessibility(),this.modal.getRoot().on(ModalEvents.hidden,(()=>{this.modal.destroy()})),this.dialogueDom.unmarkAllChooserOptionAsSelected()}async registerModalListenerEvents(){const modalRoot=(0,_normalise.getFirst)(this.modal.getRoot());modalRoot.addEventListener("shown.bs.tab",(event=>{if(this.dialogueDom.unmarkAllChooserOptionAsSelected(),event.target.closest(_selectors.default.regions.allTabNav))return;const searchInput=this.dialogueDom.getSearchInputElement();searchInput.value.length>0&&(searchInput.value="",this.toggleSearchResultsView(searchInput.value))})),modalRoot.addEventListener("click",this.handleModalClick.bind(this)),modalRoot.addEventListener("dblclick",this.handleModalDoubleClick.bind(this));const searchInput=this.dialogueDom.getSearchInputElement();searchInput.addEventListener("input",(0,_utils.debounce)((()=>{this.toggleSearchResultsView(searchInput.value)}),300,{pending:!0})),this.dialogueDom.initBootstrapComponents(),modalRoot.addEventListener("shown.bs.tab",(event=>{event.relatedTarget&&this.dialogueDom.disableFocusAllChooserOptions(event.relatedTarget),this.dialogueDom.initActiveTabNavigation()})),modalRoot.addEventListener("shown.bs.tab",(()=>{this.isFavouriteTabDirty&&!this.dialogueDom.isFavoutiteTabActive()&&this.refreshFavouritesTabContent()})),this.dialogueDom.initActiveTabNavigation();(0,_normalise.getFirst)(await this.modal.getFooterPromise()).addEventListener("click",this.handleFooterClick.bind(this)),modalRoot.addEventListener("slide.bs.carousel",(event=>{void 0!==event.to&&(this.dialogueDom.toggleActiveFooter(0===event.to),this.dialogueDom.toggleBackButton(0!==event.to),this.dialogueDom.toggleAddButton(event.to<2))}))}async handleFooterClick(event){if(event.target.closest(_selectors.default.actions.addSelectedChooserOption)&&this.submitAddSelectedModule(),event.target.closest(_selectors.default.regions.activeFooter)&&!0===this.footerData.footer){const footerjs=await(pluginName=this.footerData.customfooterjs,"function"==typeof _systemImportTransformerGlobalIdentifier.define&&_systemImportTransformerGlobalIdentifier.define.amd?new Promise((function(resolve,reject){_systemImportTransformerGlobalIdentifier.require([pluginName],resolve,reject)})):"undefined"!=typeof module&&module.exports&&"undefined"!=typeof require||"undefined"!=typeof module&&module.component&&_systemImportTransformerGlobalIdentifier.require&&"component"===_systemImportTransformerGlobalIdentifier.require.loader?Promise.resolve(require(pluginName)):Promise.resolve(_systemImportTransformerGlobalIdentifier[pluginName]));await footerjs.footerClickListener(event,this.footerData,this.modal)}var pluginName}async handleModalClick(event){const target=event.target;return target.closest(_selectors.default.actions.optionActions.showSummary)?(event.preventDefault(),void this.handleShowSummary(target)):target.closest(_selectors.default.actions.displayCategory)?(event.preventDefault(),void this.handleDisplayCategory(target)):target.closest(_selectors.default.actions.optionActions.manageFavourite)?(event.preventDefault(),void await this.handleFavouriteClick(target)):target.matches(_selectors.default.actions.closeOption)?(event.preventDefault(),void this.dialogueDom.hideModuleHelp(target.dataset.modname)):target.closest(_selectors.default.actions.clearSearch)?void this.handleClearSearch():target.closest(_selectors.default.regions.chooserOption.info)?(event.preventDefault(),void this.handleOptionSelection(target)):void 0}handleModalDoubleClick(event){null!==this.dialogueDom.getClosestChooserOption(event.target)&&this.submitAddSelectedModule(this.selectedModule)}handleShowSummary(target){const moduleName=this.dialogueDom.getClosestChooserOption(target).dataset.modname,moduleData=this.mappedModules.get(moduleName);this.handleOptionSelection(target),moduleData.showFooter=this.modal.hasFooterContent(),this.dialogueDom.setBackButtonModuleData(moduleData),this.dialogueDom.showModuleHelp(moduleData,this.modal)}handleDisplayCategory(target){const category=target.dataset.category;if(!category)return;this.dialogueDom.hideModuleHelp();const tabNav=this.dialogueDom.showCategoryTab(category);tabNav.focus(),tabNav.scrollIntoView()}async handleFavouriteClick(target){const caller=target.closest(_selectors.default.actions.optionActions.manageFavourite),id=caller.dataset.id,name=caller.dataset.name,internal=caller.dataset.internal;"true"===caller.dataset.favourited?(await Repository.unfavouriteModule(name,id),this.updateFavouriteItemValue(internal,!1)):(await Repository.favouriteModule(name,id),this.updateFavouriteItemValue(internal,!0))}handleClearSearch(){const searchInput=this.dialogueDom.getSearchInputElement();searchInput.value="",searchInput.focus(),this.toggleSearchResultsView(searchInput.value)}handleOptionSelection(target){const option=this.dialogueDom.getClosestChooserOption(target);null!==option&&(this.selectedModule=option,this.dialogueDom.markChooserOptionAsSelected(option,(0,_normalise.getFirst)(this.modal.getFooter())))}submitAddSelectedModule(){let newSelectedModule=arguments.length>0&&void 0!==arguments[0]?arguments[0]:null;newSelectedModule&&this.handleOptionSelection(newSelectedModule),null!==this.selectedModule&&(window.location.href=this.dialogueDom.getChooserOptionUrl(this.selectedModule))}setupKeyboardAccessibility(){const mainElement=(0,_normalise.getFirst)(this.modal.getModal());mainElement.tabIndex=-1,mainElement.addEventListener("keydown",(e=>{const currentOption=this.dialogueDom.getClosestChooserOption(e.target);null!==currentOption&&(e.keyCode!==_key_codes.enter&&e.keyCode!==_key_codes.space||!e.target.closest(_selectors.default.regions.chooserOption.actions))&&(e.keyCode===_key_codes.space&&(e.preventDefault(),this.handleOptionSelection(currentOption)),e.keyCode===_key_codes.enter&&(e.preventDefault(),this.submitAddSelectedModule(currentOption)),e.keyCode===_key_codes.arrowRight&&(e.preventDefault(),this.dialogueDom.focusNextChooserOption(currentOption)),e.keyCode===_key_codes.arrowLeft&&(e.preventDefault(),this.dialogueDom.focusPreviousChooserOption(currentOption)),e.keyCode===_key_codes.home&&(e.preventDefault(),this.dialogueDom.focusFirstChooserOption(currentOption)),e.keyCode===_key_codes.end&&(e.preventDefault(),this.dialogueDom.focusLastChooserOption(currentOption)))}))}async toggleSearchResultsView(searchQuery){const searchResultsData=this.searchModules(searchQuery);searchQuery.length>0?(await this.dialogueDom.refreshSearchResults(searchQuery,searchResultsData),this.dialogueDom.showAllActivitiesTab(!0)):this.dialogueDom.cleanSearchResults()}searchModules(searchTerm){if(""===searchTerm)return this.mappedModules;searchTerm=searchTerm.toLowerCase();const searchResults=[];return this.mappedModules.forEach((activity=>{const activityName=activity.title.toLowerCase(),activitySummary=activity.summary.toLowerCase(),activityDesc=activity.help.toLowerCase();(activityName.includes(searchTerm)||activitySummary.includes(searchTerm)||activityDesc.includes(searchTerm))&&searchResults.push(activity)})),searchResults}async updateFavouriteItemValue(internal,favourite){const moduleItem=this.mappedModules.find((_ref=>{let{name:name}=_ref;return name===internal}));moduleItem&&(moduleItem.favourite=favourite,this.dialogueDom.updateItemStarredIcons(internal,favourite),this.dialogueDom.isFavoutiteTabActive()?this.isFavouriteTabDirty=!0:this.refreshFavouritesTabContent())}async refreshFavouritesTabContent(){this.isFavouriteTabDirty=!1;const favouriteCount=this.mappedModules.filter((mod=>!0===mod.favourite)).size;this.dialogueDom.toggleFavouriteTabDisplay(favouriteCount>0),await this.dialogueDom.refreshFavouritesTabContent(this.mappedModules)}}})); //# sourceMappingURL=dialogue.min.js.map \ No newline at end of file diff --git a/public/course/format/amd/build/local/activitychooser/dialogue.min.js.map b/public/course/format/amd/build/local/activitychooser/dialogue.min.js.map index 4ae2b142ad7..6899946a12a 100644 --- a/public/course/format/amd/build/local/activitychooser/dialogue.min.js.map +++ b/public/course/format/amd/build/local/activitychooser/dialogue.min.js.map @@ -1 +1 @@ -{"version":3,"file":"dialogue.min.js","sources":["../../../src/local/activitychooser/dialogue.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * A type of dialogue used as for choosing options.\n *\n * @module core_courseformat/local/activitychooser/dialogue\n * @copyright 2019 Mihail Geshoski \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\n// import {addIconToContainer} from 'core/loadingicon';\nimport {debounce} from 'core/utils';\nimport DialogueDom from 'core_courseformat/local/activitychooser/dialoguedom';\nimport {end, arrowLeft, arrowRight, home, enter, space} from 'core/key_codes';\nimport Exporter from 'core_courseformat/local/activitychooser/exporter';\nimport {getFirst} from 'core/normalise';\nimport {getString} from 'core/str';\nimport Modal from 'core/modal';\nimport * as ModalEvents from 'core/modal_events';\nimport Notification from 'core/notification';\nimport * as Repository from 'core_courseformat/local/activitychooser/repository';\nimport selectors from 'core_courseformat/local/activitychooser/selectors';\nimport * as Templates from 'core/templates';\nconst getPlugin = pluginName => import(pluginName);\n\n/**\n * Display the activity chooser modal.\n *\n * @method displayActivityChooser\n * @param {Promise} footerDataPromise Promise for the footer data.\n * @param {Promise} modulesDataPromise Promise for the modules data.\n */\nexport async function displayActivityChooserModal(\n footerDataPromise,\n modulesDataPromise,\n) {\n // We want to show the modal instantly but loading whilst waiting for our data.\n let bodyPromiseResolver;\n const bodyPromise = new Promise(resolve => {\n bodyPromiseResolver = resolve;\n });\n\n const exporter = new Exporter();\n\n const footerData = await footerDataPromise;\n\n const footerPromise = Templates.render(\n 'core_courseformat/local/activitychooser/footer',\n exporter.getFooterData(footerData),\n );\n\n const sectionModal = Modal.create({\n body: bodyPromise,\n title: getString('addresourceoractivity'),\n footer: footerPromise,\n large: true,\n scrollable: false,\n templateContext: {\n classes: 'modchooser'\n },\n show: true,\n });\n\n try {\n const modulesData = await modulesDataPromise;\n\n if (!modulesData) {\n return;\n }\n\n const modal = await sectionModal;\n new ActivityChooserDialogue(modal, modulesData, footerData);\n\n const templateData = await exporter.getModChooserTemplateData(modulesData);\n bodyPromiseResolver(await Templates.render('core_courseformat/activitychooser', templateData));\n } catch (error) {\n const errorTemplateData = {\n 'errormessage': error.message\n };\n bodyPromiseResolver(\n await Templates.render('core_courseformat/local/activitychooser/error', errorTemplateData)\n );\n return;\n }\n}\n\n/**\n * Display the module chooser.\n *\n * @deprecated since Moodle 5.1\n * @todo Remove the method in Moodle 6.0 (MDL-85655).\n * @method displayChooser\n * @param {Promise} modalPromise Our created modal for the section\n * @param {Array} sectionModules An array of all of the built module information\n * @param {Function} partialFavourite Partially applied function we need to manage favourite status\n * @param {Object} footerData Our base footer object.\n */\nexport const displayChooser = (modalPromise, sectionModules, partialFavourite, footerData) => {\n window.console.warn(\n 'The displayChooser function is deprecated. ' +\n 'Please displayActivityChooserModal instead.'\n );\n\n // Register event listeners.\n modalPromise.then(modal => {\n new ActivityChooserDialogue(modal, sectionModules, footerData);\n return modal;\n }).catch(Notification.exception);\n};\n\n/**\n * Activity Chooser Dialogue class.\n *\n * @private\n */\nclass ActivityChooserDialogue {\n /**\n * Constructor for the ActivityChooserDialogue class.\n * @param {Modal} modal The modal object.\n * @param {Object} modulesData The data for the modules.\n * @param {Object} footerData The data for the footer.\n */\n constructor(modal, modulesData, footerData) {\n this.modal = modal;\n this.dialogueDom = null; // We cannot init until we have the modal body loaded.\n this.footerData = footerData;\n this.exporter = new Exporter();\n this.selectedModule = null;\n // This attribute marks when the tab content is dirty and needs to be refreshed when the user changes the tab.\n // We don't want the content to be updated while the user is managing their favourites.\n this.isFavouriteTabDirty = false;\n // Make a map so we can quickly fetch a specific module's object for either rendering or searching.\n this.mappedModules = new Map();\n modulesData.forEach((module) => {\n this.mappedModules.set(module.componentname + '_' + module.link, module);\n });\n this.init();\n }\n\n /**\n * Initialise the activity chooser dialogue.\n *\n * @return {Promise} A promise that resolves when the modal is ready.\n */\n async init() {\n await this.modal.getBodyPromise();\n await this.modal.getFooterPromise();\n this.dialogueDom = new DialogueDom(this, this.modal, this.exporter);\n this.registerModalListenerEvents();\n this.setupKeyboardAccessibility();\n // We want to focus on the action select when the dialog is closed.\n this.modal.getRoot().on(ModalEvents.hidden, () => {\n this.modal.destroy();\n });\n // When modal is open the add button should be disabled because there is no activity selected.\n this.dialogueDom.unmarkAllChooserOptionAsSelected();\n }\n\n /**\n * Register chooser related event listeners.\n *\n * @returns {Promise} A promise that resolves when events are registered\n */\n async registerModalListenerEvents() {\n const modalRoot = getFirst(this.modal.getRoot());\n\n // Changing the tab should cancel any active search.\n modalRoot.addEventListener(\n 'shown.bs.tab',\n (event) => {\n this.dialogueDom.unmarkAllChooserOptionAsSelected();\n // The all tab has the search result, so we do not want to clear the search input.\n if (event.target.closest(selectors.regions.allTabNav)) {\n return;\n }\n const searchInput = this.dialogueDom.getSearchInputElement();\n if (searchInput.value.length > 0) {\n searchInput.value = \"\";\n this.toggleSearchResultsView(searchInput.value);\n }\n },\n );\n\n // Add the listener for clicks on the full modal.\n modalRoot.addEventListener(\n 'click',\n this.handleModalClick.bind(this),\n );\n modalRoot.addEventListener(\n 'dblclick',\n this.handleModalDoubleClick.bind(this),\n );\n\n // Add a listener for an input change in the activity chooser's search bar.\n const searchInput = this.dialogueDom.getSearchInputElement();\n searchInput.addEventListener(\n 'input',\n debounce(\n () => {\n this.toggleSearchResultsView(searchInput.value);\n },\n 300,\n {pending: true},\n ),\n );\n\n this.dialogueDom.initBootstrapComponents();\n\n // Handle focus when a new tab is shown.\n modalRoot.addEventListener('shown.bs.tab', (event) => {\n if (event.relatedTarget) {\n this.dialogueDom.disableFocusAllChooserOptions(event.relatedTarget);\n }\n this.dialogueDom.initActiveTabNavigation();\n });\n\n // Update the favourite tab content when the user changes the tab.\n modalRoot.addEventListener('shown.bs.tab', () => {\n if (this.isFavouriteTabDirty && !this.dialogueDom.isFavoutiteTabActive()) {\n this.refreshFavouritesTabContent();\n }\n });\n\n this.dialogueDom.initActiveTabNavigation();\n\n const modalFooter = getFirst(await this.modal.getFooterPromise());\n\n // Add the listener for clicks on the footer.\n modalFooter.addEventListener(\n 'click',\n this.handleFooterClick.bind(this),\n );\n\n // Adapt modal footer depending on the displayed carousel page.\n modalRoot.addEventListener('slide.bs.carousel', (event) => {\n if (event.to === undefined) {\n return;\n }\n // The boostrap carousel event.to contains the index of the newly active item.\n // The zero index is the chooser options, the first index is the module help,\n // any other index are custom footer pages.\n this.dialogueDom.toggleActiveFooter(event.to === 0);\n this.dialogueDom.toggleBackButton(event.to !== 0);\n this.dialogueDom.toggleAddButton(event.to < 2);\n });\n }\n\n /**\n * Handle the click event on the footer of the modal.\n *\n * @param {Object} event The event object\n * @return {Promise} A promise that resolves when the event is handled\n */\n async handleFooterClick(event) {\n if (event.target.closest(selectors.actions.addSelectedChooserOption)) {\n this.submitAddSelectedModule();\n }\n\n if (\n event.target.closest(selectors.regions.activeFooter)\n && this.footerData.footer === true\n ) {\n const footerjs = await getPlugin(this.footerData.customfooterjs);\n await footerjs.footerClickListener(event, this.footerData, this.modal);\n }\n }\n\n /**\n * Modal click handler.\n *\n * @param {Object} event The event object\n * @return {Promise} A promise that resolves when the event is handled\n */\n async handleModalClick(event) {\n const target = event.target;\n\n if (target.closest(selectors.actions.optionActions.showSummary)) {\n event.preventDefault();\n this.handleShowSummary(target);\n return;\n }\n\n if (target.closest(selectors.actions.displayCategory)) {\n event.preventDefault();\n this.handleDisplayCategory(target);\n return;\n }\n\n if (target.closest(selectors.actions.optionActions.manageFavourite)) {\n event.preventDefault();\n await this.handleFavouriteClick(target);\n return;\n }\n\n // From the help screen go back to the module overview.\n if (target.matches(selectors.actions.closeOption)) {\n event.preventDefault();\n this.dialogueDom.hideModuleHelp(target.dataset.modname);\n return;\n }\n\n // The \"clear search\" button is triggered.\n if (target.closest(selectors.actions.clearSearch)) {\n this.handleClearSearch();\n return;\n }\n\n if (target.closest(selectors.regions.chooserOption.info)) {\n event.preventDefault();\n this.handleOptionSelection(target);\n return;\n }\n }\n\n /**\n * Handle the double click event on the modal.\n * @param {Object} event The event object\n */\n handleModalDoubleClick(event) {\n const option = this.dialogueDom.getClosestChooserOption(event.target);\n if (option !== null) {\n this.submitAddSelectedModule(this.selectedModule);\n }\n }\n\n /**\n * Show the summary of a module when the user clicks on the \"show summary\" button.\n *\n * @param {HTMLElement} target The target element that triggered the event\n */\n handleShowSummary(target) {\n const module = this.dialogueDom.getClosestChooserOption(target);\n const moduleName = module.dataset.modname;\n const moduleData = this.mappedModules.get(moduleName);\n // We select the module now. This way the back button will keep the module selected.\n this.handleOptionSelection(target);\n // We need to know if the overall modal has a footer so we know when to show a real / vs fake footer.\n moduleData.showFooter = this.modal.hasFooterContent();\n this.dialogueDom.setBackButtonModuleData(moduleData);\n this.dialogueDom.showModuleHelp(moduleData, this.modal);\n }\n\n /**\n * Handle the display of a category when the user clicks on the \"display category\" button.\n * @param {HTMLElement} target The target element that triggered the event\n */\n handleDisplayCategory(target) {\n const category = target.dataset.category;\n if (!category) {\n return;\n }\n this.dialogueDom.hideModuleHelp();\n const tabNav = this.dialogueDom.showCategoryTab(category);\n tabNav.focus();\n tabNav.scrollIntoView();\n }\n\n /**\n * Handle the favourite state of a module when the user clicks on the \"starred\" button.\n *\n * @param {HTMLElement} target The target element that triggered the event\n * @return {Promise} A promise that resolves when the event is handled\n */\n async handleFavouriteClick(target) {\n const caller = target.closest(selectors.actions.optionActions.manageFavourite);\n const id = caller.dataset.id;\n const name = caller.dataset.name;\n const internal = caller.dataset.internal;\n const isFavourite = caller.dataset.favourited;\n\n // Switch on fave or not.\n if (isFavourite === 'true') {\n await Repository.unfavouriteModule(name, id);\n this.updateFavouriteItemValue(internal, false);\n } else {\n await Repository.favouriteModule(name, id);\n this.updateFavouriteItemValue(internal, true);\n }\n }\n\n /**\n * Handle a clear search action.\n */\n handleClearSearch() {\n const searchInput = this.dialogueDom.getSearchInputElement();\n searchInput.value = \"\";\n searchInput.focus();\n this.toggleSearchResultsView(searchInput.value);\n }\n\n /**\n * Handle the click on a chooser option.\n *\n * @param {HTMLElement} target The target element that triggered the event\n */\n handleOptionSelection(target) {\n const option = this.dialogueDom.getClosestChooserOption(target);\n if (option === null) {\n return;\n }\n this.selectedModule = option;\n this.dialogueDom.markChooserOptionAsSelected(option, getFirst(this.modal.getFooter()));\n }\n\n /**\n * Submit the selected module to the chooser.\n *\n * This method will redirect the user to the URL of the selected module, if one is selected.\n *\n * @param {HTMLElement} newSelectedModule optional new selected module element.\n * @return {void}\n */\n submitAddSelectedModule(newSelectedModule = null) {\n if (newSelectedModule) {\n this.handleOptionSelection(newSelectedModule);\n }\n if (this.selectedModule === null) {\n return;\n }\n window.location.href = this.dialogueDom.getChooserOptionUrl(this.selectedModule);\n }\n\n /**\n * Set up our tabindex information across the chooser.\n *\n * @method setupKeyboardAccessibility\n */\n setupKeyboardAccessibility() {\n const mainElement = getFirst(this.modal.getModal());\n\n mainElement.tabIndex = -1;\n\n mainElement.addEventListener('keydown', (e) => {\n const currentOption = this.dialogueDom.getClosestChooserOption(e.target);\n if (currentOption === null) {\n return;\n }\n\n if (e.keyCode === enter || e.keyCode === space) {\n // Check first if the target is an internal control button (favourite or help).\n // If that is the case, the regular click will handle the event.\n if (e.target.closest(selectors.regions.chooserOption.actions)) {\n return;\n }\n }\n\n if (e.keyCode === space) {\n e.preventDefault();\n this.handleOptionSelection(currentOption);\n }\n if (e.keyCode === enter) {\n e.preventDefault();\n this.submitAddSelectedModule(currentOption);\n }\n if (e.keyCode === arrowRight) {\n e.preventDefault();\n this.dialogueDom.focusNextChooserOption(currentOption);\n }\n if (e.keyCode === arrowLeft) {\n e.preventDefault();\n this.dialogueDom.focusPreviousChooserOption(currentOption);\n }\n if (e.keyCode === home) {\n e.preventDefault();\n this.dialogueDom.focusFirstChooserOption(currentOption);\n }\n if (e.keyCode === end) {\n e.preventDefault();\n this.dialogueDom.focusLastChooserOption(currentOption);\n }\n });\n }\n\n /**\n * Toggle (display/hide) the search results depending on the value of the search query\n *\n * @method toggleSearchResultsView\n * @param {String} searchQuery The search query\n */\n async toggleSearchResultsView(searchQuery) {\n const searchResultsData = this.searchModules(searchQuery);\n\n if (searchQuery.length > 0) {\n await this.dialogueDom.refreshSearchResults(searchQuery, searchResultsData);\n this.dialogueDom.showAllActivitiesTab(true);\n } else {\n this.dialogueDom.cleanSearchResults();\n }\n }\n\n /**\n * Return the list of modules which have a name or description that matches the given search term.\n *\n * @method searchModules\n * @param {String} searchTerm The search term to match\n * @return {Array}\n */\n searchModules(searchTerm) {\n if (searchTerm === '') {\n return this.mappedModules;\n }\n searchTerm = searchTerm.toLowerCase();\n const searchResults = [];\n this.mappedModules.forEach((activity) => {\n const activityName = activity.title.toLowerCase();\n const activityDesc = activity.help.toLowerCase();\n if (activityName.includes(searchTerm) || activityDesc.includes(searchTerm)) {\n searchResults.push(activity);\n }\n });\n\n return searchResults;\n }\n\n /**\n * Update the favourite item value in the mapped modules.\n *\n * @param {String} internal The internal name of the module.\n * @param {Boolean} favourite Whether the module is a favourite or not.\n * @return {Promise} A promise that resolves when the item is updated.\n */\n async updateFavouriteItemValue(internal, favourite) {\n const moduleItem = this.mappedModules.find(({name}) => name === internal);\n if (!moduleItem) {\n return;\n }\n moduleItem.favourite = favourite;\n\n this.dialogueDom.updateItemStarredIcons(internal, favourite);\n\n if (this.dialogueDom.isFavoutiteTabActive()) {\n this.isFavouriteTabDirty = true;\n } else {\n this.refreshFavouritesTabContent();\n }\n }\n\n /**\n * Refresh the favourites tab content.\n *\n * Note: this method will also hide the favourites tab if there are no favourite modules\n * to keep the modal consistent.\n *\n * @return {Promise} A promise that resolves when the content is refreshed.\n */\n async refreshFavouritesTabContent() {\n this.isFavouriteTabDirty = false;\n const favouriteCount = this.mappedModules.filter(mod => mod.favourite === true).size;\n this.dialogueDom.toggleFavouriteTabDisplay(favouriteCount > 0);\n await this.dialogueDom.refreshFavouritesTabContent(this.mappedModules);\n }\n}\n"],"names":["footerDataPromise","modulesDataPromise","bodyPromiseResolver","bodyPromise","Promise","resolve","exporter","Exporter","footerData","footerPromise","Templates","render","getFooterData","sectionModal","Modal","create","body","title","footer","large","scrollable","templateContext","classes","show","modulesData","modal","ActivityChooserDialogue","templateData","getModChooserTemplateData","error","errorTemplateData","message","modalPromise","sectionModules","partialFavourite","window","console","warn","then","catch","Notification","exception","constructor","dialogueDom","selectedModule","isFavouriteTabDirty","mappedModules","Map","forEach","module","set","componentname","link","init","this","getBodyPromise","getFooterPromise","DialogueDom","registerModalListenerEvents","setupKeyboardAccessibility","getRoot","on","ModalEvents","hidden","destroy","unmarkAllChooserOptionAsSelected","modalRoot","addEventListener","event","target","closest","selectors","regions","allTabNav","searchInput","getSearchInputElement","value","length","toggleSearchResultsView","handleModalClick","bind","handleModalDoubleClick","pending","initBootstrapComponents","relatedTarget","disableFocusAllChooserOptions","initActiveTabNavigation","isFavoutiteTabActive","refreshFavouritesTabContent","handleFooterClick","undefined","to","toggleActiveFooter","toggleBackButton","toggleAddButton","actions","addSelectedChooserOption","submitAddSelectedModule","activeFooter","footerjs","pluginName","customfooterjs","footerClickListener","optionActions","showSummary","preventDefault","handleShowSummary","displayCategory","handleDisplayCategory","manageFavourite","handleFavouriteClick","matches","closeOption","hideModuleHelp","dataset","modname","clearSearch","handleClearSearch","chooserOption","info","handleOptionSelection","getClosestChooserOption","moduleName","moduleData","get","showFooter","hasFooterContent","setBackButtonModuleData","showModuleHelp","category","tabNav","showCategoryTab","focus","scrollIntoView","caller","id","name","internal","favourited","Repository","unfavouriteModule","updateFavouriteItemValue","favouriteModule","option","markChooserOptionAsSelected","getFooter","newSelectedModule","location","href","getChooserOptionUrl","mainElement","getModal","tabIndex","e","currentOption","keyCode","enter","space","arrowRight","focusNextChooserOption","arrowLeft","focusPreviousChooserOption","home","focusFirstChooserOption","end","focusLastChooserOption","searchQuery","searchResultsData","searchModules","refreshSearchResults","showAllActivitiesTab","cleanSearchResults","searchTerm","toLowerCase","searchResults","activity","activityName","activityDesc","help","includes","push","favourite","moduleItem","find","_ref","updateItemStarredIcons","favouriteCount","filter","mod","size","toggleFavouriteTabDisplay"],"mappings":"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"} \ No newline at end of file +{"version":3,"file":"dialogue.min.js","sources":["../../../src/local/activitychooser/dialogue.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * A type of dialogue used as for choosing options.\n *\n * @module core_courseformat/local/activitychooser/dialogue\n * @copyright 2019 Mihail Geshoski \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\n// import {addIconToContainer} from 'core/loadingicon';\nimport {debounce} from 'core/utils';\nimport DialogueDom from 'core_courseformat/local/activitychooser/dialoguedom';\nimport {end, arrowLeft, arrowRight, home, enter, space} from 'core/key_codes';\nimport Exporter from 'core_courseformat/local/activitychooser/exporter';\nimport {getFirst} from 'core/normalise';\nimport {getString} from 'core/str';\nimport Modal from 'core/modal';\nimport * as ModalEvents from 'core/modal_events';\nimport Notification from 'core/notification';\nimport * as Repository from 'core_courseformat/local/activitychooser/repository';\nimport selectors from 'core_courseformat/local/activitychooser/selectors';\nimport * as Templates from 'core/templates';\nconst getPlugin = pluginName => import(pluginName);\n\n/**\n * Display the activity chooser modal.\n *\n * @method displayActivityChooser\n * @param {Promise} footerDataPromise Promise for the footer data.\n * @param {Promise} modulesDataPromise Promise for the modules data.\n */\nexport async function displayActivityChooserModal(\n footerDataPromise,\n modulesDataPromise,\n) {\n // We want to show the modal instantly but loading whilst waiting for our data.\n let bodyPromiseResolver;\n const bodyPromise = new Promise(resolve => {\n bodyPromiseResolver = resolve;\n });\n\n const exporter = new Exporter();\n\n const footerData = await footerDataPromise;\n\n const footerPromise = Templates.render(\n 'core_courseformat/local/activitychooser/footer',\n exporter.getFooterData(footerData),\n );\n\n const sectionModal = Modal.create({\n body: bodyPromise,\n title: getString('addresourceoractivity'),\n footer: footerPromise,\n large: true,\n scrollable: false,\n templateContext: {\n classes: 'modchooser'\n },\n show: true,\n });\n\n try {\n const modulesData = await modulesDataPromise;\n\n if (!modulesData) {\n return;\n }\n\n const modal = await sectionModal;\n new ActivityChooserDialogue(modal, modulesData, footerData);\n\n const templateData = await exporter.getModChooserTemplateData(modulesData);\n bodyPromiseResolver(await Templates.render('core_courseformat/activitychooser', templateData));\n } catch (error) {\n const errorTemplateData = {\n 'errormessage': error.message\n };\n bodyPromiseResolver(\n await Templates.render('core_courseformat/local/activitychooser/error', errorTemplateData)\n );\n return;\n }\n}\n\n/**\n * Display the module chooser.\n *\n * @deprecated since Moodle 5.1\n * @todo Remove the method in Moodle 6.0 (MDL-85655).\n * @method displayChooser\n * @param {Promise} modalPromise Our created modal for the section\n * @param {Array} sectionModules An array of all of the built module information\n * @param {Function} partialFavourite Partially applied function we need to manage favourite status\n * @param {Object} footerData Our base footer object.\n */\nexport const displayChooser = (modalPromise, sectionModules, partialFavourite, footerData) => {\n window.console.warn(\n 'The displayChooser function is deprecated. ' +\n 'Please displayActivityChooserModal instead.'\n );\n\n // Register event listeners.\n modalPromise.then(modal => {\n new ActivityChooserDialogue(modal, sectionModules, footerData);\n return modal;\n }).catch(Notification.exception);\n};\n\n/**\n * Activity Chooser Dialogue class.\n *\n * @private\n */\nclass ActivityChooserDialogue {\n /**\n * Constructor for the ActivityChooserDialogue class.\n * @param {Modal} modal The modal object.\n * @param {Object} modulesData The data for the modules.\n * @param {Object} footerData The data for the footer.\n */\n constructor(modal, modulesData, footerData) {\n this.modal = modal;\n this.dialogueDom = null; // We cannot init until we have the modal body loaded.\n this.footerData = footerData;\n this.exporter = new Exporter();\n this.selectedModule = null;\n // This attribute marks when the tab content is dirty and needs to be refreshed when the user changes the tab.\n // We don't want the content to be updated while the user is managing their favourites.\n this.isFavouriteTabDirty = false;\n // Make a map so we can quickly fetch a specific module's object for either rendering or searching.\n this.mappedModules = new Map();\n modulesData.forEach((module) => {\n this.mappedModules.set(module.componentname + '_' + module.link, module);\n });\n this.init();\n }\n\n /**\n * Initialise the activity chooser dialogue.\n *\n * @return {Promise} A promise that resolves when the modal is ready.\n */\n async init() {\n await this.modal.getBodyPromise();\n await this.modal.getFooterPromise();\n this.dialogueDom = new DialogueDom(this, this.modal, this.exporter);\n this.registerModalListenerEvents();\n this.setupKeyboardAccessibility();\n // We want to focus on the action select when the dialog is closed.\n this.modal.getRoot().on(ModalEvents.hidden, () => {\n this.modal.destroy();\n });\n // When modal is open the add button should be disabled because there is no activity selected.\n this.dialogueDom.unmarkAllChooserOptionAsSelected();\n }\n\n /**\n * Register chooser related event listeners.\n *\n * @returns {Promise} A promise that resolves when events are registered\n */\n async registerModalListenerEvents() {\n const modalRoot = getFirst(this.modal.getRoot());\n\n // Changing the tab should cancel any active search.\n modalRoot.addEventListener(\n 'shown.bs.tab',\n (event) => {\n this.dialogueDom.unmarkAllChooserOptionAsSelected();\n // The all tab has the search result, so we do not want to clear the search input.\n if (event.target.closest(selectors.regions.allTabNav)) {\n return;\n }\n const searchInput = this.dialogueDom.getSearchInputElement();\n if (searchInput.value.length > 0) {\n searchInput.value = \"\";\n this.toggleSearchResultsView(searchInput.value);\n }\n },\n );\n\n // Add the listener for clicks on the full modal.\n modalRoot.addEventListener(\n 'click',\n this.handleModalClick.bind(this),\n );\n modalRoot.addEventListener(\n 'dblclick',\n this.handleModalDoubleClick.bind(this),\n );\n\n // Add a listener for an input change in the activity chooser's search bar.\n const searchInput = this.dialogueDom.getSearchInputElement();\n searchInput.addEventListener(\n 'input',\n debounce(\n () => {\n this.toggleSearchResultsView(searchInput.value);\n },\n 300,\n {pending: true},\n ),\n );\n\n this.dialogueDom.initBootstrapComponents();\n\n // Handle focus when a new tab is shown.\n modalRoot.addEventListener('shown.bs.tab', (event) => {\n if (event.relatedTarget) {\n this.dialogueDom.disableFocusAllChooserOptions(event.relatedTarget);\n }\n this.dialogueDom.initActiveTabNavigation();\n });\n\n // Update the favourite tab content when the user changes the tab.\n modalRoot.addEventListener('shown.bs.tab', () => {\n if (this.isFavouriteTabDirty && !this.dialogueDom.isFavoutiteTabActive()) {\n this.refreshFavouritesTabContent();\n }\n });\n\n this.dialogueDom.initActiveTabNavigation();\n\n const modalFooter = getFirst(await this.modal.getFooterPromise());\n\n // Add the listener for clicks on the footer.\n modalFooter.addEventListener(\n 'click',\n this.handleFooterClick.bind(this),\n );\n\n // Adapt modal footer depending on the displayed carousel page.\n modalRoot.addEventListener('slide.bs.carousel', (event) => {\n if (event.to === undefined) {\n return;\n }\n // The boostrap carousel event.to contains the index of the newly active item.\n // The zero index is the chooser options, the first index is the module help,\n // any other index are custom footer pages.\n this.dialogueDom.toggleActiveFooter(event.to === 0);\n this.dialogueDom.toggleBackButton(event.to !== 0);\n this.dialogueDom.toggleAddButton(event.to < 2);\n });\n }\n\n /**\n * Handle the click event on the footer of the modal.\n *\n * @param {Object} event The event object\n * @return {Promise} A promise that resolves when the event is handled\n */\n async handleFooterClick(event) {\n if (event.target.closest(selectors.actions.addSelectedChooserOption)) {\n this.submitAddSelectedModule();\n }\n\n if (\n event.target.closest(selectors.regions.activeFooter)\n && this.footerData.footer === true\n ) {\n const footerjs = await getPlugin(this.footerData.customfooterjs);\n await footerjs.footerClickListener(event, this.footerData, this.modal);\n }\n }\n\n /**\n * Modal click handler.\n *\n * @param {Object} event The event object\n * @return {Promise} A promise that resolves when the event is handled\n */\n async handleModalClick(event) {\n const target = event.target;\n\n if (target.closest(selectors.actions.optionActions.showSummary)) {\n event.preventDefault();\n this.handleShowSummary(target);\n return;\n }\n\n if (target.closest(selectors.actions.displayCategory)) {\n event.preventDefault();\n this.handleDisplayCategory(target);\n return;\n }\n\n if (target.closest(selectors.actions.optionActions.manageFavourite)) {\n event.preventDefault();\n await this.handleFavouriteClick(target);\n return;\n }\n\n // From the help screen go back to the module overview.\n if (target.matches(selectors.actions.closeOption)) {\n event.preventDefault();\n this.dialogueDom.hideModuleHelp(target.dataset.modname);\n return;\n }\n\n // The \"clear search\" button is triggered.\n if (target.closest(selectors.actions.clearSearch)) {\n this.handleClearSearch();\n return;\n }\n\n if (target.closest(selectors.regions.chooserOption.info)) {\n event.preventDefault();\n this.handleOptionSelection(target);\n return;\n }\n }\n\n /**\n * Handle the double click event on the modal.\n * @param {Object} event The event object\n */\n handleModalDoubleClick(event) {\n const option = this.dialogueDom.getClosestChooserOption(event.target);\n if (option !== null) {\n this.submitAddSelectedModule(this.selectedModule);\n }\n }\n\n /**\n * Show the summary of a module when the user clicks on the \"show summary\" button.\n *\n * @param {HTMLElement} target The target element that triggered the event\n */\n handleShowSummary(target) {\n const module = this.dialogueDom.getClosestChooserOption(target);\n const moduleName = module.dataset.modname;\n const moduleData = this.mappedModules.get(moduleName);\n // We select the module now. This way the back button will keep the module selected.\n this.handleOptionSelection(target);\n // We need to know if the overall modal has a footer so we know when to show a real / vs fake footer.\n moduleData.showFooter = this.modal.hasFooterContent();\n this.dialogueDom.setBackButtonModuleData(moduleData);\n this.dialogueDom.showModuleHelp(moduleData, this.modal);\n }\n\n /**\n * Handle the display of a category when the user clicks on the \"display category\" button.\n * @param {HTMLElement} target The target element that triggered the event\n */\n handleDisplayCategory(target) {\n const category = target.dataset.category;\n if (!category) {\n return;\n }\n this.dialogueDom.hideModuleHelp();\n const tabNav = this.dialogueDom.showCategoryTab(category);\n tabNav.focus();\n tabNav.scrollIntoView();\n }\n\n /**\n * Handle the favourite state of a module when the user clicks on the \"starred\" button.\n *\n * @param {HTMLElement} target The target element that triggered the event\n * @return {Promise} A promise that resolves when the event is handled\n */\n async handleFavouriteClick(target) {\n const caller = target.closest(selectors.actions.optionActions.manageFavourite);\n const id = caller.dataset.id;\n const name = caller.dataset.name;\n const internal = caller.dataset.internal;\n const isFavourite = caller.dataset.favourited;\n\n // Switch on fave or not.\n if (isFavourite === 'true') {\n await Repository.unfavouriteModule(name, id);\n this.updateFavouriteItemValue(internal, false);\n } else {\n await Repository.favouriteModule(name, id);\n this.updateFavouriteItemValue(internal, true);\n }\n }\n\n /**\n * Handle a clear search action.\n */\n handleClearSearch() {\n const searchInput = this.dialogueDom.getSearchInputElement();\n searchInput.value = \"\";\n searchInput.focus();\n this.toggleSearchResultsView(searchInput.value);\n }\n\n /**\n * Handle the click on a chooser option.\n *\n * @param {HTMLElement} target The target element that triggered the event\n */\n handleOptionSelection(target) {\n const option = this.dialogueDom.getClosestChooserOption(target);\n if (option === null) {\n return;\n }\n this.selectedModule = option;\n this.dialogueDom.markChooserOptionAsSelected(option, getFirst(this.modal.getFooter()));\n }\n\n /**\n * Submit the selected module to the chooser.\n *\n * This method will redirect the user to the URL of the selected module, if one is selected.\n *\n * @param {HTMLElement} newSelectedModule optional new selected module element.\n * @return {void}\n */\n submitAddSelectedModule(newSelectedModule = null) {\n if (newSelectedModule) {\n this.handleOptionSelection(newSelectedModule);\n }\n if (this.selectedModule === null) {\n return;\n }\n window.location.href = this.dialogueDom.getChooserOptionUrl(this.selectedModule);\n }\n\n /**\n * Set up our tabindex information across the chooser.\n *\n * @method setupKeyboardAccessibility\n */\n setupKeyboardAccessibility() {\n const mainElement = getFirst(this.modal.getModal());\n\n mainElement.tabIndex = -1;\n\n mainElement.addEventListener('keydown', (e) => {\n const currentOption = this.dialogueDom.getClosestChooserOption(e.target);\n if (currentOption === null) {\n return;\n }\n\n if (e.keyCode === enter || e.keyCode === space) {\n // Check first if the target is an internal control button (favourite or help).\n // If that is the case, the regular click will handle the event.\n if (e.target.closest(selectors.regions.chooserOption.actions)) {\n return;\n }\n }\n\n if (e.keyCode === space) {\n e.preventDefault();\n this.handleOptionSelection(currentOption);\n }\n if (e.keyCode === enter) {\n e.preventDefault();\n this.submitAddSelectedModule(currentOption);\n }\n if (e.keyCode === arrowRight) {\n e.preventDefault();\n this.dialogueDom.focusNextChooserOption(currentOption);\n }\n if (e.keyCode === arrowLeft) {\n e.preventDefault();\n this.dialogueDom.focusPreviousChooserOption(currentOption);\n }\n if (e.keyCode === home) {\n e.preventDefault();\n this.dialogueDom.focusFirstChooserOption(currentOption);\n }\n if (e.keyCode === end) {\n e.preventDefault();\n this.dialogueDom.focusLastChooserOption(currentOption);\n }\n });\n }\n\n /**\n * Toggle (display/hide) the search results depending on the value of the search query\n *\n * @method toggleSearchResultsView\n * @param {String} searchQuery The search query\n */\n async toggleSearchResultsView(searchQuery) {\n const searchResultsData = this.searchModules(searchQuery);\n\n if (searchQuery.length > 0) {\n await this.dialogueDom.refreshSearchResults(searchQuery, searchResultsData);\n this.dialogueDom.showAllActivitiesTab(true);\n } else {\n this.dialogueDom.cleanSearchResults();\n }\n }\n\n /**\n * Return the list of modules which have a name or description that matches the given search term.\n *\n * @method searchModules\n * @param {String} searchTerm The search term to match\n * @return {Array}\n */\n searchModules(searchTerm) {\n if (searchTerm === '') {\n return this.mappedModules;\n }\n searchTerm = searchTerm.toLowerCase();\n const searchResults = [];\n this.mappedModules.forEach((activity) => {\n const activityName = activity.title.toLowerCase();\n const activitySummary = activity.summary.toLowerCase();\n const activityDesc = activity.help.toLowerCase();\n if (activityName.includes(searchTerm) || activitySummary.includes(searchTerm) || activityDesc.includes(searchTerm)) {\n searchResults.push(activity);\n }\n });\n\n return searchResults;\n }\n\n /**\n * Update the favourite item value in the mapped modules.\n *\n * @param {String} internal The internal name of the module.\n * @param {Boolean} favourite Whether the module is a favourite or not.\n * @return {Promise} A promise that resolves when the item is updated.\n */\n async updateFavouriteItemValue(internal, favourite) {\n const moduleItem = this.mappedModules.find(({name}) => name === internal);\n if (!moduleItem) {\n return;\n }\n moduleItem.favourite = favourite;\n\n this.dialogueDom.updateItemStarredIcons(internal, favourite);\n\n if (this.dialogueDom.isFavoutiteTabActive()) {\n this.isFavouriteTabDirty = true;\n } else {\n this.refreshFavouritesTabContent();\n }\n }\n\n /**\n * Refresh the favourites tab content.\n *\n * Note: this method will also hide the favourites tab if there are no favourite modules\n * to keep the modal consistent.\n *\n * @return {Promise} A promise that resolves when the content is refreshed.\n */\n async refreshFavouritesTabContent() {\n this.isFavouriteTabDirty = false;\n const favouriteCount = this.mappedModules.filter(mod => mod.favourite === true).size;\n this.dialogueDom.toggleFavouriteTabDisplay(favouriteCount > 0);\n await this.dialogueDom.refreshFavouritesTabContent(this.mappedModules);\n }\n}\n"],"names":["footerDataPromise","modulesDataPromise","bodyPromiseResolver","bodyPromise","Promise","resolve","exporter","Exporter","footerData","footerPromise","Templates","render","getFooterData","sectionModal","Modal","create","body","title","footer","large","scrollable","templateContext","classes","show","modulesData","modal","ActivityChooserDialogue","templateData","getModChooserTemplateData","error","errorTemplateData","message","modalPromise","sectionModules","partialFavourite","window","console","warn","then","catch","Notification","exception","constructor","dialogueDom","selectedModule","isFavouriteTabDirty","mappedModules","Map","forEach","module","set","componentname","link","init","this","getBodyPromise","getFooterPromise","DialogueDom","registerModalListenerEvents","setupKeyboardAccessibility","getRoot","on","ModalEvents","hidden","destroy","unmarkAllChooserOptionAsSelected","modalRoot","addEventListener","event","target","closest","selectors","regions","allTabNav","searchInput","getSearchInputElement","value","length","toggleSearchResultsView","handleModalClick","bind","handleModalDoubleClick","pending","initBootstrapComponents","relatedTarget","disableFocusAllChooserOptions","initActiveTabNavigation","isFavoutiteTabActive","refreshFavouritesTabContent","handleFooterClick","undefined","to","toggleActiveFooter","toggleBackButton","toggleAddButton","actions","addSelectedChooserOption","submitAddSelectedModule","activeFooter","footerjs","pluginName","customfooterjs","footerClickListener","optionActions","showSummary","preventDefault","handleShowSummary","displayCategory","handleDisplayCategory","manageFavourite","handleFavouriteClick","matches","closeOption","hideModuleHelp","dataset","modname","clearSearch","handleClearSearch","chooserOption","info","handleOptionSelection","getClosestChooserOption","moduleName","moduleData","get","showFooter","hasFooterContent","setBackButtonModuleData","showModuleHelp","category","tabNav","showCategoryTab","focus","scrollIntoView","caller","id","name","internal","favourited","Repository","unfavouriteModule","updateFavouriteItemValue","favouriteModule","option","markChooserOptionAsSelected","getFooter","newSelectedModule","location","href","getChooserOptionUrl","mainElement","getModal","tabIndex","e","currentOption","keyCode","enter","space","arrowRight","focusNextChooserOption","arrowLeft","focusPreviousChooserOption","home","focusFirstChooserOption","end","focusLastChooserOption","searchQuery","searchResultsData","searchModules","refreshSearchResults","showAllActivitiesTab","cleanSearchResults","searchTerm","toLowerCase","searchResults","activity","activityName","activitySummary","summary","activityDesc","help","includes","push","favourite","moduleItem","find","_ref","updateItemStarredIcons","favouriteCount","filter","mod","size","toggleFavouriteTabDisplay"],"mappings":"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"} \ No newline at end of file diff --git a/public/course/format/amd/src/local/activitychooser/dialogue.js b/public/course/format/amd/src/local/activitychooser/dialogue.js index 93badad50de..d04a33e3864 100644 --- a/public/course/format/amd/src/local/activitychooser/dialogue.js +++ b/public/course/format/amd/src/local/activitychooser/dialogue.js @@ -516,8 +516,9 @@ class ActivityChooserDialogue { const searchResults = []; this.mappedModules.forEach((activity) => { const activityName = activity.title.toLowerCase(); + const activitySummary = activity.summary.toLowerCase(); const activityDesc = activity.help.toLowerCase(); - if (activityName.includes(searchTerm) || activityDesc.includes(searchTerm)) { + if (activityName.includes(searchTerm) || activitySummary.includes(searchTerm) || activityDesc.includes(searchTerm)) { searchResults.push(activity); } }); diff --git a/public/course/format/templates/local/activitychooser/help.mustache b/public/course/format/templates/local/activitychooser/help.mustache index fed53482101..3b435497159 100644 --- a/public/course/format/templates/local/activitychooser/help.mustache +++ b/public/course/format/templates/local/activitychooser/help.mustache @@ -27,6 +27,7 @@ "link": "http://yourmoodle/modedit.php?id=x&itemtype=y", "icon": "Icon", "help": "This is a description of the assignment activity", + "summary": "This is a summary of the assignment activity", "archetype": 0, "componentname": "mod_assign", "favourite": 1, @@ -51,7 +52,7 @@ data-region="summary-header" tabindex="0" > -
+
{{! Repsonsive divider. }}
- {{! General helps text. }} + {{! General text. }}
- {{{help}}} +
+ {{{summary}}} +
+
+ {{{help}}} +
diff --git a/public/lang/en/moodle.php b/public/lang/en/moodle.php index af2d0dead08..e6b41239589 100644 --- a/public/lang/en/moodle.php +++ b/public/lang/en/moodle.php @@ -2269,6 +2269,7 @@ $string['time'] = 'Time'; $string['timecreated'] = 'Time created'; $string['timecreatedcourse'] = 'Course time created'; $string['timezone'] = 'Timezone'; +$string['tip'] = 'Tip: {$a}'; $string['todate'] = 'To'; $string['torecipient'] = 'To'; $string['tocreatenewaccount'] = 'Skip to create new account'; diff --git a/public/theme/boost/scss/moodle/modchooser.scss b/public/theme/boost/scss/moodle/modchooser.scss index 4ac9f588dd9..fb69cd373ea 100644 --- a/public/theme/boost/scss/moodle/modchooser.scss +++ b/public/theme/boost/scss/moodle/modchooser.scss @@ -240,6 +240,9 @@ border-bottom: $border-width $border-style $border-color; } } + .optionsummary-summary { + font-size: larger; + } } /* Make modchooser modal full height and scrollable on small screens. */ diff --git a/public/theme/boost/style/moodle.css b/public/theme/boost/style/moodle.css index bbb31e348bd..1002c325b32 100644 --- a/public/theme/boost/style/moodle.css +++ b/public/theme/boost/style/moodle.css @@ -31927,6 +31927,9 @@ table.calendartable caption { padding-bottom: 1.5rem; border-bottom: 1px solid #dee2e6; } +.modchooser .optionsummary .optionsummary-summary { + font-size: larger; +} /* Make modchooser modal full height and scrollable on small screens. */ @media (max-width: 575.98px) { diff --git a/public/theme/classic/style/moodle.css b/public/theme/classic/style/moodle.css index 0deeb89a847..d75c53deeb1 100644 --- a/public/theme/classic/style/moodle.css +++ b/public/theme/classic/style/moodle.css @@ -31927,6 +31927,9 @@ table.calendartable caption { padding-bottom: 1.5rem; border-bottom: 1px solid #dee2e6; } +.modchooser .optionsummary .optionsummary-summary { + font-size: larger; +} /* Make modchooser modal full height and scrollable on small screens. */ @media (max-width: 575.98px) {