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