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