1 /**
2  * DokuWiki Mikio Template Javascript
3  *
4  * @link    http://dokuwiki.org/template:mikio
5  * @author  James Collins <james.collins@outlook.com.au>
6  * @license GPLv2 (http://www.gnu.org/licenses/gpl-2.0.html)
7  */
8 "use strict";
9 
10 var mikio = {
11     queueResize: false,
12     mikioCSS: false,
13     stickyItems: [],
14     stickyOffset: 0,
15     stickyIndex: 2010,
16     darkMode: 'light',
17 
18     ready: function () {
19         var self = this;
20 
21         this.initDarkMode();
22         this.addToggleClick('mikio-sidebar-toggle', 'mikio-sidebar-collapse');
23         this.addToggleClick('mikio-navbar-toggle', 'mikio-navbar-collapse');
24         this.addDropdownClick('mikio-nav-dropdown', 'mikio-dropdown');
25         this.indexmenuPatch();
26         this.typeahead();
27 
28         var updateStickyItems = function () {
29             window.removeEventListener('scroll', updateStickyScroll);
30 
31             var stickyElements = document.getElementsByClassName('mikio-sticky');
32             self.stickyItems = [];
33             if (stickyElements && stickyElements.length > 0) {
34                 var stickyOffset = stickyElements[0].offsetTop;
35                 var stickyHeightCount = stickyOffset;
36 
37                 [].forEach.call(stickyElements, (item) => {
38                     var top = stickyOffset;
39                     if (item.offsetTop - stickyHeightCount > stickyHeightCount) {
40                         top = stickyHeightCount;
41                     }
42 
43                     self.stickyItems.push({ element: item, offsetYTop: top, debugItemTop: item.offsetTop, debugOffset: stickyOffset, debugHeight: stickyHeightCount });
44                     stickyHeightCount += item.offsetHeight;
45                 });
46 
47                 window.addEventListener('scroll', updateStickyScroll);
48                 updateStickyScroll();
49             }
50         };
51 
52         var updateStickyScroll = function () {
53             self.stickyItems.forEach((item) => {
54                 if (window.pageYOffset > item.offsetYTop) {
55                     if (item.element.style.position != 'fixed') {
56                         var site = document.getElementById('dokuwiki__site');
57                         site.style.paddingTop = ((parseInt(site.style.paddingTop) || 0) + item.element.offsetHeight) + 'px';
58 
59                         item.element.style.position = 'fixed';
60                         item.element.style.top = self.stickyOffset + 'px';
61                         item.element.style.zIndex = self.stickyIndex;
62 
63                         self.stickyOffset += item.element.offsetHeight;
64                         self.stickyIndex--;
65                     }
66                 } else {
67                     if (item.element.style.position == 'fixed') {
68                         var site = document.getElementById('dokuwiki__site');
69                         site.style.paddingTop = ((parseInt(site.style.paddingTop) || 0) - item.element.offsetHeight) + 'px';
70                         self.stickyOffset -= item.element.offsetHeight;
71                         self.stickyIndex++;
72 
73                         item.element.style.position = 'relative';
74                         item.element.style.top = null;
75                         item.element.style.zIndex = null;
76                     }
77                 }
78             });
79         };
80 
81         updateStickyItems();
82 
83         window.onresize = function () {
84             if (!this.queueResize) {
85                 this.queueResize = true;
86                 window.setTimeout(function () {
87                     this.queueResize = false;
88                     Array.from(document.getElementsByClassName('mikio-dropdown')).forEach(function (elem) {
89                         if (!elem.classList.contains('closed')) {
90                             elem.classList.add('closed');
91                         }
92                     });
93 
94                     updateStickyItems();
95                 }, 100);
96             }
97         };
98 
99         // Mikio-Dropdown - Click
100         Array.from(document.getElementsByClassName('mikio-dropdown')).forEach(function (elem) {
101             elem.addEventListener('click', function (event) {
102                 event.stopPropagation();
103             });
104         });
105 
106         // Mikio-Dropdown - Close when clicked outside dropdown
107         Array.from(document.getElementsByTagName('body')).forEach(function (elem) {
108             elem.addEventListener('click', function (event) {
109                 Array.from(document.getElementsByClassName('mikio-dropdown')).forEach(function (elem) {
110                     if (!elem.classList.contains('closed')) {
111                         elem.classList.add('closed');
112                     }
113                 });
114             });
115         });
116 
117         // Mikio-Navbar-Toggle - Fix
118         Array.from(document.getElementsByClassName('mikio-navbar-toggle')).forEach(function (elem) {
119             elem.classList.add('closed');
120         });
121 
122         // Mikio-Dropdown - Fix
123         Array.from(document.getElementsByClassName('mikio-dropdown')).forEach(function (elem) {
124             elem.classList.add('closed');
125         });
126 
127         // Input File - Cleanup
128         Array.from(document.querySelectorAll('input[type=file]')).forEach(function (elem) {
129             var style = window.getComputedStyle(elem);
130 
131             if (style.display != 'none') {
132                 var parentElem = elem.parentElement;
133                 var fileRect = elem.getBoundingClientRect();
134                 var parentRect = parentElem.getBoundingClientRect();
135                 var spanElem = document.createElement('span');
136 
137                 elem.style.opacity = 0;
138                 parentElem.style.position = 'relative';
139                 spanElem.innerHTML = 'Choose file...';
140                 spanElem.classList.add('mikio-input-file');
141                 spanElem.style.left = Math.floor(fileRect.left - parentRect.left) + 'px';
142                 spanElem.style.width = Math.floor(fileRect.right - fileRect.left) + 'px';
143                 mikio.insertAfter(spanElem, elem);
144 
145                 spanElem.addEventListener('click', function (event) {
146                     if (event.target.parentElement.tagName.toLowerCase() != 'label') {
147                         let sibling = mikio.getPrevSibling(event.target, 'input');
148                         if (typeof sibling !== 'undefined') {
149                             sibling.click();
150                         }
151                     }
152                 });
153 
154                 elem.addEventListener('change', function () {
155                     if (this.files.length > 0) {
156                         let mikioInput = mikio.getNextSibling(this, '.mikio-input-file');
157                         if (typeof mikioInput !== 'undefined') {
158                             mikioInput.innerHTML = this.files[0].name;
159                         }
160                     }
161                 });
162             }
163         });
164 
165         // Input - Span (Placeholder) clear when typing
166         Array.from(document.querySelectorAll('.mikio.dokuwiki .mode_login fieldset label.block input.edit, .mikio.dokuwiki .mode_denied fieldset label.block input.edit')).forEach(function (elem) {
167             if (elem.value.length != 0) {
168                 var sibling = mikio.getPrevSibling(elem, 'span');
169                 if (sibling) {
170                     sibling.style.display = 'none';
171                 }
172             }
173 
174             elem.addEventListener('keydown', function (event) {
175                 var sibling = mikio.getPrevSibling(event.target, 'span');
176 
177                 setTimeout(function () {
178                     if (sibling) {
179                         if (event.target.value != '') {
180                             sibling.style.display = 'none';
181                         } else {
182                             sibling.style.display = 'block';
183                         }
184                     }
185                 }, 50);
186             });
187         });
188 
189         // Admin - Exit button
190         Array.from(document.querySelectorAll('a[rel="exit-admin"]')).forEach(function (elem) {
191             elem.addEventListener('click', function (event) {
192                 event.preventDefault();
193 
194                 var href = window.location.protocol + "//" + window.location.host + "/" + window.location.pathname;
195 
196                 var params = window.location.search;
197                 if (params !== '') {
198                     params = params.substr(1).split('&');
199                     if (params.length > 1) {
200                         href += '?';
201                         params.forEach(function (p) {
202                             if (p.substring(0, 3) == 'id=') {
203                                 href += p;
204                             }
205                         });
206                     }
207                 }
208 
209                 window.location = href;
210             });
211         });
212 
213         // Admin - Back button
214         Array.from(document.querySelectorAll('a[rel="exit-page"]')).forEach(function (elem) {
215             elem.addEventListener('click', function (event) {
216                 event.preventDefault();
217 
218                 var href = window.location.protocol + "//" + window.location.host + "/" + window.location.pathname;
219 
220                 var params = window.location.search;
221                 if (params != '') {
222                     params = params.substr(1).split('&');
223                     if (params.length > 1) {
224                         href += '?';
225                         params.forEach(function (p) {
226                             if (p.substring(0, 5) != 'page=') {
227                                 href += p + '&';
228                             }
229                         });
230                     }
231                 }
232 
233                 window.location = href;
234             });
235         });
236 
237         // Admin - Resize large text blocks in tasks
238         Array.from(document.querySelectorAll('.admin_tasks span.prompt')).forEach(function (elem) {
239             if (elem.offsetHeight > 48) {
240                 elem.style.fontSize = '80%';
241             }
242         });
243 
244         // Media Manager - ui-resizable is always auto
245         var mediaChangedObserver = new MutationObserver(function (mutationsList) {
246             for (let mutation of mutationsList) {
247                 if (mutation.type === 'childList') {
248                     if (mutation.addedNodes) {
249                         mutation.addedNodes.forEach(function (node) {
250                             if (node.nodeName == 'LI') {
251 
252                             }
253                         });
254                     }
255                 }
256 
257                 if (mutation.type === 'attributes' && mutation.attributeName == 'style' && mutation.target && mutation.target.style.height) {
258                     mutation.target.style.height = '';
259                 }
260             }
261         });
262 
263         var target = document.getElementById('mediamanager__page');
264         if (target) {
265             mediaChangedObserver.observe(target, { attributes: true, childList: true, subtree: true });
266         }
267 
268         // Media Manager - file click
269         Array.from(document.querySelectorAll('#mediamanager__page .filelist')).forEach(function (elem) {
270             elem.addEventListener('click', function (event) {
271                 var liElem = event.target.closest('li');
272                 if (liElem && event.target.closest('ul.thumbs')) {
273                     var aElem = liElem.querySelector('dd.name a');
274                     if (aElem) aElem.click();
275                 }
276             });
277         });
278 
279         // Popup Media Manager - clean file info
280         var mediaPopupFileInfoClean = function (elem) {
281             var file = { resolution: '', date: '', time: '', size: '' };
282 
283             var infoElem = elem.querySelector('span.info');
284             if (infoElem) {
285                 var infoText = infoElem.innerText.replace(/(<[^>]*>|[\(\)])/g, '');
286                 var detail = infoText.split(' ');
287                 while (detail.length < 4) {
288                     detail.unshift('');
289                 }
290 
291                 infoElem.innerHTML = detail[0] + '<br>' + detail[1] + ' ' + detail[2] + '<br>' + detail[3];
292             }
293 
294             Array.from(elem.querySelectorAll('img')).forEach(function (elem) {
295                 elem.removeAttribute('width');
296                 elem.removeAttribute('height');
297             });
298         }
299 
300         var mediaPopupObserver = new MutationObserver(function (mutationsList) {
301             for (let mutation of mutationsList) {
302                 if (mutation.type === 'childList') {
303                     if (mutation.addedNodes) {
304                         mutation.addedNodes.forEach(function (node) {
305                             if (node.nodeName == 'DIV') {
306                                 mediaPopupFileInfoClean(node);
307                             }
308                         });
309                     }
310                 }
311             }
312         });
313 
314         var target = document.getElementById('media__content');
315         if (target) {
316             Array.from(target.querySelectorAll('div.odd, div.even')).forEach(function (elem) {
317                 mediaPopupFileInfoClean(elem);
318             });
319 
320             mediaPopupObserver.observe(target, { attributes: false, childList: true });
321         }
322 
323         if (typeof mikioFooterRun === "function") mikioFooterRun();
324 
325         var mediaChangedObserver = new MutationObserver(function (mutationsList) {
326             for (let mutation of mutationsList) {
327                 if (mutation.type === 'attributes' && mutation.attributeName == 'href') {
328                     if (self.mikioCSS != false) {
329                         var elem = self.mikioCSS;
330                         var prev = elem.href;
331 
332                         setTimeout(function () {
333                             var url = new URL(prev);
334                             var params = url.searchParams;
335                             params.set('seed', new Date().getTime());
336                             url.search = params.toString();
337                             elem.href = url.toString();
338                         }, 500);
339                     }
340                 }
341             }
342         });
343 
344         var linkElements = document.getElementsByTagName('link');
345         for (let element of linkElements) {
346             if (element.rel == 'stylesheet' && element.href) {
347                 if (element.href.includes('/lib/exe/css.php')) {
348                     mediaChangedObserver.observe(element, { attributes: true, childList: true, subtree: true });
349                 } else if (element.href.includes('/lib/tpl/mikio/css.php')) {
350                     this.mikioCSS = element;
351                 }
352             }
353         }
354 
355         // Update color text field when selector changes
356         let colorSelectorInputs = document.querySelectorAll('div.mikio-color-picker input[type="color"]');
357         colorSelectorInputs.forEach(input => {
358             input.addEventListener('input', () => {
359                 let colorTextInput = document.querySelector(`div.mikio-color-picker input[for="${input.id}"]`);
360                 if (colorTextInput) {
361                     colorTextInput.value = input.value;
362                 }
363             });
364         });
365 
366         let colorTextInputs = document.querySelectorAll('div.mikio-color-picker input[type="text"]');
367         colorTextInputs.forEach(input => {
368             input.addEventListener('blur', () => {
369                 const id = input.getAttribute('for');
370                 let colorSelectorInput = document.querySelector(`div.mikio-color-picker input[id="${id}"]`);
371                 if (colorSelectorInput) {
372                     let s = this.colorToHex(input.value);
373                     colorSelectorInput.value = s;
374                 }
375             });
376         });
377     },
378 
379     initDarkMode: function () {
380         const showLightDark = document.querySelector('.mikio-darklight-button') != null;
381         if (showLightDark == true) {
382             let setting = this.getCookie('lightDarkToggle');
383             if (setting == 'dark' || setting == 'light' || setting == 'auto') {
384                 this.darkMode = setting;
385             }
386 
387             if (document.querySelector('.mikio-auto-darklight') == null && setting == 'auto') {
388                 this.darkMode = 'light';
389             }
390         } else {
391             if (document.querySelector('.mikio-auto-darklight') != null) {
392                 this.darkMode = 'auto';
393             }
394         }
395 
396         var self = this;
397 
398         if (window.matchMedia) {
399             window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', (event) => {
400                 self.updateDarkMode();
401             });
402         }
403 
404         this.addEventListenerByClassName('mikio-darklight-button', 'click', function (event) {
405             event.preventDefault();
406             event.stopPropagation();
407 
408             const autoAllowed = (document.querySelector('.mikio-iicon.mikio-darklight-auto') != null);
409 
410             if (self.darkMode == 'light') {
411                 self.darkMode = 'dark';
412             } else if (self.darkMode == 'dark') {
413                 if (autoAllowed == true) {
414                     self.darkMode = 'auto';
415                 } else {
416                     self.darkMode = 'light';
417                 }
418             } else if (self.darkMode == 'auto') {
419                 self.darkMode = 'light';
420             } else {
421                 self.darkMode = 'dark';
422             }
423 
424             self.updateDarkMode();
425         });
426 
427         this.updateDarkMode();
428     },
429 
430     updateDarkMode: function () {
431         const html = document.querySelector('html');
432         let themeMode = this.darkMode;
433 
434         if (this.darkMode == 'auto') {
435             html.dataset.themeAuto = true;
436 
437             themeMode = 'light';
438             if (window.matchMedia) {
439                 let prefersColorSchemeQuery = window.matchMedia('(prefers-color-scheme: dark)');
440                 themeMode = prefersColorSchemeQuery.matches ? 'dark' : 'light';
441             }
442         } else {
443             delete html.dataset.themeAuto;
444         }
445 
446         html.dataset.theme = `theme-${themeMode}`;
447         this.setCookie('lightDarkToggle', this.darkMode);
448     },
449 
450     insertAfter: function (newNode, existingNode) {
451         existingNode.parentNode.insertBefore(newNode, existingNode.nextSibling);
452     },
453 
454     addToggleClick: function (elemToggle, elemCollapse) {
455         this.addEventListenerByClassName(elemToggle, 'click', function (event) {
456             event.preventDefault();
457             event.stopPropagation();
458             let nextSibling = mikio.getNextSibling(this, '.' + elemCollapse);
459 
460             if (typeof nextSibling !== 'undefined') {
461                 mikio.toggleCollapse(this, nextSibling);
462             }
463         });
464     },
465 
466     addDropdownClick: function (elemToggle, elemCollapse) {
467         this.addEventListenerByClassName(elemToggle, 'click', function (event) {
468             event.preventDefault();
469             event.stopPropagation();
470 
471             var dropdown = this.querySelector('.' + elemCollapse);
472             if (dropdown) {
473                 mikio.toggleDropdown(dropdown);
474             }
475         });
476     },
477 
478     addEventListenerByClassName: function (className, eventType, callback) {
479         Array.from(document.getElementsByClassName(className)).forEach(function (elem) {
480             elem.addEventListener(eventType, callback);
481         });
482     },
483 
484     getNextSibling: function (elem, selector) {
485         var sibling = elem.nextElementSibling;
486 
487         while (sibling) {
488             if (sibling.matches(selector)) return sibling;
489             sibling = sibling.nextElementSibling;
490         }
491     },
492 
493     getPrevSibling: function (elem, selector) {
494         var sibling = elem.previousElementSibling;
495 
496         while (sibling) {
497             if (sibling.matches(selector)) return sibling;
498             sibling = sibling.previousElementSibling;
499         }
500     },
501 
502     toggleCollapse: function (objToggle, objCollapse) {
503         if (objToggle.classList.contains('closed')) {
504             objToggle.classList.remove('closed');
505             objToggle.classList.add('open');
506             var height = objCollapse.offsetHeight;
507             objCollapse.style.overflow = 'hidden';
508             objCollapse.style.height = 0;
509             objCollapse.style.paddingTop = 0;
510             objCollapse.style.paddingBottom = 0;
511             objCollapse.style.marginTop = 0;
512             objCollapse.style.marginBottom = 0;
513             objCollapse.offsetHeight;
514             objCollapse.style.boxSizing = 'border-box';
515             objCollapse.style.transitionProperty = "height, margin, padding";
516             objCollapse.style.transitionDuration = '500ms';
517             objCollapse.style.height = height + 'px';
518             objCollapse.style.removeProperty('padding-top');
519             objCollapse.style.removeProperty('padding-bottom');
520             objCollapse.style.removeProperty('margin-top');
521             objCollapse.style.removeProperty('margin-bottom');
522             window.setTimeout(function () {
523                 objCollapse.style.removeProperty('height');
524                 objCollapse.style.removeProperty('overflow');
525                 objCollapse.style.removeProperty('transition-duration');
526                 objCollapse.style.removeProperty('transition-property');
527                 objCollapse.style.removeProperty('box-sizing');
528             }, 500);
529         } else {
530             objCollapse.style.transitionProperty = 'height, margin, padding';
531             objCollapse.style.transitionDuration = '500ms';
532             objCollapse.style.boxSizing = 'border-box';
533             objCollapse.style.height = objCollapse.offsetHeight + 'px';
534             objCollapse.offsetHeight;
535             objCollapse.style.overflow = 'hidden';
536             objCollapse.style.height = 0;
537             objCollapse.style.paddingTop = 0;
538             objCollapse.style.paddingBottom = 0;
539             objCollapse.style.marginTop = 0;
540             objCollapse.style.marginBottom = 0;
541             window.setTimeout(function () {
542                 objToggle.classList.add('closed');
543                 objToggle.classList.remove('open');
544                 objCollapse.style.removeProperty('height');
545                 objCollapse.style.removeProperty('padding-top');
546                 objCollapse.style.removeProperty('padding-bottom');
547                 objCollapse.style.removeProperty('margin-top');
548                 objCollapse.style.removeProperty('margin-bottom');
549                 objCollapse.style.removeProperty('overflow');
550                 objCollapse.style.removeProperty('transition-duration');
551                 objCollapse.style.removeProperty('transition-property');
552                 objCollapse.style.removeProperty('box-sizing');
553             }, 500);
554         }
555     },
556 
557 
558     toggleDropdown: function (objToggle) {
559         if (objToggle.classList.contains('closed')) {
560             objToggle.classList.remove('closed');
561         } else {
562             objToggle.classList.add('closed');
563         }
564 
565         Array.from(document.getElementsByClassName('mikio-dropdown')).forEach(function (elem) {
566             if (!elem.classList.contains('closed') && elem != objToggle) {
567                 elem.classList.add('closed');
568             }
569         });
570     },
571 
572     setHeroSubTitle: function (str) {
573         Array.from(document.getElementsByClassName('mikio-hero-subtitle')).forEach(function (elem) {
574             elem.innerHTML = str;
575         });
576     },
577 
578     setHeroImage: function (str) {
579         var heroImages = document.getElementsByClassName('mikio-hero-image');
580 
581         if (heroImages.length > 0) {
582             Array.from(document.getElementsByClassName('mikio-hero-image')).forEach(function (elem) {
583                 elem.style.backgroundImage = 'url(\'' + str + '\')';
584                 elem.classList.add('mikio-hero-image-resize');
585             });
586         } else {
587             Array.from(document.getElementsByClassName('mikio-hero-text')).forEach(function (elem) {
588                 elem.insertAdjacentHTML('afterend', '<div class="mikio-hero-image mikio-hero-image-resize" style="background-image:url(\'' + str + '\');"></div>');
589             });
590         }
591     },
592 
593     setHeroColor: function (str) {
594         var colors = str.trim().replace(/ +(?= )/g, '').split(/(?!\(.*)\s(?![^(]*?\))/g);
595         if (colors.length > 0 && colors[0] != '') {
596             Array.from(document.getElementsByClassName('mikio-hero')).forEach(function (elem) {
597                 elem.style.backgroundColor = colors[0];
598             });
599 
600             if (colors.length > 1) {
601                 Array.from(document.getElementsByClassName('mikio-hero-title')).forEach(function (elem) {
602                     elem.style.color = colors[1];
603                 });
604             }
605 
606             if (colors.length > 2) {
607                 Array.from(document.getElementsByClassName('mikio-hero-subtitle')).forEach(function (elem) {
608                     elem.style.color = colors[2];
609                 });
610             }
611 
612             if (colors.length > 3) {
613                 Array.from(document.getElementsByClassName('mikio-hero')).forEach(function (parentElem) {
614                     Array.from(parentElem.querySelectorAll('.mikio-breadcrumbs ul li a')).forEach(function (elem) {
615                         elem.style.color = colors[3];
616                     });
617 
618                     Array.from(parentElem.querySelectorAll('.mikio-breadcrumbs ul li, .mikio-breadcrumbs ul li a')).forEach(function (elem) {
619                         elem.style.color = colors[3];
620                         elem.onmouseover = function () { this.style.color = (colors.length > 4 ? colors[4] : 'initial'); };
621                         elem.onmouseout = function () { this.style.color = colors[3]; };
622                     });
623                 });
624             }
625         }
626     },
627 
628     setTags: function (str) {
629         Array.from(document.getElementsByClassName('mikio-tags')).forEach(function (elem) {
630             elem.innerHTML = str;
631         });
632     },
633 
634     hidePart: function (part) {
635         var selectorArray = {
636             topheader: '.mikio-page-topheader',
637             header: '.mikio-page-header',
638             contentheader: '.mikio-page-contentheader',
639             contentfooter: '.mikio-page-contentfooter',
640             sidebarheader: '.mikio-sidebar-left .mikio-sidebar-header',
641             sidebarfooter: '.mikio-sidebar-left .mikio-sidebar-footer',
642             rightsidebarheader: '.mikio-sidebar-right .mikio-sidebar-header',
643             rightsidebarfooter: '.mikio-sidebar-right .mikio-sidebar-footer',
644             footer: '.mikio-footer',
645             bottomfooter: '.mikio-page-bottomfooter',
646             navbar: '.mikio-navbar',
647             hero: '.mikio-hero'
648         };
649 
650         if (selectorArray.hasOwnProperty(part)) {
651             Array.from(document.querySelectorAll(selectorArray[part])).forEach(function (elem) {
652                 elem.style.display = 'none';
653             });
654         }
655     },
656 
657     indexmenuPatch: function () {
658         window.setTimeout(function () {
659             Array.from(document.querySelectorAll('a.navSel')).forEach(function (elem) {
660                 let prev = mikio.getPrevSibling(elem, 'img');
661                 if (prev) {
662                     prev.style.opacity = 1;
663                 }
664             });
665         }, 50);
666 
667 
668         document.addEventListener('mouseover', function (event) {
669             const indexmenuClasses = ['nodeUrl', 'nodeSel', 'node'];
670             if ([...event.target.classList].some(className => indexmenuClasses.indexOf(className) !== -1)) {
671                 let prev = mikio.getPrevSibling(event.target, 'img');
672                 if (prev) {
673                     prev.style.opacity = 1;
674                 }
675             }
676         });
677 
678         document.addEventListener('mouseout', function (event) {
679             const indexmenuClasses = ['nodeUrl', 'nodeSel', 'node'];
680             if ([...event.target.classList].some(className => indexmenuClasses.indexOf(className) !== -1)) {
681                 let prev = mikio.getPrevSibling(event.target, 'img');
682                 if (prev) {
683                     prev.style.opacity = '';
684                 }
685             }
686         });
687     },
688 
689     // Add typeahead support for quick seach. Taken from bootstrap3 theme.
690     typeahead: function () {
691 
692         jQuery(".search_typeahead").typeahead({
693 
694             source: function (query, process) {
695 
696                 return jQuery.post(DOKU_BASE + 'lib/exe/ajax.php',
697                     {
698                         call: 'qsearch',
699                         q: encodeURI(query)
700                     },
701                     function (data) {
702 
703                         var results = [];
704 
705                         jQuery(data).find('a').each(function () {
706 
707                             var page = jQuery(this);
708 
709                             results.push({
710                                 name: page.text(),
711                                 href: page.attr('href'),
712                                 title: page.attr('title'),
713                                 category: page.attr('title').replace(/:/g, ' : '),
714                             });
715 
716                         });
717 
718                         return process(results);
719 
720                     });
721             },
722 
723             itemLink: function (item) {
724                 return item.href;
725             },
726 
727             itemTitle: function (item) {
728                 return item.title;
729             },
730 
731             followLinkOnSelect: true,
732             autoSelect: false,
733             items: 10,
734             fitToElement: true,
735             delay: 500,
736             theme: 'bootstrap4',
737 
738         });
739     },
740 
741     getCookie: function (cname) {
742         let name = cname + "=";
743         let decodedCookie = decodeURIComponent(document.cookie);
744         let ca = decodedCookie.split(';');
745         for (let i = 0; i < ca.length; i++) {
746             let c = ca[i];
747             while (c.charAt(0) == ' ') {
748                 c = c.substring(1);
749             }
750             if (c.indexOf(name) == 0) {
751                 return c.substring(name.length, c.length);
752             }
753         }
754         return "";
755     },
756 
757     setCookie: function (cname, cvalue, exdays) {
758         const d = new Date();
759         d.setTime(d.getTime() + (exdays * 24 * 60 * 60 * 1000));
760         let expires = "expires=" + d.toUTCString();
761         document.cookie = cname + "=" + cvalue + ";" + expires + ";path=/;SameSite=Lax";
762     },
763 
764     clearCookie: function (cname) {
765         document.cookie = cname + "=;expires=Thu, 01 Jan 1970 00:00:00 GMT;path=/;SameSite=Lax";
766     },
767 
768     colorToHex: function (color) {
769         // Create a canvas element
770         let canvas = document.createElement('canvas');
771         canvas.height = 1;
772         canvas.width = 1;
773         let ctx = canvas.getContext('2d');
774 
775         // Set the fillStyle to the color input
776         ctx.fillStyle = color;
777         ctx.fillRect(0, 0, 1, 1);
778 
779         // Get the pixel data from the canvas
780         let data = ctx.getImageData(0, 0, 1, 1).data;
781 
782         // Convert the RGB values to HEX
783         let hex = '#' + ((1 << 24) + (data[0] << 16) + (data[1] << 8) + data[2]).toString(16).slice(1).toUpperCase();
784 
785         return hex;
786     }
787 };
788 
789 if (document.readyState != 'loading') {
790     mikio.ready();
791 } else {
792     document.addEventListener('DOMContentLoaded', function () { mikio.ready() });
793 }
794