PageSourceSearch

https://blog.tonys-studio.top/js/utils.js

js tonys-studio.top collected 2026-09-25 20:04:51 UTC 11,007 bytes, 367 lines download raw bytes

1(() => {
2  const btfFn = {
3    debounce: (func, wait = 0, immediate = false) => {
4      let timeout
5      return (...args) => {
6        const later = () => {
7          timeout = null
8          if (!immediate) func(...args)
9        }
10        const callNow = immediate && !timeout
11        clearTimeout(timeout)
12        timeout = setTimeout(later, wait)
13        if (callNow) func(...args)
14      }
15    },
16
17    throttle: (func, wait, options = {}) => {
18      let timeout, args
19      let previous = 0
20
21      const later = () => {
22        previous = options.leading === false ? 0 : new Date().getTime()
23        timeout = null
24        func(...args)
25        if (!timeout) args = null
26      }
27
28      return (...params) => {
29        const now = new Date().getTime()
30        if (!previous && options.leading === false) previous = now
31        const remaining = wait - (now - previous)
32        args = params
33
34        if (remaining <= 0 || remaining > wait) {
35          if (timeout) {
36            clearTimeout(timeout)
37            timeout = null
38          }
39          previous = now
40          func(...args)
41          if (!timeout) args = null
42        } else if (!timeout && options.trailing !== false) {
43          timeout = setTimeout(later, remaining)
44        }
45      }
46    },
47
48    rafThrottle: fn => {
49      let rafId = null
50      return (...args) => {
51        if (rafId) return
52        rafId = requestAnimationFrame(() => {
53          fn(...args)
54          rafId = null
55        })
56      }
57    },
58
59    overflowPaddingR: (() => {
60      let headerElement = null
61      let menuElement = null
62
63      const getElements = () => {
64        if (!headerElement) {
65          headerElement = document.getElementById('page-header')
66        }
67        if (!menuElement) {
68          menuElement = document.getElementById('menus')
69        }
70        return { headerElement, menuElement }
71      }
72
73      return {
74        add: () => {
75          const paddingRight = window.innerWidth - document.body.clientWidth
76
77          if (paddingRight > 0) {
78            document.body.style.paddingRight = `${paddingRight}px`
79            document.body.style.overflow = 'hidden'
80            const { headerElement: header, menuElement: menu } = getElements()
81            if (header && menu && header.classList.contains('nav-fixed')) {
82              menu.style.paddingRight = `${paddingRight}px`
83            }
84          }
85        },
86        remove: () => {
87          document.body.style.paddingRight = ''
88          document.body.style.overflow = ''
89          const { headerElement: header, menuElement: menu } = getElements()
90          if (header && menu && header.classList.contains('nav-fixed')) {
91            menu.style.paddingRight = ''
92          }
93        }
94      }
95    })(),
96
97    snackbarShow: (text, showAction = false, duration = 2000) => {
98      const { position, bgLight, bgDark } = GLOBAL_CONFIG.Snackbar
99      const bg = document.documentElement.getAttribute('data-theme') === 'light' ? bgLight : bgDark
100      Snackbar.show({
101        text,
102        backgroundColor: bg,
103        showAction,
104        duration,
105        pos: position,
106        customClass: 'snackbar-css'
107      })
108    },
109
110    diffDate: (inputDate, more = false) => {
111      const dateNow = new Date()
112      const datePost = new Date(inputDate)
113      const diffMs = dateNow - datePost
114      const diffSec = diffMs / 1000
115      const diffMin = diffSec / 60
116      const diffHour = diffMin / 60
117      const diffDay = diffHour / 24
118      const diffMonth = diffDay / 30
119      const { dateSuffix } = GLOBAL_CONFIG
120
121      if (!more) return Math.floor(diffDay)
122
123      if (diffMonth > 12) return datePost.toISOString().slice(0, 10)
124      if (diffMonth >= 1) return `${Math.floor(diffMonth)} ${dateSuffix.month}`
125      if (diffDay >= 1) return `${Math.floor(diffDay)} ${dateSuffix.day}`
126      if (diffHour >= 1) return `${Math.floor(diffHour)} ${dateSuffix.hour}`
127      if (diffMin >= 1) return `${Math.floor(diffMin)} ${dateSuffix.min}`
128      return dateSuffix.just
129    },
130
131    loadComment: (dom, callback) => {
132      if ('IntersectionObserver' in window) {
133        const observerItem = new IntersectionObserver(entries => {
134          if (entries[0].isIntersecting) {
135            callback()
136            observerItem.disconnect()
137          }
138        }, { threshold: [0] })
139        observerItem.observe(dom)
140      } else {
141        callback()
142      }
143    },
144
145    scrollToDest: (pos, time = 500) => {
146      const currentPos = window.scrollY
147      const isNavFixed = document.getElementById('page-header').classList.contains('fixed')
148      if (currentPos > pos || isNavFixed) pos = pos - 70
149
150      if ('scrollBehavior' in document.documentElement.style) {
151        window.scrollTo({
152          top: pos,
153          behavior: 'smooth'
154        })
155        return
156      }
157
158      const startTime = performance.now()
159      const animate = currentTime => {
160        const timeElapsed = currentTime - startTime
161        const progress = Math.min(timeElapsed / time, 1)
162        const easedProgress = 1 - Math.pow(1 - progress, 4) // easeOutQuart
163        window.scrollTo(0, currentPos + (pos - currentPos) * easedProgress)
164        if (progress < 1) {
165          requestAnimationFrame(animate)
166        }
167      }
168      requestAnimationFrame(animate)
169    },
170
171    animateIn: (ele, animation) => {
172      ele.style.display = 'block'
173      ele.style.animation = animation
174    },
175
176    animateOut: (ele, animation) => {
177      const handleAnimationEnd = () => {
178        ele.style.display = ''
179        ele.style.animation = ''
180        ele.removeEventListener('animationend', handleAnimationEnd)
181      }
182      ele.addEventListener('animationend', handleAnimationEnd)
183      ele.style.animation = animation
184    },
185
186    wrap: (selector, eleType, options) => {
187      const createEle = document.createElement(eleType)
188      for (const [key, value] of Object.entries(options)) {
189        createEle.setAttribute(key, value)
190      }
191      selector.parentNode.insertBefore(createEle, selector)
192      createEle.appendChild(selector)
193    },
194
195    isHidden: ele => ele.offsetHeight === 0 && ele.offsetWidth === 0,
196
197    getEleTop: ele => ele.getBoundingClientRect().top + window.scrollY,
198
199    loadLightbox: ele => {
200      const service = GLOBAL_CONFIG.lightbox
201
202      if (service === 'medium_zoom') {
203        mediumZoom(ele, { background: 'var(--zoom-bg)' })
204        return
205      }
206
207      if (service === 'fancybox') {
208        ele.forEach(i => {
209          if (i.parentNode.tagName !== 'A') {
210            const dataSrc = i.dataset.lazySrc || i.src
211            const dataCaption = i.title || i.alt || ''
212            btf.wrap(i, 'a', { href: dataSrc, 'data-fancybox': 'gallery', 'data-caption': dataCaption, 'data-thumb'
212: dataSrc })
213          }
214        })
215
216        if (!window.fancyboxRun) {
217          let options = ''
218          if (Fancybox.version < '6') {
219            options = {
220              Hash: false,
221              Thumbs: {
222                showOnStart: false
223              },
224              Images: {
225                Panzoom: {
226                  maxScale: 4
227                }
228              },
229              Carousel: {
230                transition: 'slide'
231              },
232              Toolbar: {
233                display: {
234                  left: ['infobar'],
235                  middle: [
236                    'zoomIn',
237                    'zoomOut',
238                    'toggle1to1',
239                    'rotateCCW',
240                    'rotateCW',
241                    'flipX',
242                    'flipY'
243                  ],
244                  right: ['slideshow', 'thumbs', 'close']
245                }
246              },
247              hideScrollbar: false
248            }
249          } else {
250            options = {
251              Hash: false,
252              Carousel: {
253                transition: 'slide',
254                Thumbs: {
255                  showOnStart: false
256                },
257                Toolbar: {
258                  display: {
259                    left: ['counter'],
260                    middle: [
261                      'zoomIn',
262                      'zoomOut',
263                      'toggle1to1',
264                      'rotateCCW',
265                      'rotateCW',
266                      'flipX',
267                      'flipY',
268                      'reset'
269                    ],
270                    right: ['autoplay', 'thumbs', 'close']
271                  }
272                },
273                Zoomable: {
274                  Panzoom: {
275                    maxScale: 4
276                  }
277                }
278              },
279              hideScrollbar: false
280            }
281          }
282
283          Fancybox.bind('[data-fancybox]', options)
284          window.fancyboxRun = true
285        }
286      }
287    },
288
289    setLoading: {
290      add: ele => {
291        const html = `
292        <div class="loading-container">
293          <div class="loading-item">
294            <div></div><div></div><div></div><div></div><div></div>
295          </div>
296        </div>
297      `
298        ele.insertAdjacentHTML('afterend', html)
299      },
300      remove: ele => {
301        ele.nextElementSibling.remove()
302      }
303    },
304
305    updateAnchor: anchor => {
306      if (anchor !== window.location.hash) {
307        if (!anchor) anchor = location.pathname
308        const title = GLOBAL_CONFIG_SITE.title
309        window.history.replaceState({
310          url: location.href,
311          title
312        }, title, anchor)
313      }
314    },
315
316    getScrollPercent: (() => {
317      let docHeight, winHeight, headerHeight, contentMath
318
319      return (currentTop, ele) => {
320        if (!docHeight || ele.clientHeight !== docHeight) {
321          docHeight = ele.clientHeight
322          winHeight = window.innerHeight
323          headerHeight = ele.offsetTop
324          contentMath = Math.max(docHeight - winHeight, document.documentElement.scrollHeight - winHeight)
325        }
326
327        const scrollPercent = (currentTop - headerHeight) / contentMath
328        return Math.max(0, Math.min(100, Math.round(scrollPercent * 100)))
329      }
330    })(),
331
332    addEventListenerPjax: (ele, event, fn, option = false) => {
333      ele.addEventListener(event, fn, option)
334      btf.addGlobalFn('pjaxSendOnce', () => {
335        ele.removeEventListener(event, fn, option)
336      })
337    },
338
339    removeGlobalFnEvent: (key, parent = window) => {
340      const globalFn = parent.globalFn || {}
341      const keyObj = globalFn[key]
342      if (!keyObj) return
343
344      Object.keys(keyObj).forEach(i => keyObj[i]())
345
346      delete globalFn[key]
347    },
348
349    switchComments: (el = document, path) => {
350      const switchBtn = el.querySelector('#switch-btn')
351      if (!switchBtn) return
352
353      let switchDone = false
354      const postComment = el.querySelector('#post-comment')
355      const handleSwitchBtn = () => {
356        postComment.classList.toggle('move')
357        if (!switchDone && typeof loadOtherComment === 'function') {
358          switchDone = true
359          loadOtherComment(el, path)
360        }
361      }
362      btf.addEventListenerPjax(switchBtn, 'click', handleSwitchBtn)
363    }
364  }
365
366  window.btf = { ...window.btf, ...btfFn }
367})()

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.