PageSourceSearch

https://mount-rain.link/usr/themes/butterfly/js/main.js?v1.7.3

js mount-rain.link collected 2026-10-06 13:17:03 UTC 29,208 bytes, 842 lines download raw bytes

1document.addEventListener('DOMContentLoaded', function () {
2  let headerContentWidth, $nav
3  let mobileSidebarOpen = false
4
5  const adjustMenu = init => {
6    const getAllWidth = ele => {
7      let width = 0
8      ele.length && Array.from(ele).forEach(i => { width += i.offsetWidth })
9      return width
10    }
11
12    if (init) {
13      const blogInfoWidth = getAllWidth(document.querySelector('#blog-info > a').children)
14      const menusWidth = getAllWidth(document.getElementById('menus').children)
15      headerContentWidth = blogInfoWidth + menusWidth
16      $nav = document.getElementById('nav')
17    }
18
19    let hideMenuIndex = ''
20    if (window.innerWidth <= 768) hideMenuIndex = true
21    else hideMenuIndex = headerContentWidth > $nav.offsetWidth - 120
22
23    if (hideMenuIndex) {
24      $nav.classList.add('hide-menu')
25    } else {
26      $nav.classList.remove('hide-menu')
27    }
28  }
29
30  // 初始化header
31  const initAdjust = () => {
32    adjustMenu(true)
33    $nav.classList.add('show')
34  }
35
36  // sidebar menus
37  const sidebarFn = {
38    open: () => {
39      btf.sidebarPaddingR()
40      document.body.style.overflow = 'hidden'
41      btf.animateIn(document.getElementById('menu-mask'), 'to_show 0.5s')
42      document.getElementById('sidebar-menus').classList.add('open')
43      mobileSidebarOpen = true
44    },
45    close: () => {
46      const $body = document.body
47      $body.style.overflow = ''
48      $body.style.paddingRight = ''
49      btf.animateOut(document.getElementById('menu-mask'), 'to_hide 0.5s')
50      document.getElementById('sidebar-menus').classList.remove('open')
51      mobileSidebarOpen = false
52    }
53  }
54
55  /**
56   * 首頁top_img底下的箭頭
57   */
58  const scrollDownInIndex = () => {
59    const $scrollDownEle = document.getElementById('scroll-down')
60    $scrollDownEle && $scrollDownEle.addEventListener('click', function () {
61      btf.scrollToDest(document.getElementById('content-inner').offsetTop, 300)
62    })
63  }
64
65  /**
66   * 代碼
67   * 只適用於Hexo默認的代碼渲染
68   */
69  const addHighlightTool = function () {
70    const highLight = GLOBAL_CONFIG.highlight
71    if (!highLight) return
72
73    const isHighlightCopy = highLight.highlightCopy
74    const isHighlightLang = highLight.highlightLang
75    const isHighlightShrink = GLOBAL_CONFIG_SITE.isHighlightShrink
76    const highlightHeightLimit = highLight.highlightHeightLimit
77    const isShowTool = isHighlightCopy || isHighlightLang || isHighlightShrink !== undefined
78    const $figureHighlight = highLight.plugin === 'highlighjs' ? document.querySelectorAll('figure.highlight') : document.querySelectorAll('pre[class*="language-"]')
79
80    if (!((isShowTool || highlightHeightLimit) && $figureHighlight.length)) return
81
82    const isPrismjs = highLight.plugin === 'prismjs'
83
84    let highlightShrinkEle = ''
85    let highlightCopyEle = ''
86    const highlightShrinkClass = isHighlightShrink === true ? 'closed' : ''
87
88    if (isHighlightShrink !== undefined) {
89      highlightShrinkEle = `<i class="fas fa-angle-down expand ${highlightShrinkClass}"></i>`
90    }
91
92    if (isHighlightCopy) {
93      highlightCopyEle = '<div class="copy-notice"></div><i class="fas fa-paste copy-button"></i>'
94    }
95
96    const copy = (text, ctx) => {
97      if (document.queryCommandSupported && document.queryCommandSupported('copy')) {
98        document.execCommand('copy')
99        if (GLOBAL_CONFIG.Snackbar !== undefined) {
100          btf.snackbarShow(GLOBAL_CONFIG.copy.success)
101        } else {
102          const prevEle = ctx.previousElementSibling
103          prevEle.innerText = GLOBAL_CONFIG.copy.success
104          prevEle.style.opacity = 1
105          setTimeout(() => { prevEle.style.opacity = 0 }, 700)
106        }
107      } else {
108        if (GLOBAL_CONFIG.Snackbar !== undefined) {
109          btf.snackbarShow(GLOBAL_CONFIG.copy.noSupport)
110        } else {
111          ctx.previousElementSibling.innerText = GLOBAL_CONFIG.copy.noSupport
112        }
113      }
114    }
115
116    // click events
117    const highlightCopyFn = (ele) => {
118      const $buttonParent = ele.parentNode
119      $buttonParent.classList.add('copy-true')
120      const selection = window.getSelection()
121      const range = document.createRange()
122      if (isPrismjs) range.selectNodeContents($buttonParent.querySelectorAll('pre code')[0])
123      else range.selectNodeContents($buttonParent.querySelectorAll('table .code pre')[0])
124      selection.removeAllRanges()
125      selection.addRange(range)
126      const text = selection.toString()
127      copy(text, ele.lastChild)
128      selection.removeAllRanges()
129      $buttonParent.classList.remove('copy-true')
130    }
131
132    const highlightShrinkFn = (ele) => {
133      const $nextEle = [...ele.parentNode.children].slice(1)
134      ele.firstChild.classList.toggle('closed')
135      if (btf.isHidden($nextEle[$nextEle.length - 1])) {
136        $nextEle.forEach(e => { e.style.display = 'block' })
137      } else {
138        $nextEle.forEach(e => { e.style.display = 'none' })
139      }
140    }
141
142    const highlightToolsFn = function (e) {
143      const $target = e.target.classList
144      if ($target.contains('expand')) highlightShrinkFn(this)
145      else if ($target.contains('copy-button')) highlightCopyFn(this)
146    }
147
148    const expandCode = function () {
149      this.classList.toggle('expand-done')
150    }
151
152    function createEle (lang, item, service) {
153      const fragment = document.createDocumentFragment()
154
155      if (isShowTool) {
156        const hlTools = document.createElement('div')
157        hlTools.className = `highlight-tools ${highlightShrinkClass}`
158        hlTools.innerHTML = highlightShrinkEle + lang + highlightCopyEle
159        hlTools.addEventListener('click', highlightToolsFn)
160        fragment.appendChild(hlTools)
161      }
162
163      if (highlightHeightLimit && item.offsetHeight > highlightHeightLimit + 30) {
164        const ele = document.createElement('div')
165        ele.className = 'code-expand-btn'
166        ele.innerHTML = '<i class="fas fa-angle-double-down"></i>'
167        ele.addEventListener('click', expandCode)
168        fragment.appendChild(ele)
169      }
170
171      if (service === 'hl') {
172        item.insertBefore(fragment, item.firstChild)
173      } else {
174        item.parentNode.insertBefore(fragment, item)
175      }
176    }
177
178    if (isHighlightLang) {
179      if (isPrismjs) {
180        $figureHighlight.forEach(function (item) {
181          const langName = item.getAttribute('data-language') ? item.getAttribute('data-language') : 'Code'
182          const highlightLangEle = `<div class="code-lang">${langName}</div>`
183          btf.wrap(item, 'figure', { class: 'highlight' })
184          createEle(highlightLangEle, item)
185        })
186      } else {
187        $figureHighlight.forEach(function (item) {
188          let langName = item.getAttribute('class').split(' ')[1]
189          if (langName === 'plain' || langName === undefined) langName = 'Code'
190          const highlightLangEle = `<div class="code-lang">${langName}</div>`
191          createEle(highlightLangEle, item, 'hl')
192        })
193      }
194    } else {
195      if (isPrismjs) {
196        $figureHighlight.forEach(function (item) {
197          btf.wrap(item, 'figure', { class: 'highlight' })
198          createEle('', item)
199        })
200      } else {
201        $figureHighlight.forEach(function (item) {
202          createEle('', item, 'hl')
203        })
204      }
205    }
206  }
207
208  /**
209   * PhotoFigcaption
210   */
211  function addPhotoFigcaption () {
212    document.querySelectorAll('#article-container img').forEach(function (item) {
213      const parentEle = item.parentNode
214      const altValue = item.title || item.alt
215      if (altValue && !parentEle.parentNode.classList.contains('justified-gallery')) {
216        const ele = document.createElement('div')
217        ele.className = 'img-alt is-center'
218        ele.textContent = altValue
219        parentEle.insertBefore(ele, item.nextSibling)
220      }
221    })
222  }
223
224  /**
225   * Lightbox
226   */
227  const runLightbox = () => {
228    btf.loadLightbox(document.querySelectorAll('#article-container img:not(.no-lightbox)'))
229  }
230
231  /**
232   * justified-gallery 圖庫排版
233   */
234  const runJustifiedGallery = function (ele) {
235    const htmlStr = arr => {
236      let str = ''
237      const replaceDq = str => str.replace(/"/g, '&quot;') // replace double quotes to &quot;
238      arr.forEach(i => {
239        const alt = i.alt ? `alt="${replaceDq(i.alt)}"` : ''
240        const title = i.title ? `title="${replaceDq(i.title)}"` : ''
241        str += `<div class="fj-gallery-item"><img src="${i.url}" ${alt + title}"></div>`
242      })
243      return str
244    }
245
246    const lazyloadFn = (i, arr) => {
247      const loadItem = i.getAttribute('data-limit')
248      const arrLength = arr.length
249      if (arrLength > loadItem) i.insertAdjacentHTML('beforeend', htmlStr(arr.splice(0, loadItem)))
250      else {
251        i.insertAdjacentHTML('beforeend', htmlStr(arr))
252        i.classList.remove('lazyload')
253      }
254      return arrLength > loadItem ? loadItem : arrLength
255    }
256
257    ele.forEach(item => {
258      const arr = JSON.parse(item.querySelector('.gallery-data').textContent)
259      if (!item.classList.contains('lazyload')) item.innerHTML = htmlStr(arr)
260      else {
261        lazyloadFn(item, arr)
262        const limit = item.getAttribute('data-limit')
263        const clickBtnFn = () => {
264          const lastItemLength = lazyloadFn(item, arr)
265          fjGallery(item, 'appendImages', item.querySelectorAll(`.fj-gallery-item:nth-last-child(-n+${lastItemLength})`))
266          btf.loadLightbox(item.querySelectorAll('img'))
267          lastItemLength < limit && item.nextElementSibling.removeEventListener('click', clickBtnFn)
268        }
269        item.nextElementSibling.addEventListener('click', clickBtnFn)
270      }
271    })
272
273    if (window.fjGallery) {
274      setTimeout(() => { btf.initJustifiedGallery(ele) }, 100)
275      return
276    }
277
278    getCSS(`${GLOBAL_CONFIG.source.justifiedGallery.css}`)
279    getScript(`${GLOBAL_CONFIG.source.justifiedGallery.js}`).then(() => { btf.initJustifiedGallery(ele) })
280  }
281
282  /**
283   * rightside scroll percent
284   */
285  const rightsideScrollPercent = currentTop => {
286    const perNum = btf.getScrollPercent(currentTop, document.body)
287    const $goUp = document.getElementById('go-up')
288    if (perNum < 95) {
289      $goUp.classList.add('show-percent')
290      $goUp.querySelector('.scroll-percent').textContent = perNum
291    } else {
292      $goUp.classList.remove('show-percent')
293    }
294  }
295
296  /**
297   * 滾動處理
298   */
299  const scrollFn = function () {
300    const $rightside = document.getElementById('rightside')
301    const innerHeight = window.innerHeight + 56
302
303    // 當滾動條小于 56 的時候
304    if (document.body.scrollHeight <= innerHeight) {
305      $rightside.style.cssText = 'opacity: 1; transform: translateX(-58px)'
306      return
307    }
308
309    // find the scroll direction
310    function scrollDirection (currentTop) {
311      const result = currentTop > initTop // true is down & false is up
312      initTop = currentTop
313      return result
314    }
315
316    let initTop = 0
317    let isChatShow = true
318    const $header = document.getElementById('page-header')
319    const isChatBtnHide = typeof chatBtnHide === 'function'
320    const isChatBtnShow = typeof chatBtnShow === 'function'
321    const isShowPercent = GLOBAL_CONFIG.percent.rightside
322
323    const scrollTask = btf.throttle(() => {
324      const currentTop = window.scrollY || document.documentElement.scrollTop
325      const isDown = scrollDirection(currentTop)
326      if (currentTop > 56) {
327        if (isDown) {
328          if ($header.classList.contains('nav-visible')) $header.classList.remove('nav-visible')
329          if (isChatBtnShow && isChatShow === true) {
330            chatBtnHide()
331            isChatShow = false
332          }
333        } else {
334          if (!$header.classList.contains('nav-visible')) $header.classList.add('nav-visible')
335          if (isChatBtnHide && isChatShow === false) {
336            chatBtnShow()
337            isChatShow = true
338          }
339        }
340        $header.classList.add('nav-fixed')
341        if (window.getComputedStyle($rightside).getPropertyValue('opacity') === '0') {
342          $rightside.style.cssText = 'opacity: 0.8; transform: translateX(-58px)'
343        }
344      } else {
345        if (currentTop === 0) {
346          $header.classList.remove('nav-fixed', 'nav-visible')
347        }
348        $rightside.style.cssText = "opacity: ''; transform: ''"
349      }
350
351      isShowPercent && rightsideScrollPercent(currentTop)
352
353      if (document.body.scrollHeight <= innerHeight) {
354        $rightside.style.cssText = 'opacity: 0.8; transform: translateX(-58px)'
355      }
356    }, 200)
357
358    window.scrollCollect = scrollTask
359
360    window.addEventListener('scroll', scrollCollect)
361  }
362
363  /**
364  * toc,anchor
365  */
366  const scrollFnToDo = function () {
367    const isToc = GLOBAL_CONFIG_SITE.isToc
368    const isAnchor = GLOBAL_CONFIG.isAnchor
369    const $article = document.getElementById('article-container')
370
371    if (!($article && (isToc || isAnchor))) return
372
373    let $tocLink, $cardToc, autoScrollToc, $tocPercentage, isExpand
374
375    if (isToc) {
376      const $cardTocLayout = document.getElementById('card-toc')
377      $cardToc = $cardTocLayout.getElementsByClassName('toc-content')[0]
378      $tocLink = $cardToc.querySelectorAll('.toc-link')
379      $tocPercentage = $cardTocLayout.querySelector('.toc-percentage')
380      isExpand = $cardToc.classList.contains('is-expand')
381
382      window.mobileToc = {
383        open: () => {
384          $cardTocLayout.style.cssText = 'animation: toc-open .3s; opacity: 1; right: 55px'
385        },
386
387        close: () => {
388          $cardTocLayout.style.animation = 'toc-close .2s'
389          setTimeout(() => {
390            $cardTocLayout.style.cssText = "opacity:''; animation: ''; right: ''"
391          }, 100)
392        }
393      }
394
395      // toc元素點擊
396      $cardToc.addEventListener('click', e => {
397        e.preventDefault()
398        const target = e.target.classList
399        if (target.contains('toc-content')) return
400        const $target = target.contains('toc-link')
401          ? e.target
402          : e.target.parentElement
403        btf.scrollToDest(btf.getEleTop(document.getElementById(decodeURI($target.getAttribute('href')).replace('#', ''))), 300)
404        if (window.innerWidth < 900) {
405          window.mobileToc.close()
406        }
407      })
408
409      autoScrollToc = item => {
410        const activePosition = item.getBoundingClientRect().top
411        const sidebarScrollTop = $cardToc.scrollTop
412        if (activePosition > (document.documentElement.clientHeight - 100)) {
413          $cardToc.scrollTop = sidebarScrollTop + 150
414        }
415        if (activePosition < 100) {
416          $cardToc.scrollTop = sidebarScrollTop - 150
417        }
418      }
419    }
420
421    // find head position & add active class
422    const list = $article.querySelectorAll('h1,h2,h3,h4,h5,h6')
423    let detectItem = ''
424    const findHeadPosition = function (top) {
425      if (top === 0) {
426        return false
427      }
428
429      let currentId = ''
430      let currentIndex = ''
431
432      list.forEach(function (ele, index) {
433        if (top > btf.getEleTop(ele) - 80) {
434          const id = ele.id
435          currentId = id ? '#' + encodeURI(id) : ''
436          currentIndex = index
437        }
438      })
439
440      if (detectItem === currentIndex) return
441
442      if (isAnchor) btf.updateAnchor(currentId)
443
444      detectItem = currentIndex
445
446      if (isToc) {
447        $cardToc.querySelectorAll('.active').forEach(i => { i.classList.remove('active') })
448
449        if (currentId === '') {
450          return
451        }
452
453        const currentActive = $tocLink[currentIndex]
454        currentActive.classList.add('active')
455
456        setTimeout(() => {
457          autoScrollToc(currentActive)
458        }, 0)
459
460        if (isExpand) return
461        let parent = currentActive.parentNode
462
463        for (; !parent.matches('.toc'); parent = parent.parentNode) {
464          if (parent.matches('li')) parent.classList.add('active')
465        }
466      }
467    }
468
469    // main of scroll
470    window.tocScrollFn = btf.throttle(() => {
471      const currentTop = window.scrollY || document.documentElement.scrollTop
472      if (isToc && GLOBAL_CONFIG.percent.toc) {
473        $tocPercentage.textContent = btf.getScrollPercent(currentTop, $article)
474      }
475      findHeadPosition(currentTop)
476    }, 100)
477
478    window.addEventListener('scroll', tocScrollFn)
479  }
480
481  /**
482   * Rightside
483   */
484  const rightSideFn = {
485    switchReadMode: () => { // read-mode
486      $('.code-toolbar').addClass('read-fa')
487      const $body = document.body
488      $body.classList.add('read-mode')
489      const newEle = document.createElement('button')
490      newEle.type = 'button'
491      newEle.className = 'fas fa-sign-out-alt exit-readmode'
492      $body.appendChild(newEle)
493
494      function clickFn () {
495        $body.classList.remove('read-mode')
496        $('.code-toolbar').removeClass('read-fa')
497        newEle.remove()
498        newEle.removeEventListener('click', clickFn)
499      }
500
501      newEle.addEventListener('click', clickFn)
502    },
503    switchDarkMode: () => { // Switch Between Light And Dark Mode
504      const nowMode = document.documentElement.getAttribute('data-theme') === 'dark' ? 'dark' : 'light'
505      if (nowMode === 'light') {
506        activateDarkMode()
507        saveToLocal.set('theme', 'dark', 2)
508        GLOBAL_CONFIG.Snackbar !== undefined && btf.snackbarShow(GLOBAL_CONFIG.Snackbar.day_to_night)
509      } else {
510        activateLightMode()
511        saveToLocal.set('theme', 'light', 2)
512        GLOBAL_CONFIG.Snackbar !== undefined && btf.snackbarShow(GLOBAL_CONFIG.Snackbar.night_to_day)
513      }
514      // handle some cases
515      typeof utterancesTheme === 'function' && utterancesTheme()
516      typeof changeGiscusTheme === 'function' && changeGiscusTheme()
517      typeof FB === 'object' && window.loadFBComment()
518      window.DISQUS && document.getElementById('disqus_thread').children.length && setTimeout(() => window.disqusReset(), 200)
519      typeof runMermaid === 'function' && window.runMermaid()
520    },
521    showOrHideBtn: (e) => { // rightside 點擊設置 按鈕 展開
522      const rightsideHideClassList = document.getElementById('rightside-config-hide').classList
523      rightsideHideClassList.toggle('show')
524      if (e.classList.contains('show')) {
525        rightsideHideClassList.add('status')
526        setTimeout(() => {
527          rightsideHideClassList.remove('status')
528        }, 300)
529      }
530      e.classList.toggle('show')
531    },
532    scrollToTop: () => { // Back to top
533      btf.scrollToDest(0, 500)
534    },
535    hideAsideBtn: () => { // Hide aside
536      const $htmlDom = document.documentElement.classList
537      $htmlDom.contains('hide-aside')
538        ? saveToLocal.set('aside-status', 'show', 2)
539        : saveToLocal.set('aside-status', 'hide', 2)
540      $htmlDom.toggle('hide-aside')
541    },
542
543    runMobileToc: () => {
544      if (window.getComputedStyle(document.getElementById('card-toc')).getPropertyValue('opacity') === '0') window.mobileToc.open()
545      else window.mobileToc.close()
546    },
547    
548    adjustFontSize: (plus) => {
549      const fontSizeVal = parseInt(window.getComputedStyle(document.documentElement).getPropertyValue('--global-font-size'))
550      let newValue = ''
551      detectFontSizeChange = true
552      if (plus) {
553        if (fontSizeVal >= 20) return
554        newValue = fontSizeVal + 1
555        document.documentElement.style.setProperty('--global-font-size', newValue + 'px')
556        !document.getElementById('nav').classList.contains('hide-menu') && adjustMenu()
557      } else {
558        if (fontSizeVal <= 10) return
559        newValue = fontSizeVal - 1
560        document.documentElement.style.setProperty('--global-font-size', newValue + 'px')
561        document.getElementById('nav').classList.contains('hide-menu') && adjustMenu()
562      }
563
564      saveToLocal.set('global-font-size', newValue, 2)
565      // document.getElementById('font-text').innerText = newValue
566    }    
567  }
568
569  document.getElementById('rightside').addEventListener('click', function (e) {
570    const $target = e.target.id ? e.target : e.target.parentNode
571    switch ($target.id) {
572      case 'go-up':
573        rightSideFn.scrollToTop()
574        break
575      case 'rightside_config':
576        rightSideFn.showOrHideBtn($target)
577        break
578      case 'mobile-toc-button':
579        rightSideFn.runMobileToc()
580        break
581      case 'readmode':
582        rightSideFn.switchReadMode()
583        break
584      case 'darkmode':
585        rightSideFn.switchDarkMode()
586        break
587      case 'hide-aside-btn':
588        rightSideFn.hideAsideBtn()
589        break
590      case 'font-plus':
591        rightSideFn.adjustFontSize(true)
592        break
593      case 'font-minus':
594        rightSideFn.adjustFontSize()
595        break        
596      default:
597        break
598    }
599  })
600
601  /**
602   * menu
603   * 側邊欄sub-menu 展開/收縮
604   */
605  const clickFnOfSubMenu = () => {
606    document.querySelectorAll('#sidebar-menus .site-page.group').forEach(function (item) {
607      item.addEventListener('click', function () {
608        this.classList.toggle('hide')
609      })
610    })
611  }
612
613  /**
614   * 複製時加上版權信息
615   */
616  const addCopyright = () => {
617    const copyright = GLOBAL_CONFIG.copyright
618    document.body.oncopy = (e) => {
619      e.preventDefault()
620      let textFont; const copyFont = window.getSelection(0).toString()
621      if (copyFont.length > copyright.limitCount) {
622        textFont = copyFont + '\n' + '\n' + '\n' +
623        copyright.languages.author + '\n' +
624        copyright.languages.link + window.location.href + '\n' +
625        copyright.languages.source + '\n' +
626        copyright.languages.info
627      } else {
628        textFont = copyFont
629      }
630      if (e.clipboardData) {
631        return e.clipboardData.setData('text', textFont)
632      } else {
633        return window.clipboardData.setData('text', textFont)
634      }
635    }
636  }
637
638  /**
639   * 網頁運行時間
640   */
641  const addRuntime = () => {
642    const $runtimeCount = document.getElementById('runtimeshow')
643    if ($runtimeCount) {
644      const publishDate = $runtimeCount.getAttribute('data-publishDate')
645      $runtimeCount.innerText = btf.diffDate(publishDate) + ' ' + GLOBAL_CONFIG.runtime
646    }
647  }
648
649  /**
650   * 最後一次更新時間
651   */
652  const addLastPushDate = () => {
653    const $lastPushDateItem = document.getElementById('last-push-date')
654    if ($lastPushDateItem) {
655      const lastPushDate = $lastPushDateItem.getAttribute('data-lastPushDate')
656      $lastPushDateItem.innerText = btf.diffDate(lastPushDate, true)
657    }
658  }
659
660  /**
661   * table overflow
662   */
663  const addTableWrap = () => {
664    const $table = document.querySelectorAll('#article-container :not(.highlight) > table, #article-container > table')
665    if ($table.length) {
666      $table.forEach(item => {
667        btf.wrap(item, 'div', { class: 'table-wrap' })
668      })
669    }
670  }
671
672  /**
673   * tag-hide
674   */
675  const clickFnOfTagHide = function () {
676    const $hideInline = document.querySelectorAll('#article-container .hide-button')
677    if ($hideInline.length) {
678      $hideInline.forEach(function (item) {
679        item.addEventListener('click', function (e) {
680          const $this = this
681          $this.classList.add('open')
682          const $fjGallery = $this.nextElementSibling.querySelectorAll('.fj-gallery')
683          $fjGallery.length && btf.initJustifiedGallery($fjGallery)
684        })
685      })
686    }
687  }
688
689  const tabsFn = {
690    clickFnOfTabs: function () {
691      document.querySelectorAll('#article-container .tab > button').forEach(function (item) {
692        item.addEventListener('click', function (e) {
693          const $this = this
694          const $tabItem = $this.parentNode
695
696          if (!$tabItem.classList.contains('active')) {
697            const $tabContent = $tabItem.parentNode.nextElementSibling
698            const $siblings = btf.siblings($tabItem, '.active')[0]
699            $siblings && $siblings.classList.remove('active')
700            $tabItem.classList.add('active')
701            const tabId = $this.getAttribute('data-href').replace('#', '')
702            const childList = [...$tabContent.children]
703            childList.forEach(item => {
704              if (item.id === tabId) item.classList.add('active')
705              else item.classList.remove('active')
706            })
707            const $isTabJustifiedGallery = $tabContent.querySelectorAll(`#${tabId} .fj-gallery`)
708            if ($isTabJustifiedGallery.length > 0) {
709              btf.initJustifiedGallery($isTabJustifiedGallery)
710            }
711          }
712        })
713      })
714    },
715    backToTop: () => {
716      document.querySelectorAll('#article-container .tabs .tab-to-top').forEach(function (item) {
717        item.addEventListener('click', function () {
718          btf.scrollToDest(btf.getEleTop(btf.getParents(this, '.tabs')), 300)
719        })
720      })
721    }
722  }
723
724  const toggleCardCategory = function () {
725    const $cardCategory = document.querySelectorAll('#aside-cat-list .card-category-list-item.parent i')
726    if ($cardCategory.length) {
727      $cardCategory.forEach(function (item) {
728        item.addEventListener('click', function (e) {
729          e.preventDefault()
730          const $this = this
731          $this.classList.toggle('expand')
732          const $parentEle = $this.parentNode.nextElementSibling
733          if (btf.isHidden($parentEle)) {
734            $parentEle.style.display = 'block'
735          } else {
736            $parentEle.style.display = 'none'
737          }
738        })
739      })
740    }
741  }
742
743  const switchComments = function () {
744    let switchDone = false
745    const $switchBtn = document.querySelector('#comment-switch > .switch-btn')
746    $switchBtn && $switchBtn.addEventListener('click', function () {
747      this.classList.toggle('move')
748      document.querySelectorAll('#post-comment > .comment-wrap > div').forEach(function (item) {
749        if (btf.isHidden(item)) {
750          item.style.cssText = 'display: block;animation: tabshow .5s'
751        } else {
752          item.style.cssText = "display: none;animation: ''"
753        }
754      })
755
756      if (!switchDone && typeof loadOtherComment === 'function') {
757        switchDone = true
758        loadOtherComment()
759      }
760    })
761  }
762
763  const addPostOutdateNotice = function () {
764    const data = GLOBAL_CONFIG.noticeOutdate
765    const diffDay = btf.diffDate(GLOBAL_CONFIG_SITE.postUpdate)
766    if (diffDay >= data.limitDay) {
767      const ele = document.createElement('div')
768      ele.className = 'post-outdate-notice'
769      ele.textContent = data.messagePrev + ' ' + diffDay + ' ' + data.messageNext
770      const $targetEle = document.getElementById('article-container')
771      if (data.position === 'top') {
772        $targetEle.insertBefore(ele, $targetEle.firstChild)
773      } else {
774        $targetEle.appendChild(ele)
775      }
776    }
777  }
778
779  const lazyloadImg = () => {
780    window.lazyLoadInstance = new LazyLoad({
781      elements_selector: 'img',
782      threshold: 0,
783      data_src: 'lazy-src'
784    })
785  }
786
787  const relativeDate = function (selector) {
788    selector.forEach(item => {
789      const $this = item
790      const timeVal = $this.getAttribute('datetime')
791      $this.innerText = btf.diffDate(timeVal, true)
792      $this.style.display = 'inline'
793    })
794  }
795
796  const unRefreshFn = function () {
797    window.addEventListener('resize', () => {
798      adjustMenu(false)
799      btf.isHidden(document.getElementById('toggle-menu')) && mobileSidebarOpen && sidebarFn.close()
800    })
801
802    document.getElementById('menu-mask').addEventListener('click', e => { sidebarFn.close() })
803
804    clickFnOfSubMenu()
805    GLOBAL_CONFIG.islazyload && lazyloadImg()
806    GLOBAL_CONFIG.copyright !== undefined && addCopyright()
807  }
808
809  window.refreshFn = function () {
810    initAdjust()
811
812    if (GLOBAL_CONFIG_SITE.isPost) {
813      GLOBAL_CONFIG.noticeOutdate !== undefined && addPostOutdateNotice()
814      GLOBAL_CONFIG.relativeDate.post && relativeDate(document.querySelectorAll('#post-meta time'))
815    } else {
816      GLOBAL_CONFIG.relativeDate.homepage && relativeDate(document.querySelectorAll('#recent-posts time'))
817      GLOBAL_CONFIG.runtime && addRuntime()
818      addLastPushDate()
819      toggleCardCategory()
820    }
821
822    scrollFnToDo()
823    GLOBAL_CONFIG_SITE.isHome && scrollDownInIndex()
824    addHighlightTool()
825    GLOBAL_CONFIG.isPhotoFigcaption && addPhotoFigcaption()
826    scrollFn()
827
828    const $jgEle = document.querySelectorAll('#article-container .fj-gallery')
829    $jgEle.length && runJustifiedGallery($jgEle)
830
831    runLightbox()
832    addTableWrap()
833    clickFnOfTagHide()
834    tabsFn.clickFnOfTabs()
835    tabsFn.backToTop()
836    switchComments()
837    document.getElementById('toggle-menu').addEventListener('click', () => { sidebarFn.open() })
838  }
839
840  refreshFn()
841  unRefreshFn()
842})

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.