ÿØÿà JFIF    ÿÛ „  ( %!1"%)+...383-7(-.+  -%%--------+------------/----------------------------ÿÀ  ¶" ÿÄ     ÿÄ @    !1AQaq‘¡"±ÁÑð2’BRráS‚ñ3b¢CT²Ò #cÿÄ    ÿÄ '   1!AQ2a"‘±#ÿÚ   ? ò(/p‘—–¢·,M|¯G»R£üešT. ôŸEoCFñ‡ 4ÔkåóxR|µÁ¨ÓDà!Ç´µ˜‡p ÍQ‹§!þž^õWsŒy  åÁ§$á«%ºq˜™JyyË0y«^Ϗ -3ÃÏËÈ£Æ\P’ä„s&•‹Üs üô'àWt¹pñCp$bÚF`·7.^)¾!)ÒÙ˜–ÅÏš^h¾ø³·ìe±ŒaeÖ—Í,e=B}Jéߦ$$½—šÙÜCC¯¶è8β–Åkèwx LÉngT±æ¹£Ócœ¿Í/©Éÿ ‰ÚCÒ­mq;Tt¶–}0"zÖPcd1š­¤tˆ„ÐÓâqú[™öUCŠ2޿د­É(¸ßúçšmW,Ò ºf‹Ån™ÄLêÁ8üÁkÁ€L¥ÍuJ1œôrÂSŒÓöt!7EUíïb)AËi¬â-ÙHk8òÁiXYwÅ)O^"ÇØriÛô{»wRUKÙÏÚlÓ¬€‚’שT ] ªb—ÊîKC^×M“xùäñz¬zJ× {4[¹M'IÆeH1LCWÕ]œûº WDæà°OÅ‚¹c²ÞuA–rL`HšpCez‡V’— Ž‚Ë„–ñ­'µR0ÕÖ=À:±G»C\nÆÉ5*¢ †3šhQ4ýÒi$Õ1"Ü]¢p-q3zcQ$ªö¨¥æf›‘»´ÝÚHá„e²JÇ–iÊ:·à¬Øc5Åk»Laª*wi [0Ówk ¦a¦0Õ²ÅÅ‚T¸’³q:Æ<¹ú Ò˜Æ}2VE•Ѐp¡Ô3Úº …h³^LJºçì¥Ánó|˜ÖˆÎˆ+« ¦dø‘Mº–d\ªDðÛ„Ì×LÝ——Ð9Ç|•FèçVm™¨€™3õS–9Äå¢<̉Ìm¢±`·º˜&Z²ž Ë®D«¶»6™‰©­HÈ Àá«h­i³04æ’hxœi3ï¨(ÓL·†‹¶ËpŒ ƒÜÒ? :õdsÀ –3æÓ·Üjâ¬9—]8n¼&]I F£¼…ì–—Œ0v°LǐÅfÛ2TÖ’aµ˜N@ÖBSÏ^à´4-±°b×nÈÈœÆD=KdŒø…ÄÈK,›ÖR§fjü$^ÔÎ!„6^ s:Üun\ôkCÞò÷<—Lúl Í›G¹ÀPá¼Nc¡šÒ…ÙWͳœ±vÊáЄ×9‹P‰cEØ‘™›µŸP<×g1 º©Ø¬wiÁ^l5× I/';Ë+´Z‡ÓÄFÕp[Èð’ HÄ ó 9¬Dl4“é±Ï”V=^Hð;žLädÉa‚°Ë,#/Ž~²BÔÛ QãñQtIeórVB,ÏÂf‹4#Μc‹É3ZIÖ Œ¢¥·üxa‡Ãçè„jŠ[5 Ä!U?,3žÎ×…ðdÓ÷háŠA‰ìÄ4÷ÃÜZÃE{‰®+W\ZÍE[‰»µk»MÝ­`¢©†˜ÃV‹-f¢¡†¢a«eŠ%«Y¨©q%jâd,Ôq`) ¤¦…šˆ ¦E>0Ùb²t–Ÿ  ÷jsςҒKÈcø(é›;k(’#ôŒQ ™C> õ³M9óºÆµ¤çâ;uÀ)5>RÔ¸fÓ~Ȧ—’-$fe«"FâUˆÊwµPÏu w+"åÉó¨6s™Û9(À¨ù©hE°½†n`m~“@©›p!Vkhµ Á1šbEqqÊ@ºèl›2 –Yã%‘‚hÛ{a¶‡ÅÞM²¬çL34®Çl]®‹ŽØŒm'RÜe35æöˆ­Á­ cP9šeÁköo´L³Ì=p8]”‡B¶,šº|ɏdzXˆØk*ÇÚ{#ñ‰pã(€¶‡9ý'šÜ³½¯kƒ†Â’ëSOƒ•Å®H6+a¢µˆ‹Y¨b˜b+XˆÖ!°u!¢"µˆˆn2ˆ Å0Ä`Å Ô®c(ÓÜGº•Ä»Œ âWî%q.áÐÄ÷î%umÍ£+ÜQ,Vn¦-GphU¸¢X­&,Gt ·K¢ÅÅ·¥RÄÊÉbKn N­R2g$VµÕó Qr#–ÓZUÏð0ÎtÙÀœz ëöo“Îk¯n„T°"SÞ+³/…€½"ižãO%Í%&Δâ‘ËØ´IˆÓ°Ë‰ÞM3.™yçÁzit›`4C`k¢¼á!álÄï sÂ[×!¤l¾æo"ƒIÔ'@ªu)3›ô®“@Z à¾] üœ'‡4¸PÞkÁ-qÄH‰ÓÍBd´ÿ —¹°ÄA&ÊUC„öz‚‚RæŠtf¬>˜ÑidšEçÑL÷áý¡V±h§­c¿¥¨ƒ@g¼…Ýö^ÞËc.ž„æ“I[ ª+·zÐÒÚ,½Ð»¶€C›7jk$๖€Ž¾ÅÙpp¶ÍþéΤ ½ØÎŒq ñpæžÅ¥#Øâ49ÃÂK^MYV“,¶Éz_òæ:…;Õ«¦I3âJ£¤û5$7Èxün\â]Êe¯(Ÿ&Ž‹¶2<6Ćf× æ¢ƱqWı[(ÙÂŽç‚T7¨@ÈÉÍß‚ïÙ"&3TYlGށ†)†#5ŠA©^T2ÆÁ†)©†©†¤yGXÁ†©"†)©¼ÅV WS†#©]R–r‹q5ÕbâWþBh¯u+ªÅÄ×ü„nÑ^êkªÁj‰jeœWˆÕÕ`µDµ2Ì+Ä µDµµ5Ô{¤Þ0RF’H÷AÛ<ÊÁ§ ƈæ0Ñ£ê4 öÚµ[¬/1²€â<-¬šö²r×0o×w ´xÐÒË„ g01¬„åEM蛇Ã\6µE›‘fÙO k÷CºnÓ1`iîÒ² áÂ!ÑLÄÿ ³¬\õ*:M=Ç»c‹2[‰ØsÕLò\ŒK L^™üÄN\¶#» Ç\……ßïIœÉ$¸ÎñÍ®ÔHÇ Ûí/´>Li”„†g^$ 7NÍÑÑ2M2ªNg=k½Ð}Ÿl6‡ —JWˆ”ÞéT·+µ¦üÂFè¢Vs6ýÆ4»2+2IÝ9ô9®ÇGÙÇ1+Ä8;*Õ§ž½D*îÑWÚçá3Ma¤·ˆ¥t}š²Ü…Q+Æüµ8HíS”üxGéÁˆ¦Ël„éÈ 5 µÄƒ6äÜ(kIš¯S†fÖ&¸®×v|Äyˆ*禌Éù@7-®Ä[]ÎÖ¿ê`º…=:…žJVnݳvðÖKE¼‚ »mà ÷f®:Ë ™–4rð¡4` ܹçž~‹C=œ…¾Ïj|VDtK.ƒ"9 Ï ­–éXá³6WÒïIL-Ї Ç PK\ܔrepository.min.jsnu[define("core_grades/searchwidget/repository",["exports","core/ajax"],(function(_exports,_ajax){var obj; /** * A repo for the search widget. * * @module core_grades/searchwidget/repository * @copyright 2022 Mathew May * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.userFetch=_exports.gradeitemFetch=void 0,_ajax=(obj=_ajax)&&obj.__esModule?obj:{default:obj};_exports.gradeitemFetch=courseid=>{const request={methodname:"gradereport_singleview_get_grade_items_for_search_widget",args:{courseid:courseid}};return _ajax.default.call([request])[0]};_exports.userFetch=(courseid,groupId)=>{const request={methodname:"core_grades_get_enrolled_users_for_selector",args:{courseid:courseid,groupid:groupId}};return _ajax.default.call([request])[0]}})); //# sourceMappingURL=repository.min.js.mapPK\c,((initials.min.js.mapnu[{"version":3,"file":"initials.min.js","sources":["../../src/searchwidget/initials.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 small dropdown to filter users within the gradebook.\n *\n * @module core_grades/searchwidget/initials\n * @copyright 2022 Mathew May \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n * @deprecated since Moodle 4.5 - please use core_course/actionbar/initials instead.\n * @todo Final deprecation in Moodle 6.0. See MDL-82421.\n */\n\nimport Pending from 'core/pending';\nimport * as Url from 'core/url';\nimport CustomEvents from \"core/custom_interaction_events\";\nimport Dropdown from 'theme_boost/bootstrap/dropdown';\n\n/**\n * Whether the event listener has already been registered for this module.\n *\n * @type {boolean}\n */\nlet registered = false;\n\n// Contain our selectors within this file until they could be of use elsewhere.\nconst selectors = {\n pageListItem: 'page-item',\n pageClickableItem: '.page-link',\n activeItem: 'active',\n formDropdown: '.initialsdropdownform',\n parentDomNode: '.initials-selector',\n firstInitial: 'firstinitial',\n lastInitial: 'lastinitial',\n initialBars: '.initialbar', // Both first and last name use this class.\n targetButton: 'initialswidget',\n formItems: {\n type: 'submit',\n save: 'save',\n cancel: 'cancel'\n }\n};\n\n/**\n * Our initial hook into the module which will eventually allow us to handle the dropdown initials bar form.\n *\n * @param {String} callingLink The link to redirect upon form submission.\n * @param {Null|Number} gpr_userid The user id to filter by.\n * @param {Null|String} gpr_search The search value to filter by.\n */\nexport const init = (callingLink, gpr_userid = null, gpr_search = null) => {\n if (registered) {\n return;\n }\n const pendingPromise = new Pending();\n registerListenerEvents(callingLink, gpr_userid, gpr_search);\n // BS events always bubble so, we need to listen for the event higher up the chain.\n document.querySelector(selectors.parentDomNode).addEventListener('shown.bs.dropdown', () => {\n document.querySelector(selectors.pageClickableItem).focus({preventScroll: true});\n });\n pendingPromise.resolve();\n registered = true;\n};\n\n/**\n * Register event listeners.\n *\n * @param {String} callingLink The link to redirect upon form submission.\n * @param {Null|Number} gpr_userid The user id to filter by.\n * @param {Null|String} gpr_search The search value to filter by.\n */\nconst registerListenerEvents = (callingLink, gpr_userid = null, gpr_search = null) => {\n const events = [\n 'click',\n CustomEvents.events.activate,\n CustomEvents.events.keyboardActivate\n ];\n CustomEvents.define(document, events);\n\n // Register events.\n events.forEach((event) => {\n document.addEventListener(event, (e) => {\n // Always fetch the latest information when we click as state is a fickle thing.\n let {firstActive, lastActive, sifirst, silast} = onClickVariables();\n let itemToReset = '';\n\n // Prevent the usual form behaviour.\n if (e.target.closest(selectors.formDropdown)) {\n e.preventDefault();\n }\n\n // Handle the state of active initials before form submission.\n if (e.target.closest(`${selectors.formDropdown} .${selectors.pageListItem}`)) {\n // Ensure the li items don't cause weird clicking emptying out the form.\n if (e.target.classList.contains(selectors.pageListItem)) {\n return;\n }\n\n const initialsBar = e.target.closest(selectors.initialBars); // Find out which initial bar we are in.\n\n // We want to find the current active item in the menu area the user selected.\n // We also want to fetch the raw item out of the array for instant manipulation.\n if (initialsBar.classList.contains(selectors.firstInitial)) {\n sifirst = e.target;\n itemToReset = firstActive;\n } else {\n silast = e.target;\n itemToReset = lastActive;\n }\n swapActiveItems(itemToReset, e);\n }\n\n // Handle form submissions.\n if (e.target.closest(`${selectors.formDropdown}`) && e.target.type === selectors.formItems.type) {\n if (e.target.dataset.action === selectors.formItems.save) {\n // Ensure we strip out the value (All) as it messes with the PHP side of the initials bar.\n // Then we will redirect the user back onto the page with new filters applied.\n const params = {\n 'id': e.target.closest(selectors.formDropdown).dataset.courseid,\n 'gpr_search': gpr_search !== null ? gpr_search : '',\n 'sifirst': sifirst.parentElement.classList.contains('initialbarall') ? '' : sifirst.value,\n 'silast': silast.parentElement.classList.contains('initialbarall') ? '' : silast.value,\n };\n if (gpr_userid !== null) {\n params.gpr_userid = gpr_userid;\n }\n window.location = Url.relativeUrl(callingLink, params);\n }\n if (e.target.dataset.action === selectors.formItems.cancel) {\n Dropdown.getOrCreateInstance(document.querySelector(`.${selectors.targetButton}`)).toggle();\n }\n }\n });\n });\n};\n\n/**\n * A small abstracted helper function which allows us to ensure we have up-to-date lists of nodes.\n *\n * @returns {{firstActive: HTMLElement, lastActive: HTMLElement, sifirst: ?String, silast: ?String}}\n */\nconst onClickVariables = () => {\n // Ensure we have an up-to-date initials bar.\n const firstItems = [...document.querySelectorAll(`.${selectors.firstInitial} li`)];\n const lastItems = [...document.querySelectorAll(`.${selectors.lastInitial} li`)];\n const firstActive = firstItems.filter((item) => item.classList.contains(selectors.activeItem))[0];\n const lastActive = lastItems.filter((item) => item.classList.contains(selectors.activeItem))[0];\n // Ensure we retain both of the selections from a previous instance.\n let sifirst = firstActive.querySelector(selectors.pageClickableItem);\n let silast = lastActive.querySelector(selectors.pageClickableItem);\n return {firstActive, lastActive, sifirst, silast};\n};\n\n/**\n * Given we are provided the old li and current click event, swap around the active properties.\n *\n * @param {HTMLElement} itemToReset\n * @param {Event} e\n */\nconst swapActiveItems = (itemToReset, e) => {\n itemToReset.classList.remove(selectors.activeItem);\n itemToReset.querySelector(selectors.pageClickableItem).ariaCurrent = false;\n\n // Set the select item as the current item.\n const itemToSetActive = e.target.parentElement;\n itemToSetActive.classList.add(selectors.activeItem);\n e.target.ariaCurrent = true;\n};\n"],"names":["registered","selectors","type","save","cancel","callingLink","gpr_userid","gpr_search","pendingPromise","Pending","registerListenerEvents","document","querySelector","addEventListener","focus","preventScroll","resolve","events","CustomEvents","activate","keyboardActivate","define","forEach","event","e","firstActive","lastActive","sifirst","silast","onClickVariables","itemToReset","target","closest","preventDefault","classList","contains","swapActiveItems","dataset","action","params","courseid","parentElement","value","window","location","Url","relativeUrl","getOrCreateInstance","toggle","firstItems","querySelectorAll","lastItems","filter","item","remove","ariaCurrent","add"],"mappings":";;;;;;;;;g5BAmCIA,YAAa,QAGXC,uBACY,YADZA,4BAEiB,aAFjBA,qBAGU,SAHVA,uBAIY,wBAJZA,wBAKa,qBALbA,uBAMY,eANZA,sBAOW,cAPXA,sBAQW,cARXA,uBASY,iBATZA,oBAUS,CACPC,KAAM,SACNC,KAAM,OACNC,OAAQ,wBAWI,SAACC,iBAAaC,kEAAa,KAAMC,kEAAa,QAC1DP,wBAGEQ,eAAiB,IAAIC,iBAC3BC,uBAAuBL,YAAaC,WAAYC,YAEhDI,SAASC,cAAcX,yBAAyBY,iBAAiB,qBAAqB,KAClFF,SAASC,cAAcX,6BAA6Ba,MAAM,CAACC,eAAe,OAE9EP,eAAeQ,UACfhB,YAAa,SAUXU,uBAAyB,SAACL,iBAAaC,kEAAa,KAAMC,kEAAa,WACnEU,OAAS,CACX,QACAC,mCAAaD,OAAOE,SACpBD,mCAAaD,OAAOG,qDAEXC,OAAOV,SAAUM,QAG9BA,OAAOK,SAASC,QACZZ,SAASE,iBAAiBU,OAAQC,QAE1BC,YAACA,YAADC,WAAcA,WAAdC,QAA0BA,QAA1BC,OAAmCA,QAAUC,mBAC7CC,YAAc,MAGdN,EAAEO,OAAOC,QAAQ/B,yBACjBuB,EAAES,iBAIFT,EAAEO,OAAOC,kBAAW/B,oCAA2BA,yBAA2B,IAEtEuB,EAAEO,OAAOG,UAAUC,SAASlC,+BAIZuB,EAAEO,OAAOC,QAAQ/B,uBAIrBiC,UAAUC,SAASlC,yBAC/B0B,QAAUH,EAAEO,OACZD,YAAcL,cAEdG,OAASJ,EAAEO,OACXD,YAAcJ,YAElBU,gBAAgBN,YAAaN,MAI7BA,EAAEO,OAAOC,kBAAW/B,0BAA6BuB,EAAEO,OAAO7B,OAASD,oBAAoBC,KAAM,IACzFsB,EAAEO,OAAOM,QAAQC,SAAWrC,oBAAoBE,KAAM,OAGhDoC,OAAS,IACLf,EAAEO,OAAOC,QAAQ/B,wBAAwBoC,QAAQG,oBAC1B,OAAfjC,WAAsBA,WAAa,WACtCoB,QAAQc,cAAcP,UAAUC,SAAS,iBAAmB,GAAKR,QAAQe,aAC1Ed,OAAOa,cAAcP,UAAUC,SAAS,iBAAmB,GAAKP,OAAOc,OAElE,OAAfpC,aACAiC,OAAOjC,WAAaA,YAExBqC,OAAOC,SAAWC,IAAIC,YAAYzC,YAAakC,QAE/Cf,EAAEO,OAAOM,QAAQC,SAAWrC,oBAAoBG,0BACvC2C,oBAAoBpC,SAASC,yBAAkBX,0BAA2B+C,iBAYjGnB,iBAAmB,WAEfoB,WAAa,IAAItC,SAASuC,4BAAqBjD,gCAC/CkD,UAAY,IAAIxC,SAASuC,4BAAqBjD,+BAC9CwB,YAAcwB,WAAWG,QAAQC,MAASA,KAAKnB,UAAUC,SAASlC,wBAAuB,GACzFyB,WAAayB,UAAUC,QAAQC,MAASA,KAAKnB,UAAUC,SAASlC,wBAAuB,OAEzF0B,QAAUF,YAAYb,cAAcX,6BACpC2B,OAASF,WAAWd,cAAcX,mCAC/B,CAACwB,YAAAA,YAAaC,WAAAA,WAAYC,QAAAA,QAASC,OAAAA,SASxCQ,gBAAkB,CAACN,YAAaN,KAClCM,YAAYI,UAAUoB,OAAOrD,sBAC7B6B,YAAYlB,cAAcX,6BAA6BsD,aAAc,EAG7C/B,EAAEO,OAAOU,cACjBP,UAAUsB,IAAIvD,sBAC9BuB,EAAEO,OAAOwB,aAAc"}PK\ repository.min.js.mapnu[{"version":3,"file":"repository.min.js","sources":["../../src/searchwidget/repository.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 repo for the search widget.\n *\n * @module core_grades/searchwidget/repository\n * @copyright 2022 Mathew May \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport ajax from 'core/ajax';\n\n/**\n * Given a course ID, we want to fetch the gradable items, so we may fetch reports based on activity items.\n * Note: This will be worked upon in the single view issue.\n *\n * @method gradeitemFetch\n * @param {int} courseid ID of the course to fetch the users of.\n * @return {object} jQuery promise\n */\nexport const gradeitemFetch = (courseid) => {\n const request = {\n methodname: 'gradereport_singleview_get_grade_items_for_search_widget',\n args: {\n courseid: courseid,\n },\n };\n return ajax.call([request])[0];\n};\n\n/**\n * Given a course ID, we want to fetch the enrolled learners, so we may fetch their reports.\n *\n * @method userFetch\n * @param {int} courseid ID of the course to fetch the users of.\n * @param {int} groupId ID of the group to fetch the users of.\n * @return {object} jQuery promise\n */\nexport const userFetch = (courseid, groupId) => {\n const request = {\n methodname: 'core_grades_get_enrolled_users_for_selector',\n args: {\n courseid: courseid,\n groupid: groupId,\n },\n };\n return ajax.call([request])[0];\n};\n"],"names":["courseid","request","methodname","args","ajax","call","groupId","groupid"],"mappings":";;;;;;;2LAiC+BA,iBACrBC,QAAU,CACZC,WAAY,2DACZC,KAAM,CACFH,SAAUA,kBAGXI,cAAKC,KAAK,CAACJ,UAAU,uBAWP,CAACD,SAAUM,iBAC1BL,QAAU,CACZC,WAAY,8CACZC,KAAM,CACFH,SAAUA,SACVO,QAASD,iBAGVF,cAAKC,KAAK,CAACJ,UAAU"}PK\g9selectors.min.js.mapnu[{"version":3,"file":"selectors.min.js","sources":["../../src/searchwidget/selectors.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * Selectors for the search widget.\n *\n * @module core_grades/searchwidget/selectors\n * @copyright 2022 Mihail Geshoski \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nexport default {\n regions: {\n searchResults: '[data-region=\"search-results-container-widget\"]',\n unsearchableContent: '[data-region=\"unsearchable-content-container-widget\"]',\n },\n actions: {\n search: '[data-action=\"search\"]',\n clearSearch: '[data-action=\"clearsearch\"]',\n },\n elements: {\n getSearchWidgetSelector: searchtype => `.search-widget[data-searchtype=\"${searchtype}\"]`,\n getSearchWidgetDropdownSelector: searchtype => `.search-widget[data-searchtype=\"${searchtype}\"] .dropdown-menu`,\n getSearchWidgetSelectOption:\n searchInput => `#${searchInput.getAttribute('aria-controls')} [role=\"option\"][aria-selected=\"true\"]`,\n },\n};\n"],"names":["regions","searchResults","unsearchableContent","actions","search","clearSearch","elements","getSearchWidgetSelector","searchtype","getSearchWidgetDropdownSelector","getSearchWidgetSelectOption","searchInput","getAttribute"],"mappings":"oLAuBe,CACXA,QAAS,CACLC,cAAe,kDACfC,oBAAqB,yDAEzBC,QAAS,CACLC,OAAQ,yBACRC,YAAa,+BAEjBC,SAAU,CACNC,wBAAyBC,sDAAiDA,iBAC1EC,gCAAiCD,sDAAiDA,gCAClFE,4BACIC,wBAAmBA,YAAYC,aAAa"}PK\giinitials.min.jsnu[define("core_grades/searchwidget/initials",["exports","core/pending","core/url","core/custom_interaction_events","theme_boost/bootstrap/dropdown"],(function(_exports,_pending,Url,_custom_interaction_events,_dropdown){function _getRequireWildcardCache(nodeInterop){if("function"!=typeof WeakMap)return null;var cacheBabelInterop=new WeakMap,cacheNodeInterop=new WeakMap;return(_getRequireWildcardCache=function(nodeInterop){return nodeInterop?cacheNodeInterop:cacheBabelInterop})(nodeInterop)}function _interopRequireDefault(obj){return obj&&obj.__esModule?obj:{default:obj}} /** * A small dropdown to filter users within the gradebook. * * @module core_grades/searchwidget/initials * @copyright 2022 Mathew May * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later * @deprecated since Moodle 4.5 - please use core_course/actionbar/initials instead. * @todo Final deprecation in Moodle 6.0. See MDL-82421. */Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.init=void 0,_pending=_interopRequireDefault(_pending),Url=function(obj,nodeInterop){if(!nodeInterop&&obj&&obj.__esModule)return obj;if(null===obj||"object"!=typeof obj&&"function"!=typeof obj)return{default:obj};var cache=_getRequireWildcardCache(nodeInterop);if(cache&&cache.has(obj))return cache.get(obj);var newObj={},hasPropertyDescriptor=Object.defineProperty&&Object.getOwnPropertyDescriptor;for(var key in obj)if("default"!==key&&Object.prototype.hasOwnProperty.call(obj,key)){var desc=hasPropertyDescriptor?Object.getOwnPropertyDescriptor(obj,key):null;desc&&(desc.get||desc.set)?Object.defineProperty(newObj,key,desc):newObj[key]=obj[key]}newObj.default=obj,cache&&cache.set(obj,newObj);return newObj}(Url),_custom_interaction_events=_interopRequireDefault(_custom_interaction_events),_dropdown=_interopRequireDefault(_dropdown);let registered=!1;const selectors_pageListItem="page-item",selectors_pageClickableItem=".page-link",selectors_activeItem="active",selectors_formDropdown=".initialsdropdownform",selectors_parentDomNode=".initials-selector",selectors_firstInitial="firstinitial",selectors_lastInitial="lastinitial",selectors_initialBars=".initialbar",selectors_targetButton="initialswidget",selectors_formItems={type:"submit",save:"save",cancel:"cancel"};_exports.init=function(callingLink){let gpr_userid=arguments.length>1&&void 0!==arguments[1]?arguments[1]:null,gpr_search=arguments.length>2&&void 0!==arguments[2]?arguments[2]:null;if(registered)return;const pendingPromise=new _pending.default;registerListenerEvents(callingLink,gpr_userid,gpr_search),document.querySelector(selectors_parentDomNode).addEventListener("shown.bs.dropdown",(()=>{document.querySelector(selectors_pageClickableItem).focus({preventScroll:!0})})),pendingPromise.resolve(),registered=!0};const registerListenerEvents=function(callingLink){let gpr_userid=arguments.length>1&&void 0!==arguments[1]?arguments[1]:null,gpr_search=arguments.length>2&&void 0!==arguments[2]?arguments[2]:null;const events=["click",_custom_interaction_events.default.events.activate,_custom_interaction_events.default.events.keyboardActivate];_custom_interaction_events.default.define(document,events),events.forEach((event=>{document.addEventListener(event,(e=>{let{firstActive:firstActive,lastActive:lastActive,sifirst:sifirst,silast:silast}=onClickVariables(),itemToReset="";if(e.target.closest(selectors_formDropdown)&&e.preventDefault(),e.target.closest("".concat(selectors_formDropdown," .").concat(selectors_pageListItem))){if(e.target.classList.contains(selectors_pageListItem))return;e.target.closest(selectors_initialBars).classList.contains(selectors_firstInitial)?(sifirst=e.target,itemToReset=firstActive):(silast=e.target,itemToReset=lastActive),swapActiveItems(itemToReset,e)}if(e.target.closest("".concat(selectors_formDropdown))&&e.target.type===selectors_formItems.type){if(e.target.dataset.action===selectors_formItems.save){const params={id:e.target.closest(selectors_formDropdown).dataset.courseid,gpr_search:null!==gpr_search?gpr_search:"",sifirst:sifirst.parentElement.classList.contains("initialbarall")?"":sifirst.value,silast:silast.parentElement.classList.contains("initialbarall")?"":silast.value};null!==gpr_userid&&(params.gpr_userid=gpr_userid),window.location=Url.relativeUrl(callingLink,params)}e.target.dataset.action===selectors_formItems.cancel&&_dropdown.default.getOrCreateInstance(document.querySelector(".".concat(selectors_targetButton))).toggle()}}))}))},onClickVariables=()=>{const firstItems=[...document.querySelectorAll(".".concat(selectors_firstInitial," li"))],lastItems=[...document.querySelectorAll(".".concat(selectors_lastInitial," li"))],firstActive=firstItems.filter((item=>item.classList.contains(selectors_activeItem)))[0],lastActive=lastItems.filter((item=>item.classList.contains(selectors_activeItem)))[0];let sifirst=firstActive.querySelector(selectors_pageClickableItem),silast=lastActive.querySelector(selectors_pageClickableItem);return{firstActive:firstActive,lastActive:lastActive,sifirst:sifirst,silast:silast}},swapActiveItems=(itemToReset,e)=>{itemToReset.classList.remove(selectors_activeItem),itemToReset.querySelector(selectors_pageClickableItem).ariaCurrent=!1;e.target.parentElement.classList.add(selectors_activeItem),e.target.ariaCurrent=!0}})); //# sourceMappingURL=initials.min.js.mapPK\kiGGselectors.min.jsnu[define("core_grades/searchwidget/selectors",["exports"],(function(_exports){Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.default=void 0;return _exports.default={regions:{searchResults:'[data-region="search-results-container-widget"]',unsearchableContent:'[data-region="unsearchable-content-container-widget"]'},actions:{search:'[data-action="search"]',clearSearch:'[data-action="clearsearch"]'},elements:{getSearchWidgetSelector:searchtype=>'.search-widget[data-searchtype="'.concat(searchtype,'"]'),getSearchWidgetDropdownSelector:searchtype=>'.search-widget[data-searchtype="'.concat(searchtype,'"] .dropdown-menu'),getSearchWidgetSelectOption:searchInput=>"#".concat(searchInput.getAttribute("aria-controls"),' [role="option"][aria-selected="true"]')}},_exports.default})); //# sourceMappingURL=selectors.min.js.mapPK\088basewidget.min.js.mapnu[{"version":3,"file":"basewidget.min.js","sources":["../../src/searchwidget/basewidget.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 widget to search users or grade items within the gradebook.\n *\n * @module core_grades/searchwidget/basewidget\n * @copyright 2022 Mathew May \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\nimport {debounce} from 'core/utils';\nimport * as Templates from 'core/templates';\nimport * as Selectors from 'core_grades/searchwidget/selectors';\nimport Notification from 'core/notification';\nimport Log from 'core/log';\n\n/**\n * Build the base searching widget.\n *\n * @method init\n * @param {HTMLElement} widgetContentContainer The selector for the widget container element.\n * @param {Promise} bodyPromise The promise from the callee of the contents to place in the widget container.\n * @param {Array} data An array of all the data generated by the callee.\n * @param {Function} searchFunc Partially applied function we need to manage search the passed dataset.\n * @param {string|null} unsearchableContent The content rendered in a non-searchable area.\n * @param {Function|null} afterSelect Callback executed after an item is selected.\n */\nexport const init = async(\n widgetContentContainer,\n bodyPromise,\n data,\n searchFunc,\n unsearchableContent = null,\n afterSelect = null,\n) => {\n Log.debug('The core_grades/searchwidget/basewidget component is deprecated. Please refer to core/search_combobox() instead.');\n bodyPromise.then(async(bodyContent) => {\n // Render the body content.\n widgetContentContainer.innerHTML = bodyContent;\n\n // Render the unsearchable content if defined.\n if (unsearchableContent) {\n const unsearchableContentContainer = widgetContentContainer.querySelector(Selectors.regions.unsearchableContent);\n unsearchableContentContainer.innerHTML += unsearchableContent;\n }\n\n const searchResultsContainer = widgetContentContainer.querySelector(Selectors.regions.searchResults);\n // Display a loader until the search results are rendered.\n await showLoader(searchResultsContainer);\n // Render the search results.\n await renderSearchResults(searchResultsContainer, data);\n\n registerListenerEvents(widgetContentContainer, data, searchFunc, afterSelect);\n\n }).catch(Notification.exception);\n};\n\n/**\n * Register the event listeners for the search widget.\n *\n * @method registerListenerEvents\n * @param {HTMLElement} widgetContentContainer The selector for the widget container element.\n * @param {Array} data An array of all the data generated by the callee.\n * @param {Function} searchFunc Partially applied function we need to manage search the passed dataset.\n * @param {Function|null} afterSelect Callback executed after an item is selected.\n */\nexport const registerListenerEvents = (widgetContentContainer, data, searchFunc, afterSelect = null) => {\n const searchResultsContainer = widgetContentContainer.querySelector(Selectors.regions.searchResults);\n const searchInput = widgetContentContainer.querySelector(Selectors.actions.search);\n\n if (!searchInput) {\n // Too late. The widget is already closed and its content is empty.\n return;\n }\n\n // We want to focus on the first known user interable element within the dropdown.\n searchInput.focus();\n const clearSearchButton = widgetContentContainer.querySelector(Selectors.actions.clearSearch);\n\n // The search input is triggered.\n searchInput.addEventListener('input', debounce(async() => {\n // If search query is present display the 'clear search' button, otherwise hide it.\n if (searchInput.value.length > 0) {\n clearSearchButton.classList.remove('d-none');\n } else {\n clearSearchButton.classList.add('d-none');\n }\n // Remove aria-activedescendant when the available options change.\n searchInput.removeAttribute('aria-activedescendant');\n // Display the search results.\n await renderSearchResults(\n searchResultsContainer,\n debounceCallee(\n searchInput.value,\n data,\n searchFunc()\n )\n );\n }, 300));\n\n // Clear search is triggered.\n clearSearchButton.addEventListener('click', async(e) => {\n e.stopPropagation();\n // Clear the entered search query in the search bar.\n searchInput.value = \"\";\n searchInput.focus();\n clearSearchButton.classList.add('d-none');\n\n // Remove aria-activedescendant when the available options change.\n searchInput.removeAttribute('aria-activedescendant');\n\n // Display all results.\n await renderSearchResults(\n searchResultsContainer,\n debounceCallee(\n searchInput.value,\n data,\n searchFunc()\n )\n );\n });\n\n const inputElement = document.getElementById(searchInput.dataset.inputElement);\n if (inputElement && afterSelect) {\n inputElement.addEventListener('change', e => {\n const selectedOption = widgetContentContainer.querySelector(\n Selectors.elements.getSearchWidgetSelectOption(searchInput),\n );\n\n if (selectedOption) {\n afterSelect(e.target.value);\n }\n });\n }\n\n // Backward compatibility. Handle the click event for the following cases:\n // - When we have
  • tags without an afterSelect callback function being provided (old js).\n // - When we have tags without href (old template).\n widgetContentContainer.addEventListener('click', e => {\n const deprecatedOption = e.target.closest(\n 'a.dropdown-item[role=\"menuitem\"]:not([href]), .dropdown-item[role=\"option\"]:not([href])'\n );\n if (deprecatedOption) {\n // We are in one of these situations:\n // - We have
  • tags without an afterSelect callback function being provided.\n // - We have tags without href.\n if (inputElement && afterSelect) {\n afterSelect(deprecatedOption.dataset.value);\n } else {\n const url = (data.find(object => object.id == deprecatedOption.dataset.value) || {url: ''}).url;\n location.href = url;\n }\n }\n });\n\n // Backward compatibility. Handle the keydown event for the following cases:\n // - When we have
  • tags without an afterSelect callback function being provided (old js).\n // - When we have tags without href (old template).\n widgetContentContainer.addEventListener('keydown', e => {\n const deprecatedOption = e.target.closest(\n 'a.dropdown-item[role=\"menuitem\"]:not([href]), .dropdown-item[role=\"option\"]:not([href])'\n );\n if (deprecatedOption && (e.key === ' ' || e.key === 'Enter')) {\n // We are in one of these situations:\n // - We have
  • tags without an afterSelect callback function being provided.\n // - We have tags without href.\n e.preventDefault();\n if (inputElement && afterSelect) {\n afterSelect(deprecatedOption.dataset.value);\n } else {\n const url = (data.find(object => object.id == deprecatedOption.dataset.value) || {url: ''}).url;\n location.href = url;\n }\n }\n });\n};\n\n/**\n * Renders the loading placeholder for the search widget.\n *\n * @method showLoader\n * @param {HTMLElement} container The DOM node where we'll render the loading placeholder.\n */\nexport const showLoader = async(container) => {\n container.innerHTML = '';\n const {html, js} = await Templates.renderForPromise('core_grades/searchwidget/loading', {});\n Templates.replaceNodeContents(container, html, js);\n};\n\n/**\n * We have a small helper that'll call the curried search function allowing callers to filter\n * the data set however we want rather than defining how data must be filtered.\n *\n * @method debounceCallee\n * @param {String} searchValue The input from the user that we'll search against.\n * @param {Array} data An array of all the data generated by the callee.\n * @param {Function} searchFunction Partially applied function we need to manage search the passed dataset.\n * @return {Array} The filtered subset of the provided data that we'll then render into the results.\n */\nconst debounceCallee = (searchValue, data, searchFunction) => {\n if (searchValue.length > 0) { // Search query is present.\n return searchFunction(data, searchValue);\n }\n return data;\n};\n\n/**\n * Given the output of the callers' search function, render out the results into the search results container.\n *\n * @method renderSearchResults\n * @param {HTMLElement} searchResultsContainer The DOM node of the widget where we'll render the provided results.\n * @param {Array} searchResultsData The filtered subset of the provided data that we'll then render into the results.\n */\nconst renderSearchResults = async(searchResultsContainer, searchResultsData) => {\n const templateData = {\n 'searchresults': searchResultsData,\n };\n // Build up the html & js ready to place into the help section.\n const {html, js} = await Templates.renderForPromise('core_grades/searchwidget/searchresults', templateData);\n await Templates.replaceNodeContents(searchResultsContainer, html, js);\n\n // Backward compatibility.\n if (searchResultsContainer.getAttribute('role') !== 'listbox') {\n const deprecatedOptions = searchResultsContainer.querySelectorAll(\n 'a.dropdown-item[role=\"menuitem\"][href=\"\"], .dropdown-item[role=\"option\"]:not([href])'\n );\n for (const option of deprecatedOptions) {\n option.tabIndex = 0;\n option.removeAttribute('href');\n }\n }\n};\n\n/**\n * We want to create the basic promises and hooks that the caller will implement, so we can build the search widget\n * ahead of time and allow the caller to resolve their promises once complete.\n *\n * @method promisesAndResolvers\n * @returns {{bodyPromise: Promise, bodyPromiseResolver}}\n */\nexport const promisesAndResolvers = () => {\n // We want to show the widget instantly but loading whilst waiting for our data.\n let bodyPromiseResolver;\n const bodyPromise = new Promise(resolve => {\n bodyPromiseResolver = resolve;\n });\n\n return {bodyPromiseResolver, bodyPromise};\n};\n"],"names":["async","widgetContentContainer","bodyPromise","data","searchFunc","unsearchableContent","afterSelect","debug","then","innerHTML","bodyContent","querySelector","Selectors","regions","searchResultsContainer","searchResults","showLoader","renderSearchResults","registerListenerEvents","catch","Notification","exception","searchInput","actions","search","focus","clearSearchButton","clearSearch","addEventListener","value","length","classList","remove","add","removeAttribute","debounceCallee","e","stopPropagation","inputElement","document","getElementById","dataset","elements","getSearchWidgetSelectOption","target","deprecatedOption","closest","url","find","object","id","location","href","key","preventDefault","container","html","js","Templates","renderForPromise","replaceNodeContents","searchValue","searchFunction","searchResultsData","templateData","getAttribute","deprecatedOptions","querySelectorAll","option","tabIndex","bodyPromiseResolver","Promise","resolve"],"mappings":";;;;;;;kWAuCoBA,eAChBC,uBACAC,YACAC,KACAC,gBACAC,2EAAsB,KACtBC,mEAAc,kBAEVC,MAAM,oHACVL,YAAYM,MAAKR,MAAAA,iBAEbC,uBAAuBQ,UAAYC,YAG/BL,oBAAqB,CACgBJ,uBAAuBU,cAAcC,UAAUC,QAAQR,qBAC/DI,WAAaJ,0BAGxCS,uBAAyBb,uBAAuBU,cAAcC,UAAUC,QAAQE,qBAEhFC,WAAWF,8BAEXG,oBAAoBH,uBAAwBX,MAElDe,uBAAuBjB,uBAAwBE,KAAMC,WAAYE,gBAElEa,MAAMC,sBAAaC,kBAYbH,uBAAyB,SAACjB,uBAAwBE,KAAMC,gBAAYE,mEAAc,WACrFQ,uBAAyBb,uBAAuBU,cAAcC,UAAUC,QAAQE,eAChFO,YAAcrB,uBAAuBU,cAAcC,UAAUW,QAAQC,YAEtEF,mBAMLA,YAAYG,cACNC,kBAAoBzB,uBAAuBU,cAAcC,UAAUW,QAAQI,aAGjFL,YAAYM,iBAAiB,SAAS,oBAAS5B,UAEvCsB,YAAYO,MAAMC,OAAS,EAC3BJ,kBAAkBK,UAAUC,OAAO,UAEnCN,kBAAkBK,UAAUE,IAAI,UAGpCX,YAAYY,gBAAgB,+BAEtBjB,oBACFH,uBACAqB,eACIb,YAAYO,MACZ1B,KACAC,iBAGT,MAGHsB,kBAAkBE,iBAAiB,SAAS5B,MAAAA,IACxCoC,EAAEC,kBAEFf,YAAYO,MAAQ,GACpBP,YAAYG,QACZC,kBAAkBK,UAAUE,IAAI,UAGhCX,YAAYY,gBAAgB,+BAGtBjB,oBACFH,uBACAqB,eACIb,YAAYO,MACZ1B,KACAC,wBAKNkC,aAAeC,SAASC,eAAelB,YAAYmB,QAAQH,cAC7DA,cAAgBhC,aAChBgC,aAAaV,iBAAiB,UAAUQ,IACbnC,uBAAuBU,cAC1CC,UAAU8B,SAASC,4BAA4BrB,eAI/ChB,YAAY8B,EAAEQ,OAAOf,UAQjC5B,uBAAuB2B,iBAAiB,SAASQ,UACvCS,iBAAmBT,EAAEQ,OAAOE,QAC9B,8FAEAD,oBAIIP,cAAgBhC,YAChBA,YAAYuC,iBAAiBJ,QAAQZ,WAClC,OACGkB,KAAO5C,KAAK6C,MAAKC,QAAUA,OAAOC,IAAML,iBAAiBJ,QAAQZ,SAAU,CAACkB,IAAK,KAAKA,IAC5FI,SAASC,KAAOL,QAQ5B9C,uBAAuB2B,iBAAiB,WAAWQ,UACzCS,iBAAmBT,EAAEQ,OAAOE,QAC9B,8FAEAD,mBAA+B,MAAVT,EAAEiB,KAAyB,UAAVjB,EAAEiB,QAIxCjB,EAAEkB,iBACEhB,cAAgBhC,YAChBA,YAAYuC,iBAAiBJ,QAAQZ,WAClC,OACGkB,KAAO5C,KAAK6C,MAAKC,QAAUA,OAAOC,IAAML,iBAAiBJ,QAAQZ,SAAU,CAACkB,IAAK,KAAKA,IAC5FI,SAASC,KAAOL,sEAYnB/B,WAAahB,MAAAA,YACtBuD,UAAU9C,UAAY,SAChB+C,KAACA,KAADC,GAAOA,UAAYC,UAAUC,iBAAiB,mCAAoC,IACxFD,UAAUE,oBAAoBL,UAAWC,KAAMC,0CAa7CtB,eAAiB,CAAC0B,YAAa1D,KAAM2D,iBACnCD,YAAY/B,OAAS,EACdgC,eAAe3D,KAAM0D,aAEzB1D,KAULc,oBAAsBjB,MAAMc,uBAAwBiD,2BAChDC,aAAe,eACAD,oBAGfP,KAACA,KAADC,GAAOA,UAAYC,UAAUC,iBAAiB,yCAA0CK,uBACxFN,UAAUE,oBAAoB9C,uBAAwB0C,KAAMC,IAGd,YAAhD3C,uBAAuBmD,aAAa,QAAuB,OACrDC,kBAAoBpD,uBAAuBqD,iBAC7C,4FAEC,MAAMC,UAAUF,kBACjBE,OAAOC,SAAW,EAClBD,OAAOlC,gBAAgB,wCAYC,SAE5BoC,0BACEpE,YAAc,IAAIqE,SAAQC,UAC5BF,oBAAsBE,iBAGnB,CAACF,oBAAAA,oBAAqBpE,YAAAA"}PK\B"L``basewidget.min.jsnu[define("core_grades/searchwidget/basewidget",["exports","core/utils","core/templates","core_grades/searchwidget/selectors","core/notification","core/log"],(function(_exports,_utils,Templates,Selectors,_notification,_log){function _interopRequireDefault(obj){return obj&&obj.__esModule?obj:{default:obj}}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} /** * A widget to search users or grade items within the gradebook. * * @module core_grades/searchwidget/basewidget * @copyright 2022 Mathew May * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.showLoader=_exports.registerListenerEvents=_exports.promisesAndResolvers=_exports.init=void 0,Templates=_interopRequireWildcard(Templates),Selectors=_interopRequireWildcard(Selectors),_notification=_interopRequireDefault(_notification),_log=_interopRequireDefault(_log);_exports.init=async function(widgetContentContainer,bodyPromise,data,searchFunc){let unsearchableContent=arguments.length>4&&void 0!==arguments[4]?arguments[4]:null,afterSelect=arguments.length>5&&void 0!==arguments[5]?arguments[5]:null;_log.default.debug("The core_grades/searchwidget/basewidget component is deprecated. Please refer to core/search_combobox() instead."),bodyPromise.then((async bodyContent=>{if(widgetContentContainer.innerHTML=bodyContent,unsearchableContent){widgetContentContainer.querySelector(Selectors.regions.unsearchableContent).innerHTML+=unsearchableContent}const searchResultsContainer=widgetContentContainer.querySelector(Selectors.regions.searchResults);await showLoader(searchResultsContainer),await renderSearchResults(searchResultsContainer,data),registerListenerEvents(widgetContentContainer,data,searchFunc,afterSelect)})).catch(_notification.default.exception)};const registerListenerEvents=function(widgetContentContainer,data,searchFunc){let afterSelect=arguments.length>3&&void 0!==arguments[3]?arguments[3]:null;const searchResultsContainer=widgetContentContainer.querySelector(Selectors.regions.searchResults),searchInput=widgetContentContainer.querySelector(Selectors.actions.search);if(!searchInput)return;searchInput.focus();const clearSearchButton=widgetContentContainer.querySelector(Selectors.actions.clearSearch);searchInput.addEventListener("input",(0,_utils.debounce)((async()=>{searchInput.value.length>0?clearSearchButton.classList.remove("d-none"):clearSearchButton.classList.add("d-none"),searchInput.removeAttribute("aria-activedescendant"),await renderSearchResults(searchResultsContainer,debounceCallee(searchInput.value,data,searchFunc()))}),300)),clearSearchButton.addEventListener("click",(async e=>{e.stopPropagation(),searchInput.value="",searchInput.focus(),clearSearchButton.classList.add("d-none"),searchInput.removeAttribute("aria-activedescendant"),await renderSearchResults(searchResultsContainer,debounceCallee(searchInput.value,data,searchFunc()))}));const inputElement=document.getElementById(searchInput.dataset.inputElement);inputElement&&afterSelect&&inputElement.addEventListener("change",(e=>{widgetContentContainer.querySelector(Selectors.elements.getSearchWidgetSelectOption(searchInput))&&afterSelect(e.target.value)})),widgetContentContainer.addEventListener("click",(e=>{const deprecatedOption=e.target.closest('a.dropdown-item[role="menuitem"]:not([href]), .dropdown-item[role="option"]:not([href])');if(deprecatedOption)if(inputElement&&afterSelect)afterSelect(deprecatedOption.dataset.value);else{const url=(data.find((object=>object.id==deprecatedOption.dataset.value))||{url:""}).url;location.href=url}})),widgetContentContainer.addEventListener("keydown",(e=>{const deprecatedOption=e.target.closest('a.dropdown-item[role="menuitem"]:not([href]), .dropdown-item[role="option"]:not([href])');if(deprecatedOption&&(" "===e.key||"Enter"===e.key))if(e.preventDefault(),inputElement&&afterSelect)afterSelect(deprecatedOption.dataset.value);else{const url=(data.find((object=>object.id==deprecatedOption.dataset.value))||{url:""}).url;location.href=url}}))};_exports.registerListenerEvents=registerListenerEvents;const showLoader=async container=>{container.innerHTML="";const{html:html,js:js}=await Templates.renderForPromise("core_grades/searchwidget/loading",{});Templates.replaceNodeContents(container,html,js)};_exports.showLoader=showLoader;const debounceCallee=(searchValue,data,searchFunction)=>searchValue.length>0?searchFunction(data,searchValue):data,renderSearchResults=async(searchResultsContainer,searchResultsData)=>{const templateData={searchresults:searchResultsData},{html:html,js:js}=await Templates.renderForPromise("core_grades/searchwidget/searchresults",templateData);if(await Templates.replaceNodeContents(searchResultsContainer,html,js),"listbox"!==searchResultsContainer.getAttribute("role")){const deprecatedOptions=searchResultsContainer.querySelectorAll('a.dropdown-item[role="menuitem"][href=""], .dropdown-item[role="option"]:not([href])');for(const option of deprecatedOptions)option.tabIndex=0,option.removeAttribute("href")}};_exports.promisesAndResolvers=()=>{let bodyPromiseResolver;const bodyPromise=new Promise((resolve=>{bodyPromiseResolver=resolve}));return{bodyPromiseResolver:bodyPromiseResolver,bodyPromise:bodyPromise}}})); //# sourceMappingURL=basewidget.min.js.mapPKr\ \P repository.jsnu[// 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 . /** * A repo for the search widget. * * @module core_grades/searchwidget/repository * @copyright 2022 Mathew May * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ import ajax from 'core/ajax'; /** * Given a course ID, we want to fetch the gradable items, so we may fetch reports based on activity items. * Note: This will be worked upon in the single view issue. * * @method gradeitemFetch * @param {int} courseid ID of the course to fetch the users of. * @return {object} jQuery promise */ export const gradeitemFetch = (courseid) => { const request = { methodname: 'gradereport_singleview_get_grade_items_for_search_widget', args: { courseid: courseid, }, }; return ajax.call([request])[0]; }; /** * Given a course ID, we want to fetch the enrolled learners, so we may fetch their reports. * * @method userFetch * @param {int} courseid ID of the course to fetch the users of. * @param {int} groupId ID of the group to fetch the users of. * @return {object} jQuery promise */ export const userFetch = (courseid, groupId) => { const request = { methodname: 'core_grades_get_enrolled_users_for_selector', args: { courseid: courseid, groupid: groupId, }, }; return ajax.call([request])[0]; }; PKr\,  initials.jsnu[// 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 . /** * A small dropdown to filter users within the gradebook. * * @module core_grades/searchwidget/initials * @copyright 2022 Mathew May * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later * @deprecated since Moodle 4.5 - please use core_course/actionbar/initials instead. * @todo Final deprecation in Moodle 6.0. See MDL-82421. */ import Pending from 'core/pending'; import * as Url from 'core/url'; import CustomEvents from "core/custom_interaction_events"; import Dropdown from 'theme_boost/bootstrap/dropdown'; /** * Whether the event listener has already been registered for this module. * * @type {boolean} */ let registered = false; // Contain our selectors within this file until they could be of use elsewhere. const selectors = { pageListItem: 'page-item', pageClickableItem: '.page-link', activeItem: 'active', formDropdown: '.initialsdropdownform', parentDomNode: '.initials-selector', firstInitial: 'firstinitial', lastInitial: 'lastinitial', initialBars: '.initialbar', // Both first and last name use this class. targetButton: 'initialswidget', formItems: { type: 'submit', save: 'save', cancel: 'cancel' } }; /** * Our initial hook into the module which will eventually allow us to handle the dropdown initials bar form. * * @param {String} callingLink The link to redirect upon form submission. * @param {Null|Number} gpr_userid The user id to filter by. * @param {Null|String} gpr_search The search value to filter by. */ export const init = (callingLink, gpr_userid = null, gpr_search = null) => { if (registered) { return; } const pendingPromise = new Pending(); registerListenerEvents(callingLink, gpr_userid, gpr_search); // BS events always bubble so, we need to listen for the event higher up the chain. document.querySelector(selectors.parentDomNode).addEventListener('shown.bs.dropdown', () => { document.querySelector(selectors.pageClickableItem).focus({preventScroll: true}); }); pendingPromise.resolve(); registered = true; }; /** * Register event listeners. * * @param {String} callingLink The link to redirect upon form submission. * @param {Null|Number} gpr_userid The user id to filter by. * @param {Null|String} gpr_search The search value to filter by. */ const registerListenerEvents = (callingLink, gpr_userid = null, gpr_search = null) => { const events = [ 'click', CustomEvents.events.activate, CustomEvents.events.keyboardActivate ]; CustomEvents.define(document, events); // Register events. events.forEach((event) => { document.addEventListener(event, (e) => { // Always fetch the latest information when we click as state is a fickle thing. let {firstActive, lastActive, sifirst, silast} = onClickVariables(); let itemToReset = ''; // Prevent the usual form behaviour. if (e.target.closest(selectors.formDropdown)) { e.preventDefault(); } // Handle the state of active initials before form submission. if (e.target.closest(`${selectors.formDropdown} .${selectors.pageListItem}`)) { // Ensure the li items don't cause weird clicking emptying out the form. if (e.target.classList.contains(selectors.pageListItem)) { return; } const initialsBar = e.target.closest(selectors.initialBars); // Find out which initial bar we are in. // We want to find the current active item in the menu area the user selected. // We also want to fetch the raw item out of the array for instant manipulation. if (initialsBar.classList.contains(selectors.firstInitial)) { sifirst = e.target; itemToReset = firstActive; } else { silast = e.target; itemToReset = lastActive; } swapActiveItems(itemToReset, e); } // Handle form submissions. if (e.target.closest(`${selectors.formDropdown}`) && e.target.type === selectors.formItems.type) { if (e.target.dataset.action === selectors.formItems.save) { // Ensure we strip out the value (All) as it messes with the PHP side of the initials bar. // Then we will redirect the user back onto the page with new filters applied. const params = { 'id': e.target.closest(selectors.formDropdown).dataset.courseid, 'gpr_search': gpr_search !== null ? gpr_search : '', 'sifirst': sifirst.parentElement.classList.contains('initialbarall') ? '' : sifirst.value, 'silast': silast.parentElement.classList.contains('initialbarall') ? '' : silast.value, }; if (gpr_userid !== null) { params.gpr_userid = gpr_userid; } window.location = Url.relativeUrl(callingLink, params); } if (e.target.dataset.action === selectors.formItems.cancel) { Dropdown.getOrCreateInstance(document.querySelector(`.${selectors.targetButton}`)).toggle(); } } }); }); }; /** * A small abstracted helper function which allows us to ensure we have up-to-date lists of nodes. * * @returns {{firstActive: HTMLElement, lastActive: HTMLElement, sifirst: ?String, silast: ?String}} */ const onClickVariables = () => { // Ensure we have an up-to-date initials bar. const firstItems = [...document.querySelectorAll(`.${selectors.firstInitial} li`)]; const lastItems = [...document.querySelectorAll(`.${selectors.lastInitial} li`)]; const firstActive = firstItems.filter((item) => item.classList.contains(selectors.activeItem))[0]; const lastActive = lastItems.filter((item) => item.classList.contains(selectors.activeItem))[0]; // Ensure we retain both of the selections from a previous instance. let sifirst = firstActive.querySelector(selectors.pageClickableItem); let silast = lastActive.querySelector(selectors.pageClickableItem); return {firstActive, lastActive, sifirst, silast}; }; /** * Given we are provided the old li and current click event, swap around the active properties. * * @param {HTMLElement} itemToReset * @param {Event} e */ const swapActiveItems = (itemToReset, e) => { itemToReset.classList.remove(selectors.activeItem); itemToReset.querySelector(selectors.pageClickableItem).ariaCurrent = false; // Set the select item as the current item. const itemToSetActive = e.target.parentElement; itemToSetActive.classList.add(selectors.activeItem); e.target.ariaCurrent = true; }; PKr\9D+D+ basewidget.jsnu[// 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 . /** * A widget to search users or grade items within the gradebook. * * @module core_grades/searchwidget/basewidget * @copyright 2022 Mathew May * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ import {debounce} from 'core/utils'; import * as Templates from 'core/templates'; import * as Selectors from 'core_grades/searchwidget/selectors'; import Notification from 'core/notification'; import Log from 'core/log'; /** * Build the base searching widget. * * @method init * @param {HTMLElement} widgetContentContainer The selector for the widget container element. * @param {Promise} bodyPromise The promise from the callee of the contents to place in the widget container. * @param {Array} data An array of all the data generated by the callee. * @param {Function} searchFunc Partially applied function we need to manage search the passed dataset. * @param {string|null} unsearchableContent The content rendered in a non-searchable area. * @param {Function|null} afterSelect Callback executed after an item is selected. */ export const init = async( widgetContentContainer, bodyPromise, data, searchFunc, unsearchableContent = null, afterSelect = null, ) => { Log.debug('The core_grades/searchwidget/basewidget component is deprecated. Please refer to core/search_combobox() instead.'); bodyPromise.then(async(bodyContent) => { // Render the body content. widgetContentContainer.innerHTML = bodyContent; // Render the unsearchable content if defined. if (unsearchableContent) { const unsearchableContentContainer = widgetContentContainer.querySelector(Selectors.regions.unsearchableContent); unsearchableContentContainer.innerHTML += unsearchableContent; } const searchResultsContainer = widgetContentContainer.querySelector(Selectors.regions.searchResults); // Display a loader until the search results are rendered. await showLoader(searchResultsContainer); // Render the search results. await renderSearchResults(searchResultsContainer, data); registerListenerEvents(widgetContentContainer, data, searchFunc, afterSelect); }).catch(Notification.exception); }; /** * Register the event listeners for the search widget. * * @method registerListenerEvents * @param {HTMLElement} widgetContentContainer The selector for the widget container element. * @param {Array} data An array of all the data generated by the callee. * @param {Function} searchFunc Partially applied function we need to manage search the passed dataset. * @param {Function|null} afterSelect Callback executed after an item is selected. */ export const registerListenerEvents = (widgetContentContainer, data, searchFunc, afterSelect = null) => { const searchResultsContainer = widgetContentContainer.querySelector(Selectors.regions.searchResults); const searchInput = widgetContentContainer.querySelector(Selectors.actions.search); if (!searchInput) { // Too late. The widget is already closed and its content is empty. return; } // We want to focus on the first known user interable element within the dropdown. searchInput.focus(); const clearSearchButton = widgetContentContainer.querySelector(Selectors.actions.clearSearch); // The search input is triggered. searchInput.addEventListener('input', debounce(async() => { // If search query is present display the 'clear search' button, otherwise hide it. if (searchInput.value.length > 0) { clearSearchButton.classList.remove('d-none'); } else { clearSearchButton.classList.add('d-none'); } // Remove aria-activedescendant when the available options change. searchInput.removeAttribute('aria-activedescendant'); // Display the search results. await renderSearchResults( searchResultsContainer, debounceCallee( searchInput.value, data, searchFunc() ) ); }, 300)); // Clear search is triggered. clearSearchButton.addEventListener('click', async(e) => { e.stopPropagation(); // Clear the entered search query in the search bar. searchInput.value = ""; searchInput.focus(); clearSearchButton.classList.add('d-none'); // Remove aria-activedescendant when the available options change. searchInput.removeAttribute('aria-activedescendant'); // Display all results. await renderSearchResults( searchResultsContainer, debounceCallee( searchInput.value, data, searchFunc() ) ); }); const inputElement = document.getElementById(searchInput.dataset.inputElement); if (inputElement && afterSelect) { inputElement.addEventListener('change', e => { const selectedOption = widgetContentContainer.querySelector( Selectors.elements.getSearchWidgetSelectOption(searchInput), ); if (selectedOption) { afterSelect(e.target.value); } }); } // Backward compatibility. Handle the click event for the following cases: // - When we have
  • tags without an afterSelect callback function being provided (old js). // - When we have tags without href (old template). widgetContentContainer.addEventListener('click', e => { const deprecatedOption = e.target.closest( 'a.dropdown-item[role="menuitem"]:not([href]), .dropdown-item[role="option"]:not([href])' ); if (deprecatedOption) { // We are in one of these situations: // - We have
  • tags without an afterSelect callback function being provided. // - We have tags without href. if (inputElement && afterSelect) { afterSelect(deprecatedOption.dataset.value); } else { const url = (data.find(object => object.id == deprecatedOption.dataset.value) || {url: ''}).url; location.href = url; } } }); // Backward compatibility. Handle the keydown event for the following cases: // - When we have
  • tags without an afterSelect callback function being provided (old js). // - When we have tags without href (old template). widgetContentContainer.addEventListener('keydown', e => { const deprecatedOption = e.target.closest( 'a.dropdown-item[role="menuitem"]:not([href]), .dropdown-item[role="option"]:not([href])' ); if (deprecatedOption && (e.key === ' ' || e.key === 'Enter')) { // We are in one of these situations: // - We have
  • tags without an afterSelect callback function being provided. // - We have tags without href. e.preventDefault(); if (inputElement && afterSelect) { afterSelect(deprecatedOption.dataset.value); } else { const url = (data.find(object => object.id == deprecatedOption.dataset.value) || {url: ''}).url; location.href = url; } } }); }; /** * Renders the loading placeholder for the search widget. * * @method showLoader * @param {HTMLElement} container The DOM node where we'll render the loading placeholder. */ export const showLoader = async(container) => { container.innerHTML = ''; const {html, js} = await Templates.renderForPromise('core_grades/searchwidget/loading', {}); Templates.replaceNodeContents(container, html, js); }; /** * We have a small helper that'll call the curried search function allowing callers to filter * the data set however we want rather than defining how data must be filtered. * * @method debounceCallee * @param {String} searchValue The input from the user that we'll search against. * @param {Array} data An array of all the data generated by the callee. * @param {Function} searchFunction Partially applied function we need to manage search the passed dataset. * @return {Array} The filtered subset of the provided data that we'll then render into the results. */ const debounceCallee = (searchValue, data, searchFunction) => { if (searchValue.length > 0) { // Search query is present. return searchFunction(data, searchValue); } return data; }; /** * Given the output of the callers' search function, render out the results into the search results container. * * @method renderSearchResults * @param {HTMLElement} searchResultsContainer The DOM node of the widget where we'll render the provided results. * @param {Array} searchResultsData The filtered subset of the provided data that we'll then render into the results. */ const renderSearchResults = async(searchResultsContainer, searchResultsData) => { const templateData = { 'searchresults': searchResultsData, }; // Build up the html & js ready to place into the help section. const {html, js} = await Templates.renderForPromise('core_grades/searchwidget/searchresults', templateData); await Templates.replaceNodeContents(searchResultsContainer, html, js); // Backward compatibility. if (searchResultsContainer.getAttribute('role') !== 'listbox') { const deprecatedOptions = searchResultsContainer.querySelectorAll( 'a.dropdown-item[role="menuitem"][href=""], .dropdown-item[role="option"]:not([href])' ); for (const option of deprecatedOptions) { option.tabIndex = 0; option.removeAttribute('href'); } } }; /** * We want to create the basic promises and hooks that the caller will implement, so we can build the search widget * ahead of time and allow the caller to resolve their promises once complete. * * @method promisesAndResolvers * @returns {{bodyPromise: Promise, bodyPromiseResolver}} */ export const promisesAndResolvers = () => { // We want to show the widget instantly but loading whilst waiting for our data. let bodyPromiseResolver; const bodyPromise = new Promise(resolve => { bodyPromiseResolver = resolve; }); return {bodyPromiseResolver, bodyPromise}; }; PKr\ݎUU selectors.jsnu[// 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 . /** * Selectors for the search widget. * * @module core_grades/searchwidget/selectors * @copyright 2022 Mihail Geshoski * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ export default { regions: { searchResults: '[data-region="search-results-container-widget"]', unsearchableContent: '[data-region="unsearchable-content-container-widget"]', }, actions: { search: '[data-action="search"]', clearSearch: '[data-action="clearsearch"]', }, elements: { getSearchWidgetSelector: searchtype => `.search-widget[data-searchtype="${searchtype}"]`, getSearchWidgetDropdownSelector: searchtype => `.search-widget[data-searchtype="${searchtype}"] .dropdown-menu`, getSearchWidgetSelectOption: searchInput => `#${searchInput.getAttribute('aria-controls')} [role="option"][aria-selected="true"]`, }, }; PK\ܔrepository.min.jsnu[PK\c,((initials.min.js.mapnu[PK\ -repository.min.js.mapnu[PK\g9&7selectors.min.js.mapnu[PK\gi@initials.min.jsnu[PK\kiGGIUselectors.min.jsnu[PK\088Xbasewidget.min.js.mapnu[PK\B"L``basewidget.min.jsnu[PKr\ \P repository.jsnu[PKr\,  ɲinitials.jsnu[PKr\9D+D+ basewidget.jsnu[PKr\ݎUU selectors.jsnu[PK %