301 lines
9.7 KiB
JavaScript
301 lines
9.7 KiB
JavaScript
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();
|
|
}
|
|
};
|