1window.Util = { 2 isElement(element) { 3 return typeof HTMLElement === "object" ? element instanceof HTMLElement : 4 element && typeof element === "object" && element !== null && element.nodeType === 1 && typeof element.nodeName === "string"; 5 }, 6 7 defaults(options = null, defaults = null) { 8 options = options ?? {}; 9 defaults = defaults ?? {}; 10 Object.keys(defaults).forEach((option) => { 11 if(typeof options[option] === 'object' && options[option] !== null && options[option].constructor === ({}).constructor 12 && typeof defaults[option] === 'object' && defaults[option] !== null && defaults[option].constructor === ({}).constructor) { 13 options[option] = this.defaults(options[option], defaults[option]); 14 } else if(typeof options[option] === 'undefined') { 15 options[option] = defaults[option]; 16 } 17 }); 18 return options; 19 }, 20 21 overrides(options = null, overrides = null) { 22 options = options ?? {}; 23 overrides = overrides ?? {}; 24 Object.keys(overrides).forEach((option) => { 25 if(typeof options[option] === 'object' && options[option] !== null && options[option].constructor === ({}).constructor 26 && typeof overrides[option] === 'object' && overrides[option] !== null && overrides[option].constructor === ({}).constructor) { 27 options[option] = this.overrides(options[option], overrides[option]); 28 } else { 29 options[option] = overrides[option]; 30 } 31 }); 32 return options; 33 }, 34 35 wrapHtml(html, wrapper) { 36 if(this.isElement(wrapper)) { 37 wrapper = wrapper.cloneNode(); 38 } else { 39 wrapper = document.createElement(wrapper); 40 } 41 42 wrapper.innerHTML = html; 43 return wrapper; 44 }, 45 46 wrapElement(element, wrapper) { 47 if(this.isElement(wrapper)) { 48 wrapper = wrapper.cloneNode(); 49 } else { 50 wrapper = document.createElement(wrapper); 51 } 52 53 element.replaceWith(wrapper); 54 wrapper.appendChild(element); 55 return element; 56 }, 57 58 replaceHtmlTag(element, new_tag) { 59 let replacement = document.createElement(new_tag); 60 replacement.innerHTML = element.innerHTML; 61 62 // Copy the attributes 63 Array.from(element.attributes).forEach(a => replacement.attributes.setNamedItem(a.cloneNode())); 64 65 // Replace source element 66 element.parentNode.replaceChild(replacement, element); 67 return element; 68 }, 69 70 stop() { 71 if(window.stop) 72 window.stop(); 73 }, 74 75 humanByteSize(bytes, si = false, decimal_places = 0, short_units = false) { 76 const thresh = si ? 1000 : 1024; 77 78 if(isNaN(bytes)) 79 return null; 80 if(Math.abs(bytes) < thresh) 81 return bytes + ' B'; 82 83 const units = short_units ? ['k', 'M', 'G', 'T', 'P', 'E', 'Z', 'Y'] : (si ? ['kB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'] : ['KiB', 'MiB', 'GiB', 'TiB', 'PiB', 'EiB', 'ZiB', 'YiB']); 84 let u = -1; 85 86 do { 87 bytes /= thresh; 88 ++u; 89 } while (Math.round(Math.abs(bytes) * (10**decimal_places)) / (10**decimal_places) >= thresh && u < units.length - 1); 90 91 return bytes.toFixed(decimal_places) + ' ' + units[u]; 92 }, 93 94 humanSecondInterval(seconds) { 95 if(isNaN(seconds)) 96 return null; 97 if(Math.abs(seconds) < 60) 98 return seconds + ' s'; 99 100 const units = [ 'm', 'h', 'd' ]; 101 const thresh = [ 60, 60, 24 ] 102 let u = -1; 103 104 do { 105 seconds /= thresh[u+1]; 106 ++u; 107 } while (Math.round(Math.abs(seconds) * 10) / 10 >= thresh[u+1] && u < units.length - 1); 108 109 return seconds.toFixed(0) + ' ' + units[u]; 110 }, 111 112 submitForm(form, detail = null) { 113 if(form instanceof Event) 114 form = form.currentTarget || form.target; 115 if(!this.isElement(form)) 116 form = document.body.querySelector(form); 117 118 form.dispatchEvent(new Event('submit', { 119 cancelable: true, 120 detail: detail, 121 })); 122 }, 123 124 cookieGet(name) { 125 var cookies = document.cookie.split(';'); 126 127 for(var i = 0; i < cookies.length; i++) { 128 var cookie = cookies[i].split('='); 129 if(name === cookie[0].trim()) return decodeURIComponent(cookie[1]); 130 } 131 132 return null; 133 }, 134 135 cookieSet(name, value, timeout = null, type = 'core') { 136 if(type === null || window.config.cookie_types.includes(type)) { 137 timeout = timeout === null ? '' : ' Max-Age=' + (timeout * 1000) + ';'; 138 document.cookie = name + '=' + encodeURIComponent(value) + ';' + timeout + ' Path=' + config.root + '; ' + (window.config.https ? 'Secure; ' : '') + 'SameSite=Lax'; 139 } else {
140 console.warn(`Attempted to set cookie '${name}' of type '${type}', but it was not allowed by the user`); 141 // TODO: Notify the user 142 } 143 }, 144 145 cookieUnset(name) { 146 if(this.cookieGet(name)) 147 this.cookieSet(name, ''); 148 }, 149 150 cookieConsent(types = []) { 151 this.cookieSet('CscAllowedCookieTypes', types.join(','), 365 * 24 * 60 * 60, null); 152 window.config.cookie_types = types; 153 }, 154 155 dispatchEvent(element, name, detail = undefined, options = {}, overrides = {}) { 156 if(Array.isArray(element)) 157 return element.map(e => this.dispatchEvent(e, name, detail, options, overrides)); 158 if(typeof element.elements === 'function') 159 return this.dispatchEvent(element.elements(), name, detail, options, { ...overrides, component: element }); 160 161 options = this.defaults(options, { bubbles: true, detail }); 162 return element.dispatchEvent(this.overrides(new CustomEvent(name, options), overrides)); 163 }, 164 165 functionOverride(_super, _override) { 166 if(typeof _super !== 'function') 167 return _super; 168 169 let tmp = { 170 [_super.name]: function() { 171 let args = Array.from(arguments); 172 args.unshift(_super); 173 return _override.apply(this, args); 174 }, 175 }; 176 177 let definedProps = Object.getOwnPropertyNames(tmp[_super.name]); 178 let props = Object.getOwnPropertyNames(_super).filter(p => !definedProps.includes(p)); 179 180 props.forEach(prop => { 181 Object.defineProperty(tmp[_super.name], prop, { 182 get: () => _super[prop], 183 set: v => _super[prop] = v, 184 }); 185 }); 186 187 return tmp[_super.name]; 188 }, 189 190 findDotKey(obj, key) { 191 return key.split('.').reduce((o, i) => o === undefined ? undefined : o[i], obj); 192 }, 193 194 delayedTimeoutMap: new Map(), 195 delayed(previous, callback, timeout = 0, ignore_previous = false) { 196 clearTimeout(previous); 197 let new_callback; 198 let new_timeout = setTimeout(() => new_callback(true), timeout); 199 200 new_callback = (last) => { 201 if(!ignore_previous && this.delayedTimeoutMap.has(previous)) 202 this.delayedTimeoutMap.get(previous)(false); 203 callback(last); 204 this.delayedTimeoutMap.delete(new_timeout); 205 }; 206 207 this.delayedTimeoutMap.set(new_timeout, new_callback) 208 return new_timeout; 209 }, 210 211 objectProxy(options, ...objects) { 212 const callOnFirst = (key = undefined, callback = () => {}) => { 213 for(obj of objects) 214 if(key === undefined || Reflect.has(obj, key)) 215 return callback(obj); 216 }; 217 218 return new Proxy(objects[0], { 219 deleteProperty: (target, prop) => options.readonlyFallbacks ? Reflect.deleteProperty(target, prop) : callOnFirst(prop, obj => Reflect.deleteProperty(obj, prop)), 220 get: (target, prop, receiver) => callOnFirst(prop, obj => Reflect.get(obj, prop, receiver)), 221 getOwnPropertyDescriptor: (target, prop) => callOnFirst(prop, obj => Reflect.getOwnPropertyDescriptor(obj, prop)), 222 has: (target, key) => callOnFirst(key, obj => true) === true, 223 ownKeys: (target) => { 224 let keys = new Set(); 225 for(obj of objects) 226 keys = new Set([...keys, ...Reflect.ownKeys(obj)]); 227 return [...keys]; 228 }, 229 set: (target, prop, value) => options.readonlyFallbacks ? Reflect.set(target, prop, value) : callOnFirst(prop, obj => Reflect.set(obj, prop, value)), 230 }); 231 }, 232 233 bindEventDebugger(...events) { 234 function debug(event) { 235 let component = event.target && event.target.$component || null; 236 let id = component && component.$attrs.id || event.target; 237 console.debug(id, event.type, event.detail); 238 }; 239 240 return events.reduce((obj, ev) => ({ ...obj, [ev]: e => debug(e) }), {}); 241 }, 242 243 reactiveStorageMap: new Map(), 244 reactiveStorage(key, initialValue = null) { 245 if(this.reactiveStorageMap.has(key)) 246 return this.reactiveStorageMap.get(key); 247 248 try { 249 if(typeof localStorage.getItem(key) === "string") 250 initialValue = JSON.parse(localStorage.getItem(key)); 251 } catch (SyntaxError) {} 252 253 const value = Vue.reactive({ 254 value: initialValue, 255 }); 256 257 let recursionBlock = false; 258 Vue.watchEffect(() => { 259 if(recursionBlock) 260 return; 261 262 recursionBlock = true; 263 if(localStorage.getItem(key) !== JSON.stringify(value.value)) 264 localStorage.setItem(key, JSON.stringify(value.value)); 265 recursionBlock = false; 266 }); 267 268 window.addEventListener('storage', event => { 269 if(recursionBlock) 270 return; 271 272 recursionBlock = true; 273 try { 274 if(event.key === key) 275 value.value = JSON.parse(localStorage.getItem(key)); 276 } catch (e) { 277 console.error(e) 278 } 279 recursionBlock = false;
280 }); 281 282 Storage.prototype.setItem = this.functionOverride(Storage.prototype.setItem, function(_super, setKey, setValue) { 283 if(recursionBlock || this !== window.localStorage) 284 return _super.call(this, setKey, setValue); 285 286 recursionBlock = true; 287 try { 288 if(setKey === key) 289 value.value = JSON.parse(setValue); 290 } catch (e) { 291 console.error(e); 292 } 293 const result = _super.call(this, setKey, setValue); 294 recursionBlock = false; 295 return result; 296 }); 297 298 this.reactiveStorageMap.set(key, value); 299 return value; 300 }, 301 302 objectToFormData(formData, data, parentKey) { 303 if(data && typeof data === 'object' && !(data instanceof Date) && !(data instanceof File)) { 304 Object.keys(data).forEach(key => { 305 buildFormData(formData, data[key], parentKey ? `${parentKey}[${key}]` : key); 306 }); 307 } else if(parentKey && parentKey.substr(-1) === ']') { 308 formData.append(parentKey, data == null ? '' : data); 309 } else { 310 formData.set(parentKey, data == null ? '' : data); 311 } 312 }, 313 314 scrollToElement(element, offset = 0, options = {}) { 315 window.scrollTo({ 316 top: element.getBoundingClientRect().top + window.pageYOffset + offset, 317 ...options, 318 }); 319 }, 320 321 debounce(root, fn, timeout = 100) { 322 if(root.cscDebounceTimer) { 323 clearTimeout(root.cscDebounceTimer); 324 root.cscDebounceTimer = null; 325 } 326 327 return root.cscDebounceTimer = setTimeout(() => { 328 root.cscDebounceTimer = null; 329 fn(); 330 }, timeout); 331 }, 332 333 youtubeLinkToId(youtube_link) { 334 const regExp = /^.*((youtu.be\/)|(v\/)|(\/u\/\w\/)|(embed\/)|(watch))(\?v=)?([^#&?]*).*/; 335 const match = youtube_link.match(regExp); 336 return match ? match[8] : null; 337 }, 338 339 cssUrl(href) { 340 return config.root + 'v' + config.version + '/css' + href; 341 }, 342 343 toggleClass(target, options = {}) { 344 options = this.defaults(options, { class: "toggle", hideRest: false }); 345 346 if(target instanceof Event) 347 target = target.currentTarget; 348 if(!this.isElement(target)) 349 target = document.querySelector(target); 350 351 if(options.hideRest && !target.classList.contains(options.class)) 352 Array.from(document.getElementsByClassName(options.class)) 353 .forEach(e => e.classList.remove(options.class)); 354 355 target.classList.toggle(options.class); 356 }, 357 358 currentGridBreakpoint() { 359 const breakpoints = ["xs", "sm", "md", "lg", "xl"]; 360 const style = getComputedStyle(document.body); 361 const width = window.innerWidth; 362 363 for(const breakpoint of breakpoints) 364 if(width < parseInt(style.getPropertyValue(`--grid-break-${breakpoint}`))) 365 return breakpoint; 366 return breakpoints[breakpoints.length - 1]; 367 }, 368};
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.