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