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