1document.addEventListener('DOMContentLoaded', function () { 2 let headerContentWidth, $nav 3 let mobileSidebarOpen = false 4 5 const adjustMenu = init => { 6 const getAllWidth = ele => { 7 return Array.from(ele).reduce((width, i) => width + i.offsetWidth, 0) 8 } 9 10 if (init) { 11 const blogInfoWidth = getAllWidth(document.querySelector('#blog-info > a').children) 12 const menusWidth = getAllWidth(document.getElementById('menus').children) 13 headerContentWidth = blogInfoWidth + menusWidth 14 $nav = document.getElementById('nav') 15 } 16 17 const hideMenuIndex = window.innerWidth <= 768 || headerContentWidth > $nav.offsetWidth - 120 18 $nav.classList.toggle('hide-menu', hideMenuIndex) 19 } 20 21 // åå§åheader 22 const initAdjust = () => { 23 adjustMenu(true) 24 $nav.classList.add('show') 25 } 26 27 // sidebar menus 28 const sidebarFn = { 29 open: () => { 30 btf.sidebarPaddingR() 31 document.body.style.overflow = 'hidden' 32 btf.animateIn(document.getElementById('menu-mask'), 'to_show 0.5s') 33 document.getElementById('sidebar-menus').classList.add('open') 34 mobileSidebarOpen = true 35 }, 36 close: () => { 37 const $body = document.body 38 $body.style.overflow = '' 39 $body.style.paddingRight = '' 40 btf.animateOut(document.getElementById('menu-mask'), 'to_hide 0.5s') 41 document.getElementById('sidebar-menus').classList.remove('open') 42 mobileSidebarOpen = false 43 } 44 } 45 46 /** 47 * é¦é top_imgåºä¸çç®é 48 */ 49 const scrollDownInIndex = () => { 50 const handleScrollToDest = () => { 51 btf.scrollToDest(document.getElementById('content-inner').offsetTop, 300) 52 } 53 54 const $scrollDownEle = document.getElementById('scroll-down') 55 $scrollDownEle && btf.addEventListenerPjax($scrollDownEle, 'click', handleScrollToDest) 56 } 57 58 /** 59 * 代碼 60 * åªé©ç¨æ¼Hexoé»èªç代碼渲æ 61 */ 62 const addHighlightTool = () => { 63 const highLight = GLOBAL_CONFIG.highlight 64 if (!highLight) return 65 66 const { highlightCopy, highlightLang, highlightHeightLimit, plugin } = highLight 67 const isHighlightShrink = GLOBAL_CONFIG_SITE.isHighlightShrink 68 const isShowTool = highlightCopy || highlightLang || isHighlightShrink !== undefined 69 const $figureHighlight = plugin === 'highlight.js' ? document.querySelectorAll('figure.highlight') : document.querySelectorAll('pre[class*="language-"]') 70 71 if (!((isShowTool || highlightHeightLimit) && $figureHighlight.length)) return 72 73 const isPrismjs = plugin === 'prismjs' 74 const highlightShrinkClass = isHighlightShrink === true ? 'closed' : '' 75 const highlightShrinkEle = isHighlightShrink !== undefined ? '<i class="fas fa-angle-down expand"></i>' : '' 76 const highlightCopyEle = highlightCopy ? '<div class="copy-notice"></div><i class="fas fa-paste copy-button"></i>' : '' 77 78 const alertInfo = (ele, text) => { 79 if (GLOBAL_CONFIG.Snackbar !== undefined) { 80 btf.snackbarShow(text) 81 } else { 82 const prevEle = ele.previousElementSibling 83 prevEle.textContent = text 84 prevEle.style.opacity = 1 85 setTimeout(() => { prevEle.style.opacity = 0 }, 800) 86 } 87 } 88 89 const copy = ctx => { 90 if (document.queryCommandSupported && document.queryCommandSupported('copy')) { 91 document.execCommand('copy') 92 alertInfo(ctx, GLOBAL_CONFIG.copy.success) 93 } else { 94 alertInfo(ctx, GLOBAL_CONFIG.copy.noSupport) 95 } 96 } 97 98 // click events 99 const highlightCopyFn = ele => { 100 const $buttonParent = ele.parentNode 101 $buttonParent.classList.add('copy-true') 102 const selection = window.getSelection() 103 const range = document.createRange() 104 const preCodeSelector = isPrismjs ? 'pre code' : 'table .code pre' 105 range.selectNodeContents($buttonParent.querySelectorAll(`${preCodeSelector}`)[0]) 106 selection.removeAllRanges() 107 selection.addRange(range) 108 copy(ele.lastChild) 109 selection.removeAllRanges() 110 $buttonParent.classList.remove('copy-true') 111 } 112 113 const highlightShrinkFn = ele => { 114 ele.classList.toggle('closed') 115 } 116 117 const highlightToolsFn = function (e) { 118 const $target = e.target.classList 119 if ($target.contains('expand')) highlightShrinkFn(this) 120 else if ($target.contains('copy-button')) highlightCopyFn(this) 121 } 122 123 const expandCode = function () { 124 this.classList.toggle('expand-done') 125 } 126 127 const createEle = (lang, item, service) => { 128 const fragment = document.createDocumentFragment() 129 130 if (isShowTool) { 131 const hlTools = document.createElement('div') 132 hlTools.className = `highlight-tools ${highlightShrinkClass}` 133 hlTools.innerHTML = highlightShrinkEle + lang + highlightCopyEle 134 btf.addEventListenerPjax(hlTools, 'click', highlightToolsFn) 135 fragment.appendChild(hlTools) 136 } 137 138 if (highlightHeightLimit && item.offsetHeight > highlightHeightLimit + 30) { 139 const ele = document.createElement('div') 140 ele.className = 'code-expand-btn'
141 ele.innerHTML = '<i class="fas fa-angle-double-down"></i>' 142 btf.addEventListenerPjax(ele, 'click', expandCode) 143 fragment.appendChild(ele) 144 } 145 146 if (service === 'hl') { 147 item.insertBefore(fragment, item.firstChild) 148 } else { 149 item.parentNode.insertBefore(fragment, item) 150 } 151 } 152 153 if (isPrismjs) { 154 $figureHighlight.forEach(item => { 155 if (highlightLang) { 156 const langName = item.getAttribute('data-language') || 'Code' 157 const highlightLangEle = `<div class="code-lang">${langName}</div>` 158 btf.wrap(item, 'figure', { class: 'highlight' }) 159 createEle(highlightLangEle, item) 160 } else { 161 btf.wrap(item, 'figure', { class: 'highlight' }) 162 createEle('', item) 163 } 164 }) 165 } else { 166 $figureHighlight.forEach(item => { 167 if (highlightLang) { 168 let langName = item.getAttribute('class').split(' ')[1] 169 if (langName === 'plain' || langName === undefined) langName = 'Code' 170 const highlightLangEle = `<div class="code-lang">${langName}</div>` 171 createEle(highlightLangEle, item, 'hl') 172 } else { 173 createEle('', item, 'hl') 174 } 175 }) 176 } 177 } 178 179 /** 180 * PhotoFigcaption 181 */ 182 const addPhotoFigcaption = () => { 183 document.querySelectorAll('#article-container img').forEach(item => { 184 const altValue = item.title || item.alt 185 if (!altValue) return 186 const ele = document.createElement('div') 187 ele.className = 'img-alt is-center' 188 ele.textContent = altValue 189 item.insertAdjacentElement('afterend', ele) 190 }) 191 } 192 193 /** 194 * Lightbox 195 */ 196 const runLightbox = () => { 197 btf.loadLightbox(document.querySelectorAll('#article-container img:not(.no-lightbox)')) 198 } 199 200 /** 201 * justified-gallery å庫æç 202 */ 203 204 const fetchUrl = async (url) => { 205 const response = await fetch(url) 206 return await response.json() 207 } 208 209 const runJustifiedGallery = (item, data, isButton = false, tabs) => { 210 const dataLength = data.length 211 212 const ig = new InfiniteGrid.JustifiedInfiniteGrid(item, { 213 gap: 5, 214 isConstantSize: true, 215 sizeRange: [150, 600], 216 useResizeObserver: true, 217 observeChildren: true, 218 useTransform: true 219 // useRecycle: false 220 }) 221 222 if (tabs) { 223 btf.addGlobalFn('igOfTabs', () => { ig.destroy() }, false, tabs) 224 } 225 226 const replaceDq = str => str.replace(/"/g, '"') // replace double quotes to " 227 228 const getItems = (nextGroupKey, count) => { 229 const nextItems = [] 230 const startCount = (nextGroupKey - 1) * count 231 232 for (let i = 0; i < count; ++i) { 233 const num = startCount + i 234 if (num >= dataLength) { 235 break 236 } 237 238 const item = data[num] 239 const alt = item.alt ? `alt="${replaceDq(item.alt)}"` : '' 240 const title = item.title ? `title="${replaceDq(item.title)}"` : '' 241 242 nextItems.push(`<div class="item "> 243 <img src="${item.url}" data-grid-maintained-target="true" ${alt + title} /> 244 </div>`) 245 } 246 return nextItems 247 } 248 249 const buttonText = GLOBAL_CONFIG.infinitegrid.buttonText 250 const addButton = item => { 251 const button = document.createElement('button') 252 button.textContent = buttonText 253 254 const buttonFn = e => { 255 e.target.removeEventListener('click', buttonFn) 256 e.target.remove() 257 btf.setLoading.add(item) 258 appendItem(ig.getGroups().length + 1, 10) 259 } 260 261 button.addEventListener('click', buttonFn) 262 item.insertAdjacentElement('afterend', button) 263 } 264 265 const appendItem = (nextGroupKey, count) => { 266 ig.append(getItems(nextGroupKey, count), nextGroupKey) 267 } 268 269 const maxGroupKey = Math.ceil(dataLength / 10) 270 271 const completeFn = e => { 272 const { updated, isResize, mounted } = e 273 if (!updated.length || !mounted.length || isResize) { 274 return 275 } 276 277 btf.loadLightbox(item.querySelectorAll('img:not(.medium-zoom-image)')) 278 279 if (ig.getGroups().length === maxGroupKey) { 280 btf.setLoading.remove(item) 281 ig.off('renderComplete', completeFn) 282 return 283 } 284 285 if (isButton) { 286 btf.setLoading.remove(item) 287 addButton(item) 288 } 289 } 290 291 const requestAppendFn = btf.debounce(e => { 292 const nextGroupKey = (+e.groupKey || 0) + 1 293 appendItem(nextGroupKey, 10) 294 295 if (nextGroupKey === maxGroupKey) { 296 ig.off('requestAppend', requestAppendFn) 297 } 298 }, 300) 299 300 btf.setLoading.add(item) 301 ig.on('renderComplete', completeFn) 302 303 if (isButton) { 304 appendItem(1, 10) 305 } else { 306 ig.on('requestAppend', requestAppendFn) 307 ig.renderItems() 308 } 309 310 btf.addGlobalFn('justifiedGallery', () =>
310 { ig.destroy() }) 311 } 312 313 const addJustifiedGallery = async (ele, tabs = false) => { 314 const init = async () => { 315 for (const item of ele) { 316 if (btf.isHidden(item)) continue 317 if (tabs && item.classList.contains('loaded')) { 318 item.querySelector('.gallery-items').innerHTML = '' 319 const button = item.querySelector(':scope > button') 320 const loadingContainer = item.querySelector(':scope > .loading-container') 321 button && button.remove() 322 loadingContainer && loadingContainer.remove() 323 } 324 325 const isButton = item.getAttribute('data-button') === 'true' 326 const text = item.firstElementChild.textContent 327 item.classList.add('loaded') 328 const content = item.getAttribute('data-type') === 'url' ? await fetchUrl(text) : JSON.parse(text) 329 runJustifiedGallery(item.lastElementChild, content, isButton, tabs) 330 } 331 } 332 333 if (typeof InfiniteGrid === 'function') { 334 init() 335 } else { 336 await getScript(`${GLOBAL_CONFIG.infinitegrid.js}`) 337 init() 338 } 339 } 340 341 /** 342 * rightside scroll percent 343 */ 344 const rightsideScrollPercent = currentTop => { 345 const scrollPercent = btf.getScrollPercent(currentTop, document.body) 346 const goUpElement = document.getElementById('go-up') 347 348 if (scrollPercent < 95) { 349 goUpElement.classList.add('show-percent') 350 goUpElement.querySelector('.scroll-percent').textContent = scrollPercent 351 } else { 352 goUpElement.classList.remove('show-percent') 353 } 354 } 355 356 /** 357 * 滾åèç 358 */ 359 const scrollFn = () => { 360 const $rightside = document.getElementById('rightside') 361 const innerHeight = window.innerHeight + 56 362 let initTop = 0 363 const $header = document.getElementById('page-header') 364 const isChatBtn = typeof chatBtn !== 'undefined' 365 const isShowPercent = GLOBAL_CONFIG.percent.rightside 366 367 // ç¶æ»¾åæ¢å°äº 56 çæå
368 if (document.body.scrollHeight <= innerHeight) { 369 $rightside.classList.add('rightside-show') 370 return 371 } 372 373 // find the scroll direction 374 const scrollDirection = currentTop => { 375 const result = currentTop > initTop // true is down & false is up 376 initTop = currentTop 377 return result 378 } 379 380 let flag = '' 381 const scrollTask = btf.throttle(() => { 382 const currentTop = window.scrollY || document.documentElement.scrollTop 383 const isDown = scrollDirection(currentTop) 384 if (currentTop > 56) { 385 if (flag === '') { 386 $header.classList.add('nav-fixed') 387 $rightside.classList.add('rightside-show') 388 } 389 390 if (isDown) { 391 if (flag !== 'down') { 392 $header.classList.remove('nav-visible') 393 isChatBtn && window.chatBtn.hide() 394 flag = 'down' 395 } 396 } else { 397 if (flag !== 'up') { 398 $header.classList.add('nav-visible') 399 isChatBtn && window.chatBtn.show() 400 flag = 'up' 401 } 402 } 403 } else { 404 flag = '' 405 if (currentTop === 0) { 406 $header.classList.remove('nav-fixed', 'nav-visible') 407 } 408 $rightside.classList.remove('rightside-show') 409 } 410 411 isShowPercent && rightsideScrollPercent(currentTop) 412
413 if (document.body.scrollHeight <= innerHeight) { 414 $rightside.classList.add('rightside-show') 415 } 416 }, 300) 417 418 btf.addEventListenerPjax(window, 'scroll', scrollTask, { passive: true }) 419 } 420 421 /** 422 * toc,anchor 423 */ 424 const scrollFnToDo = () => { 425 const isToc = GLOBAL_CONFIG_SITE.isToc 426 const isAnchor = GLOBAL_CONFIG.isAnchor 427 const $article = document.getElementById('article-container') 428 429 if (!($article && (isToc || isAnchor))) return 430 431 let $tocLink, $cardToc, autoScrollToc, $tocPercentage, isExpand 432 433 if (isToc) { 434 const $cardTocLayout = document.getElementById('card-toc') 435 $cardToc = $cardTocLayout.querySelector('.toc-content') 436 $tocLink = $cardToc.querySelectorAll('.toc-link') 437 $tocPercentage = $cardTocLayout.querySelector('.toc-percentage') 438 isExpand = $cardToc.classList.contains('is-expand') 439 440 // tocå ç´ é»æ 441 const tocItemClickFn = e => { 442 const target = e.target.closest('.toc-link') 443 if (!target) return 444 445 e.preventDefault() 446 btf.scrollToDest(btf.getEleTop(document.getElementById(decodeURI(target.getAttribute('href')).replace('#', ''))), 300) 447 if (window.innerWidth < 900) { 448 $cardTocLayout.classList.remove('open') 449 } 450 } 451 452 btf.addEventListenerPjax($cardToc, 'click', tocItemClickFn) 453 454 autoScrollToc = item => { 455 const activePosition = item.getBoundingClientRect().top 456 const sidebarScrollTop = $cardToc.scrollTop 457 if (activePosition > (document.documentElement.clientHeight - 100)) { 458 $cardToc.scrollTop = sidebarScrollTop + 150 459 } 460 if (activePosition < 100) { 461 $cardToc.scrollTop = sidebarScrollTop - 150 462 } 463 } 464 } 465 466 // find head position & add active class 467 const $articleList = $article.querySelectorAll('h1,h2,h3,h4,h5,h6') 468 let detectItem = '' 469 const findHeadPosition = top => { 470 if (top === 0) { 471 return false 472 } 473 474 let currentId = '' 475 let currentIndex = '' 476 477 $articleList.forEach((ele, index) => { 478 if (top > btf.getEleTop(ele) - 80) { 479 const id = ele.id 480 currentId = id ? '#' + encodeURI(id) : '' 481 currentIndex = index 482 } 483 }) 484 485 if (detectItem === currentIndex) return 486 487 if (isAnchor) btf.updateAnchor(currentId) 488 489 detectItem = currentIndex 490 491 if (isToc) { 492 $cardToc.querySelectorAll('.active').forEach(i => { i.classList.remove('active') }) 493 494 if (currentId === '') { 495 return 496 } 497 498 const currentActive = $tocLink[currentIndex] 499 currentActive.classList.add('active') 500 501 setTimeout(() => { 502 autoScrollToc(currentActive) 503 }, 0) 504 505 if (isExpand) return 506 let parent = currentActive.parentNode 507 508 for (; !parent.matches('.toc'); parent = parent.parentNode) { 509 if (parent.matches('li')) parent.classList.add('active') 510 } 511 } 512 } 513 514 // main of scroll 515 const tocScrollFn = btf.throttle(() => { 516 const currentTop = window.scrollY || document.documentElement.scrollTop 517 if (isToc && GLOBAL_CONFIG.percent.toc) { 518 $tocPercentage.textContent = btf.getScrollPercent(currentTop, $article) 519 } 520 findHeadPosition(currentTop) 521 }, 100) 522 523 btf.addEventListenerPjax(window, 'scroll', tocScrollFn, { passive: true }) 524 } 525 526 const handleThemeChange = mode => { 527 const globalFn = window.globalFn || {} 528 const themeChange = globalFn.themeChange || {} 529 if (!themeChange) { 530 return 531 } 532 533 Object.keys(themeChange).forEach(key => { 534 const themeChangeFn = themeChange[key] 535 if (['disqus', 'disqusjs'].includes(key)) { 536 setTimeout(() => themeChangeFn(mode), 300) 537 } else { 538 themeChangeFn(mode) 539 } 540 }) 541 } 542 543 /** 544 * Rightside 545 */ 546 const rightSideFn = { 547 readmode: () => { // read mode 548 const $body = document.body 549 $body.classList.add('read-mode') 550 const newEle = document.createElement('button') 551 newEle.type = 'button' 552 newEle.className = 'fas fa-sign-out-alt exit-readmode' 553 $body.appendChild(newEle) 554
555 const clickFn = () => { 556 $body.classList.remove('read-mode') 557 newEle.remove() 558 newEle.removeEventListener('click', clickFn) 559 } 560 561 newEle.addEventListener('click', clickFn) 562 }, 563 darkmode: () => { // switch between light and dark mode 564 const willChangeMode = document.documentElement.getAttribute('data-theme') === 'dark' ? 'light' : 'dark' 565 if (willChangeMode === 'dark') { 566 activateDarkMode() 567 GLOBAL_CONFIG.Snackbar !== undefined && btf.snackbarShow(GLOBAL_CONFIG.Snackbar.day_to_night) 568 } else { 569 activateLightMode() 570 GLOBAL_CONFIG.Snackbar !== undefined && btf.snackbarShow(GLOBAL_CONFIG.Snackbar.night_to_day) 571 } 572 saveToLocal.set('theme', willChangeMode, 2) 573 handleThemeChange(willChangeMode) 574 }, 575 'rightside-config': item => { // Show or hide rightside-hide-btn 576 const hideLayout = item.firstElementChild 577 if (hideLayout.classList.contains('show')) { 578 hideLayout.classList.add('status') 579 setTimeout(() => { 580 hideLayout.classList.remove('status') 581 }, 300) 582 } 583 584 hideLayout.classList.toggle('show') 585 }, 586 'go-up': () => { // Back to top 587 btf.scrollToDest(0, 500) 588 }, 589 'hide-aside-btn': () => { // Hide aside 590 const $htmlDom = document.documentElement.classList 591 const saveStatus = $htmlDom.contains('hide-aside') ? 'show' : 'hide' 592 saveToLocal.set('aside-status', saveStatus, 2) 593 $htmlDom.toggle('hide-aside') 594 }, 595 'mobile-toc-button': item => { // Show mobile toc 596 const tocEle = document.getElementById('card-toc') 597 tocEle.style.transition = 'transform 0.3s ease-in-out' 598 tocEle.classList.toggle('open') 599 tocEle.addEventListener('transitionend', () => { 600 tocEle.style.transition = '' 601 }, { once: true }) 602 }, 603 'chat-btn': () => { // Show chat 604 window.chatBtnFn() 605 }, 606 translateLink: () => { // switch between traditional and simplified chinese 607 window.translateFn.translatePage() 608 } 609 } 610 611 document.getElementById('rightside').addEventListener('click', function (e) { 612 const $target = e.target.closest('[id]') 613 if ($target && rightSideFn[$target.id]) { 614 rightSideFn[$target.id](this) 615 } 616 }) 617 618 /** 619 * menu 620 * å´éæ¬sub-menu å±é/æ¶ç¸® 621 */ 622 const clickFnOfSubMenu = () => { 623 const handleClickOfSubMenu = e => { 624 const target = e.target.closest('.site-page.group') 625 if (!target) return 626 target.classList.toggle('hide') 627 } 628 629 document.querySelector('#sidebar-menus .menus_items').addEventListener('click', handleClickOfSubMenu) 630 } 631 632 /** 633 * ææºç«¯ç®å½ç¹å» 634 */ 635 const openMobileMenu = () => { 636 const handleClick = () => { sidebarFn.open() } 637 btf.addEventListenerPjax(document.getElementById('toggle-menu'), 'click', handleClick) 638 } 639 640 /** 641 * è¤è£½æå ä¸çæ¬ä¿¡æ¯ 642 */ 643 const addCopyright = () => { 644 const { limitCount, languages } = GLOBAL_CONFIG.copyright 645 646 const handleCopy = (e) => { 647 e.preventDefault() 648 const copyFont = window.getSelection(0).toString() 649 let textFont = copyFont 650 if (copyFont.length > limitCount) { 651 textFont = `${copyFont}\n\n\n${languages.author}\n${languages.link}${window.location.href}\n${languages.source}\n${languages.info}` 652 } 653 if (e.clipboardData) { 654 return e.clipboardData.setData('text', textFont) 655 } else { 656 return window.clipboardData.setData('text', textFont) 657 } 658 } 659 660 document.body.addEventListener('copy', handleCopy) 661 } 662 663 /** 664 * ç¶²é éè¡æé 665 */ 666 const addRuntime = () => { 667 const $runtimeCount = document.getElementById('runtimeshow') 668 if ($runtimeCount) { 669 const publishDate = $runtimeCount.getAttribute('data-publishDate') 670 $runtimeCount.textContent = `${btf.diffDate(publishDate)} ${GLOBAL_CONFIG.runtime}` 671 } 672 } 673 674 /** 675 * æå¾ä¸æ¬¡æ´æ°æé 676 */ 677 const addLastPushDate = () => { 678 const $lastPushDateItem = document.getElementById('last-push-date') 679 if ($lastPushDateItem) { 680 const lastPushDate = $lastPushDateItem.getAttribute('data-lastPushDate') 681 $lastPushDateItem.textContent = btf.diffDate(lastPushDate, true) 682 } 683 } 684 685 /** 686 * table overflow 687 */ 688 const addTableWrap = () => { 689 const $table = document.querySelectorAll('#article-container table') 690 if (!$table.length) return 691 692 $table.forEach(item => { 693 if (!item.closest('.highlight')) { 694 btf.wrap(item, 'div', { class: 'table-wrap' }) 695 } 696 }) 697 } 698 699 /** 700 * tag-hide 701 */
702 const clickFnOfTagHide = () => { 703 const hideButtons = document.querySelectorAll('#article-container .hide-button') 704 if (!hideButtons.length) return 705 const handleClick = function (e) { 706 const $this = this 707 $this.classList.add('open') 708 const $fjGallery = $this.nextElementSibling.querySelectorAll('.gallery-container') 709 $fjGallery.length && addJustifiedGallery($fjGallery) 710 } 711 712 hideButtons.forEach(item => { 713 item.addEventListener('click', handleClick, { once: true }) 714 }) 715 } 716 717 const tabsFn = () => { 718 const navTabsElement = document.querySelectorAll('#article-container .tabs') 719 if (!navTabsElement.length) return 720 721 const removeAndAddActiveClass = (elements, detect) => { 722 Array.from(elements).forEach(element => { 723 element.classList.remove('active') 724 if (element === detect || element.id === detect) { 725 element.classList.add('active') 726 } 727 }) 728 } 729 730 const addTabNavEventListener = (item, isJustifiedGallery) => { 731 const navClickHandler = function (e) { 732 const target = e.target.closest('button') 733 if (target.classList.contains('active')) return 734 removeAndAddActiveClass(this.children, target) 735 this.classList.remove('no-default') 736 const tabId = target.getAttribute('data-href') 737 const tabContent = this.nextElementSibling 738 removeAndAddActiveClass(tabContent.children, tabId) 739 if (isJustifiedGallery) { 740 btf.removeGlobalFnEvent('igOfTabs', this) 741 const justifiedGalleryItems = tabContent.querySelectorAll(`:scope > #${tabId} .gallery-container`) 742 justifiedGalleryItems.length && addJustifiedGallery(justifiedGalleryItems, this) 743 } 744 } 745 btf.addEventListenerPjax(item.firstElementChild, 'click', navClickHandler) 746 } 747 748 const addTabToTopEventListener = item => { 749 const btnClickHandler = (e) => { 750 const target = e.target.closest('button') 751 if (!target) return 752 btf.scrollToDest(btf.getEleTop(item), 300) 753 } 754 btf.addEventListenerPjax(item.lastElementChild, 'click', btnClickHandler) 755 } 756 757 navTabsElement.forEach(item => { 758 const isJustifiedGallery = !!item.querySelectorAll('.gallery-container') 759 addTabNavEventListener(item, isJustifiedGallery) 760 addTabToTopEventListener(item) 761 }) 762 } 763 764 const toggleCardCategory = () => { 765 const cardCategory = document.querySelector('#aside-cat-list.expandBtn') 766 if (!cardCategory) return 767 768 const handleToggleBtn = (e) => { 769 const target = e.target 770 if (target.nodeName === 'I') { 771 e.preventDefault() 772 target.parentNode.classList.toggle('expand') 773 } 774 } 775 btf.addEventListenerPjax(cardCategory, 'click', handleToggleBtn, true) 776 } 777 778 const switchComments = () => { 779 const switchBtn = document.getElementById('switch-btn') 780 if (!switchBtn) return 781 let switchDone = false 782 const commentContainer = document.getElementById('post-comment') 783 const handleSwitchBtn = () => { 784 commentContainer.classList.toggle('move') 785 if (!switchDone && typeof loadOtherComment === 'function') { 786 switchDone = true 787 loadOtherComment() 788 } 789 } 790 btf.addEventListenerPjax(switchBtn, 'click', handleSwitchBtn) 791 } 792 793 const addPostOutdateNotice = () => { 794 const { limitDay, messagePrev, messageNext, position } = GLOBAL_CONFIG.noticeOutdate 795 const diffDay = btf.diffDate(GLOBAL_CONFIG_SITE.postUpdate) 796 if (diffDay >= limitDay) { 797 const ele = document.createElement('div') 798 ele.className = 'post-outdate-notice' 799 ele.textContent = `${messagePrev} ${diffDay} ${messageNext}` 800 const $targetEle = document.getElementById('article-container') 801 if (position === 'top') { 802 $targetEle.insertBefore(ele, $targetEle.firstChild) 803 } else { 804 $targetEle.appendChild(ele) 805 } 806 } 807 } 808 809 const lazyloadImg = () => { 810 window.lazyLoadInstance = new LazyLoad({ 811 elements_selector: 'img', 812 threshold: 0, 813 data_src: 'lazy-src' 814 }) 815 } 816 817 const relativeDate = function (selector) { 818 selector.forEach(item => { 819 const timeVal = item.getAttribute('datetime') 820 item.textContent = btf.diffDate(timeVal, true) 821 item.style.display = 'inline' 822 }) 823 } 824 825 const unRefreshFn = function () { 826 window.addEventListener('resize', () => { 827 adjustMenu(false) 828 mobileSidebarOpen && btf.isHidden(document.getElementById('toggle-menu')) && sidebarFn.close() 829 }) 830 831 document.getElementById('menu-mask').addEventListener('click', e => { sidebarFn.close() }) 832
833 clickFnOfSubMenu() 834 GLOBAL_CONFIG.islazyload && lazyloadImg() 835 GLOBAL_CONFIG.copyright !== undefined && addCopyright() 836 837 if (GLOBAL_CONFIG.autoDarkmode) { 838 window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', e => { 839 if (saveToLocal.get('theme') !== undefined) return 840 e.matches ? handleThemeChange('dark') : handleThemeChange('light') 841 }) 842 } 843 } 844 845 window.refreshFn = function () { 846 initAdjust() 847 848 if (GLOBAL_CONFIG_SITE.isPost) { 849 GLOBAL_CONFIG.noticeOutdate !== undefined && addPostOutdateNotice() 850 GLOBAL_CONFIG.relativeDate.post && relativeDate(document.querySelectorAll('#post-meta time')) 851 } else { 852 GLOBAL_CONFIG.relativeDate.homepage && relativeDate(document.querySelectorAll('#recent-posts time')) 853 GLOBAL_CONFIG.runtime && addRuntime() 854 addLastPushDate() 855 toggleCardCategory() 856 } 857 858 scrollFnToDo() 859 GLOBAL_CONFIG_SITE.isHome && scrollDownInIndex() 860 addHighlightTool() 861 GLOBAL_CONFIG.isPhotoFigcaption && addPhotoFigcaption() 862 scrollFn() 863 864 btf.removeGlobalFnEvent('justifiedGallery') 865 const galleryContainer = document.querySelectorAll('#article-container .gallery-container') 866 galleryContainer.length && addJustifiedGallery(galleryContainer) 867 868 runLightbox() 869 addTableWrap() 870 clickFnOfTagHide() 871 tabsFn() 872 switchComments() 873 openMobileMenu() 874 } 875 876 refreshFn() 877 unRefreshFn() 878})
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.