const $i = document.getElementById.bind(document); const $c = document.getElementsByClassName.bind(document); const $q = document.querySelector.bind(document); const $qa = document.querySelectorAll.bind(document); const $t = document.createTextNode.bind(document); const $r = function(element) { if(element && element.parentNode) element.parentNode.removeChild(element); }; const $ri = function(name) { $r($i(name)); }; const $rq = function(query) { $r($q(query)); }; const $ib = function(ref, elem) { ref.parentNode.insertBefore(elem, ref); }; const $rc = function(element) { while(element.lastChild) element.removeChild(element.lastChild); }; const $e = function(info, attrs, child, created) { info = info || {}; if(typeof info === 'string') { info = {tag: info}; if(attrs) info.attrs = attrs; if(child) info.child = child; if(created) info.created = created; } const elem = document.createElement(info.tag || 'div'); if(info.attrs) { const attrs = info.attrs; for(let key in attrs) { const attr = attrs[key]; if(attr === undefined || attr === null) continue; switch(typeof attr) { case 'function': if(key.substring(0, 2) === 'on') key = key.substring(2).toLowerCase(); elem.addEventListener(key, attr); break; case 'object': if(attr instanceof Array) { if(key === 'class') key = 'classList'; const prop = elem[key]; let addFunc = null; if(prop instanceof Array) addFunc = prop.push.bind(prop); else if(prop instanceof DOMTokenList) addFunc = prop.add.bind(prop); if(addFunc !== null) { for(let j = 0; j < attr.length; ++j) addFunc(attr[j]); } else { if(key === 'classList') key = 'class'; elem.setAttribute(key, attr.toString()); } } else { for(const attrKey in attr) elem[key][attrKey] = attr[attrKey]; } break; case 'boolean': if(attr) elem.setAttribute(key, ''); break; default: if(key === 'className') key = 'class'; elem.setAttribute(key, attr.toString()); break; } } } if(info.child) { let children = info.child; if(!Array.isArray(children)) children = [children]; for(const child of children) { switch(typeof child) { case 'string': elem.appendChild($t(child)); break; case 'object': if(child instanceof Element) elem.appendChild(child); else if(child.getElement) { const childElem = child.getElement(); if(childElem instanceof Element) elem.appendChild(childElem); else elem.appendChild($e(child)); } else elem.appendChild($e(child)); break; default: elem.appendChild($t(child.toString())); break; } } } if(info.created) info.created(elem); return elem; }; const $er = (type, props, ...children) => $e({ tag: type, attrs: props, child: children }); const $ar = function(array, index) { array.splice(index, 1); }; const $ari = function(array, item) { let index; while(array.length > 0 && (index = array.indexOf(item)) >= 0) $ar(array, index); }; const $arf = function(array, predicate) { let index; while(array.length > 0 && (index = array.findIndex(predicate)) >= 0) $ar(array, index); }; const $as = function(array) { if(array.length < 2) return; for(let i = array.length - 1; i > 0; --i) { let j = Math.floor(Math.random() * (i + 1)), tmp = array[i]; array[i] = array[j]; array[j] = tmp; } }; const $x = (function() { const send = function(method, url, options, body) { if(options === undefined) options = {}; else if(typeof options !== 'object') throw 'options must be undefined or an object'; const xhr = new XMLHttpRequest; const requestHeaders = new Map; if('headers' in options && typeof options.headers === 'object') for(const name in options.headers) if(options.headers.hasOwnProperty(name)) requestHeaders.set(name.toLowerCase(), options.headers[name]); if(typeof options.download === 'function') { xhr.onloadstart = ev => options.download(ev); xhr.onprogress = ev => options.download(ev); xhr.onloadend = ev => options.download(ev); } if(typeof options.upload === 'function') { xhr.upload.onloadstart = ev => options.upload(ev); xhr.upload.onprogress = ev => options.upload(ev); xhr.upload.onloadend = ev => options.upload(ev); } if(options.authed) xhr.withCredentials = true; if(typeof options.timeout === 'number') xhr.timeout = options.timeout; if(typeof options.type === 'string') xhr.responseType = options.type; if(typeof options.abort === 'function') options.abort(() => xhr.abort()); if(typeof options.xhr === 'function') options.xhr(() => xhr); if(typeof body === 'object') { if(body instanceof URLSearchParams) { requestHeaders.set('content-type', 'application/x-www-form-urlencoded'); } else if(body instanceof FormData) { // content-type is implicitly set } else if(body instanceof Blob || body instanceof ArrayBuffer || body instanceof DataView) { if(!requestHeaders.has('content-type')) requestHeaders.set('content-type', 'application/octet-stream'); } else if(!requestHeaders.has('content-type')) { const bodyParts = []; for(const name in body) if(body.hasOwnProperty(name)) bodyParts.push(encodeURIComponent(name) + '=' + encodeURIComponent(body[name])); body = bodyParts.join('&'); requestHeaders.set('content-type', 'application/x-www-form-urlencoded'); } } return new Promise((resolve, reject) => { let responseHeaders = undefined; xhr.onload = ev => resolve({ status: xhr.status, body: () => xhr.response, text: () => xhr.responseText, headers: () => { if(responseHeaders !== undefined) return responseHeaders; responseHeaders = new Map; const raw = xhr.getAllResponseHeaders().trim().split(/[\r\n]+/); for(const name in raw) if(raw.hasOwnProperty(name)) { const parts = raw[name].split(': '); responseHeaders.set(parts.shift(), parts.join(': ')); } return responseHeaders; }, xhr: xhr, ev: ev, }); xhr.onerror = ev => reject({ xhr: xhr, ev: ev, }); xhr.open(method, url); for(const [name, value] of requestHeaders) xhr.setRequestHeader(name, value); xhr.send(body); }); }; return { send: send, get: (url, options, body) => send('GET', url, options, body), post: (url, options, body) => send('POST', url, options, body), delete: (url, options, body) => send('DELETE', url, options, body), patch: (url, options, body) => send('PATCH', url, options, body), put: (url, options, body) => send('PUT', url, options, body), }; })(); const $insertTags = function(target, tagOpen, tagClose) { tagOpen = tagOpen || ''; tagClose = tagClose || ''; if(document.selection) { target.focus(); const selected = document.selection.createRange(); selected.text = tagOpen + selected.text + tagClose; target.focus(); } else if(target.selectionStart || target.selectionStart === 0) { const startPos = target.selectionStart, endPos = target.selectionEnd, scrollTop = target.scrollTop; target.value = target.value.substring(0, startPos) + tagOpen + target.value.substring(startPos, endPos) + tagClose + target.value.substring(endPos, target.value.length); target.focus(); target.selectionStart = startPos + tagOpen.length; target.selectionEnd = endPos + tagOpen.length; target.scrollTop = scrollTop; } else { target.value += tagOpen + tagClose; target.focus(); } };