mirror of
https://github.com/jerryc127/hexo-theme-butterfly.git
synced 2026-04-12 14:07:06 +08:00
由於整合了多個設定並更改了部分設定名稱,升級到 5.0 版本時,請重新設定 _config.yml 文件。
1. 新增 macstyle 設定,取消 mac / mac light 主題設定 2. 整合搜索相關設定 3. 修改程式碼區塊設定 4. 主頁文章新增多種版面配置 5. 新增說說頁面 6. 適配 hexo-blog-encrypt 加密插件 7. 改善手機端目錄的開啟效果 8. 新增平滑滾動功能 9. 支持以程式碼區塊方式撰寫 mermaid 圖表 10. 可自訂文章標題位置 11. 新增程式碼全螢幕按鈕 12. 友情連結頭像改為圓角設計 13. 優化程式碼,使用 hexo-util 的參數和 hexo 內建參數 14. 可自訂搜索框提示文字 15. 未設定選單時,不顯示側邊欄目錄和按鈕 16. 螢幕寬度超過 2000px 時,增加卡片高度 17. 根據語言設定調整字體:簡體中文使用雅黑,其他使用正黑體 18. 更新 plugins.yml 19. 全新的側邊欄界面設計 20. 新增 giscus 的 js 設定 21. 調整 utterances js 的設定位置 22. 新增 utterances option 設定 23. 修改 giscus 的主題設定 24. 多個界面元素改為圓角設計 25. 可選擇圓角或直角界面風格 26. 圖庫加載按鈕新增圖標 27. 改善標籤頁面的滑鼠懸停效果 28. 調整側邊欄的滑鼠懸停效果 29. 微調部分界面元素 1. 修復 Hexo 新版本下 Prism.js 無法正確高亮的問題 2. 修復文章標籤為空時可能出現的錯誤 3. 修正 mermaid 圖表可能出現的錯誤 4. 解決未設定選單時控制台報錯的問題 5. 修復 Algolia 搜索的每頁顯示數量設定無效的問題 6. 解決 Algolia 搜索結果出現滾動條的問題 7. 修正滾動條出現上下按鈕的問題 8. 修復圖庫遠程連結未加前綴的問題 9. 修正 label 標籤外掛右側多餘空格的問題 10. 解決 APlayer 報告內存洩漏的問題 1. 優化 PJAX 下的函數調用 2. 整體代碼優化 3. 提升兼容性 4. 改善 Lighthouse 評分 5. 在 PJAX 關閉時減少不必要的全局變量 6. 優化 Waline 的 import 兼容性 7. 改善頁面進入效果 8. 優化程式碼區塊工具列顯示邏輯 9. 改善不同螢幕寬度下文章標題位置的顯示 10. 優化標籤顏色生成算法,避免過暗或過亮 11. 調整 Artalk 和 Waline 在夜間模式下的字體顏色,與主題保持一致 12. 調整 Algolia 搜索加載動畫位置,避免換行 13. 優化 Algolia 搜索結果為空時的處理 14. 改善系列文章的滑鼠懸停效果 15. 優化 404 頁面代碼 16. 解決搜索和側邊欄開啟時窗口抖動的問題 17. 優化 tabs 標籤外掛的代碼和效能 18. 改善 tabs 中使用 gallery 標籤外掛時的圖片加載邏輯 19. 優化目錄滾動效果,使當前標題保持在中間 20. 調整螢幕寬度超過 1024px 時 gallerygroup 的顯示數量
This commit is contained in:
@@ -1,11 +1,9 @@
|
||||
document.addEventListener('DOMContentLoaded', function () {
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
let headerContentWidth, $nav
|
||||
let mobileSidebarOpen = false
|
||||
|
||||
const adjustMenu = init => {
|
||||
const getAllWidth = ele => {
|
||||
return Array.from(ele).reduce((width, i) => width + i.offsetWidth, 0)
|
||||
}
|
||||
const getAllWidth = ele => Array.from(ele).reduce((width, i) => width + i.offsetWidth, 0)
|
||||
|
||||
if (init) {
|
||||
const blogInfoWidth = getAllWidth(document.querySelector('#blog-info > a').children)
|
||||
@@ -27,16 +25,13 @@ document.addEventListener('DOMContentLoaded', function () {
|
||||
// sidebar menus
|
||||
const sidebarFn = {
|
||||
open: () => {
|
||||
btf.sidebarPaddingR()
|
||||
document.body.style.overflow = 'hidden'
|
||||
btf.overflowPaddingR.add()
|
||||
btf.animateIn(document.getElementById('menu-mask'), 'to_show 0.5s')
|
||||
document.getElementById('sidebar-menus').classList.add('open')
|
||||
mobileSidebarOpen = true
|
||||
},
|
||||
close: () => {
|
||||
const $body = document.body
|
||||
$body.style.overflow = ''
|
||||
$body.style.paddingRight = ''
|
||||
btf.overflowPaddingR.remove()
|
||||
btf.animateOut(document.getElementById('menu-mask'), 'to_hide 0.5s')
|
||||
document.getElementById('sidebar-menus').classList.remove('open')
|
||||
mobileSidebarOpen = false
|
||||
@@ -72,7 +67,7 @@ document.addEventListener('DOMContentLoaded', function () {
|
||||
|
||||
const isPrismjs = plugin === 'prismjs'
|
||||
const highlightShrinkClass = isHighlightShrink === true ? 'closed' : ''
|
||||
const highlightShrinkEle = isHighlightShrink !== undefined ? '<div><i class="fas fa-angle-down expand"></i></div>' : ''
|
||||
const highlightShrinkEle = isHighlightShrink !== undefined ? '<i class="fas fa-angle-down expand"></i>' : ''
|
||||
const highlightCopyEle = highlightCopy ? '<div class="copy-notice"></div><i class="fas fa-paste copy-button"></i>' : ''
|
||||
const highlightMacStyleEle = '<div class="macStyle"><div class="mac-close"></div><div class="mac-minimize"></div><div class="mac-maximize"></div></div>'
|
||||
const highlightFullpageEle = highlightFullpage ? '<i class="fa-solid fa-up-right-and-down-left-from-center fullpage-button"></i>' : ''
|
||||
@@ -111,9 +106,7 @@ document.addEventListener('DOMContentLoaded', function () {
|
||||
$buttonParent.classList.remove('copy-true')
|
||||
}
|
||||
|
||||
const highlightShrinkFn = ele => {
|
||||
ele.classList.toggle('closed')
|
||||
}
|
||||
const highlightShrinkFn = ele => ele.classList.toggle('closed')
|
||||
|
||||
const codeFullpage = (item, clickEle) => {
|
||||
const wrapEle = item.closest('figure.highlight')
|
||||
@@ -124,47 +117,44 @@ document.addEventListener('DOMContentLoaded', function () {
|
||||
clickEle.classList.toggle('fa-up-right-and-down-left-from-center', !isFullpage)
|
||||
}
|
||||
|
||||
const highlightToolsFn = function (e) {
|
||||
const highlightToolsFn = e => {
|
||||
const $target = e.target.classList
|
||||
if ($target.contains('expand')) highlightShrinkFn(this)
|
||||
else if ($target.contains('copy-button')) highlightCopyFn(this, e.target)
|
||||
else if ($target.contains('fullpage-button')) codeFullpage(this, e.target)
|
||||
const currentElement = e.currentTarget
|
||||
if ($target.contains('expand')) highlightShrinkFn(currentElement)
|
||||
else if ($target.contains('copy-button')) highlightCopyFn(currentElement, e.target)
|
||||
else if ($target.contains('fullpage-button')) codeFullpage(currentElement, e.target)
|
||||
}
|
||||
|
||||
const expandCode = function () {
|
||||
this.classList.toggle('expand-done')
|
||||
}
|
||||
const expandCode = e => e.currentTarget.classList.toggle('expand-done')
|
||||
|
||||
// 获取隐藏状态下元素的真实高度
|
||||
const getActualHeight = function (item) {
|
||||
let tmp = []
|
||||
let hidden = []
|
||||
function fix() {
|
||||
|
||||
let current = item
|
||||
while (current !== document.body && current != null) {
|
||||
if (window.getComputedStyle(current).display === 'none') {
|
||||
hidden.push(current)
|
||||
}
|
||||
current = current.parentNode
|
||||
// 獲取隱藏狀態下元素的真實高度
|
||||
const getActualHeight = item => {
|
||||
const hiddenElements = new Map()
|
||||
|
||||
const fix = () => {
|
||||
let current = item
|
||||
while (current !== document.body && current != null) {
|
||||
if (window.getComputedStyle(current).display === 'none') {
|
||||
hiddenElements.set(current, current.getAttribute('style') || '')
|
||||
}
|
||||
let style = 'visibility: hidden !important; display: block !important; '
|
||||
|
||||
hidden.forEach(function (elem) {
|
||||
var thisStyle = elem.getAttribute('style') || ''
|
||||
tmp.push(thisStyle)
|
||||
elem.setAttribute('style', thisStyle ? thisStyle + ';' + style : style)
|
||||
})
|
||||
current = current.parentNode
|
||||
}
|
||||
|
||||
const style = 'visibility: hidden !important; display: block !important;'
|
||||
hiddenElements.forEach((originalStyle, elem) => {
|
||||
elem.setAttribute('style', originalStyle ? originalStyle + ';' + style : style)
|
||||
})
|
||||
}
|
||||
function restore() {
|
||||
hidden.forEach((elem, idx) => {
|
||||
let _tmp = tmp[idx]
|
||||
if( _tmp === '' ) elem.removeAttribute('style')
|
||||
else elem.setAttribute('style', _tmp)
|
||||
})
|
||||
|
||||
const restore = () => {
|
||||
hiddenElements.forEach((originalStyle, elem) => {
|
||||
if (originalStyle === '') elem.removeAttribute('style')
|
||||
else elem.setAttribute('style', originalStyle)
|
||||
})
|
||||
}
|
||||
|
||||
fix()
|
||||
let height = item.offsetHeight
|
||||
const height = item.offsetHeight
|
||||
restore()
|
||||
return height
|
||||
}
|
||||
@@ -236,7 +226,7 @@ document.addEventListener('DOMContentLoaded', function () {
|
||||
* justified-gallery 圖庫排版
|
||||
*/
|
||||
|
||||
const fetchUrl = async (url) => {
|
||||
const fetchUrl = async url => {
|
||||
const response = await fetch(url)
|
||||
return await response.json()
|
||||
}
|
||||
@@ -254,10 +244,6 @@ document.addEventListener('DOMContentLoaded', function () {
|
||||
// useRecycle: false
|
||||
})
|
||||
|
||||
if (tabs) {
|
||||
btf.addGlobalFn('igOfTabs', () => { ig.destroy() }, false, tabs)
|
||||
}
|
||||
|
||||
const replaceDq = str => str.replace(/"/g, '"') // replace double quotes to "
|
||||
|
||||
const getItems = (nextGroupKey, count) => {
|
||||
@@ -274,7 +260,7 @@ document.addEventListener('DOMContentLoaded', function () {
|
||||
const alt = item.alt ? `alt="${replaceDq(item.alt)}"` : ''
|
||||
const title = item.title ? `title="${replaceDq(item.title)}"` : ''
|
||||
|
||||
nextItems.push(`<div class="item ">
|
||||
nextItems.push(`<div class="item">
|
||||
<img src="${item.url}" data-grid-maintained-target="true" ${alt + title} />
|
||||
</div>`)
|
||||
}
|
||||
@@ -284,16 +270,14 @@ document.addEventListener('DOMContentLoaded', function () {
|
||||
const buttonText = GLOBAL_CONFIG.infinitegrid.buttonText
|
||||
const addButton = item => {
|
||||
const button = document.createElement('button')
|
||||
button.textContent = buttonText
|
||||
button.innerHTML = buttonText + '<i class="fa-solid fa-arrow-down"></i>'
|
||||
|
||||
const buttonFn = e => {
|
||||
e.target.removeEventListener('click', buttonFn)
|
||||
e.target.remove()
|
||||
button.addEventListener('click', e => {
|
||||
e.target.closest('button').remove()
|
||||
btf.setLoading.add(item)
|
||||
appendItem(ig.getGroups().length + 1, 10)
|
||||
}
|
||||
}, { once: true })
|
||||
|
||||
button.addEventListener('click', buttonFn)
|
||||
item.insertAdjacentElement('afterend', button)
|
||||
}
|
||||
|
||||
@@ -302,8 +286,22 @@ document.addEventListener('DOMContentLoaded', function () {
|
||||
}
|
||||
|
||||
const maxGroupKey = Math.ceil(dataLength / 10)
|
||||
let isLayoutHidden = false
|
||||
|
||||
const completeFn = e => {
|
||||
if (tabs) {
|
||||
const parentNode = item.parentNode
|
||||
|
||||
if (isLayoutHidden) {
|
||||
parentNode.style.visibility = 'visible'
|
||||
}
|
||||
|
||||
if (item.offsetHeight === 0) {
|
||||
parentNode.style.visibility = 'hidden'
|
||||
isLayoutHidden = true
|
||||
}
|
||||
}
|
||||
|
||||
const { updated, isResize, mounted } = e
|
||||
if (!updated.length || !mounted.length || isResize) {
|
||||
return
|
||||
@@ -313,7 +311,7 @@ document.addEventListener('DOMContentLoaded', function () {
|
||||
|
||||
if (ig.getGroups().length === maxGroupKey) {
|
||||
btf.setLoading.remove(item)
|
||||
ig.off('renderComplete', completeFn)
|
||||
!tabs && ig.off('renderComplete', completeFn)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -342,29 +340,30 @@ document.addEventListener('DOMContentLoaded', function () {
|
||||
ig.renderItems()
|
||||
}
|
||||
|
||||
btf.addGlobalFn('justifiedGallery', () => { ig.destroy() })
|
||||
btf.addGlobalFn('pjaxSendOnce', () => { ig.destroy() })
|
||||
}
|
||||
|
||||
const addJustifiedGallery = async (ele, tabs = false) => {
|
||||
const init = async () => {
|
||||
for (const item of ele) {
|
||||
if (btf.isHidden(item)) continue
|
||||
if (tabs && item.classList.contains('loaded')) {
|
||||
item.querySelector('.gallery-items').innerHTML = ''
|
||||
const button = item.querySelector(':scope > button')
|
||||
const loadingContainer = item.querySelector(':scope > .loading-container')
|
||||
button && button.remove()
|
||||
loadingContainer && loadingContainer.remove()
|
||||
}
|
||||
if (btf.isHidden(item) || item.classList.contains('loaded')) continue
|
||||
|
||||
const isButton = item.getAttribute('data-button') === 'true'
|
||||
const text = item.firstElementChild.textContent
|
||||
const children = item.firstElementChild
|
||||
const text = children.textContent
|
||||
children.textContent = ''
|
||||
item.classList.add('loaded')
|
||||
const content = item.getAttribute('data-type') === 'url' ? await fetchUrl(text) : JSON.parse(text)
|
||||
runJustifiedGallery(item.lastElementChild, content, isButton, tabs)
|
||||
try {
|
||||
const content = item.getAttribute('data-type') === 'url' ? await fetchUrl(text) : JSON.parse(text)
|
||||
runJustifiedGallery(children, content, isButton, tabs)
|
||||
} catch (e) {
|
||||
console.error('Gallery data parsing failed:', e)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!ele.length) return
|
||||
|
||||
if (typeof InfiniteGrid === 'function') {
|
||||
init()
|
||||
} else {
|
||||
@@ -399,12 +398,18 @@ document.addEventListener('DOMContentLoaded', function () {
|
||||
const isChatBtn = typeof chatBtn !== 'undefined'
|
||||
const isShowPercent = GLOBAL_CONFIG.percent.rightside
|
||||
|
||||
// 當滾動條小于 56 的時候
|
||||
if (document.body.scrollHeight <= innerHeight) {
|
||||
$rightside.classList.add('rightside-show')
|
||||
return
|
||||
// 檢查文檔高度是否小於視窗高度
|
||||
const checkDocumentHeight = () => {
|
||||
if (document.body.scrollHeight <= innerHeight) {
|
||||
$rightside.classList.add('rightside-show')
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// 如果文檔高度小於視窗高度,直接返回
|
||||
if (checkDocumentHeight()) return
|
||||
|
||||
// find the scroll direction
|
||||
const scrollDirection = currentTop => {
|
||||
const result = currentTop > initTop // true is down & false is up
|
||||
@@ -444,10 +449,7 @@ document.addEventListener('DOMContentLoaded', function () {
|
||||
}
|
||||
|
||||
isShowPercent && rightsideScrollPercent(currentTop)
|
||||
|
||||
if (document.body.scrollHeight <= innerHeight) {
|
||||
$rightside.classList.add('rightside-show')
|
||||
}
|
||||
checkDocumentHeight()
|
||||
}, 300)
|
||||
|
||||
btf.addEventListenerPjax(window, 'scroll', scrollTask, { passive: true })
|
||||
@@ -487,13 +489,15 @@ document.addEventListener('DOMContentLoaded', function () {
|
||||
btf.addEventListenerPjax($cardToc, 'click', tocItemClickFn)
|
||||
|
||||
autoScrollToc = item => {
|
||||
const activePosition = item.getBoundingClientRect().top
|
||||
const sidebarScrollTop = $cardToc.scrollTop
|
||||
if (activePosition > (document.documentElement.clientHeight - 100)) {
|
||||
$cardToc.scrollTop = sidebarScrollTop + 150
|
||||
}
|
||||
if (activePosition < 100) {
|
||||
$cardToc.scrollTop = sidebarScrollTop - 150
|
||||
const sidebarHeight = $cardToc.clientHeight
|
||||
const itemOffsetTop = item.offsetTop
|
||||
const itemHeight = item.clientHeight
|
||||
const scrollTop = $cardToc.scrollTop
|
||||
const offset = itemOffsetTop - scrollTop
|
||||
const middlePosition = (sidebarHeight - itemHeight) / 2
|
||||
|
||||
if (offset !== middlePosition) {
|
||||
$cardToc.scrollTop = scrollTop + (offset - middlePosition)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -504,21 +508,23 @@ document.addEventListener('DOMContentLoaded', function () {
|
||||
// find head position & add active class
|
||||
const $articleList = $article.querySelectorAll('h1,h2,h3,h4,h5,h6')
|
||||
let detectItem = ''
|
||||
|
||||
const findHeadPosition = top => {
|
||||
if (top === 0) {
|
||||
return false
|
||||
}
|
||||
if (top === 0) return false
|
||||
|
||||
let currentId = ''
|
||||
let currentIndex = ''
|
||||
|
||||
$articleList.forEach((ele, index) => {
|
||||
for (let i = 0; i < $articleList.length; i++) {
|
||||
const ele = $articleList[i]
|
||||
if (top > btf.getEleTop(ele) - 80) {
|
||||
const id = ele.id
|
||||
currentId = id ? '#' + encodeURI(id) : ''
|
||||
currentIndex = index
|
||||
currentIndex = i
|
||||
} else {
|
||||
break
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
if (detectItem === currentIndex) return
|
||||
|
||||
@@ -527,24 +533,21 @@ document.addEventListener('DOMContentLoaded', function () {
|
||||
detectItem = currentIndex
|
||||
|
||||
if (isToc) {
|
||||
$cardToc.querySelectorAll('.active').forEach(i => { i.classList.remove('active') })
|
||||
$cardToc.querySelectorAll('.active').forEach(i => i.classList.remove('active'))
|
||||
|
||||
if (currentId === '') {
|
||||
return
|
||||
}
|
||||
if (currentId) {
|
||||
const currentActive = $tocLink[currentIndex]
|
||||
currentActive.classList.add('active')
|
||||
|
||||
const currentActive = $tocLink[currentIndex]
|
||||
currentActive.classList.add('active')
|
||||
setTimeout(() => autoScrollToc(currentActive), 0)
|
||||
|
||||
setTimeout(() => {
|
||||
autoScrollToc(currentActive)
|
||||
}, 0)
|
||||
|
||||
if (isExpand) return
|
||||
let parent = currentActive.parentNode
|
||||
|
||||
for (; !parent.matches('.toc'); parent = parent.parentNode) {
|
||||
if (parent.matches('li')) parent.classList.add('active')
|
||||
if (!isExpand) {
|
||||
let parent = currentActive.parentNode
|
||||
while (!parent.matches('.toc')) {
|
||||
if (parent.matches('li')) parent.classList.add('active')
|
||||
parent = parent.parentNode
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -630,7 +633,7 @@ document.addEventListener('DOMContentLoaded', function () {
|
||||
btf.saveToLocal.set('aside-status', saveStatus, 2)
|
||||
$htmlDom.toggle('hide-aside')
|
||||
},
|
||||
'mobile-toc-button': function (p, item) { // Show mobile toc
|
||||
'mobile-toc-button': (p, item) => { // Show mobile toc
|
||||
const tocEle = document.getElementById('card-toc')
|
||||
tocEle.style.transition = 'transform 0.3s ease-in-out'
|
||||
|
||||
@@ -655,10 +658,10 @@ document.addEventListener('DOMContentLoaded', function () {
|
||||
}
|
||||
}
|
||||
|
||||
document.getElementById('rightside').addEventListener('click', function (e) {
|
||||
document.getElementById('rightside').addEventListener('click', e => {
|
||||
const $target = e.target.closest('[id]')
|
||||
if ($target && rightSideFn[$target.id]) {
|
||||
rightSideFn[$target.id](this, $target)
|
||||
rightSideFn[$target.id](e.currentTarget, $target)
|
||||
}
|
||||
})
|
||||
|
||||
@@ -673,15 +676,17 @@ document.addEventListener('DOMContentLoaded', function () {
|
||||
target.classList.toggle('hide')
|
||||
}
|
||||
|
||||
document.querySelector('#sidebar-menus .menus_items').addEventListener('click', handleClickOfSubMenu)
|
||||
const menusItems = document.querySelector('#sidebar-menus .menus_items')
|
||||
menusItems && menusItems.addEventListener('click', handleClickOfSubMenu)
|
||||
}
|
||||
|
||||
/**
|
||||
* 手机端目录点击
|
||||
*/
|
||||
const openMobileMenu = () => {
|
||||
const handleClick = () => { sidebarFn.open() }
|
||||
btf.addEventListenerPjax(document.getElementById('toggle-menu'), 'click', handleClick)
|
||||
const toggleMenu = document.getElementById('toggle-menu')
|
||||
if (!toggleMenu) return
|
||||
btf.addEventListenerPjax(toggleMenu, 'click', () => { sidebarFn.open() })
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -749,62 +754,45 @@ document.addEventListener('DOMContentLoaded', function () {
|
||||
const clickFnOfTagHide = () => {
|
||||
const hideButtons = document.querySelectorAll('#article-container .hide-button')
|
||||
if (!hideButtons.length) return
|
||||
const handleClick = function (e) {
|
||||
const $this = this
|
||||
$this.classList.add('open')
|
||||
const $fjGallery = $this.nextElementSibling.querySelectorAll('.gallery-container')
|
||||
$fjGallery.length && addJustifiedGallery($fjGallery)
|
||||
}
|
||||
|
||||
hideButtons.forEach(item => {
|
||||
item.addEventListener('click', handleClick, { once: true })
|
||||
})
|
||||
hideButtons.forEach(item => item.addEventListener('click', e => {
|
||||
const currentTarget = e.currentTarget
|
||||
currentTarget.classList.add('open')
|
||||
addJustifiedGallery(currentTarget.nextElementSibling.querySelectorAll('.gallery-container'))
|
||||
}, { once: true }))
|
||||
}
|
||||
|
||||
const tabsFn = () => {
|
||||
const navTabsElement = document.querySelectorAll('#article-container .tabs')
|
||||
if (!navTabsElement.length) return
|
||||
const navTabsElements = document.querySelectorAll('#article-container .tabs')
|
||||
if (!navTabsElements.length) return
|
||||
|
||||
const removeAndAddActiveClass = (elements, detect) => {
|
||||
Array.from(elements).forEach(element => {
|
||||
element.classList.remove('active')
|
||||
if (element === detect || element.id === detect) {
|
||||
element.classList.add('active')
|
||||
}
|
||||
const setActiveClass = (elements, activeIndex) => {
|
||||
elements.forEach((el, index) => {
|
||||
el.classList.toggle('active', index === activeIndex)
|
||||
})
|
||||
}
|
||||
|
||||
const addTabNavEventListener = (item, isJustifiedGallery) => {
|
||||
const navClickHandler = function (e) {
|
||||
const target = e.target.closest('button')
|
||||
if (target.classList.contains('active')) return
|
||||
removeAndAddActiveClass(this.children, target)
|
||||
this.classList.remove('no-default')
|
||||
const tabId = target.getAttribute('data-href')
|
||||
const tabContent = this.nextElementSibling
|
||||
removeAndAddActiveClass(tabContent.children, tabId)
|
||||
if (isJustifiedGallery) {
|
||||
btf.removeGlobalFnEvent('igOfTabs', this)
|
||||
const justifiedGalleryItems = tabContent.querySelectorAll(`:scope > #${tabId} .gallery-container`)
|
||||
justifiedGalleryItems.length && addJustifiedGallery(justifiedGalleryItems, this)
|
||||
}
|
||||
}
|
||||
btf.addEventListenerPjax(item.firstElementChild, 'click', navClickHandler)
|
||||
const handleNavClick = e => {
|
||||
const target = e.target.closest('button')
|
||||
if (!target || target.classList.contains('active')) return
|
||||
|
||||
const navItems = [...e.currentTarget.children]
|
||||
const tabContents = [...e.currentTarget.nextElementSibling.children]
|
||||
const indexOfButton = navItems.indexOf(target)
|
||||
setActiveClass(navItems, indexOfButton)
|
||||
e.currentTarget.classList.remove('no-default')
|
||||
setActiveClass(tabContents, indexOfButton)
|
||||
addJustifiedGallery(tabContents[indexOfButton].querySelectorAll('.gallery-container'), true)
|
||||
}
|
||||
|
||||
const addTabToTopEventListener = item => {
|
||||
const btnClickHandler = (e) => {
|
||||
const target = e.target.closest('button')
|
||||
if (!target) return
|
||||
btf.scrollToDest(btf.getEleTop(item), 300)
|
||||
const handleToTopClick = tabElement => e => {
|
||||
if (e.target.closest('button')) {
|
||||
btf.scrollToDest(btf.getEleTop(tabElement), 300)
|
||||
}
|
||||
btf.addEventListenerPjax(item.lastElementChild, 'click', btnClickHandler)
|
||||
}
|
||||
|
||||
navTabsElement.forEach(item => {
|
||||
const isJustifiedGallery = !!item.querySelectorAll('.gallery-container')
|
||||
addTabNavEventListener(item, isJustifiedGallery)
|
||||
addTabToTopEventListener(item)
|
||||
navTabsElements.forEach(tabElement => {
|
||||
btf.addEventListenerPjax(tabElement.firstElementChild, 'click', handleNavClick)
|
||||
btf.addEventListenerPjax(tabElement.lastElementChild, 'click', handleToTopClick(tabElement))
|
||||
})
|
||||
}
|
||||
|
||||
@@ -812,7 +800,7 @@ document.addEventListener('DOMContentLoaded', function () {
|
||||
const cardCategory = document.querySelector('#aside-cat-list.expandBtn')
|
||||
if (!cardCategory) return
|
||||
|
||||
const handleToggleBtn = (e) => {
|
||||
const handleToggleBtn = e => {
|
||||
const target = e.target
|
||||
if (target.nodeName === 'I') {
|
||||
e.preventDefault()
|
||||
@@ -825,10 +813,10 @@ document.addEventListener('DOMContentLoaded', function () {
|
||||
const switchComments = () => {
|
||||
const switchBtn = document.getElementById('switch-btn')
|
||||
if (!switchBtn) return
|
||||
|
||||
let switchDone = false
|
||||
const commentContainer = document.getElementById('post-comment')
|
||||
const handleSwitchBtn = () => {
|
||||
commentContainer.classList.toggle('move')
|
||||
document.getElementById('post-comment').classList.toggle('move')
|
||||
if (!switchDone && typeof loadOtherComment === 'function') {
|
||||
switchDone = true
|
||||
loadOtherComment()
|
||||
@@ -865,28 +853,45 @@ document.addEventListener('DOMContentLoaded', function () {
|
||||
}, 'lazyload')
|
||||
}
|
||||
|
||||
const relativeDate = function (selector) {
|
||||
const relativeDate = selector => {
|
||||
selector.forEach(item => {
|
||||
const timeVal = item.getAttribute('datetime')
|
||||
item.textContent = btf.diffDate(timeVal, true)
|
||||
item.textContent = btf.diffDate(item.getAttribute('datetime'), true)
|
||||
item.style.display = 'inline'
|
||||
})
|
||||
}
|
||||
|
||||
const unRefreshFn = function () {
|
||||
const justifiedIndexPostUI = () => {
|
||||
const recentPostsElement = document.getElementById('recent-posts')
|
||||
if (!(recentPostsElement && recentPostsElement.classList.contains('masonry'))) return
|
||||
|
||||
const init = () => {
|
||||
const masonryItem = new InfiniteGrid.MasonryInfiniteGrid('.recent-post-items', {
|
||||
gap: { horizontal: 10, vertical: 20 },
|
||||
useTransform: true,
|
||||
useResizeObserver: true
|
||||
})
|
||||
masonryItem.renderItems()
|
||||
btf.addGlobalFn('pjaxCompleteOnce', () => { masonryItem.destroy() }, 'removeJustifiedIndexPostUI')
|
||||
}
|
||||
|
||||
typeof InfiniteGrid === 'function' ? init() : btf.getScript(`${GLOBAL_CONFIG.infinitegrid.js}`).then(init)
|
||||
}
|
||||
|
||||
const unRefreshFn = () => {
|
||||
window.addEventListener('resize', () => {
|
||||
adjustMenu(false)
|
||||
mobileSidebarOpen && btf.isHidden(document.getElementById('toggle-menu')) && sidebarFn.close()
|
||||
})
|
||||
|
||||
document.getElementById('menu-mask').addEventListener('click', e => { sidebarFn.close() })
|
||||
const menuMask = document.getElementById('menu-mask')
|
||||
menuMask && menuMask.addEventListener('click', () => { sidebarFn.close() })
|
||||
|
||||
clickFnOfSubMenu()
|
||||
GLOBAL_CONFIG.islazyload && lazyloadImg()
|
||||
GLOBAL_CONFIG.copyright !== undefined && addCopyright()
|
||||
|
||||
if (GLOBAL_CONFIG.autoDarkmode) {
|
||||
window.matchMedia('(prefers-color-scheme: dark)').addListener(e => {
|
||||
window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', e => {
|
||||
if (btf.saveToLocal.get('theme') !== undefined) return
|
||||
e.matches ? handleThemeChange('dark') : handleThemeChange('light')
|
||||
})
|
||||
@@ -896,11 +901,7 @@ document.addEventListener('DOMContentLoaded', function () {
|
||||
const forPostFn = () => {
|
||||
addHighlightTool()
|
||||
addPhotoFigcaption()
|
||||
|
||||
btf.removeGlobalFnEvent('justifiedGallery')
|
||||
const galleryContainer = document.querySelectorAll('#article-container .gallery-container')
|
||||
galleryContainer.length && addJustifiedGallery(galleryContainer)
|
||||
|
||||
addJustifiedGallery(document.querySelectorAll('#article-container .gallery-container'))
|
||||
runLightbox()
|
||||
scrollFnToDo()
|
||||
addTableWrap()
|
||||
@@ -910,6 +911,7 @@ document.addEventListener('DOMContentLoaded', function () {
|
||||
|
||||
const refreshFn = () => {
|
||||
initAdjust()
|
||||
justifiedIndexPostUI()
|
||||
|
||||
if (GLOBAL_CONFIG_SITE.isPost) {
|
||||
GLOBAL_CONFIG.noticeOutdate !== undefined && addPostOutdateNotice()
|
||||
|
||||
@@ -1,43 +1,51 @@
|
||||
window.addEventListener('load', () => {
|
||||
const { algolia } = GLOBAL_CONFIG
|
||||
const { appId, apiKey, indexName, hitsPerPage = 5, languages } = algolia
|
||||
|
||||
if (!appId || !apiKey || !indexName) {
|
||||
return console.error('Algolia setting is invalid!')
|
||||
}
|
||||
|
||||
const $searchMask = document.getElementById('search-mask')
|
||||
const $searchDialog = document.querySelector('#algolia-search .search-dialog')
|
||||
|
||||
const animateElements = show => {
|
||||
const action = show ? 'animateIn' : 'animateOut'
|
||||
const maskAnimation = show ? 'to_show 0.5s' : 'to_hide 0.5s'
|
||||
const dialogAnimation = show ? 'titleScale 0.5s' : 'search_close .5s'
|
||||
btf[action]($searchMask, maskAnimation)
|
||||
btf[action]($searchDialog, dialogAnimation)
|
||||
}
|
||||
|
||||
const fixSafariHeight = () => {
|
||||
if (window.innerWidth < 768) {
|
||||
$searchDialog.style.setProperty('--search-height', `${window.innerHeight}px`)
|
||||
}
|
||||
}
|
||||
|
||||
const openSearch = () => {
|
||||
const bodyStyle = document.body.style
|
||||
bodyStyle.width = '100%'
|
||||
bodyStyle.overflow = 'hidden'
|
||||
btf.animateIn($searchMask, 'to_show 0.5s')
|
||||
btf.animateIn($searchDialog, 'titleScale 0.5s')
|
||||
btf.overflowPaddingR.add()
|
||||
animateElements(true)
|
||||
setTimeout(() => { document.querySelector('#algolia-search .ais-SearchBox-input').focus() }, 100)
|
||||
|
||||
// shortcut: ESC
|
||||
document.addEventListener('keydown', function f (event) {
|
||||
const handleEscape = event => {
|
||||
if (event.code === 'Escape') {
|
||||
closeSearch()
|
||||
document.removeEventListener('keydown', f)
|
||||
document.removeEventListener('keydown', handleEscape)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
document.addEventListener('keydown', handleEscape)
|
||||
fixSafariHeight()
|
||||
window.addEventListener('resize', fixSafariHeight)
|
||||
}
|
||||
|
||||
const closeSearch = () => {
|
||||
const bodyStyle = document.body.style
|
||||
bodyStyle.width = ''
|
||||
bodyStyle.overflow = ''
|
||||
btf.animateOut($searchDialog, 'search_close .5s')
|
||||
btf.animateOut($searchMask, 'to_hide 0.5s')
|
||||
btf.overflowPaddingR.remove()
|
||||
animateElements(false)
|
||||
window.removeEventListener('resize', fixSafariHeight)
|
||||
}
|
||||
|
||||
// fix safari
|
||||
const fixSafariHeight = () => {
|
||||
if (window.innerWidth < 768) {
|
||||
$searchDialog.style.setProperty('--search-height', window.innerHeight + 'px')
|
||||
}
|
||||
}
|
||||
|
||||
const searchClickFn = () => {
|
||||
btf.addEventListenerPjax(document.querySelector('#search-button > .search'), 'click', openSearch)
|
||||
}
|
||||
@@ -47,11 +55,9 @@ window.addEventListener('load', () => {
|
||||
document.querySelector('#algolia-search .search-close-button').addEventListener('click', closeSearch)
|
||||
}
|
||||
|
||||
const cutContent = content => {
|
||||
if (content === '') return ''
|
||||
|
||||
const cutContent = (content) => {
|
||||
if (!content) return ''
|
||||
const firstOccur = content.indexOf('<mark>')
|
||||
|
||||
let start = firstOccur - 30
|
||||
let end = firstOccur + 120
|
||||
let pre = ''
|
||||
@@ -70,108 +76,98 @@ window.addEventListener('load', () => {
|
||||
post = '...'
|
||||
}
|
||||
|
||||
const matchContent = pre + content.substring(start, end) + post
|
||||
return matchContent
|
||||
return `${pre}${content.substring(start, end)}${post}`
|
||||
}
|
||||
|
||||
const algolia = GLOBAL_CONFIG.algolia
|
||||
const isAlgoliaValid = algolia.appId && algolia.apiKey && algolia.indexName
|
||||
if (!isAlgoliaValid) {
|
||||
return console.error('Algolia setting is invalid!')
|
||||
}
|
||||
const disableDiv = [
|
||||
document.getElementById('algolia-hits'),
|
||||
document.getElementById('algolia-pagination'),
|
||||
document.querySelector('#algolia-info .algolia-stats')
|
||||
]
|
||||
|
||||
const search = instantsearch({
|
||||
indexName: algolia.indexName,
|
||||
/* global algoliasearch */
|
||||
searchClient: algoliasearch(algolia.appId, algolia.apiKey),
|
||||
indexName,
|
||||
searchClient: algoliasearch(appId, apiKey),
|
||||
searchFunction (helper) {
|
||||
helper.state.query && helper.search()
|
||||
disableDiv.forEach(item => {
|
||||
item.style.display = helper.state.query ? '' : 'none'
|
||||
})
|
||||
if (helper.state.query) helper.search()
|
||||
}
|
||||
})
|
||||
|
||||
const configure = instantsearch.widgets.configure({
|
||||
hitsPerPage: 5
|
||||
})
|
||||
|
||||
const searchBox = instantsearch.widgets.searchBox({
|
||||
container: '#algolia-search-input',
|
||||
showReset: false,
|
||||
showSubmit: false,
|
||||
placeholder: GLOBAL_CONFIG.algolia.languages.input_placeholder,
|
||||
showLoadingIndicator: true
|
||||
})
|
||||
|
||||
const hits = instantsearch.widgets.hits({
|
||||
container: '#algolia-hits',
|
||||
templates: {
|
||||
item (data) {
|
||||
const link = data.permalink ? data.permalink : (GLOBAL_CONFIG.root + data.path)
|
||||
const result = data._highlightResult
|
||||
const content = result.contentStripTruncate
|
||||
? cutContent(result.contentStripTruncate.value)
|
||||
: result.contentStrip
|
||||
? cutContent(result.contentStrip.value)
|
||||
: result.content
|
||||
? cutContent(result.content.value)
|
||||
: ''
|
||||
return `
|
||||
<a href="${link}" class="algolia-hit-item-link">
|
||||
<span class="algolia-hits-item-title">${result.title.value || 'no-title'}</span>
|
||||
<p class="algolia-hit-item-content">${content}</p>
|
||||
</a>`
|
||||
},
|
||||
empty: function (data) {
|
||||
return (
|
||||
'<div id="algolia-hits-empty">' +
|
||||
GLOBAL_CONFIG.algolia.languages.hits_empty.replace(/\$\{query}/, data.query) +
|
||||
'</div>'
|
||||
)
|
||||
const widgets = [
|
||||
instantsearch.widgets.configure({ hitsPerPage }),
|
||||
instantsearch.widgets.searchBox({
|
||||
container: '#algolia-search-input',
|
||||
showReset: false,
|
||||
showSubmit: false,
|
||||
placeholder: languages.input_placeholder,
|
||||
showLoadingIndicator: true
|
||||
}),
|
||||
instantsearch.widgets.hits({
|
||||
container: '#algolia-hits',
|
||||
templates: {
|
||||
item (data) {
|
||||
const link = data.permalink || (GLOBAL_CONFIG.root + data.path)
|
||||
const result = data._highlightResult
|
||||
const content = result.contentStripTruncate
|
||||
? cutContent(result.contentStripTruncate.value)
|
||||
: result.contentStrip
|
||||
? cutContent(result.contentStrip.value)
|
||||
: result.content
|
||||
? cutContent(result.content.value)
|
||||
: ''
|
||||
return `
|
||||
<a href="${link}" class="algolia-hit-item-link">
|
||||
<span class="algolia-hits-item-title">${result.title.value || 'no-title'}</span>
|
||||
${content ? `<div class="algolia-hit-item-content">${content}</div>` : ''}
|
||||
</a>`
|
||||
},
|
||||
empty (data) {
|
||||
return `<div id="algolia-hits-empty">${languages.hits_empty.replace(/\$\{query}/, data.query)}</div>`
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
const stats = instantsearch.widgets.stats({
|
||||
container: '#algolia-info > .algolia-stats',
|
||||
templates: {
|
||||
text: function (data) {
|
||||
const stats = GLOBAL_CONFIG.algolia.languages.hits_stats
|
||||
.replace(/\$\{hits}/, data.nbHits)
|
||||
.replace(/\$\{time}/, data.processingTimeMS)
|
||||
return (
|
||||
`<hr>${stats}`
|
||||
)
|
||||
}),
|
||||
instantsearch.widgets.stats({
|
||||
container: '#algolia-info > .algolia-stats',
|
||||
templates: {
|
||||
text (data) {
|
||||
const stats = languages.hits_stats
|
||||
.replace(/\$\{hits}/, data.nbHits)
|
||||
.replace(/\$\{time}/, data.processingTimeMS)
|
||||
return `<hr>${stats}`
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
const powerBy = instantsearch.widgets.poweredBy({
|
||||
container: '#algolia-info > .algolia-poweredBy'
|
||||
})
|
||||
|
||||
const pagination = instantsearch.widgets.pagination({
|
||||
container: '#algolia-pagination',
|
||||
totalPages: 5,
|
||||
templates: {
|
||||
first: '<i class="fas fa-angle-double-left"></i>',
|
||||
last: '<i class="fas fa-angle-double-right"></i>',
|
||||
previous: '<i class="fas fa-angle-left"></i>',
|
||||
next: '<i class="fas fa-angle-right"></i>'
|
||||
}
|
||||
})
|
||||
|
||||
search.addWidgets([configure, searchBox, hits, stats, powerBy, pagination]) // add the widgets to the instantsearch instance
|
||||
}),
|
||||
instantsearch.widgets.poweredBy({
|
||||
container: '#algolia-info > .algolia-poweredBy'
|
||||
}),
|
||||
instantsearch.widgets.pagination({
|
||||
container: '#algolia-pagination',
|
||||
totalPages: 5,
|
||||
templates: {
|
||||
first: '<i class="fas fa-angle-double-left"></i>',
|
||||
last: '<i class="fas fa-angle-double-right"></i>',
|
||||
previous: '<i class="fas fa-angle-left"></i>',
|
||||
next: '<i class="fas fa-angle-right"></i>'
|
||||
}
|
||||
})
|
||||
]
|
||||
|
||||
search.addWidgets(widgets)
|
||||
search.start()
|
||||
|
||||
searchClickFn()
|
||||
searchFnOnce()
|
||||
|
||||
window.addEventListener('pjax:complete', () => {
|
||||
!btf.isHidden($searchMask) && closeSearch()
|
||||
if (!btf.isHidden($searchMask)) closeSearch()
|
||||
searchClickFn()
|
||||
})
|
||||
|
||||
window.pjax && search.on('render', () => {
|
||||
window.pjax.refresh(document.getElementById('algolia-hits'))
|
||||
})
|
||||
if (window.pjax) {
|
||||
search.on('render', () => {
|
||||
window.pjax.refresh(document.getElementById('algolia-hits'))
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
@@ -301,9 +301,7 @@ window.addEventListener('load', () => {
|
||||
}
|
||||
|
||||
const openSearch = () => {
|
||||
const bodyStyle = document.body.style
|
||||
bodyStyle.width = '100%'
|
||||
bodyStyle.overflow = 'hidden'
|
||||
btf.overflowPaddingR.add()
|
||||
btf.animateIn($searchMask, 'to_show 0.5s')
|
||||
btf.animateIn($searchDialog, 'titleScale 0.5s')
|
||||
setTimeout(() => { input.focus() }, 300)
|
||||
@@ -325,9 +323,7 @@ window.addEventListener('load', () => {
|
||||
}
|
||||
|
||||
const closeSearch = () => {
|
||||
const bodyStyle = document.body.style
|
||||
bodyStyle.width = ''
|
||||
bodyStyle.overflow = ''
|
||||
btf.overflowPaddingR.remove()
|
||||
btf.animateOut($searchDialog, 'search_close .5s')
|
||||
btf.animateOut($searchMask, 'to_hide 0.5s')
|
||||
window.removeEventListener('resize', fixSafariHeight)
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -47,12 +47,28 @@
|
||||
return throttled
|
||||
},
|
||||
|
||||
sidebarPaddingR: () => {
|
||||
const innerWidth = window.innerWidth
|
||||
const clientWidth = document.body.clientWidth
|
||||
const paddingRight = innerWidth - clientWidth
|
||||
if (innerWidth !== clientWidth) {
|
||||
document.body.style.paddingRight = paddingRight + 'px'
|
||||
overflowPaddingR: {
|
||||
add: () => {
|
||||
const innerWidth = window.innerWidth
|
||||
const clientWidth = document.body.clientWidth
|
||||
const paddingRight = innerWidth - clientWidth
|
||||
|
||||
if (paddingRight > 0) {
|
||||
document.body.style.paddingRight = `${paddingRight}px`
|
||||
document.body.style.overflow = 'hidden'
|
||||
const menuElement = document.querySelector('#page-header.nav-fixed #menus')
|
||||
if (menuElement) {
|
||||
menuElement.style.paddingRight = `${paddingRight}px`
|
||||
}
|
||||
}
|
||||
},
|
||||
remove: () => {
|
||||
document.body.style.paddingRight = ''
|
||||
document.body.style.overflow = ''
|
||||
const menuElement = document.querySelector('#page-header.nav-fixed #menus')
|
||||
if (menuElement) {
|
||||
menuElement.style.paddingRight = ''
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
@@ -69,28 +85,24 @@
|
||||
})
|
||||
},
|
||||
|
||||
diffDate: (d, more = false) => {
|
||||
diffDate: (inputDate, more = false) => {
|
||||
const dateNow = new Date()
|
||||
const datePost = new Date(d)
|
||||
const dateDiff = dateNow.getTime() - datePost.getTime()
|
||||
const minute = 1000 * 60
|
||||
const hour = minute * 60
|
||||
const day = hour * 24
|
||||
const month = day * 30
|
||||
const datePost = new Date(inputDate)
|
||||
const diffMs = dateNow - datePost
|
||||
const diffSec = diffMs / 1000
|
||||
const diffMin = diffSec / 60
|
||||
const diffHour = diffMin / 60
|
||||
const diffDay = diffHour / 24
|
||||
const diffMonth = diffDay / 30
|
||||
const { dateSuffix } = GLOBAL_CONFIG
|
||||
|
||||
if (!more) return parseInt(dateDiff / day)
|
||||
if (!more) return Math.floor(diffDay)
|
||||
|
||||
const monthCount = dateDiff / month
|
||||
const dayCount = dateDiff / day
|
||||
const hourCount = dateDiff / hour
|
||||
const minuteCount = dateDiff / minute
|
||||
|
||||
if (monthCount > 12) return datePost.toISOString().slice(0, 10)
|
||||
if (monthCount >= 1) return `${parseInt(monthCount)} ${dateSuffix.month}`
|
||||
if (dayCount >= 1) return `${parseInt(dayCount)} ${dateSuffix.day}`
|
||||
if (hourCount >= 1) return `${parseInt(hourCount)} ${dateSuffix.hour}`
|
||||
if (minuteCount >= 1) return `${parseInt(minuteCount)} ${dateSuffix.min}`
|
||||
if (diffMonth > 24) return datePost.toISOString().slice(0, 10)
|
||||
if (diffMonth >= 3) return `${Math.floor(diffMonth)} ${dateSuffix.month}`
|
||||
if (diffDay >= 3) return `${Math.floor(diffDay)} ${dateSuffix.day}`
|
||||
if (diffHour >= 3) return `${Math.floor(diffHour)} ${dateSuffix.hour}`
|
||||
if (diffMin >= 1) return `${Math.floor(diffMin)} ${dateSuffix.min}`
|
||||
return dateSuffix.just
|
||||
},
|
||||
|
||||
@@ -109,7 +121,7 @@
|
||||
},
|
||||
|
||||
scrollToDest: (pos, time = 500) => {
|
||||
const currentPos = window.pageYOffset
|
||||
const currentPos = window.scrollY
|
||||
const isNavFixed = document.getElementById('page-header').classList.contains('fixed')
|
||||
if (currentPos > pos || isNavFixed) pos = pos - 70
|
||||
|
||||
@@ -121,22 +133,16 @@
|
||||
return
|
||||
}
|
||||
|
||||
let start = null
|
||||
pos = +pos
|
||||
window.requestAnimationFrame(function step (currentTime) {
|
||||
start = !start ? currentTime : start
|
||||
const progress = currentTime - start
|
||||
if (currentPos < pos) {
|
||||
window.scrollTo(0, ((pos - currentPos) * progress / time) + currentPos)
|
||||
} else {
|
||||
window.scrollTo(0, currentPos - ((currentPos - pos) * progress / time))
|
||||
const startTime = performance.now()
|
||||
const animate = currentTime => {
|
||||
const timeElapsed = currentTime - startTime
|
||||
const progress = Math.min(timeElapsed / time, 1)
|
||||
window.scrollTo(0, currentPos + (pos - currentPos) * progress)
|
||||
if (progress < 1) {
|
||||
requestAnimationFrame(animate)
|
||||
}
|
||||
if (progress < time) {
|
||||
window.requestAnimationFrame(step)
|
||||
} else {
|
||||
window.scrollTo(0, pos)
|
||||
}
|
||||
})
|
||||
}
|
||||
requestAnimationFrame(animate)
|
||||
},
|
||||
|
||||
animateIn: (ele, text) => {
|
||||
@@ -179,7 +185,7 @@
|
||||
loadLightbox: ele => {
|
||||
const service = GLOBAL_CONFIG.lightbox
|
||||
|
||||
if (service === 'mediumZoom') {
|
||||
if (service === 'medium_zoom') {
|
||||
mediumZoom(ele, { background: 'var(--zoom-bg)' })
|
||||
}
|
||||
|
||||
@@ -243,7 +249,7 @@
|
||||
}
|
||||
},
|
||||
|
||||
updateAnchor: (anchor) => {
|
||||
updateAnchor: anchor => {
|
||||
if (anchor !== window.location.hash) {
|
||||
if (!anchor) anchor = location.pathname
|
||||
const title = GLOBAL_CONFIG_SITE.title
|
||||
@@ -254,33 +260,37 @@
|
||||
}
|
||||
},
|
||||
|
||||
getScrollPercent: (currentTop, ele) => {
|
||||
const docHeight = ele.clientHeight
|
||||
const winHeight = document.documentElement.clientHeight
|
||||
const headerHeight = ele.offsetTop
|
||||
const contentMath = (docHeight > winHeight) ? (docHeight - winHeight) : (document.documentElement.scrollHeight - winHeight)
|
||||
const scrollPercent = (currentTop - headerHeight) / (contentMath)
|
||||
const scrollPercentRounded = Math.round(scrollPercent * 100)
|
||||
const percentage = (scrollPercentRounded > 100) ? 100 : (scrollPercentRounded <= 0) ? 0 : scrollPercentRounded
|
||||
return percentage
|
||||
},
|
||||
getScrollPercent: (() => {
|
||||
let docHeight, winHeight, headerHeight, contentMath
|
||||
|
||||
return (currentTop, ele) => {
|
||||
if (!docHeight || ele.clientHeight !== docHeight) {
|
||||
docHeight = ele.clientHeight
|
||||
winHeight = window.innerHeight
|
||||
headerHeight = ele.offsetTop
|
||||
contentMath = Math.max(docHeight - winHeight, document.documentElement.scrollHeight - winHeight)
|
||||
}
|
||||
|
||||
const scrollPercent = (currentTop - headerHeight) / contentMath
|
||||
return Math.max(0, Math.min(100, Math.round(scrollPercent * 100)))
|
||||
}
|
||||
})(),
|
||||
|
||||
addEventListenerPjax: (ele, event, fn, option = false) => {
|
||||
ele.addEventListener(event, fn, option)
|
||||
btf.addGlobalFn('pjax', () => {
|
||||
btf.addGlobalFn('pjaxSendOnce', () => {
|
||||
ele.removeEventListener(event, fn, option)
|
||||
})
|
||||
},
|
||||
|
||||
removeGlobalFnEvent: (key, parent = window) => {
|
||||
const { globalFn = {} } = parent
|
||||
const keyObj = globalFn[key] || {}
|
||||
const keyArr = Object.keys(keyObj)
|
||||
if (!keyArr.length) return
|
||||
keyArr.forEach(i => {
|
||||
keyObj[i]()
|
||||
})
|
||||
delete parent.globalFn[key]
|
||||
const globalFn = parent.globalFn || {}
|
||||
const keyObj = globalFn[key]
|
||||
if (!keyObj) return
|
||||
|
||||
Object.keys(keyObj).forEach(i => keyObj[i]())
|
||||
|
||||
delete globalFn[key]
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user