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