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