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