| 1 | efrain | 1 | // This file is part of Moodle - http://moodle.org/
 | 
        
           |  |  | 2 | //
 | 
        
           |  |  | 3 | // Moodle is free software: you can redistribute it and/or modify
 | 
        
           |  |  | 4 | // it under the terms of the GNU General Public License as published by
 | 
        
           |  |  | 5 | // the Free Software Foundation, either version 3 of the License, or
 | 
        
           |  |  | 6 | // (at your option) any later version.
 | 
        
           |  |  | 7 | //
 | 
        
           |  |  | 8 | // Moodle is distributed in the hope that it will be useful,
 | 
        
           |  |  | 9 | // but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
        
           |  |  | 10 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
        
           |  |  | 11 | // GNU General Public License for more details.
 | 
        
           |  |  | 12 | //
 | 
        
           |  |  | 13 | // You should have received a copy of the GNU General Public License
 | 
        
           |  |  | 14 | // along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
 | 
        
           |  |  | 15 |   | 
        
           |  |  | 16 | /**
 | 
        
           |  |  | 17 |  * Template renderer for Moodle. Load and render Moodle templates with Mustache.
 | 
        
           |  |  | 18 |  *
 | 
        
           |  |  | 19 |  * @module     theme_boost/loader
 | 
        
           |  |  | 20 |  * @copyright  2015 Damyon Wiese <damyon@moodle.com>
 | 
        
           |  |  | 21 |  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
 | 
        
           |  |  | 22 |  * @since      2.9
 | 
        
           |  |  | 23 |  */
 | 
        
           |  |  | 24 |   | 
        
           |  |  | 25 | import * as Aria from './aria';
 | 
        
           | 1441 | ariadna | 26 | import * as Bootstrap from './index';
 | 
        
           | 1 | efrain | 27 | import Pending from 'core/pending';
 | 
        
           | 1441 | ariadna | 28 | import {DefaultAllowlist} from './bootstrap/util/sanitizer';
 | 
        
           | 1 | efrain | 29 | import setupBootstrapPendingChecks from './pending';
 | 
        
           | 1441 | ariadna | 30 | import EventHandler from './bootstrap/dom/event-handler';
 | 
        
           | 1 | efrain | 31 |   | 
        
           |  |  | 32 | /**
 | 
        
           |  |  | 33 |  * Rember the last visited tabs.
 | 
        
           |  |  | 34 |  */
 | 
        
           |  |  | 35 | const rememberTabs = () => {
 | 
        
           | 1441 | ariadna | 36 |     const tabTriggerList = document.querySelectorAll('a[data-bs-toggle="tab"]');
 | 
        
           |  |  | 37 |     [...tabTriggerList].map(tabTriggerEl => tabTriggerEl.addEventListener('shown.bs.tab', (e) => {
 | 
        
           |  |  | 38 |         var hash = e.target.getAttribute('href');
 | 
        
           | 1 | efrain | 39 |         if (history.replaceState) {
 | 
        
           |  |  | 40 |             history.replaceState(null, null, hash);
 | 
        
           |  |  | 41 |         } else {
 | 
        
           |  |  | 42 |             location.hash = hash;
 | 
        
           |  |  | 43 |         }
 | 
        
           | 1441 | ariadna | 44 |     }));
 | 
        
           | 1 | efrain | 45 |     const hash = window.location.hash;
 | 
        
           |  |  | 46 |     if (hash) {
 | 
        
           |  |  | 47 |         const tab = document.querySelector('[role="tablist"] [href="' + hash + '"]');
 | 
        
           |  |  | 48 |         if (tab) {
 | 
        
           |  |  | 49 |             tab.click();
 | 
        
           |  |  | 50 |         }
 | 
        
           |  |  | 51 |     }
 | 
        
           |  |  | 52 | };
 | 
        
           |  |  | 53 |   | 
        
           |  |  | 54 | /**
 | 
        
           |  |  | 55 |  * Enable all popovers
 | 
        
           |  |  | 56 |  *
 | 
        
           |  |  | 57 |  */
 | 
        
           |  |  | 58 | const enablePopovers = () => {
 | 
        
           | 1441 | ariadna | 59 |     const popoverTriggerList = document.querySelectorAll('[data-bs-toggle="popover"]');
 | 
        
           |  |  | 60 |     const popoverConfig = {
 | 
        
           | 1 | efrain | 61 |         container: 'body',
 | 
        
           |  |  | 62 |         trigger: 'focus',
 | 
        
           | 1441 | ariadna | 63 |         allowList: Object.assign(DefaultAllowlist, {table: [], thead: [], tbody: [], tr: [], th: [], td: []}),
 | 
        
           |  |  | 64 |     };
 | 
        
           |  |  | 65 |     [...popoverTriggerList].map(popoverTriggerEl => new Bootstrap.Popover(popoverTriggerEl, popoverConfig));
 | 
        
           |  |  | 66 |   | 
        
           |  |  | 67 |     // Enable dynamically created popovers inside modals.
 | 
        
           |  |  | 68 |     document.addEventListener('core/modal:bodyRendered', (e) => {
 | 
        
           |  |  | 69 |         const modal = e.target;
 | 
        
           |  |  | 70 |         const popoverTriggerList = modal.querySelectorAll('[data-bs-toggle="popover"]');
 | 
        
           |  |  | 71 |         [...popoverTriggerList].map(popoverTriggerEl => new Bootstrap.Popover(popoverTriggerEl, popoverConfig));
 | 
        
           | 1 | efrain | 72 |     });
 | 
        
           |  |  | 73 |   | 
        
           |  |  | 74 |     document.addEventListener('keydown', e => {
 | 
        
           | 1441 | ariadna | 75 |         const popoverTrigger = e.target.closest('[data-bs-toggle="popover"]');
 | 
        
           |  |  | 76 |         if (e.key === 'Escape' && popoverTrigger) {
 | 
        
           |  |  | 77 |             Bootstrap.Popover.getOrCreateInstance(popoverTrigger).hide();
 | 
        
           | 1 | efrain | 78 |         }
 | 
        
           | 1441 | ariadna | 79 |         if (e.key === 'Enter' && popoverTrigger) {
 | 
        
           |  |  | 80 |             Bootstrap.Popover.getOrCreateInstance(popoverTrigger).show();
 | 
        
           |  |  | 81 |         }
 | 
        
           | 1 | efrain | 82 |     });
 | 
        
           | 1441 | ariadna | 83 |     document.addEventListener('click', e => {
 | 
        
           |  |  | 84 |         const popoverTrigger = e.target.closest('[data-bs-toggle="popover"]');
 | 
        
           |  |  | 85 |         if (!popoverTrigger) {
 | 
        
           |  |  | 86 |             return;
 | 
        
           |  |  | 87 |         }
 | 
        
           |  |  | 88 |         const popover = Bootstrap.Popover.getOrCreateInstance(popoverTrigger);
 | 
        
           |  |  | 89 |         if (!popover._isShown()) {
 | 
        
           |  |  | 90 |             popover.show();
 | 
        
           |  |  | 91 |         }
 | 
        
           |  |  | 92 |     });
 | 
        
           | 1 | efrain | 93 | };
 | 
        
           |  |  | 94 |   | 
        
           |  |  | 95 | /**
 | 
        
           |  |  | 96 |  * Enable tooltips
 | 
        
           |  |  | 97 |  *
 | 
        
           |  |  | 98 |  */
 | 
        
           |  |  | 99 | const enableTooltips = () => {
 | 
        
           | 1441 | ariadna | 100 |     const tooltipTriggerList = document.querySelectorAll('[data-bs-toggle="tooltip"]');
 | 
        
           |  |  | 101 |     const tooltipList = [...tooltipTriggerList].map(tooltipTriggerEl => new Bootstrap.Tooltip(tooltipTriggerEl));
 | 
        
           |  |  | 102 |   | 
        
           |  |  | 103 |     document.addEventListener('keydown', e => {
 | 
        
           |  |  | 104 |         if (e.key === 'Escape') {
 | 
        
           |  |  | 105 |             tooltipList.forEach(tooltip => {
 | 
        
           |  |  | 106 |                 tooltip.hide();
 | 
        
           |  |  | 107 |             });
 | 
        
           |  |  | 108 |         }
 | 
        
           | 1 | efrain | 109 |     });
 | 
        
           |  |  | 110 | };
 | 
        
           |  |  | 111 |   | 
        
           | 1441 | ariadna | 112 | /**
 | 
        
           |  |  | 113 |  * Realocate Bootstrap events to the body element.
 | 
        
           |  |  | 114 |  *
 | 
        
           |  |  | 115 |  * Bootstrap 5 has a unique event handling mechanism that attaches all event handlers at the document level
 | 
        
           |  |  | 116 |  * during the capture phase, rather than the usual bubbling phase. As a result, original Bootstrap events
 | 
        
           |  |  | 117 |  * cannot be stopped or prevented, since the document is the first node executed in the capture phase.
 | 
        
           |  |  | 118 |  * For certain advanced UI elements, such as form autocomplete, it is important to capture key-down events before
 | 
        
           |  |  | 119 |  * Bootstrap's handlers to prevent unintended closures of elements. Therefore, we need to change the Bootstrap handler
 | 
        
           |  |  | 120 |  * so that it operates one level lower, specifically at the body level.
 | 
        
           |  |  | 121 |  */
 | 
        
           |  |  | 122 | const realocateBootstrapEvents = () => {
 | 
        
           |  |  | 123 |     EventHandler.off(document, 'keydown.bs.dropdown.data-api', '.dropdown-menu', Bootstrap.Dropdown.dataApiKeydownHandler);
 | 
        
           |  |  | 124 |     EventHandler.on(document.body, 'keydown.bs.dropdown.data-api', '.dropdown-menu', Bootstrap.Dropdown.dataApiKeydownHandler);
 | 
        
           |  |  | 125 | };
 | 
        
           |  |  | 126 |   | 
        
           | 1 | efrain | 127 | const pendingPromise = new Pending('theme_boost/loader:init');
 | 
        
           |  |  | 128 |   | 
        
           |  |  | 129 | // Add pending promise event listeners to relevant Bootstrap custom events.
 | 
        
           |  |  | 130 | setupBootstrapPendingChecks();
 | 
        
           |  |  | 131 |   | 
        
           |  |  | 132 | // Setup Aria helpers for Bootstrap features.
 | 
        
           |  |  | 133 | Aria.init();
 | 
        
           |  |  | 134 |   | 
        
           |  |  | 135 | // Remember the last visited tabs.
 | 
        
           |  |  | 136 | rememberTabs();
 | 
        
           |  |  | 137 |   | 
        
           |  |  | 138 | // Enable all popovers.
 | 
        
           |  |  | 139 | enablePopovers();
 | 
        
           |  |  | 140 |   | 
        
           |  |  | 141 | // Enable all tooltips.
 | 
        
           |  |  | 142 | enableTooltips();
 | 
        
           |  |  | 143 |   | 
        
           | 1441 | ariadna | 144 | // Realocate Bootstrap events to the body element.
 | 
        
           |  |  | 145 | realocateBootstrapEvents();
 | 
        
           | 1 | efrain | 146 |   | 
        
           |  |  | 147 | pendingPromise.resolve();
 | 
        
           |  |  | 148 |   | 
        
           |  |  | 149 | export {
 | 
        
           |  |  | 150 |     Bootstrap,
 | 
        
           |  |  | 151 | };
 |