PageSourceSearch

https://www.helloseraphine.top/js/main.js

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

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.