From d2751479fc7fb81409c49a3993464ff6f5b5e6be Mon Sep 17 00:00:00 2001 From: Paul Holden Date: Sun, 29 Jun 2025 23:06:09 +0800 Subject: [PATCH] MDL-85779 block_timeline: fix/restore sort toggle dropdown behaviour. Changes during Bootstrap upgrade, in conjunction with both 8a8c8ab2 and 0c586b41, caused the layout to deviate from that expected elsewhere for managing of Bootstrap dropdown elements. Thus the dynamically chosen item was no longer being shown as selected. Fix that here. --- blocks/timeline/amd/build/view_nav.min.js | 2 +- blocks/timeline/amd/build/view_nav.min.js.map | 2 +- blocks/timeline/amd/src/view_nav.js | 5 +++-- blocks/timeline/templates/nav-view-selector.mustache | 6 +++--- 4 files changed, 8 insertions(+), 7 deletions(-) diff --git a/blocks/timeline/amd/build/view_nav.min.js b/blocks/timeline/amd/build/view_nav.min.js index 95c65475de7..c406ce34786 100644 --- a/blocks/timeline/amd/build/view_nav.min.js +++ b/blocks/timeline/amd/build/view_nav.min.js @@ -4,6 +4,6 @@ define("block_timeline/view_nav",["exports","jquery","core/custom_interaction_ev * * @copyright 2018 Ryan Wyllie * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */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}Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.init=void 0,_jquery=(obj=_jquery)&&obj.__esModule?obj:{default:obj},CustomEvents=_interopRequireWildcard(CustomEvents),View=_interopRequireWildcard(View),Notification=_interopRequireWildcard(Notification),Utils=_interopRequireWildcard(Utils),UserRepository=_interopRequireWildcard(UserRepository);const SELECTORS_TIMELINE_DAY_FILTER='[data-region="day-filter"]',SELECTORS_TIMELINE_DAY_FILTER_OPTION="[data-from]",SELECTORS_TIMELINE_VIEW_SELECTOR='[data-region="view-selector"]',SELECTORS_DATA_DAYS_OFFSET="[data-days-offset]",SELECTORS_TIMELINE_SEARCH_INPUT='[data-action="search"]',SELECTORS_TIMELINE_SEARCH_CLEAR_ICON='[data-action="clearsearch"]',SELECTORS_NO_COURSES_EMPTY_MESSAGE='[data-region="no-courses-empty-message"]',activeSearchState=(clearSearchIcon,timelineViewRoot)=>{clearSearchIcon.removeClass("d-none"),View.reset(timelineViewRoot)},clearSearchState=(clearSearchIcon,timelineViewRoot)=>{clearSearchIcon.addClass("d-none"),View.reset(timelineViewRoot)};_exports.init=function(root,timelineViewRoot){(function(root,timelineViewRoot){const viewSelector=root.find(SELECTORS_TIMELINE_VIEW_SELECTOR);(0,_normalise.getFirst)(viewSelector).querySelectorAll('[data-bs-toggle="tab"]').forEach((tab=>{tab.addEventListener("shown.bs.tab",(()=>{View.shown(timelineViewRoot)}))})),CustomEvents.define(viewSelector,[CustomEvents.events.activate]),viewSelector.on(CustomEvents.events.activate,"[data-bs-toggle='tab']",(function(e){var filtername=(0,_jquery.default)(e.currentTarget).data("filtername");UserRepository.setUserPreference("block_timeline_user_sort_preference",filtername).catch(Notification.exception)}))})(root=(0,_jquery.default)(root),timelineViewRoot),root.find(SELECTORS_NO_COURSES_EMPTY_MESSAGE).length||(function(root,timelineViewRoot){const timelineDaySelectorContainer=root.find(SELECTORS_TIMELINE_DAY_FILTER);CustomEvents.define(timelineDaySelectorContainer,[CustomEvents.events.activate]),timelineDaySelectorContainer.on(CustomEvents.events.activate,SELECTORS_TIMELINE_DAY_FILTER_OPTION,(function(e,data){var filtername=(0,_jquery.default)(e.currentTarget).data("filtername");UserRepository.setUserPreference("block_timeline_user_filter_preference",filtername).catch(Notification.exception);var option=(0,_jquery.default)(e.target).closest(SELECTORS_TIMELINE_DAY_FILTER_OPTION);if("true"!=option.attr("aria-selected")){var daysOffset=option.attr("data-from"),daysLimit=option.attr("data-to"),elementsWithDaysOffset=root.find(SELECTORS_DATA_DAYS_OFFSET);elementsWithDaysOffset.attr("data-days-offset",daysOffset),null!=daysLimit?elementsWithDaysOffset.attr("data-days-limit",daysLimit):elementsWithDaysOffset.removeAttr("data-days-limit"),"overdue"===option.attr("data-filtername")?elementsWithDaysOffset.attr("data-filter-overdue",!0):elementsWithDaysOffset.removeAttr("data-filter-overdue"),View.reset(timelineViewRoot),data.originalEvent.preventDefault()}}))}(root,timelineViewRoot),((root,timelineViewRoot)=>{const searchInput=root.find(SELECTORS_TIMELINE_SEARCH_INPUT),clearSearchIcon=root.find(SELECTORS_TIMELINE_SEARCH_CLEAR_ICON);searchInput.on("input",Utils.debounce((()=>{""!==searchInput.val()?activeSearchState(clearSearchIcon,timelineViewRoot):clearSearchState(clearSearchIcon,timelineViewRoot)}),1e3)),clearSearchIcon.on("click",(()=>{searchInput.val(""),clearSearchState(clearSearchIcon,timelineViewRoot),searchInput.focus()}))})(root,timelineViewRoot))}})); + */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}Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.init=void 0,_jquery=(obj=_jquery)&&obj.__esModule?obj:{default:obj},CustomEvents=_interopRequireWildcard(CustomEvents),View=_interopRequireWildcard(View),Notification=_interopRequireWildcard(Notification),Utils=_interopRequireWildcard(Utils),UserRepository=_interopRequireWildcard(UserRepository);const SELECTORS_TIMELINE_DAY_FILTER='[data-region="day-filter"]',SELECTORS_TIMELINE_DAY_FILTER_OPTION="[data-from]",SELECTORS_TIMELINE_VIEW_SELECTOR='[data-region="view-selector"]',SELECTORS_DATA_DAYS_OFFSET="[data-days-offset]",SELECTORS_TIMELINE_SEARCH_INPUT='[data-action="search"]',SELECTORS_TIMELINE_SEARCH_CLEAR_ICON='[data-action="clearsearch"]',SELECTORS_NO_COURSES_EMPTY_MESSAGE='[data-region="no-courses-empty-message"]',activeSearchState=(clearSearchIcon,timelineViewRoot)=>{clearSearchIcon.removeClass("d-none"),View.reset(timelineViewRoot)},clearSearchState=(clearSearchIcon,timelineViewRoot)=>{clearSearchIcon.addClass("d-none"),View.reset(timelineViewRoot)};_exports.init=function(root,timelineViewRoot){(function(root,timelineViewRoot){const viewSelector=root.find(SELECTORS_TIMELINE_VIEW_SELECTOR);(0,_normalise.getFirst)(viewSelector).querySelectorAll('[data-bs-toggle="tab"]').forEach((tab=>{tab.addEventListener("shown.bs.tab",(e=>{View.shown(timelineViewRoot),(0,_jquery.default)(e.target).removeClass("active")}))})),CustomEvents.define(viewSelector,[CustomEvents.events.activate]),viewSelector.on(CustomEvents.events.activate,"[data-bs-toggle='tab']",(function(e){var filtername=(0,_jquery.default)(e.currentTarget).data("filtername");UserRepository.setUserPreference("block_timeline_user_sort_preference",filtername).catch(Notification.exception)}))})(root=(0,_jquery.default)(root),timelineViewRoot),root.find(SELECTORS_NO_COURSES_EMPTY_MESSAGE).length||(function(root,timelineViewRoot){const timelineDaySelectorContainer=root.find(SELECTORS_TIMELINE_DAY_FILTER);CustomEvents.define(timelineDaySelectorContainer,[CustomEvents.events.activate]),timelineDaySelectorContainer.on(CustomEvents.events.activate,SELECTORS_TIMELINE_DAY_FILTER_OPTION,(function(e,data){var filtername=(0,_jquery.default)(e.currentTarget).data("filtername");UserRepository.setUserPreference("block_timeline_user_filter_preference",filtername).catch(Notification.exception);var option=(0,_jquery.default)(e.target).closest(SELECTORS_TIMELINE_DAY_FILTER_OPTION);if("true"!=option.attr("aria-current")){var daysOffset=option.attr("data-from"),daysLimit=option.attr("data-to"),elementsWithDaysOffset=root.find(SELECTORS_DATA_DAYS_OFFSET);elementsWithDaysOffset.attr("data-days-offset",daysOffset),null!=daysLimit?elementsWithDaysOffset.attr("data-days-limit",daysLimit):elementsWithDaysOffset.removeAttr("data-days-limit"),"overdue"===option.attr("data-filtername")?elementsWithDaysOffset.attr("data-filter-overdue",!0):elementsWithDaysOffset.removeAttr("data-filter-overdue"),View.reset(timelineViewRoot),data.originalEvent.preventDefault()}}))}(root,timelineViewRoot),((root,timelineViewRoot)=>{const searchInput=root.find(SELECTORS_TIMELINE_SEARCH_INPUT),clearSearchIcon=root.find(SELECTORS_TIMELINE_SEARCH_CLEAR_ICON);searchInput.on("input",Utils.debounce((()=>{""!==searchInput.val()?activeSearchState(clearSearchIcon,timelineViewRoot):clearSearchState(clearSearchIcon,timelineViewRoot)}),1e3)),clearSearchIcon.on("click",(()=>{searchInput.val(""),clearSearchState(clearSearchIcon,timelineViewRoot),searchInput.focus()}))})(root,timelineViewRoot))}})); //# sourceMappingURL=view_nav.min.js.map \ No newline at end of file diff --git a/blocks/timeline/amd/build/view_nav.min.js.map b/blocks/timeline/amd/build/view_nav.min.js.map index 7f823c474ce..5c1c749bb88 100644 --- a/blocks/timeline/amd/build/view_nav.min.js.map +++ b/blocks/timeline/amd/build/view_nav.min.js.map @@ -1 +1 @@ -{"version":3,"file":"view_nav.min.js","sources":["../src/view_nav.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 * Manage the timeline view navigation for the timeline block.\n *\n * @copyright 2018 Ryan Wyllie \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport $ from 'jquery';\nimport * as CustomEvents from 'core/custom_interaction_events';\nimport * as View from 'block_timeline/view';\nimport * as Notification from 'core/notification';\nimport * as Utils from 'core/utils';\nimport * as UserRepository from 'core_user/repository';\nimport {getFirst} from 'core/normalise';\n\nconst SELECTORS = {\n TIMELINE_DAY_FILTER: '[data-region=\"day-filter\"]',\n TIMELINE_DAY_FILTER_OPTION: '[data-from]',\n TIMELINE_VIEW_SELECTOR: '[data-region=\"view-selector\"]',\n DATA_DAYS_OFFSET: '[data-days-offset]',\n DATA_DAYS_LIMIT: '[data-days-limit]',\n TIMELINE_SEARCH_INPUT: '[data-action=\"search\"]',\n TIMELINE_SEARCH_CLEAR_ICON: '[data-action=\"clearsearch\"]',\n NO_COURSES_EMPTY_MESSAGE: '[data-region=\"no-courses-empty-message\"]',\n};\n\n/**\n * Event listener for the day selector (\"Next 7 days\", \"Next 30 days\", etc).\n *\n * @param {object} root The root element for the timeline block\n * @param {object} timelineViewRoot The root element for the timeline view\n */\nconst registerTimelineDaySelector = function(root, timelineViewRoot) {\n const timelineDaySelectorContainer = root.find(SELECTORS.TIMELINE_DAY_FILTER);\n\n CustomEvents.define(timelineDaySelectorContainer, [CustomEvents.events.activate]);\n timelineDaySelectorContainer.on(\n CustomEvents.events.activate,\n SELECTORS.TIMELINE_DAY_FILTER_OPTION,\n function(e, data) {\n // Update the user preference\n var filtername = $(e.currentTarget).data('filtername');\n var type = 'block_timeline_user_filter_preference';\n UserRepository.setUserPreference(type, filtername)\n .catch(Notification.exception);\n\n var option = $(e.target).closest(SELECTORS.TIMELINE_DAY_FILTER_OPTION);\n\n if (option.attr('aria-selected') == 'true') {\n // If it's already active then we don't need to do anything.\n return;\n }\n\n var daysOffset = option.attr('data-from');\n var daysLimit = option.attr('data-to');\n var elementsWithDaysOffset = root.find(SELECTORS.DATA_DAYS_OFFSET);\n\n elementsWithDaysOffset.attr('data-days-offset', daysOffset);\n\n if (daysLimit != undefined) {\n elementsWithDaysOffset.attr('data-days-limit', daysLimit);\n } else {\n elementsWithDaysOffset.removeAttr('data-days-limit');\n }\n\n if (option.attr('data-filtername') === 'overdue') {\n elementsWithDaysOffset.attr('data-filter-overdue', true);\n } else {\n elementsWithDaysOffset.removeAttr('data-filter-overdue');\n }\n\n // Reset the views to reinitialise the event lists now that we've\n // updated the day limits.\n View.reset(timelineViewRoot);\n\n data.originalEvent.preventDefault();\n }\n );\n};\n\n/**\n * Event listener for the \"sort\" button in the timeline navigation that allows for\n * changing between the timeline dates and courses views.\n *\n * On a view change we tell the timeline view module that the view has been shown\n * so that it can handle how to display the appropriate view.\n *\n * @param {object} root The root element for the timeline block\n * @param {object} timelineViewRoot The root element for the timeline view\n */\nconst registerViewSelector = function(root, timelineViewRoot) {\n const viewSelector = root.find(SELECTORS.TIMELINE_VIEW_SELECTOR);\n\n // Listen for when the user changes tab so that we can show the first set of courses\n // and load their events when they request the sort by courses view for the first time.\n getFirst(viewSelector).querySelectorAll('[data-bs-toggle=\"tab\"]').forEach((tab) => {\n tab.addEventListener('shown.bs.tab', () => {\n View.shown(timelineViewRoot);\n });\n });\n\n\n // Event selector for user_sort\n CustomEvents.define(viewSelector, [CustomEvents.events.activate]);\n viewSelector.on(CustomEvents.events.activate, \"[data-bs-toggle='tab']\", function(e) {\n var filtername = $(e.currentTarget).data('filtername');\n var type = 'block_timeline_user_sort_preference';\n UserRepository.setUserPreference(type, filtername)\n .catch(Notification.exception);\n });\n};\n\n/**\n * Event listener for the \"search\" input field in the timeline navigation that allows for\n * searching the activity name, course name and activity type.\n *\n * @param {object} root The root element for the timeline block\n * @param {object} timelineViewRoot The root element for the timeline view\n */\nconst registerSearch = (root, timelineViewRoot) => {\n const searchInput = root.find(SELECTORS.TIMELINE_SEARCH_INPUT);\n const clearSearchIcon = root.find(SELECTORS.TIMELINE_SEARCH_CLEAR_ICON);\n searchInput.on('input', Utils.debounce(() => {\n if (searchInput.val() !== '') {\n activeSearchState(clearSearchIcon, timelineViewRoot);\n } else {\n clearSearchState(clearSearchIcon, timelineViewRoot);\n }\n }, 1000));\n clearSearchIcon.on('click', () => {\n searchInput.val('');\n clearSearchState(clearSearchIcon, timelineViewRoot);\n searchInput.focus();\n });\n};\n\n/**\n * Show the clear search icon.\n *\n * @param {object} clearSearchIcon Clear search icon element.\n * @param {object} timelineViewRoot The root element for the timeline view\n */\nconst activeSearchState = (clearSearchIcon, timelineViewRoot) => {\n clearSearchIcon.removeClass('d-none');\n View.reset(timelineViewRoot);\n};\n\n/**\n * Hide the clear search icon.\n *\n * @param {object} clearSearchIcon Clear search icon element.\n * @param {object} timelineViewRoot The root element for the timeline view\n */\nconst clearSearchState = (clearSearchIcon, timelineViewRoot) => {\n clearSearchIcon.addClass('d-none');\n View.reset(timelineViewRoot);\n};\n\n/**\n * Initialise the timeline view navigation by adding event listeners to\n * the navigation elements.\n *\n * @param {jQuery|HTMLElement} root The root element for the timeline block\n * @param {object} timelineViewRoot The root element for the timeline view\n */\nexport const init = function(root, timelineViewRoot) {\n root = $(root);\n\n registerViewSelector(root, timelineViewRoot);\n\n // Only need to handle filtering if the user is actively enrolled in a course.\n if (!root.find(SELECTORS.NO_COURSES_EMPTY_MESSAGE).length) {\n registerTimelineDaySelector(root, timelineViewRoot);\n registerSearch(root, timelineViewRoot);\n }\n};\n"],"names":["SELECTORS","activeSearchState","clearSearchIcon","timelineViewRoot","removeClass","View","reset","clearSearchState","addClass","root","viewSelector","find","querySelectorAll","forEach","tab","addEventListener","shown","CustomEvents","define","events","activate","on","e","filtername","currentTarget","data","UserRepository","setUserPreference","catch","Notification","exception","registerViewSelector","length","timelineDaySelectorContainer","option","target","closest","attr","daysOffset","daysLimit","elementsWithDaysOffset","undefined","removeAttr","originalEvent","preventDefault","registerTimelineDaySelector","searchInput","Utils","debounce","val","focus","registerSearch"],"mappings":";;;;;;4yCA8BMA,8BACmB,6BADnBA,qCAE0B,cAF1BA,iCAGsB,gCAHtBA,2BAIgB,qBAJhBA,gCAMqB,yBANrBA,qCAO0B,8BAP1BA,mCAQwB,2CAuHxBC,kBAAoB,CAACC,gBAAiBC,oBACxCD,gBAAgBE,YAAY,UAC5BC,KAAKC,MAAMH,mBASTI,iBAAmB,CAACL,gBAAiBC,oBACvCD,gBAAgBM,SAAS,UACzBH,KAAKC,MAAMH,iCAUK,SAASM,KAAMN,mBA3EN,SAASM,KAAMN,wBAClCO,aAAeD,KAAKE,KAAKX,0DAItBU,cAAcE,iBAAiB,0BAA0BC,SAASC,MACvEA,IAAIC,iBAAiB,gBAAgB,KACjCV,KAAKW,MAAMb,wBAMnBc,aAAaC,OAAOR,aAAc,CAACO,aAAaE,OAAOC,WACvDV,aAAaW,GAAGJ,aAAaE,OAAOC,SAAU,0BAA0B,SAASE,OACzEC,YAAa,mBAAED,EAAEE,eAAeC,KAAK,cAEzCC,eAAeC,kBADJ,sCAC4BJ,YAClCK,MAAMC,aAAaC,eA4D5BC,CAFAtB,MAAO,mBAAEA,MAEkBN,kBAGtBM,KAAKE,KAAKX,oCAAoCgC,SA3InB,SAASvB,KAAMN,wBACzC8B,6BAA+BxB,KAAKE,KAAKX,+BAE/CiB,aAAaC,OAAOe,6BAA8B,CAAChB,aAAaE,OAAOC,WACvEa,6BAA6BZ,GACzBJ,aAAaE,OAAOC,SACpBpB,sCACA,SAASsB,EAAGG,UAEJF,YAAa,mBAAED,EAAEE,eAAeC,KAAK,cAEzCC,eAAeC,kBADJ,wCAC4BJ,YAClCK,MAAMC,aAAaC,eAEpBI,QAAS,mBAAEZ,EAAEa,QAAQC,QAAQpC,yCAEG,QAAhCkC,OAAOG,KAAK,sBAKZC,WAAaJ,OAAOG,KAAK,aACzBE,UAAYL,OAAOG,KAAK,WACxBG,uBAAyB/B,KAAKE,KAAKX,4BAEvCwC,uBAAuBH,KAAK,mBAAoBC,YAE/BG,MAAbF,UACAC,uBAAuBH,KAAK,kBAAmBE,WAE/CC,uBAAuBE,WAAW,mBAGC,YAAnCR,OAAOG,KAAK,mBACZG,uBAAuBH,KAAK,uBAAuB,GAEnDG,uBAAuBE,WAAW,uBAKtCrC,KAAKC,MAAMH,kBAEXsB,KAAKkB,cAAcC,qBAiGvBC,CAA4BpC,KAAMN,kBArDnB,EAACM,KAAMN,0BACpB2C,YAAcrC,KAAKE,KAAKX,iCACxBE,gBAAkBO,KAAKE,KAAKX,sCAClC8C,YAAYzB,GAAG,QAAS0B,MAAMC,UAAS,KACT,KAAtBF,YAAYG,MACZhD,kBAAkBC,gBAAiBC,kBAEnCI,iBAAiBL,gBAAiBC,oBAEvC,MACHD,gBAAgBmB,GAAG,SAAS,KACxByB,YAAYG,IAAI,IAChB1C,iBAAiBL,gBAAiBC,kBAClC2C,YAAYI,YAyCZC,CAAe1C,KAAMN"} \ No newline at end of file +{"version":3,"file":"view_nav.min.js","sources":["../src/view_nav.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 * Manage the timeline view navigation for the timeline block.\n *\n * @copyright 2018 Ryan Wyllie \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport $ from 'jquery';\nimport * as CustomEvents from 'core/custom_interaction_events';\nimport * as View from 'block_timeline/view';\nimport * as Notification from 'core/notification';\nimport * as Utils from 'core/utils';\nimport * as UserRepository from 'core_user/repository';\nimport {getFirst} from 'core/normalise';\n\nconst SELECTORS = {\n TIMELINE_DAY_FILTER: '[data-region=\"day-filter\"]',\n TIMELINE_DAY_FILTER_OPTION: '[data-from]',\n TIMELINE_VIEW_SELECTOR: '[data-region=\"view-selector\"]',\n DATA_DAYS_OFFSET: '[data-days-offset]',\n DATA_DAYS_LIMIT: '[data-days-limit]',\n TIMELINE_SEARCH_INPUT: '[data-action=\"search\"]',\n TIMELINE_SEARCH_CLEAR_ICON: '[data-action=\"clearsearch\"]',\n NO_COURSES_EMPTY_MESSAGE: '[data-region=\"no-courses-empty-message\"]',\n};\n\n/**\n * Event listener for the day selector (\"Next 7 days\", \"Next 30 days\", etc).\n *\n * @param {object} root The root element for the timeline block\n * @param {object} timelineViewRoot The root element for the timeline view\n */\nconst registerTimelineDaySelector = function(root, timelineViewRoot) {\n const timelineDaySelectorContainer = root.find(SELECTORS.TIMELINE_DAY_FILTER);\n\n CustomEvents.define(timelineDaySelectorContainer, [CustomEvents.events.activate]);\n timelineDaySelectorContainer.on(\n CustomEvents.events.activate,\n SELECTORS.TIMELINE_DAY_FILTER_OPTION,\n function(e, data) {\n // Update the user preference\n var filtername = $(e.currentTarget).data('filtername');\n var type = 'block_timeline_user_filter_preference';\n UserRepository.setUserPreference(type, filtername)\n .catch(Notification.exception);\n\n var option = $(e.target).closest(SELECTORS.TIMELINE_DAY_FILTER_OPTION);\n\n if (option.attr('aria-current') == 'true') {\n // If it's already active then we don't need to do anything.\n return;\n }\n\n var daysOffset = option.attr('data-from');\n var daysLimit = option.attr('data-to');\n var elementsWithDaysOffset = root.find(SELECTORS.DATA_DAYS_OFFSET);\n\n elementsWithDaysOffset.attr('data-days-offset', daysOffset);\n\n if (daysLimit != undefined) {\n elementsWithDaysOffset.attr('data-days-limit', daysLimit);\n } else {\n elementsWithDaysOffset.removeAttr('data-days-limit');\n }\n\n if (option.attr('data-filtername') === 'overdue') {\n elementsWithDaysOffset.attr('data-filter-overdue', true);\n } else {\n elementsWithDaysOffset.removeAttr('data-filter-overdue');\n }\n\n // Reset the views to reinitialise the event lists now that we've\n // updated the day limits.\n View.reset(timelineViewRoot);\n\n data.originalEvent.preventDefault();\n }\n );\n};\n\n/**\n * Event listener for the \"sort\" button in the timeline navigation that allows for\n * changing between the timeline dates and courses views.\n *\n * On a view change we tell the timeline view module that the view has been shown\n * so that it can handle how to display the appropriate view.\n *\n * @param {object} root The root element for the timeline block\n * @param {object} timelineViewRoot The root element for the timeline view\n */\nconst registerViewSelector = function(root, timelineViewRoot) {\n const viewSelector = root.find(SELECTORS.TIMELINE_VIEW_SELECTOR);\n\n // Listen for when the user changes tab so that we can show the first set of courses\n // and load their events when they request the sort by courses view for the first time.\n getFirst(viewSelector).querySelectorAll('[data-bs-toggle=\"tab\"]').forEach((tab) => {\n tab.addEventListener('shown.bs.tab', (e) => {\n View.shown(timelineViewRoot);\n $(e.target).removeClass('active');\n });\n });\n\n\n // Event selector for user_sort\n CustomEvents.define(viewSelector, [CustomEvents.events.activate]);\n viewSelector.on(CustomEvents.events.activate, \"[data-bs-toggle='tab']\", function(e) {\n var filtername = $(e.currentTarget).data('filtername');\n var type = 'block_timeline_user_sort_preference';\n UserRepository.setUserPreference(type, filtername)\n .catch(Notification.exception);\n });\n};\n\n/**\n * Event listener for the \"search\" input field in the timeline navigation that allows for\n * searching the activity name, course name and activity type.\n *\n * @param {object} root The root element for the timeline block\n * @param {object} timelineViewRoot The root element for the timeline view\n */\nconst registerSearch = (root, timelineViewRoot) => {\n const searchInput = root.find(SELECTORS.TIMELINE_SEARCH_INPUT);\n const clearSearchIcon = root.find(SELECTORS.TIMELINE_SEARCH_CLEAR_ICON);\n searchInput.on('input', Utils.debounce(() => {\n if (searchInput.val() !== '') {\n activeSearchState(clearSearchIcon, timelineViewRoot);\n } else {\n clearSearchState(clearSearchIcon, timelineViewRoot);\n }\n }, 1000));\n clearSearchIcon.on('click', () => {\n searchInput.val('');\n clearSearchState(clearSearchIcon, timelineViewRoot);\n searchInput.focus();\n });\n};\n\n/**\n * Show the clear search icon.\n *\n * @param {object} clearSearchIcon Clear search icon element.\n * @param {object} timelineViewRoot The root element for the timeline view\n */\nconst activeSearchState = (clearSearchIcon, timelineViewRoot) => {\n clearSearchIcon.removeClass('d-none');\n View.reset(timelineViewRoot);\n};\n\n/**\n * Hide the clear search icon.\n *\n * @param {object} clearSearchIcon Clear search icon element.\n * @param {object} timelineViewRoot The root element for the timeline view\n */\nconst clearSearchState = (clearSearchIcon, timelineViewRoot) => {\n clearSearchIcon.addClass('d-none');\n View.reset(timelineViewRoot);\n};\n\n/**\n * Initialise the timeline view navigation by adding event listeners to\n * the navigation elements.\n *\n * @param {jQuery|HTMLElement} root The root element for the timeline block\n * @param {object} timelineViewRoot The root element for the timeline view\n */\nexport const init = function(root, timelineViewRoot) {\n root = $(root);\n\n registerViewSelector(root, timelineViewRoot);\n\n // Only need to handle filtering if the user is actively enrolled in a course.\n if (!root.find(SELECTORS.NO_COURSES_EMPTY_MESSAGE).length) {\n registerTimelineDaySelector(root, timelineViewRoot);\n registerSearch(root, timelineViewRoot);\n }\n};\n"],"names":["SELECTORS","activeSearchState","clearSearchIcon","timelineViewRoot","removeClass","View","reset","clearSearchState","addClass","root","viewSelector","find","querySelectorAll","forEach","tab","addEventListener","e","shown","target","CustomEvents","define","events","activate","on","filtername","currentTarget","data","UserRepository","setUserPreference","catch","Notification","exception","registerViewSelector","length","timelineDaySelectorContainer","option","closest","attr","daysOffset","daysLimit","elementsWithDaysOffset","undefined","removeAttr","originalEvent","preventDefault","registerTimelineDaySelector","searchInput","Utils","debounce","val","focus","registerSearch"],"mappings":";;;;;;4yCA8BMA,8BACmB,6BADnBA,qCAE0B,cAF1BA,iCAGsB,gCAHtBA,2BAIgB,qBAJhBA,gCAMqB,yBANrBA,qCAO0B,8BAP1BA,mCAQwB,2CAwHxBC,kBAAoB,CAACC,gBAAiBC,oBACxCD,gBAAgBE,YAAY,UAC5BC,KAAKC,MAAMH,mBASTI,iBAAmB,CAACL,gBAAiBC,oBACvCD,gBAAgBM,SAAS,UACzBH,KAAKC,MAAMH,iCAUK,SAASM,KAAMN,mBA5EN,SAASM,KAAMN,wBAClCO,aAAeD,KAAKE,KAAKX,0DAItBU,cAAcE,iBAAiB,0BAA0BC,SAASC,MACvEA,IAAIC,iBAAiB,gBAAiBC,IAClCX,KAAKY,MAAMd,sCACTa,EAAEE,QAAQd,YAAY,gBAMhCe,aAAaC,OAAOV,aAAc,CAACS,aAAaE,OAAOC,WACvDZ,aAAaa,GAAGJ,aAAaE,OAAOC,SAAU,0BAA0B,SAASN,OACzEQ,YAAa,mBAAER,EAAES,eAAeC,KAAK,cAEzCC,eAAeC,kBADJ,sCAC4BJ,YAClCK,MAAMC,aAAaC,eA4D5BC,CAFAvB,MAAO,mBAAEA,MAEkBN,kBAGtBM,KAAKE,KAAKX,oCAAoCiC,SA5InB,SAASxB,KAAMN,wBACzC+B,6BAA+BzB,KAAKE,KAAKX,+BAE/CmB,aAAaC,OAAOc,6BAA8B,CAACf,aAAaE,OAAOC,WACvEY,6BAA6BX,GACzBJ,aAAaE,OAAOC,SACpBtB,sCACA,SAASgB,EAAGU,UAEJF,YAAa,mBAAER,EAAES,eAAeC,KAAK,cAEzCC,eAAeC,kBADJ,wCAC4BJ,YAClCK,MAAMC,aAAaC,eAEpBI,QAAS,mBAAEnB,EAAEE,QAAQkB,QAAQpC,yCAEE,QAA/BmC,OAAOE,KAAK,qBAKZC,WAAaH,OAAOE,KAAK,aACzBE,UAAYJ,OAAOE,KAAK,WACxBG,uBAAyB/B,KAAKE,KAAKX,4BAEvCwC,uBAAuBH,KAAK,mBAAoBC,YAE/BG,MAAbF,UACAC,uBAAuBH,KAAK,kBAAmBE,WAE/CC,uBAAuBE,WAAW,mBAGC,YAAnCP,OAAOE,KAAK,mBACZG,uBAAuBH,KAAK,uBAAuB,GAEnDG,uBAAuBE,WAAW,uBAKtCrC,KAAKC,MAAMH,kBAEXuB,KAAKiB,cAAcC,qBAkGvBC,CAA4BpC,KAAMN,kBArDnB,EAACM,KAAMN,0BACpB2C,YAAcrC,KAAKE,KAAKX,iCACxBE,gBAAkBO,KAAKE,KAAKX,sCAClC8C,YAAYvB,GAAG,QAASwB,MAAMC,UAAS,KACT,KAAtBF,YAAYG,MACZhD,kBAAkBC,gBAAiBC,kBAEnCI,iBAAiBL,gBAAiBC,oBAEvC,MACHD,gBAAgBqB,GAAG,SAAS,KACxBuB,YAAYG,IAAI,IAChB1C,iBAAiBL,gBAAiBC,kBAClC2C,YAAYI,YAyCZC,CAAe1C,KAAMN"} \ No newline at end of file diff --git a/blocks/timeline/amd/src/view_nav.js b/blocks/timeline/amd/src/view_nav.js index 626f35d19df..49cf234c5a3 100644 --- a/blocks/timeline/amd/src/view_nav.js +++ b/blocks/timeline/amd/src/view_nav.js @@ -61,7 +61,7 @@ const registerTimelineDaySelector = function(root, timelineViewRoot) { var option = $(e.target).closest(SELECTORS.TIMELINE_DAY_FILTER_OPTION); - if (option.attr('aria-selected') == 'true') { + if (option.attr('aria-current') == 'true') { // If it's already active then we don't need to do anything. return; } @@ -109,8 +109,9 @@ const registerViewSelector = function(root, timelineViewRoot) { // Listen for when the user changes tab so that we can show the first set of courses // and load their events when they request the sort by courses view for the first time. getFirst(viewSelector).querySelectorAll('[data-bs-toggle="tab"]').forEach((tab) => { - tab.addEventListener('shown.bs.tab', () => { + tab.addEventListener('shown.bs.tab', (e) => { View.shown(timelineViewRoot); + $(e.target).removeClass('active'); }); }); diff --git a/blocks/timeline/templates/nav-view-selector.mustache b/blocks/timeline/templates/nav-view-selector.mustache index 4699cc9b7ad..0262ab57445 100644 --- a/blocks/timeline/templates/nav-view-selector.mustache +++ b/blocks/timeline/templates/nav-view-selector.mustache @@ -31,13 +31,13 @@ {{#sorttimelinedates}}{{#str}} sortbydates, block_timeline {{/str}}{{/sorttimelinedates}} -