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.