PageSourceSearch

https://032.im/js/utils.js

js 032.im collected 2026-09-25 21:48:14 UTC 8,741 bytes, 307 lines download raw bytes

1const btf = {
2  debounce: function (func, wait, immediate) {
3    let timeout
4    return function () {
5      const context = this
6      const args = arguments
7      const later = function () {
8        timeout = null
9        if (!immediate) func.apply(context, args)
10      }
11      const callNow = immediate && !timeout
12      clearTimeout(timeout)
13      timeout = setTimeout(later, wait)
14      if (callNow) func.apply(context, args)
15    }
16  },
17
18  throttle: function (func, wait, options) {
19    let timeout, context, args
20    let previous = 0
21    if (!options) options = {}
22
23    const later = function () {
24      previous = options.leading === false ? 0 : new Date().getTime()
25      timeout = null
26      func.apply(context, args)
27      if (!timeout) context = args = null
28    }
29
30    const throttled = function () {
31      const now = new Date().getTime()
32      if (!previous && options.leading === false) previous = now
33      const remaining = wait - (now - previous)
34      context = this
35      args = arguments
36      if (remaining <= 0 || remaining > wait) {
37        if (timeout) {
38          clearTimeout(timeout)
39          timeout = null
40        }
41        previous = now
42        func.apply(context, args)
43        if (!timeout) context = args = null
44      } else if (!timeout && options.trailing !== false) {
45        timeout = setTimeout(later, remaining)
46      }
47    }
48
49    return throttled
50  },
51
52  sidebarPaddingR: () => {
53    const innerWidth = window.innerWidth
54    const clientWidth = document.body.clientWidth
55    const paddingRight = innerWidth - clientWidth
56    if (innerWidth !== clientWidth) {
57      document.body.style.paddingRight = paddingRight + 'px'
58    }
59  },
60
61  snackbarShow: (text, showAction = false, duration = 2000) => {
62    const { position, bgLight, bgDark } = GLOBAL_CONFIG.Snackbar
63    const bg = document.documentElement.getAttribute('data-theme') === 'light' ? bgLight : bgDark
64    Snackbar.show({
65      text,
66      backgroundColor: bg,
67      showAction,
68      duration,
69      pos: position,
70      customClass: 'snackbar-css'
71    })
72  },
73
74  diffDate: (d, more = false) => {
75    const dateNow = new Date()
76    const datePost = new Date(d)
77    const dateDiff = dateNow.getTime() - datePost.getTime()
78    const minute = 1000 * 60
79    const hour = minute * 60
80    const day = hour * 24
81    const month = day * 30
82    const { dateSuffix } = GLOBAL_CONFIG
83
84    if (!more) return parseInt(dateDiff / day)
85
86    const monthCount = dateDiff / month
87    const dayCount = dateDiff / day
88    const hourCount = dateDiff / hour
89    const minuteCount = dateDiff / minute
90
91    if (monthCount > 12) return datePost.toISOString().slice(0, 10)
92    if (monthCount >= 1) return `${parseInt(monthCount)} ${dateSuffix.month}`
93    if (dayCount >= 1) return `${parseInt(dayCount)} ${dateSuffix.day}`
94    if (hourCount >= 1) return `${parseInt(hourCount)} ${dateSuffix.hour}`
95    if (minuteCount >= 1) return `${parseInt(minuteCount)} ${dateSuffix.min}`
96    return dateSuffix.just
97  },
98
99  loadComment: (dom, callback) => {
100    if ('IntersectionObserver' in window) {
101      const observerItem = new IntersectionObserver((entries) => {
102        if (entries[0].isIntersecting) {
103          callback()
104          observerItem.disconnect()
105        }
106      }, { threshold: [0] })
107      observerItem.observe(dom)
108    } else {
109      callback()
110    }
111  },
112
113  scrollToDest: (pos, time = 500) => {
114    const currentPos = window.pageYOffset
115    const isNavFixed = document.getElementById('page-header').classList.contains('fixed')
116    if (currentPos > pos || isNavFixed) pos = pos - 70
117
118    if ('scrollBehavior' in document.documentElement.style) {
119      window.scrollTo({
120        top: pos,
121        behavior: 'smooth'
122      })
123      return
124    }
125
126    let start = null
127    pos = +pos
128    window.requestAnimationFrame(function step (currentTime) {
129      start = !start ? currentTime : start
130      const progress = currentTime - start
131      if (currentPos < pos) {
132        window.scrollTo(0, ((pos - currentPos) * progress / time) + currentPos)
133      } else {
134        window.scrollTo(0, currentPos - ((currentPos - pos) * progress / time))
135      }
136      if (progress < time) {
137        window.requestAnimationFrame(step)
138      } else {
139        window.scrollTo(0, pos)
140      }
141    })
142  },
143
144  animateIn: (ele, text) => {
145    ele.style.display = 'block'
146    ele.style.animation = text
147  },
148
149  animateOut: (ele, text) => {
150    ele.addEventListener('animationend', function f () {
151      ele.style.display = ''
152      ele.style.animation = ''
153      ele.removeEventListener('animationend', f)
154    })
155    ele.style.animation = text
156  },
157
158  getParents: (elem, selector) => {
159    for (; elem && elem !== document; elem = elem.parentNode) {
160      if (elem.matches(selector)) return elem
161    }
162    return null
163  },
164
165  siblings: (ele, selector) => {
166    return [...ele.parentNode.children].filter((child) => {
167      if (selector) {
168        return child !== ele && child.matches(selector)
169      }
170      return child !== ele
171    })
172  },
173
174  /**
175   * @param {*} selector
176   * @param {*} eleType the type of create element
177   * @param {*} options object key: value
178   */
179  wrap: (selector, eleType, options) => {
180    const createEle = document.createElement(eleType)
181    for (const [key, value] of Object.entries(options)) {
182      createEle.setAttribute(key, value)
183    }
184    selector.parentNode.insertBefore(createEle, selector)
185    createEle.appendChild(selector)
186  },
187
188  unwrap: el => {
189    const parent = el.parentNode
190    if (parent && parent !== document.body) {
191      parent.replaceChild(el, parent)
192    }
193  },
194
195  isHidden: ele => ele.offsetHeight === 0 && ele.offsetWidth === 0,
196
197  getEleTop: ele => {
198    let actualTop = ele.offsetTop
199    let current = ele.offsetParent
200
201    while (current !== null) {
202      actualTop += current.offsetTop
203      current = current.offsetParent
204    }
205
206    return actualTop
207  },
208
209  loadLightbox: ele => {
210    const service = GLOBAL_CONFIG.lightbox
211
212    if (service === 'mediumZoom') {
213      mediumZoom(ele, { background: 'var(--zoom-bg)' })
214    }
215
216    if (service === 'fancybox') {
217      ele.forEach(i => {
218        if (i.parentNode.tagName !== 'A') {
219          const dataSrc = i.dataset.lazySrc || i.src
220          const dataCaption = i.title || i.alt || ''
221          btf.wrap(i, 'a', { href: dataSrc, 'data-fancybox': 'gallery', 'data-caption': dataCaption, 'data-thumb': dataSrc })
222        }
223      })
224
225      if (!window.fancyboxRun) {
226        Fancybox.bind('[data-fancybox]', {
227          Hash: false,
228          Thumbs: {
229            showOnStart: false
230          },
231          Images: {
232            Panzoom: {
233              maxScale: 4
234            }
235          },
236          Carousel: {
237            transition: 'slide'
238          },
239          Toolbar: {
240            display: {
241              left: ['infobar'],
242              middle: [
243                'zoomIn',
244                'zoomOut',
245                'toggle1to1',
246                'rotateCCW',
247                'rotateCW',
248                'flipX',
249                'flipY'
250              ],
251              right: ['slideshow', 'thumbs', 'close']
252            }
253          }
254        })
255        window.fancyboxRun = true
256      }
257    }
258  },
259
260  initJustifiedGallery: function (selector) {
261    const runJustifiedGallery = i => {
262      if (!btf.isHidden(i)) {
263        fjGallery(i, {
264          itemSelector: '.fj-gallery-item',
265          rowHeight: i.getAttribute('data-rowHeight'),
266          gutter: 4,
267          onJustify: function () {
268            this.$container.style.opacity = '1'
269          }
270        })
271      }
272    }
273
274    if (Array.from(selector).length === 0) runJustifiedGallery(selector)
275    else selector.forEach(i => { runJustifiedGallery(i) })
276  },
277
278  updateAnchor: (anchor) => {
279    if (anchor !== window.location.hash) {
280      if (!anchor) anchor = location.pathname
281      const title = GLOBAL_CONFIG_SITE.title
282      window.history.replaceState({
283        url: location.href,
284        title
285      }, title, anchor)
286    }
287  },
288
289  getScrollPercent: (currentTop, ele) => {
290    const docHeight = ele.clientHeight
291    const winHeight = document.documentElement.clientHeight
292    const headerHeight = ele.offsetTop
293    const contentMath = (docHeight > winHeight) ? (docHeight - winHeight) : (document.documentElement.scrollHeight - winHeight)
294    const scrollPercent = (currentTop - headerHeight) / (contentMath)
295    const scrollPercentRounded = Math.round(scrollPercent * 100)
296    const percentage = (scrollPercentRounded > 100) ? 100 : (scrollPercentRounded <= 0) ? 0 : scrollPercentRounded
297    return percentage
298  },
299
300  addModeChange: (name, fn) => {
301    if (window.themeChange && window.themeChange[name]) return
302    window.themeChange = {
303      ...window.themeChange,
304      [name]: fn
305    }
306  }
307}

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.