1
0
mirror of https://github.com/hrfee/jfa-go.git synced 2024-06-28 20:37:46 +02:00
jfa-go/ts/modules/common.ts
Harvey Tindall 2d83718f81
activity: sort, load more, compromises for client-side search
my initial intent before starting search was for it to be server-sided,
considering this activity log could rack up 100s or 1000s of entries,
and then I forgot and did it client-sided.

this commit adds a feature to load more results when scrolled to the
bottom, and when a search returns few or no results (this is limited, so
it wont loop infinitely). Also finally got rid of the useless left
column, since my ideas didn't match my implementation.

also, sorting is only by date, can't be bothered with anything else.
2023-10-22 00:31:30 +01:00

241 lines
9.7 KiB
TypeScript

declare var window: Window;
export function toDateString(date: Date): string {
const locale = window.language || (window as any).navigator.userLanguage || window.navigator.language;
const t12 = document.getElementById("lang-12h") as HTMLInputElement;
const t24 = document.getElementById("lang-24h") as HTMLInputElement;
let args1 = {};
let args2: Intl.DateTimeFormatOptions = {
hour: "2-digit",
minute: "2-digit"
};
if (t12 && t24) {
if (t12.checked) {
args1["hour12"] = true;
args2["hour12"] = true;
} else if (t24.checked) {
args1["hour12"] = false;
args2["hour12"] = false;
}
}
return date.toLocaleDateString(locale, args1) + " " + date.toLocaleString(locale, args2);
}
export const _get = (url: string, data: Object, onreadystatechange: (req: XMLHttpRequest) => void): void => {
let req = new XMLHttpRequest();
if (window.URLBase) { url = window.URLBase + url; }
req.open("GET", url, true);
req.responseType = 'json';
req.setRequestHeader("Authorization", "Bearer " + window.token);
req.setRequestHeader('Content-Type', 'application/json; charset=UTF-8');
req.onreadystatechange = () => {
if (req.status == 0) {
window.notifications.connectionError();
return;
} else if (req.status == 401) {
window.notifications.customError("401Error", window.lang.notif("error401Unauthorized"));
}
onreadystatechange(req);
};
req.send(JSON.stringify(data));
};
export const _post = (url: string, data: Object, onreadystatechange: (req: XMLHttpRequest) => void, response?: boolean, statusHandler?: (req: XMLHttpRequest) => void): void => {
let req = new XMLHttpRequest();
req.open("POST", window.URLBase + url, true);
if (response) {
req.responseType = 'json';
}
req.setRequestHeader("Authorization", "Bearer " + window.token);
req.setRequestHeader('Content-Type', 'application/json; charset=UTF-8');
req.onreadystatechange = () => {
if (statusHandler) { statusHandler(req); }
else if (req.status == 0) {
window.notifications.connectionError();
return;
} else if (req.status == 401) {
window.notifications.customError("401Error", window.lang.notif("error401Unauthorized"));
}
onreadystatechange(req);
};
req.send(JSON.stringify(data));
};
export function _delete(url: string, data: Object, onreadystatechange: (req: XMLHttpRequest) => void): void {
let req = new XMLHttpRequest();
req.open("DELETE", window.URLBase + url, true);
req.setRequestHeader("Authorization", "Bearer " + window.token);
req.setRequestHeader('Content-Type', 'application/json; charset=UTF-8');
req.onreadystatechange = () => {
if (req.status == 0) {
window.notifications.connectionError();
return;
} else if (req.status == 401) {
window.notifications.customError("401Error", window.lang.notif("error401Unauthorized"));
}
onreadystatechange(req);
};
req.send(JSON.stringify(data));
}
export function toClipboard (str: string) {
const el = document.createElement('textarea') as HTMLTextAreaElement;
el.value = str;
el.readOnly = true;
el.style.position = "absolute";
el.style.left = "-9999px";
document.body.appendChild(el);
const selected = document.getSelection().rangeCount > 0 ? document.getSelection().getRangeAt(0) : false;
el.select();
document.execCommand("copy");
document.body.removeChild(el);
if (selected) {
document.getSelection().removeAllRanges();
document.getSelection().addRange(selected);
}
}
export class notificationBox implements NotificationBox {
private _box: HTMLDivElement;
private _errorTypes: { [type: string]: boolean } = {};
private _positiveTypes: { [type: string]: boolean } = {};
timeout: number;
constructor(box: HTMLDivElement, timeout?: number) { this._box = box; this.timeout = timeout || 5; }
private _error = (message: string): HTMLElement => {
const noti = document.createElement('aside');
noti.classList.add("aside", "~critical", "@low", "mt-2", "notification-error");
let error = "";
if (window.lang) {
error = window.lang.strings("error") + ":"
}
noti.innerHTML = `<strong>${error}</strong> ${message}`;
const closeButton = document.createElement('span') as HTMLSpanElement;
closeButton.classList.add("button", "~critical", "@low", "ml-4");
closeButton.innerHTML = `<i class="icon ri-close-line"></i>`;
closeButton.onclick = () => this._close(noti);
noti.classList.add("animate-slide-in");
noti.appendChild(closeButton);
return noti;
}
private _positive = (bold: string, message: string): HTMLElement => {
const noti = document.createElement('aside');
noti.classList.add("aside", "~positive", "@low", "mt-2", "notification-positive");
noti.innerHTML = `<strong>${bold}</strong> ${message}`;
const closeButton = document.createElement('span') as HTMLSpanElement;
closeButton.classList.add("button", "~positive", "@low", "ml-4");
closeButton.innerHTML = `<i class="icon ri-close-line"></i>`;
closeButton.onclick = () => this._close(noti);
noti.classList.add("animate-slide-in");
noti.appendChild(closeButton);
return noti;
}
private _close = (noti: HTMLElement) => {
noti.classList.remove("animate-slide-in");
noti.classList.add("animate-slide-out");
noti.addEventListener(window.animationEvent, () => {
this._box.removeChild(noti);
}, false);
}
connectionError = () => { this.customError("connectionError", window.lang.notif("errorConnection")); }
customError = (type: string, message: string) => {
this._errorTypes[type] = this._errorTypes[type] || false;
const noti = this._error(message);
noti.classList.add("error-" + type);
const previousNoti: HTMLElement | undefined = this._box.querySelector("aside.error-" + type);
if (this._errorTypes[type] && previousNoti !== undefined && previousNoti != null) {
this._box.removeChild(previousNoti);
noti.classList.add("animate-pulse");
noti.classList.remove("animate-slide-in");
}
this._box.appendChild(noti);
this._errorTypes[type] = true;
setTimeout(() => { if (this._box.contains(noti)) { this._close(noti); this._errorTypes[type] = false; } }, this.timeout*1000);
}
customPositive = (type: string, bold: string, message: string) => {
this._positiveTypes[type] = this._positiveTypes[type] || false;
const noti = this._positive(bold, message);
noti.classList.add("positive-" + type);
const previousNoti: HTMLElement | undefined = this._box.querySelector("aside.positive-" + type);
if (this._positiveTypes[type] && previousNoti !== undefined && previousNoti != null) {
this._box.removeChild(previousNoti);
noti.classList.add("animate-pulse");
noti.classList.remove("animate-slide-in");
}
this._box.appendChild(noti);
this._positiveTypes[type] = true;
setTimeout(() => { if (this._box.contains(noti)) { this._close(noti); this._positiveTypes[type] = false; } }, this.timeout*1000);
}
customSuccess = (type: string, message: string) => this.customPositive(type, window.lang.strings("success") + ":", message)
}
export const whichAnimationEvent = () => {
const el = document.createElement("fakeElement");
if (el.style["animation"] !== void 0) {
return "animationend";
}
return "webkitAnimationEnd";
}
export function toggleLoader(el: HTMLElement, small: boolean = true) {
if (el.classList.contains("loader")) {
el.classList.remove("loader");
el.classList.remove("loader-sm");
const dot = el.querySelector("span.dot");
if (dot) { dot.remove(); }
} else {
el.classList.add("loader");
if (small) { el.classList.add("loader-sm"); }
const dot = document.createElement("span") as HTMLSpanElement;
dot.classList.add("dot")
el.appendChild(dot);
}
}
export function addLoader(el: HTMLElement, small: boolean = true, relative: boolean = false) {
if (!el.classList.contains("loader")) {
el.classList.add("loader");
if (relative) el.classList.add("rel");
if (small) { el.classList.add("loader-sm"); }
const dot = document.createElement("span") as HTMLSpanElement;
dot.classList.add("dot")
el.appendChild(dot);
}
}
export function removeLoader(el: HTMLElement, small: boolean = true) {
if (el.classList.contains("loader")) {
el.classList.remove("loader");
el.classList.remove("loader-sm");
el.classList.remove("rel");
const dot = el.querySelector("span.dot");
if (dot) { dot.remove(); }
}
}
export function insertText(textarea: HTMLTextAreaElement, text: string) {
// https://kubyshkin.name/posts/insert-text-into-textarea-at-cursor-position <3
const isSuccess = document.execCommand("insertText", false, text);
// Firefox (non-standard method)
if (!isSuccess && typeof textarea.setRangeText === "function") {
const start = textarea.selectionStart;
textarea.setRangeText(text);
// update cursor to be at the end of insertion
textarea.selectionStart = textarea.selectionEnd = start + text.length;
// Notify any possible listeners of the change
const e = document.createEvent("UIEvent");
e.initEvent("input", true, false);
textarea.dispatchEvent(e);
textarea.focus();
}
}