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