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 } 190 191 if (service === 'fancybox') { 192 Array.from(ele).forEach(i => { 193 if (i.parentNode.tagName !== 'A') { 194 const dataSrc = i.dataset.lazySrc || i.src 195 const dataCaption = i.title || i.alt || '' 196 btf.wrap(i, 'a', { href: dataSrc, 'data-fancybox': 'gallery', 'data-caption': dataCaption, 'data-thumb'
196: dataSrc }) 197 } 198 }) 199 200 if (!window.fancyboxRun) { 201 Fancybox.bind('[data-fancybox]', { 202 Hash: false, 203 Thumbs: { 204 showOnStart: false 205 }, 206 Images: { 207 Panzoom: { 208 maxScale: 4 209 } 210 }, 211 Carousel: { 212 transition: 'slide' 213 }, 214 Toolbar: { 215 display: { 216 left: ['infobar'], 217 middle: [ 218 'zoomIn', 219 'zoomOut', 220 'toggle1to1', 221 'rotateCCW', 222 'rotateCW', 223 'flipX', 224 'flipY' 225 ], 226 right: ['slideshow', 'thumbs', 'close'] 227 } 228 } 229 }) 230 window.fancyboxRun = true 231 } 232 } 233 }, 234 235 setLoading: { 236 add: ele => { 237 const html = ` 238 <div class="loading-container"> 239 <div class="loading-item"> 240 <div></div><div></div><div></div><div></div><div></div> 241 </div> 242 </div> 243 ` 244 ele.insertAdjacentHTML('afterend', html) 245 }, 246 remove: ele => { 247 ele.nextElementSibling.remove() 248 } 249 }, 250 251 updateAnchor: anchor => { 252 if (anchor !== window.location.hash) { 253 if (!anchor) anchor = location.pathname 254 const title = GLOBAL_CONFIG_SITE.title 255 window.history.replaceState({ 256 url: location.href, 257 title 258 }, title, anchor) 259 } 260 }, 261 262 getScrollPercent: (() => { 263 let docHeight, winHeight, headerHeight, contentMath 264 265 return (currentTop, ele) => { 266 if (!docHeight || ele.clientHeight !== docHeight) { 267 docHeight = ele.clientHeight 268 winHeight = window.innerHeight 269 headerHeight = ele.offsetTop 270 contentMath = Math.max(docHeight - winHeight, document.documentElement.scrollHeight - winHeight) 271 } 272 273 const scrollPercent = (currentTop - headerHeight) / contentMath 274 return Math.max(0, Math.min(100, Math.round(scrollPercent * 100))) 275 } 276 })(), 277 278 addEventListenerPjax: (ele, event, fn, option = false) => { 279 ele.addEventListener(event, fn, option) 280 btf.addGlobalFn('pjaxSendOnce', () => { 281 ele.removeEventListener(event, fn, option) 282 }) 283 }, 284 285 removeGlobalFnEvent: (key, parent = window) => { 286 const globalFn = parent.globalFn || {} 287 const keyObj = globalFn[key] 288 if (!keyObj) return 289 290 Object.keys(keyObj).forEach(i => keyObj[i]()) 291 292 delete globalFn[key] 293 } 294 } 295 296 window.btf = { ...window.btf, ...btfFn } 297})()
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.