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