mirror of
https://github.com/hrfee/jfa-go.git
synced 2026-01-18 16:47:42 +01:00
ts: format finally
formatted with biome, a config file is provided.
This commit is contained in:
9
biome.json
Normal file
9
biome.json
Normal file
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"formatter": {
|
||||
"enabled": true,
|
||||
"indentStyle": "space",
|
||||
"indentWidth": 4,
|
||||
"formatWithErrors": false,
|
||||
"lineWidth": 120
|
||||
}
|
||||
}
|
||||
90
ts/admin.ts
90
ts/admin.ts
@@ -34,26 +34,28 @@ window.availableProfiles = window.availableProfiles || [];
|
||||
(() => {
|
||||
window.modals = {} as Modals;
|
||||
|
||||
window.modals.login = new Modal(document.getElementById('modal-login'), true);
|
||||
window.modals.login = new Modal(document.getElementById("modal-login"), true);
|
||||
|
||||
window.modals.addUser = new Modal(document.getElementById('modal-add-user'));
|
||||
window.modals.addUser = new Modal(document.getElementById("modal-add-user"));
|
||||
|
||||
window.modals.about = new Modal(document.getElementById('modal-about'));
|
||||
(document.getElementById('setting-about') as HTMLSpanElement).onclick = window.modals.about.toggle;
|
||||
window.modals.about = new Modal(document.getElementById("modal-about"));
|
||||
(document.getElementById("setting-about") as HTMLSpanElement).onclick = window.modals.about.toggle;
|
||||
|
||||
window.modals.modifyUser = new Modal(document.getElementById('modal-modify-user'));
|
||||
window.modals.modifyUser = new Modal(document.getElementById("modal-modify-user"));
|
||||
|
||||
window.modals.deleteUser = new Modal(document.getElementById('modal-delete-user'));
|
||||
window.modals.deleteUser = new Modal(document.getElementById("modal-delete-user"));
|
||||
|
||||
window.modals.settingsRestart = new Modal(document.getElementById('modal-restart'));
|
||||
window.modals.settingsRestart = new Modal(document.getElementById("modal-restart"));
|
||||
|
||||
window.modals.settingsRefresh = new Modal(document.getElementById('modal-refresh'));
|
||||
window.modals.settingsRefresh = new Modal(document.getElementById("modal-refresh"));
|
||||
|
||||
window.modals.ombiProfile = new Modal(document.getElementById('modal-ombi-profile'));
|
||||
document.getElementById('form-ombi-defaults').addEventListener('submit', window.modals.ombiProfile.close);
|
||||
window.modals.ombiProfile = new Modal(document.getElementById("modal-ombi-profile"));
|
||||
document.getElementById("form-ombi-defaults").addEventListener("submit", window.modals.ombiProfile.close);
|
||||
|
||||
window.modals.jellyseerrProfile = new Modal(document.getElementById('modal-jellyseerr-profile'));
|
||||
document.getElementById('form-jellyseerr-defaults').addEventListener('submit', window.modals.jellyseerrProfile.close);
|
||||
window.modals.jellyseerrProfile = new Modal(document.getElementById("modal-jellyseerr-profile"));
|
||||
document
|
||||
.getElementById("form-jellyseerr-defaults")
|
||||
.addEventListener("submit", window.modals.jellyseerrProfile.close);
|
||||
|
||||
window.modals.profiles = new Modal(document.getElementById("modal-user-profiles"));
|
||||
|
||||
@@ -111,7 +113,7 @@ var settings = new settingsList();
|
||||
|
||||
var profiles = new ProfileEditor();
|
||||
|
||||
window.notifications = new notificationBox(document.getElementById('notification-box') as HTMLDivElement, 5);
|
||||
window.notifications = new notificationBox(document.getElementById("notification-box") as HTMLDivElement, 5);
|
||||
|
||||
/*const modifySettingsSource = function () {
|
||||
const profile = document.getElementById('radio-use-profile') as HTMLInputElement;
|
||||
@@ -131,46 +133,47 @@ let isInviteURL = window.invites.isInviteURL();
|
||||
let isAccountURL = accounts.isAccountURL();
|
||||
|
||||
// load tabs
|
||||
const tabs: { id: string, url: string, reloader: () => void, unloader?: () => void }[] = [
|
||||
const tabs: { id: string; url: string; reloader: () => void; unloader?: () => void }[] = [
|
||||
{
|
||||
id: "invites",
|
||||
url: "",
|
||||
reloader: () => window.invites.reload(() => {
|
||||
if (isInviteURL) {
|
||||
window.invites.loadInviteURL();
|
||||
// Don't keep loading the same item on every tab refresh
|
||||
isInviteURL = false;
|
||||
}
|
||||
}),
|
||||
reloader: () =>
|
||||
window.invites.reload(() => {
|
||||
if (isInviteURL) {
|
||||
window.invites.loadInviteURL();
|
||||
// Don't keep loading the same item on every tab refresh
|
||||
isInviteURL = false;
|
||||
}
|
||||
}),
|
||||
},
|
||||
{
|
||||
id: "accounts",
|
||||
url: "accounts",
|
||||
reloader: () => accounts.reload(() => {
|
||||
if (isAccountURL) {
|
||||
accounts.loadAccountURL();
|
||||
// Don't keep loading the same item on every tab refresh
|
||||
isAccountURL = false;
|
||||
}
|
||||
accounts.bindPageEvents();
|
||||
}),
|
||||
unloader: accounts.unbindPageEvents
|
||||
|
||||
reloader: () =>
|
||||
accounts.reload(() => {
|
||||
if (isAccountURL) {
|
||||
accounts.loadAccountURL();
|
||||
// Don't keep loading the same item on every tab refresh
|
||||
isAccountURL = false;
|
||||
}
|
||||
accounts.bindPageEvents();
|
||||
}),
|
||||
unloader: accounts.unbindPageEvents,
|
||||
},
|
||||
{
|
||||
id: "activity",
|
||||
url: "activity",
|
||||
reloader: () => {
|
||||
activity.reload()
|
||||
activity.reload();
|
||||
activity.bindPageEvents();
|
||||
},
|
||||
unloader: activity.unbindPageEvents
|
||||
unloader: activity.unbindPageEvents,
|
||||
},
|
||||
{
|
||||
id: "settings",
|
||||
url: "settings",
|
||||
reloader: settings.reload
|
||||
}
|
||||
reloader: settings.reload,
|
||||
},
|
||||
];
|
||||
|
||||
const defaultTab = tabs[0];
|
||||
@@ -178,10 +181,16 @@ const defaultTab = tabs[0];
|
||||
window.tabs = new Tabs();
|
||||
|
||||
for (let tab of tabs) {
|
||||
window.tabs.addTab(tab.id, window.pages.Base + window.pages.Admin + "/" + tab.url, null, tab.reloader, tab.unloader || null);
|
||||
window.tabs.addTab(
|
||||
tab.id,
|
||||
window.pages.Base + window.pages.Admin + "/" + tab.url,
|
||||
null,
|
||||
tab.reloader,
|
||||
tab.unloader || null,
|
||||
);
|
||||
}
|
||||
|
||||
let matchedTab = false
|
||||
let matchedTab = false;
|
||||
for (const tab of tabs) {
|
||||
if (window.location.pathname.startsWith(window.pages.Base + window.pages.Current + "/" + tab.url)) {
|
||||
window.tabs.switch(tab.url, true);
|
||||
@@ -199,10 +208,13 @@ login.onLogin = () => {
|
||||
window.updater = new Updater();
|
||||
// FIXME: Decide whether to autoload activity or not
|
||||
reloadProfileNames();
|
||||
setInterval(() => { window.invites.reload(); accounts.reloadIfNotInScroll(); }, 30*1000);
|
||||
setInterval(() => {
|
||||
window.invites.reload();
|
||||
accounts.reloadIfNotInScroll();
|
||||
}, 30 * 1000);
|
||||
// Triggers pre and post funcs, even though we're already on that page
|
||||
window.tabs.switch(window.tabs.current);
|
||||
}
|
||||
};
|
||||
|
||||
bindManualDropdowns();
|
||||
|
||||
|
||||
@@ -22,7 +22,7 @@ const buttonChange = (type: string) => {
|
||||
buttonNormal.classList.add("@low");
|
||||
buttonNormal.classList.remove("@high");
|
||||
}
|
||||
}
|
||||
};
|
||||
buttonNormal.onclick = () => buttonChange("normal");
|
||||
buttonSanitized.onclick = () => buttonChange("sanitized");
|
||||
|
||||
|
||||
153
ts/form.ts
153
ts/form.ts
@@ -50,7 +50,6 @@ window.animationEvent = whichAnimationEvent();
|
||||
|
||||
window.successModal = new Modal(document.getElementById("modal-success"), true);
|
||||
|
||||
|
||||
var telegramVerified = false;
|
||||
if (window.telegramEnabled) {
|
||||
window.telegramModal = new Modal(document.getElementById("modal-telegram"), window.telegramRequired);
|
||||
@@ -74,12 +73,14 @@ if (window.telegramEnabled) {
|
||||
checkbox.parentElement.classList.remove("unfocused");
|
||||
checkbox.checked = true;
|
||||
validator.validate();
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
const telegram = new Telegram(telegramConf);
|
||||
|
||||
telegramButton.onclick = () => { telegram.onclick(); };
|
||||
telegramButton.onclick = () => {
|
||||
telegram.onclick();
|
||||
};
|
||||
}
|
||||
|
||||
var discordVerified = false;
|
||||
@@ -90,7 +91,7 @@ if (window.discordEnabled) {
|
||||
const discordConf: ServiceConfiguration = {
|
||||
modal: window.discordModal as Modal,
|
||||
pin: window.discordPIN,
|
||||
inviteURL: window.discordInviteLink ? (window.pages.Form + "/" + window.code + "/discord/invite") : "",
|
||||
inviteURL: window.discordInviteLink ? window.pages.Form + "/" + window.code + "/discord/invite" : "",
|
||||
pinURL: "",
|
||||
verifiedURL: window.pages.Form + "/" + window.code + "/discord/verified/",
|
||||
invalidCodeError: window.messages["errorInvalidPIN"],
|
||||
@@ -103,15 +104,17 @@ if (window.discordEnabled) {
|
||||
document.getElementById("contact-via").classList.remove("unfocused");
|
||||
document.getElementById("contact-via-email").parentElement.classList.remove("unfocused");
|
||||
const checkbox = document.getElementById("contact-via-discord") as HTMLInputElement;
|
||||
checkbox.parentElement.classList.remove("unfocused")
|
||||
checkbox.parentElement.classList.remove("unfocused");
|
||||
checkbox.checked = true;
|
||||
validator.validate();
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
const discord = new Discord(discordConf);
|
||||
|
||||
discordButton.onclick = () => { discord.onclick(); };
|
||||
discordButton.onclick = () => {
|
||||
discord.onclick();
|
||||
};
|
||||
}
|
||||
|
||||
var matrixVerified = false;
|
||||
@@ -138,12 +141,14 @@ if (window.matrixEnabled) {
|
||||
checkbox.parentElement.classList.remove("unfocused");
|
||||
checkbox.checked = true;
|
||||
validator.validate();
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
const matrix = new Matrix(matrixConf);
|
||||
|
||||
matrixButton.onclick = () => { matrix.show(); };
|
||||
matrixButton.onclick = () => {
|
||||
matrix.show();
|
||||
};
|
||||
}
|
||||
|
||||
if (window.confirmation) {
|
||||
@@ -154,7 +159,7 @@ declare var window: formWindow;
|
||||
if (window.userExpiryEnabled) {
|
||||
const messageEl = document.getElementById("user-expiry-message") as HTMLElement;
|
||||
const calculateTime = () => {
|
||||
let time = new Date()
|
||||
let time = new Date();
|
||||
time.setMonth(time.getMonth() + window.userExpiryMonths);
|
||||
time.setDate(time.getDate() + window.userExpiryDays);
|
||||
time.setHours(time.getHours() + window.userExpiryHours);
|
||||
@@ -162,7 +167,7 @@ if (window.userExpiryEnabled) {
|
||||
messageEl.textContent = window.userExpiryMessage.replace("{date}", toDateString(time));
|
||||
setTimeout(calculateTime, 1000);
|
||||
};
|
||||
document.addEventListener("timefmt-change", calculateTime)
|
||||
document.addEventListener("timefmt-change", calculateTime);
|
||||
calculateTime();
|
||||
}
|
||||
|
||||
@@ -174,7 +179,8 @@ let usernameField = document.getElementById("create-username") as HTMLInputEleme
|
||||
const emailField = document.getElementById("create-email") as HTMLInputElement;
|
||||
window.emailRequired &&= window.collectEmail;
|
||||
if (!window.usernameEnabled) {
|
||||
usernameField.parentElement.remove(); usernameField = emailField;
|
||||
usernameField.parentElement.remove();
|
||||
usernameField = emailField;
|
||||
} else if (!window.collectEmail) {
|
||||
emailField.parentElement.classList.add("unfocused");
|
||||
emailField.value = "";
|
||||
@@ -238,14 +244,14 @@ let validatorConf: ValidatorConf = {
|
||||
rePasswordField: rePasswordField,
|
||||
submitInput: submitInput,
|
||||
submitButton: submitSpan,
|
||||
validatorFunc: baseValidator
|
||||
validatorFunc: baseValidator,
|
||||
};
|
||||
|
||||
let validator = new Validator(validatorConf);
|
||||
var requirements = validator.requirements;
|
||||
|
||||
if (window.emailRequired) {
|
||||
emailField.addEventListener("keyup", validator.validate)
|
||||
emailField.addEventListener("keyup", validator.validate);
|
||||
}
|
||||
|
||||
interface sendDTO {
|
||||
@@ -273,7 +279,6 @@ if (window.captcha && !window.reCAPTCHA) {
|
||||
const create = (event: SubmitEvent) => {
|
||||
event.preventDefault();
|
||||
if (window.captcha && !window.reCAPTCHA && !captcha.verified) {
|
||||
|
||||
}
|
||||
addLoader(submitSpan);
|
||||
let send: sendDTO = {
|
||||
@@ -281,8 +286,8 @@ const create = (event: SubmitEvent) => {
|
||||
username: usernameField.value,
|
||||
email: emailField.value,
|
||||
email_contact: true,
|
||||
password: passwordField.value
|
||||
}
|
||||
password: passwordField.value,
|
||||
};
|
||||
if (telegramVerified) {
|
||||
send.telegram_pin = window.telegramPIN;
|
||||
const checkbox = document.getElementById("contact-via-telegram") as HTMLInputElement;
|
||||
@@ -316,62 +321,74 @@ const create = (event: SubmitEvent) => {
|
||||
send.captcha_text = captcha.input.value;
|
||||
}
|
||||
}
|
||||
_post("/user/invite", send, (req: XMLHttpRequest) => {
|
||||
if (req.readyState != 4) return;
|
||||
removeLoader(submitSpan);
|
||||
let vals = req.response as ValidatorRespDTO;
|
||||
let valid = true;
|
||||
for (let type in vals) {
|
||||
if (requirements[type]) requirements[type].valid = vals[type];
|
||||
if (!vals[type]) valid = false;
|
||||
}
|
||||
if (req.status == 200 && valid) {
|
||||
if (window.redirectToJellyfin == true) {
|
||||
const url = ((document.getElementById("modal-success") as HTMLDivElement).querySelector("a.submit") as HTMLAnchorElement).href;
|
||||
window.location.href = url;
|
||||
} else {
|
||||
if (window.customSuccessCard) {
|
||||
const content = window.successModal.asElement().querySelector(".card");
|
||||
content.innerHTML = content.innerHTML.replace(new RegExp("{username}", "g"), send.username)
|
||||
} else if (window.userPageEnabled) {
|
||||
const userPageNoticeArea = document.getElementById("modal-success-user-page-area");
|
||||
const link = `<a href="${window.userPageAddress}" target="_blank">${userPageNoticeArea.getAttribute("my-account-term")}</a>`;
|
||||
userPageNoticeArea.innerHTML = userPageNoticeArea.textContent.replace("{myAccount}", link);
|
||||
}
|
||||
window.successModal.show();
|
||||
_post(
|
||||
"/user/invite",
|
||||
send,
|
||||
(req: XMLHttpRequest) => {
|
||||
if (req.readyState != 4) return;
|
||||
removeLoader(submitSpan);
|
||||
let vals = req.response as ValidatorRespDTO;
|
||||
let valid = true;
|
||||
for (let type in vals) {
|
||||
if (requirements[type]) requirements[type].valid = vals[type];
|
||||
if (!vals[type]) valid = false;
|
||||
}
|
||||
} else if (req.status != 401 && req.status != 400){
|
||||
submitSpan.classList.add("~critical");
|
||||
submitSpan.classList.remove("~urge");
|
||||
if (req.response["error"] as string) {
|
||||
submitSpan.textContent = window.messages[req.response["error"]];
|
||||
} else {
|
||||
submitSpan.textContent = window.messages["errorPassword"];
|
||||
}
|
||||
setTimeout(() => {
|
||||
submitSpan.classList.add("~urge");
|
||||
submitSpan.classList.remove("~critical");
|
||||
submitSpan.textContent = submitText;
|
||||
}, 1000);
|
||||
}
|
||||
}, true, (req: XMLHttpRequest) => {
|
||||
if (req.readyState != 4) return;
|
||||
removeLoader(submitSpan);
|
||||
if (req.status == 401 || req.status == 400) {
|
||||
if (req.response["error"] as string) {
|
||||
if (req.response["error"] == "confirmEmail") {
|
||||
window.confirmationModal.show();
|
||||
return;
|
||||
if (req.status == 200 && valid) {
|
||||
if (window.redirectToJellyfin == true) {
|
||||
const url = (
|
||||
(document.getElementById("modal-success") as HTMLDivElement).querySelector(
|
||||
"a.submit",
|
||||
) as HTMLAnchorElement
|
||||
).href;
|
||||
window.location.href = url;
|
||||
} else {
|
||||
if (window.customSuccessCard) {
|
||||
const content = window.successModal.asElement().querySelector(".card");
|
||||
content.innerHTML = content.innerHTML.replace(new RegExp("{username}", "g"), send.username);
|
||||
} else if (window.userPageEnabled) {
|
||||
const userPageNoticeArea = document.getElementById("modal-success-user-page-area");
|
||||
const link = `<a href="${window.userPageAddress}" target="_blank">${userPageNoticeArea.getAttribute("my-account-term")}</a>`;
|
||||
userPageNoticeArea.innerHTML = userPageNoticeArea.textContent.replace("{myAccount}", link);
|
||||
}
|
||||
window.successModal.show();
|
||||
}
|
||||
if (req.response["error"] in window.messages) {
|
||||
} else if (req.status != 401 && req.status != 400) {
|
||||
submitSpan.classList.add("~critical");
|
||||
submitSpan.classList.remove("~urge");
|
||||
if (req.response["error"] as string) {
|
||||
submitSpan.textContent = window.messages[req.response["error"]];
|
||||
} else {
|
||||
submitSpan.textContent = req.response["error"];
|
||||
submitSpan.textContent = window.messages["errorPassword"];
|
||||
}
|
||||
setTimeout(() => { submitSpan.textContent = submitText; }, 1000);
|
||||
setTimeout(() => {
|
||||
submitSpan.classList.add("~urge");
|
||||
submitSpan.classList.remove("~critical");
|
||||
submitSpan.textContent = submitText;
|
||||
}, 1000);
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
true,
|
||||
(req: XMLHttpRequest) => {
|
||||
if (req.readyState != 4) return;
|
||||
removeLoader(submitSpan);
|
||||
if (req.status == 401 || req.status == 400) {
|
||||
if (req.response["error"] as string) {
|
||||
if (req.response["error"] == "confirmEmail") {
|
||||
window.confirmationModal.show();
|
||||
return;
|
||||
}
|
||||
if (req.response["error"] in window.messages) {
|
||||
submitSpan.textContent = window.messages[req.response["error"]];
|
||||
} else {
|
||||
submitSpan.textContent = req.response["error"];
|
||||
}
|
||||
setTimeout(() => {
|
||||
submitSpan.textContent = submitText;
|
||||
}, 1000);
|
||||
}
|
||||
}
|
||||
},
|
||||
);
|
||||
};
|
||||
|
||||
validator.validate();
|
||||
@@ -379,6 +396,6 @@ validator.validate();
|
||||
form.onsubmit = create;
|
||||
|
||||
const invitedByAside = document.getElementById("invite-from-user");
|
||||
if (typeof(invitedByAside) != "undefined" && invitedByAside != null) {
|
||||
if (typeof invitedByAside != "undefined" && invitedByAside != null) {
|
||||
invitedByAside.textContent = invitedByAside.textContent.replace("{user}", invitedByAside.getAttribute("data-from"));
|
||||
}
|
||||
|
||||
@@ -45,7 +45,7 @@ export interface ServiceConfiguration {
|
||||
accountLinkedError: string;
|
||||
successError: string;
|
||||
successFunc: (modalClosed: boolean) => void;
|
||||
};
|
||||
}
|
||||
|
||||
export interface DiscordInvite {
|
||||
invite: string;
|
||||
@@ -61,7 +61,9 @@ export class ServiceLinker {
|
||||
protected _name: string;
|
||||
protected _pin: string;
|
||||
|
||||
get verified(): boolean { return this._verified; }
|
||||
get verified(): boolean {
|
||||
return this._verified;
|
||||
}
|
||||
|
||||
constructor(conf: ServiceConfiguration) {
|
||||
this._conf = conf;
|
||||
@@ -100,7 +102,6 @@ export class ServiceLinker {
|
||||
this._conf.successFunc(true);
|
||||
}
|
||||
}, 2000);
|
||||
|
||||
} else if (!this._modalClosed) {
|
||||
setTimeout(this._checkVerified, 1500);
|
||||
}
|
||||
@@ -135,29 +136,29 @@ export class ServiceLinker {
|
||||
}
|
||||
|
||||
export class Discord extends ServiceLinker {
|
||||
|
||||
constructor(conf: ServiceConfiguration) {
|
||||
super(conf);
|
||||
this._name = "discord";
|
||||
this._waiting = document.getElementById("discord-waiting") as HTMLSpanElement;
|
||||
}
|
||||
|
||||
private _getInviteURL = () => _get(this._conf.inviteURL, null, (req: XMLHttpRequest) => {
|
||||
if (req.readyState != 4) return;
|
||||
const inv = req.response as DiscordInvite;
|
||||
const link = document.getElementById("discord-invite") as HTMLSpanElement;
|
||||
(link.parentElement as HTMLAnchorElement).href = inv.invite;
|
||||
(link.parentElement as HTMLAnchorElement).target = "_blank";
|
||||
let innerHTML = ``;
|
||||
if (inv.icon != "") {
|
||||
innerHTML += `<span class="img-circle lg"><img class="img-circle" src="${inv.icon}" width="64" height="64"></span>${window.discordServerName}`;
|
||||
} else {
|
||||
innerHTML += `
|
||||
private _getInviteURL = () =>
|
||||
_get(this._conf.inviteURL, null, (req: XMLHttpRequest) => {
|
||||
if (req.readyState != 4) return;
|
||||
const inv = req.response as DiscordInvite;
|
||||
const link = document.getElementById("discord-invite") as HTMLSpanElement;
|
||||
(link.parentElement as HTMLAnchorElement).href = inv.invite;
|
||||
(link.parentElement as HTMLAnchorElement).target = "_blank";
|
||||
let innerHTML = ``;
|
||||
if (inv.icon != "") {
|
||||
innerHTML += `<span class="img-circle lg"><img class="img-circle" src="${inv.icon}" width="64" height="64"></span>${window.discordServerName}`;
|
||||
} else {
|
||||
innerHTML += `
|
||||
<span class="shield bg-discord"><i class="ri-discord-fill ri-xl text-white"></i></span>${window.discordServerName}
|
||||
`;
|
||||
}
|
||||
link.innerHTML = innerHTML;
|
||||
});
|
||||
}
|
||||
link.innerHTML = innerHTML;
|
||||
});
|
||||
|
||||
onclick() {
|
||||
if (this._conf.inviteURL != "") {
|
||||
@@ -176,7 +177,7 @@ export class Telegram extends ServiceLinker {
|
||||
this._name = "telegram";
|
||||
this._waiting = document.getElementById("telegram-waiting") as HTMLSpanElement;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export interface MatrixConfiguration {
|
||||
modal: Modal;
|
||||
@@ -198,14 +199,20 @@ export class Matrix {
|
||||
private _input: HTMLInputElement;
|
||||
private _submit: HTMLSpanElement;
|
||||
|
||||
get verified(): boolean { return this._verified; }
|
||||
get pin(): string { return this._pin; }
|
||||
get verified(): boolean {
|
||||
return this._verified;
|
||||
}
|
||||
get pin(): string {
|
||||
return this._pin;
|
||||
}
|
||||
|
||||
constructor(conf: MatrixConfiguration) {
|
||||
this._conf = conf;
|
||||
this._input = document.getElementById("matrix-userid") as HTMLInputElement;
|
||||
this._submit = document.getElementById("matrix-send") as HTMLSpanElement;
|
||||
this._submit.onclick = () => { this._onclick(); };
|
||||
this._submit.onclick = () => {
|
||||
this._onclick();
|
||||
};
|
||||
}
|
||||
|
||||
private _onclick = () => {
|
||||
@@ -220,52 +227,53 @@ export class Matrix {
|
||||
show = () => {
|
||||
this._input.value = "";
|
||||
this._conf.modal.show();
|
||||
}
|
||||
};
|
||||
|
||||
private _sendMessage = () => _post(this._conf.sendMessageURL, { "user_id": this._input.value }, (req: XMLHttpRequest) => {
|
||||
if (req.readyState != 4) return;
|
||||
removeLoader(this._submit);
|
||||
if (req.status == 400 && req.response["error"] == "errorAccountLinked") {
|
||||
this._conf.modal.close();
|
||||
window.notifications.customError("accountLinkedError", this._conf.accountLinkedError);
|
||||
return;
|
||||
} else if (req.status != 200) {
|
||||
this._conf.modal.close();
|
||||
window.notifications.customError("unknownError", this._conf.unknownError);
|
||||
return;
|
||||
}
|
||||
this._userID = this._input.value;
|
||||
this._submit.classList.add("~positive");
|
||||
this._submit.classList.remove("~info");
|
||||
setTimeout(() => {
|
||||
this._submit.classList.add("~info");
|
||||
this._submit.classList.remove("~positive");
|
||||
}, 2000);
|
||||
this._input.placeholder = "PIN";
|
||||
this._input.value = "";
|
||||
});
|
||||
|
||||
private _verifyCode = () => _get(this._conf.verifiedURL + this._userID + "/" + this._input.value, null, (req: XMLHttpRequest) => {
|
||||
if (req.readyState != 4) return;
|
||||
removeLoader(this._submit);
|
||||
const valid = req.response["success"] as boolean;
|
||||
if (valid) {
|
||||
this._conf.modal.close();
|
||||
window.notifications.customPositive(this._name + "Verified", "", this._conf.successError);
|
||||
this._verified = true;
|
||||
this._pin = this._input.value;
|
||||
if (this._conf.successFunc) {
|
||||
this._conf.successFunc();
|
||||
private _sendMessage = () =>
|
||||
_post(this._conf.sendMessageURL, { user_id: this._input.value }, (req: XMLHttpRequest) => {
|
||||
if (req.readyState != 4) return;
|
||||
removeLoader(this._submit);
|
||||
if (req.status == 400 && req.response["error"] == "errorAccountLinked") {
|
||||
this._conf.modal.close();
|
||||
window.notifications.customError("accountLinkedError", this._conf.accountLinkedError);
|
||||
return;
|
||||
} else if (req.status != 200) {
|
||||
this._conf.modal.close();
|
||||
window.notifications.customError("unknownError", this._conf.unknownError);
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
window.notifications.customError("invalidCodeError", this._conf.invalidCodeError);
|
||||
this._submit.classList.add("~critical");
|
||||
this._userID = this._input.value;
|
||||
this._submit.classList.add("~positive");
|
||||
this._submit.classList.remove("~info");
|
||||
setTimeout(() => {
|
||||
this._submit.classList.add("~info");
|
||||
this._submit.classList.remove("~critical");
|
||||
}, 800);
|
||||
}
|
||||
});
|
||||
}
|
||||
this._submit.classList.remove("~positive");
|
||||
}, 2000);
|
||||
this._input.placeholder = "PIN";
|
||||
this._input.value = "";
|
||||
});
|
||||
|
||||
private _verifyCode = () =>
|
||||
_get(this._conf.verifiedURL + this._userID + "/" + this._input.value, null, (req: XMLHttpRequest) => {
|
||||
if (req.readyState != 4) return;
|
||||
removeLoader(this._submit);
|
||||
const valid = req.response["success"] as boolean;
|
||||
if (valid) {
|
||||
this._conf.modal.close();
|
||||
window.notifications.customPositive(this._name + "Verified", "", this._conf.successError);
|
||||
this._verified = true;
|
||||
this._pin = this._input.value;
|
||||
if (this._conf.successFunc) {
|
||||
this._conf.successFunc();
|
||||
}
|
||||
} else {
|
||||
window.notifications.customError("invalidCodeError", this._conf.invalidCodeError);
|
||||
this._submit.classList.add("~critical");
|
||||
this._submit.classList.remove("~info");
|
||||
setTimeout(() => {
|
||||
this._submit.classList.add("~info");
|
||||
this._submit.classList.remove("~critical");
|
||||
}, 800);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,5 +1,11 @@
|
||||
import { _get, _post, _delete, toDateString } from "../modules/common.js";
|
||||
import { SearchConfiguration, QueryType, SearchableItem, SearchableItems, SearchableItemDataAttribute } from "../modules/search.js";
|
||||
import {
|
||||
SearchConfiguration,
|
||||
QueryType,
|
||||
SearchableItem,
|
||||
SearchableItems,
|
||||
SearchableItemDataAttribute,
|
||||
} from "../modules/search.js";
|
||||
import { accountURLEvent } from "../modules/accounts.js";
|
||||
import { inviteURLEvent } from "../modules/invites.js";
|
||||
import { PaginatedList } from "./list.js";
|
||||
@@ -24,135 +30,137 @@ export interface activity {
|
||||
}
|
||||
|
||||
var activityTypeMoods = {
|
||||
"creation": 1,
|
||||
"deletion": -1,
|
||||
"disabled": -1,
|
||||
"enabled": 1,
|
||||
"contactLinked": 1,
|
||||
"contactUnlinked": -1,
|
||||
"changePassword": 0,
|
||||
"resetPassword": 0,
|
||||
"createInvite": 1,
|
||||
"deleteInvite": -1
|
||||
creation: 1,
|
||||
deletion: -1,
|
||||
disabled: -1,
|
||||
enabled: 1,
|
||||
contactLinked: 1,
|
||||
contactUnlinked: -1,
|
||||
changePassword: 0,
|
||||
resetPassword: 0,
|
||||
createInvite: 1,
|
||||
deleteInvite: -1,
|
||||
};
|
||||
|
||||
// window.lang doesn't exist at page load, so I made this a function that's invoked by activityList.
|
||||
const queries = (): { [field: string]: QueryType } => { return {
|
||||
"id": {
|
||||
name: window.lang.strings("activityID"),
|
||||
getter: "id",
|
||||
bool: false,
|
||||
string: true,
|
||||
date: false
|
||||
},
|
||||
"title": {
|
||||
name: window.lang.strings("title"),
|
||||
getter: "title",
|
||||
bool: false,
|
||||
string: true,
|
||||
date: false,
|
||||
localOnly: true
|
||||
},
|
||||
"user": {
|
||||
name: window.lang.strings("usersMentioned"),
|
||||
getter: "mentionedUsers",
|
||||
bool: false,
|
||||
string: true,
|
||||
date: false
|
||||
},
|
||||
"actor": {
|
||||
name: window.lang.strings("actor"),
|
||||
description: window.lang.strings("actorDescription"),
|
||||
getter: "actor",
|
||||
bool: false,
|
||||
string: true,
|
||||
date: false
|
||||
},
|
||||
"referrer": {
|
||||
name: window.lang.strings("referrer"),
|
||||
getter: "referrer",
|
||||
bool: true,
|
||||
string: true,
|
||||
date: false
|
||||
},
|
||||
"time": {
|
||||
name: window.lang.strings("date"),
|
||||
getter: "time",
|
||||
bool: false,
|
||||
string: false,
|
||||
date: true
|
||||
},
|
||||
"account-creation": {
|
||||
name: window.lang.strings("accountCreationFilter"),
|
||||
getter: "accountCreation",
|
||||
bool: true,
|
||||
string: false,
|
||||
date: false
|
||||
},
|
||||
"account-deletion": {
|
||||
name: window.lang.strings("accountDeletionFilter"),
|
||||
getter: "accountDeletion",
|
||||
bool: true,
|
||||
string: false,
|
||||
date: false
|
||||
},
|
||||
"account-disabled": {
|
||||
name: window.lang.strings("accountDisabledFilter"),
|
||||
getter: "accountDisabled",
|
||||
bool: true,
|
||||
string: false,
|
||||
date: false
|
||||
},
|
||||
"account-enabled": {
|
||||
name: window.lang.strings("accountEnabledFilter"),
|
||||
getter: "accountEnabled",
|
||||
bool: true,
|
||||
string: false,
|
||||
date: false
|
||||
},
|
||||
"contact-linked": {
|
||||
name: window.lang.strings("contactLinkedFilter"),
|
||||
getter: "contactLinked",
|
||||
bool: true,
|
||||
string: false,
|
||||
date: false
|
||||
},
|
||||
"contact-unlinked": {
|
||||
name: window.lang.strings("contactUnlinkedFilter"),
|
||||
getter: "contactUnlinked",
|
||||
bool: true,
|
||||
string: false,
|
||||
date: false
|
||||
},
|
||||
"password-change": {
|
||||
name: window.lang.strings("passwordChangeFilter"),
|
||||
getter: "passwordChange",
|
||||
bool: true,
|
||||
string: false,
|
||||
date: false
|
||||
},
|
||||
"password-reset": {
|
||||
name: window.lang.strings("passwordResetFilter"),
|
||||
getter: "passwordReset",
|
||||
bool: true,
|
||||
string: false,
|
||||
date: false
|
||||
},
|
||||
"invite-created": {
|
||||
name: window.lang.strings("inviteCreatedFilter"),
|
||||
getter: "inviteCreated",
|
||||
bool: true,
|
||||
string: false,
|
||||
date: false
|
||||
},
|
||||
"invite-deleted": {
|
||||
name: window.lang.strings("inviteDeletedFilter"),
|
||||
getter: "inviteDeleted",
|
||||
bool: true,
|
||||
string: false,
|
||||
date: false
|
||||
}
|
||||
}};
|
||||
const queries = (): { [field: string]: QueryType } => {
|
||||
return {
|
||||
id: {
|
||||
name: window.lang.strings("activityID"),
|
||||
getter: "id",
|
||||
bool: false,
|
||||
string: true,
|
||||
date: false,
|
||||
},
|
||||
title: {
|
||||
name: window.lang.strings("title"),
|
||||
getter: "title",
|
||||
bool: false,
|
||||
string: true,
|
||||
date: false,
|
||||
localOnly: true,
|
||||
},
|
||||
user: {
|
||||
name: window.lang.strings("usersMentioned"),
|
||||
getter: "mentionedUsers",
|
||||
bool: false,
|
||||
string: true,
|
||||
date: false,
|
||||
},
|
||||
actor: {
|
||||
name: window.lang.strings("actor"),
|
||||
description: window.lang.strings("actorDescription"),
|
||||
getter: "actor",
|
||||
bool: false,
|
||||
string: true,
|
||||
date: false,
|
||||
},
|
||||
referrer: {
|
||||
name: window.lang.strings("referrer"),
|
||||
getter: "referrer",
|
||||
bool: true,
|
||||
string: true,
|
||||
date: false,
|
||||
},
|
||||
time: {
|
||||
name: window.lang.strings("date"),
|
||||
getter: "time",
|
||||
bool: false,
|
||||
string: false,
|
||||
date: true,
|
||||
},
|
||||
"account-creation": {
|
||||
name: window.lang.strings("accountCreationFilter"),
|
||||
getter: "accountCreation",
|
||||
bool: true,
|
||||
string: false,
|
||||
date: false,
|
||||
},
|
||||
"account-deletion": {
|
||||
name: window.lang.strings("accountDeletionFilter"),
|
||||
getter: "accountDeletion",
|
||||
bool: true,
|
||||
string: false,
|
||||
date: false,
|
||||
},
|
||||
"account-disabled": {
|
||||
name: window.lang.strings("accountDisabledFilter"),
|
||||
getter: "accountDisabled",
|
||||
bool: true,
|
||||
string: false,
|
||||
date: false,
|
||||
},
|
||||
"account-enabled": {
|
||||
name: window.lang.strings("accountEnabledFilter"),
|
||||
getter: "accountEnabled",
|
||||
bool: true,
|
||||
string: false,
|
||||
date: false,
|
||||
},
|
||||
"contact-linked": {
|
||||
name: window.lang.strings("contactLinkedFilter"),
|
||||
getter: "contactLinked",
|
||||
bool: true,
|
||||
string: false,
|
||||
date: false,
|
||||
},
|
||||
"contact-unlinked": {
|
||||
name: window.lang.strings("contactUnlinkedFilter"),
|
||||
getter: "contactUnlinked",
|
||||
bool: true,
|
||||
string: false,
|
||||
date: false,
|
||||
},
|
||||
"password-change": {
|
||||
name: window.lang.strings("passwordChangeFilter"),
|
||||
getter: "passwordChange",
|
||||
bool: true,
|
||||
string: false,
|
||||
date: false,
|
||||
},
|
||||
"password-reset": {
|
||||
name: window.lang.strings("passwordResetFilter"),
|
||||
getter: "passwordReset",
|
||||
bool: true,
|
||||
string: false,
|
||||
date: false,
|
||||
},
|
||||
"invite-created": {
|
||||
name: window.lang.strings("inviteCreatedFilter"),
|
||||
getter: "inviteCreated",
|
||||
bool: true,
|
||||
string: false,
|
||||
date: false,
|
||||
},
|
||||
"invite-deleted": {
|
||||
name: window.lang.strings("inviteDeletedFilter"),
|
||||
getter: "inviteDeleted",
|
||||
bool: true,
|
||||
string: false,
|
||||
date: false,
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
// var moodColours = ["~warning", "~neutral", "~urge"];
|
||||
|
||||
@@ -173,37 +181,58 @@ export class Activity implements activity, SearchableItem {
|
||||
|
||||
_genUserText = (): string => {
|
||||
return `<span class="font-medium">${this._act.username || this._act.user_id.substring(0, 5)}</span>`;
|
||||
}
|
||||
};
|
||||
|
||||
_genSrcUserText = (): string => {
|
||||
return `<span class="font-medium">${this._act.source_username || this._act.source.substring(0, 5)}</span>`;
|
||||
}
|
||||
};
|
||||
|
||||
_genUserLink = (): string => {
|
||||
return `<a role="link" tabindex="0" class="hover:underline cursor-pointer activity-pseudo-link-user" data-id="${this._act.user_id}" href="${window.pages.Base}${window.pages.Admin}/accounts?user=${this._act.user_id}">${this._genUserText()}</a>`;
|
||||
}
|
||||
};
|
||||
|
||||
_genSrcUserLink = (): string => {
|
||||
return `<a role="link" tabindex="0" class="hover:underline cursor-pointer activity-pseudo-link-user" data-id="${this._act.user_id}" href="${window.pages.Base}${window.pages.Admin}/accounts?user=${this._act.source}">${this._genSrcUserText()}</a>`;
|
||||
}
|
||||
};
|
||||
|
||||
private _renderInvText = (): string => { return `<span class="font-medium font-mono">${this.value || this.invite_code || "???"}</span>`; }
|
||||
private _renderInvText = (): string => {
|
||||
return `<span class="font-medium font-mono">${this.value || this.invite_code || "???"}</span>`;
|
||||
};
|
||||
|
||||
private _genInvLink = (): string => {
|
||||
return `<a role="link" tabindex="0" class="hover:underline cursor-pointer activity-pseudo-link-invite" data-id="${this.invite_code}" href="${window.pages.Base}${window.pages.Admin}/?invite=${this.invite_code}">${this._renderInvText()}</a>`;
|
||||
};
|
||||
|
||||
get accountCreation(): boolean {
|
||||
return this.type == "creation";
|
||||
}
|
||||
get accountDeletion(): boolean {
|
||||
return this.type == "deletion";
|
||||
}
|
||||
get accountDisabled(): boolean {
|
||||
return this.type == "disabled";
|
||||
}
|
||||
get accountEnabled(): boolean {
|
||||
return this.type == "enabled";
|
||||
}
|
||||
get contactLinked(): boolean {
|
||||
return this.type == "contactLinked";
|
||||
}
|
||||
get contactUnlinked(): boolean {
|
||||
return this.type == "contactUnlinked";
|
||||
}
|
||||
get passwordChange(): boolean {
|
||||
return this.type == "changePassword";
|
||||
}
|
||||
get passwordReset(): boolean {
|
||||
return this.type == "resetPassword";
|
||||
}
|
||||
get inviteCreated(): boolean {
|
||||
return this.type == "createInvite";
|
||||
}
|
||||
get inviteDeleted(): boolean {
|
||||
return this.type == "deleteInvite";
|
||||
}
|
||||
|
||||
|
||||
get accountCreation(): boolean { return this.type == "creation"; }
|
||||
get accountDeletion(): boolean { return this.type == "deletion"; }
|
||||
get accountDisabled(): boolean { return this.type == "disabled"; }
|
||||
get accountEnabled(): boolean { return this.type == "enabled"; }
|
||||
get contactLinked(): boolean { return this.type == "contactLinked"; }
|
||||
get contactUnlinked(): boolean { return this.type == "contactUnlinked"; }
|
||||
get passwordChange(): boolean { return this.type == "changePassword"; }
|
||||
get passwordReset(): boolean { return this.type == "resetPassword"; }
|
||||
get inviteCreated(): boolean { return this.type == "createInvite"; }
|
||||
get inviteDeleted(): boolean { return this.type == "deleteInvite"; }
|
||||
|
||||
get mentionedUsers(): string {
|
||||
return (this.username + " " + this.source_username).toLowerCase();
|
||||
@@ -220,7 +249,9 @@ export class Activity implements activity, SearchableItem {
|
||||
return this.source_username.toLowerCase();
|
||||
}
|
||||
|
||||
get type(): string { return this._act.type; }
|
||||
get type(): string {
|
||||
return this._act.type;
|
||||
}
|
||||
set type(v: string) {
|
||||
this._act.type = v;
|
||||
|
||||
@@ -309,13 +340,17 @@ export class Activity implements activity, SearchableItem {
|
||||
}
|
||||
}
|
||||
|
||||
get time(): number { return this._timeUnix; }
|
||||
get time(): number {
|
||||
return this._timeUnix;
|
||||
}
|
||||
set time(v: number) {
|
||||
this._timeUnix = v;
|
||||
this._time.textContent = toDateString(new Date(v*1000));
|
||||
this._time.textContent = toDateString(new Date(v * 1000));
|
||||
}
|
||||
|
||||
get source_type(): string { return this._act.source_type; }
|
||||
get source_type(): string {
|
||||
return this._act.source_type;
|
||||
}
|
||||
set source_type(v: string) {
|
||||
this._act.source_type = v;
|
||||
if ((this.source_type == "anon" || this.source_type == "user") && this.type == "creation") {
|
||||
@@ -329,7 +364,9 @@ export class Activity implements activity, SearchableItem {
|
||||
}
|
||||
}
|
||||
|
||||
get ip(): string { return this._act.ip; }
|
||||
get ip(): string {
|
||||
return this._act.ip;
|
||||
}
|
||||
set ip(v: string) {
|
||||
this._act.ip = v;
|
||||
if (v) {
|
||||
@@ -341,42 +378,68 @@ export class Activity implements activity, SearchableItem {
|
||||
}
|
||||
}
|
||||
|
||||
get invite_code(): string { return this._act.invite_code; }
|
||||
get invite_code(): string {
|
||||
return this._act.invite_code;
|
||||
}
|
||||
set invite_code(v: string) {
|
||||
this._act.invite_code = v;
|
||||
}
|
||||
|
||||
get value(): string { return this._act.value; }
|
||||
get value(): string {
|
||||
return this._act.value;
|
||||
}
|
||||
set value(v: string) {
|
||||
this._act.value = v;
|
||||
}
|
||||
|
||||
get source(): string { return this._act.source; }
|
||||
get source(): string {
|
||||
return this._act.source;
|
||||
}
|
||||
set source(v: string) {
|
||||
this._act.source = v;
|
||||
if ((this.source_type == "anon" || this.source_type == "user") && this.type == "creation") {
|
||||
this._source.innerHTML = this._genInvLink();
|
||||
} else if ((this.source_type == "admin" || this.source_type == "user") && this._act.source != "" && this._act.source_username != "") {
|
||||
} else if (
|
||||
(this.source_type == "admin" || this.source_type == "user") &&
|
||||
this._act.source != "" &&
|
||||
this._act.source_username != ""
|
||||
) {
|
||||
this._source.innerHTML = this._genSrcUserLink();
|
||||
}
|
||||
}
|
||||
|
||||
get id(): string { return this._act.id; }
|
||||
get id(): string {
|
||||
return this._act.id;
|
||||
}
|
||||
set id(v: string) {
|
||||
this._act.id = v;
|
||||
this._card.setAttribute(SearchableItemDataAttribute, v);
|
||||
}
|
||||
|
||||
get user_id(): string { return this._act.user_id; }
|
||||
set user_id(v: string) { this._act.user_id = v; }
|
||||
get user_id(): string {
|
||||
return this._act.user_id;
|
||||
}
|
||||
set user_id(v: string) {
|
||||
this._act.user_id = v;
|
||||
}
|
||||
|
||||
get username(): string { return this._act.username; }
|
||||
set username(v: string) { this._act.username = v; }
|
||||
get username(): string {
|
||||
return this._act.username;
|
||||
}
|
||||
set username(v: string) {
|
||||
this._act.username = v;
|
||||
}
|
||||
|
||||
get source_username(): string { return this._act.source_username; }
|
||||
set source_username(v: string) { this._act.source_username = v; }
|
||||
get source_username(): string {
|
||||
return this._act.source_username;
|
||||
}
|
||||
set source_username(v: string) {
|
||||
this._act.source_username = v;
|
||||
}
|
||||
|
||||
get title(): string { return this._title.textContent; }
|
||||
get title(): string {
|
||||
return this._title.textContent;
|
||||
}
|
||||
|
||||
matchesSearch = (query: string): boolean => {
|
||||
// console.log(this.title, "matches", query, ":", this.title.includes(query));
|
||||
@@ -385,7 +448,7 @@ export class Activity implements activity, SearchableItem {
|
||||
this.username.toLowerCase().includes(query) ||
|
||||
this.source_username.toLowerCase().includes(query)
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
constructor(act: activity) {
|
||||
this._card = document.createElement("div");
|
||||
@@ -431,8 +494,12 @@ export class Activity implements activity, SearchableItem {
|
||||
|
||||
this.update(act);
|
||||
|
||||
const pseudoUsers = this._card.getElementsByClassName("activity-pseudo-link-user") as HTMLCollectionOf<HTMLAnchorElement>;
|
||||
const pseudoInvites = this._card.getElementsByClassName("activity-pseudo-link-invite") as HTMLCollectionOf<HTMLAnchorElement>;
|
||||
const pseudoUsers = this._card.getElementsByClassName(
|
||||
"activity-pseudo-link-user",
|
||||
) as HTMLCollectionOf<HTMLAnchorElement>;
|
||||
const pseudoInvites = this._card.getElementsByClassName(
|
||||
"activity-pseudo-link-invite",
|
||||
) as HTMLCollectionOf<HTMLAnchorElement>;
|
||||
|
||||
for (let i = 0; i < pseudoUsers.length; i++) {
|
||||
/*const navigate = (event: Event) => {
|
||||
@@ -465,24 +532,27 @@ export class Activity implements activity, SearchableItem {
|
||||
this.time = act.time;
|
||||
this.source = act.source;
|
||||
this.value = act.value;
|
||||
this.type = act.type;
|
||||
this.type = act.type;
|
||||
this.ip = act.ip;
|
||||
}
|
||||
};
|
||||
|
||||
delete = () => _delete("/activity/" + this._act.id, null, (req: XMLHttpRequest) => {
|
||||
if (req.readyState != 4) return;
|
||||
if (req.status == 200) {
|
||||
window.notifications.customSuccess("activityDeleted", window.lang.notif("activityDeleted"));
|
||||
}
|
||||
document.dispatchEvent(activityReload);
|
||||
});
|
||||
delete = () =>
|
||||
_delete("/activity/" + this._act.id, null, (req: XMLHttpRequest) => {
|
||||
if (req.readyState != 4) return;
|
||||
if (req.status == 200) {
|
||||
window.notifications.customSuccess("activityDeleted", window.lang.notif("activityDeleted"));
|
||||
}
|
||||
document.dispatchEvent(activityReload);
|
||||
});
|
||||
|
||||
asElement = () => { return this._card; };
|
||||
asElement = () => {
|
||||
return this._card;
|
||||
};
|
||||
}
|
||||
|
||||
interface ActivitiesReqDTO extends PaginatedReqDTO {
|
||||
type: string[];
|
||||
};
|
||||
}
|
||||
|
||||
interface ActivitiesDTO extends paginatedDTO {
|
||||
activities: activity[];
|
||||
@@ -494,14 +564,20 @@ export class activityList extends PaginatedList {
|
||||
|
||||
protected _ascending: boolean;
|
||||
|
||||
get activities(): { [id: string]: Activity } { return this._search.items as { [id: string]: Activity }; }
|
||||
get activities(): { [id: string]: Activity } {
|
||||
return this._search.items as { [id: string]: Activity };
|
||||
}
|
||||
// set activities(v: { [id: string]: Activity }) { this._search.items = v as SearchableItems; }
|
||||
|
||||
constructor() {
|
||||
super({
|
||||
loader: document.getElementById("activity-loader"),
|
||||
loadMoreButtons: Array.from([document.getElementById("activity-load-more") as HTMLButtonElement]) as Array<HTMLButtonElement>,
|
||||
loadAllButtons: Array.from(document.getElementsByClassName("activity-load-all")) as Array<HTMLButtonElement>,
|
||||
loadMoreButtons: Array.from([
|
||||
document.getElementById("activity-load-more") as HTMLButtonElement,
|
||||
]) as Array<HTMLButtonElement>,
|
||||
loadAllButtons: Array.from(
|
||||
document.getElementsByClassName("activity-load-all"),
|
||||
) as Array<HTMLButtonElement>,
|
||||
refreshButton: document.getElementById("activity-refresh") as HTMLButtonElement,
|
||||
filterArea: document.getElementById("activity-filter-area"),
|
||||
searchOptionsHeader: document.getElementById("activity-search-options-header"),
|
||||
@@ -513,7 +589,7 @@ export class activityList extends PaginatedList {
|
||||
maxItemsLoadedForSearch: 200,
|
||||
appendNewItems: (resp: paginatedDTO) => {
|
||||
let ordering: string[] = this._search.ordering;
|
||||
for (let act of ((resp as ActivitiesDTO).activities || [])) {
|
||||
for (let act of (resp as ActivitiesDTO).activities || []) {
|
||||
this.activities[act.id] = new Activity(act);
|
||||
ordering.push(act.id);
|
||||
}
|
||||
@@ -538,10 +614,10 @@ export class activityList extends PaginatedList {
|
||||
window.notifications.customError("loadActivitiesError", window.lang.notif("errorLoadActivities"));
|
||||
return;
|
||||
}
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
this._container = document.getElementById("activity-card-list")
|
||||
this._container = document.getElementById("activity-card-list");
|
||||
document.addEventListener("activity-reload", () => this.reload());
|
||||
|
||||
let searchConfig: SearchConfiguration = {
|
||||
@@ -561,23 +637,20 @@ export class activityList extends PaginatedList {
|
||||
onSearchCallback: null,
|
||||
searchServer: null,
|
||||
clearServerSearch: null,
|
||||
}
|
||||
};
|
||||
|
||||
this.initSearch(searchConfig);
|
||||
|
||||
this.ascending = this._c.defaultSortAscending;
|
||||
this._sortDirection.addEventListener("click", () => this.ascending = !this.ascending);
|
||||
this._sortDirection.addEventListener("click", () => (this.ascending = !this.ascending));
|
||||
}
|
||||
|
||||
reload = (callback?: (resp: paginatedDTO) => void) => {
|
||||
this._reload(callback);
|
||||
}
|
||||
};
|
||||
|
||||
loadMore = (loadAll: boolean = false, callback?: () => void) => {
|
||||
this._loadMore(
|
||||
loadAll,
|
||||
callback
|
||||
);
|
||||
this._loadMore(loadAll, callback);
|
||||
};
|
||||
|
||||
loadAll = (callback?: (resp?: paginatedDTO) => void) => {
|
||||
@@ -616,5 +689,4 @@ export class activityList extends PaginatedList {
|
||||
this._keepSearchingDescription.classList.add("unfocused");
|
||||
}
|
||||
};*/
|
||||
|
||||
}
|
||||
|
||||
@@ -13,9 +13,13 @@ export class Captcha {
|
||||
reCAPTCHA = false;
|
||||
code = "";
|
||||
|
||||
get value(): string { return this.input.value; }
|
||||
get value(): string {
|
||||
return this.input.value;
|
||||
}
|
||||
|
||||
hasChanged = (): boolean => { return this.value != this.previous; }
|
||||
hasChanged = (): boolean => {
|
||||
return this.value != this.previous;
|
||||
};
|
||||
|
||||
baseValidatorWrapper = (_baseValidator: (oncomplete: (valid: boolean) => void, captchaValid: boolean) => void) => {
|
||||
return (oncomplete: (valid: boolean) => void): void => {
|
||||
@@ -30,35 +34,47 @@ export class Captcha {
|
||||
};
|
||||
};
|
||||
|
||||
verify = (callback: () => void) => _post("/captcha/verify/" + this.code + "/" + this.captchaID + "/" + this.input.value + (this.isPWR ? "?pwr=true" : ""), null, (req: XMLHttpRequest) => {
|
||||
if (req.readyState == 4) {
|
||||
if (req.status == 204) {
|
||||
this.checkbox.innerHTML = `<i class="ri-check-line"></i>`;
|
||||
this.checkbox.classList.add("~positive");
|
||||
this.checkbox.classList.remove("~critical");
|
||||
this.verified = true;
|
||||
} else {
|
||||
this.checkbox.innerHTML = `<i class="ri-close-line"></i>`;
|
||||
this.checkbox.classList.add("~critical");
|
||||
this.checkbox.classList.remove("~positive");
|
||||
this.verified = false;
|
||||
}
|
||||
callback();
|
||||
}
|
||||
});
|
||||
verify = (callback: () => void) =>
|
||||
_post(
|
||||
"/captcha/verify/" +
|
||||
this.code +
|
||||
"/" +
|
||||
this.captchaID +
|
||||
"/" +
|
||||
this.input.value +
|
||||
(this.isPWR ? "?pwr=true" : ""),
|
||||
null,
|
||||
(req: XMLHttpRequest) => {
|
||||
if (req.readyState == 4) {
|
||||
if (req.status == 204) {
|
||||
this.checkbox.innerHTML = `<i class="ri-check-line"></i>`;
|
||||
this.checkbox.classList.add("~positive");
|
||||
this.checkbox.classList.remove("~critical");
|
||||
this.verified = true;
|
||||
} else {
|
||||
this.checkbox.innerHTML = `<i class="ri-close-line"></i>`;
|
||||
this.checkbox.classList.add("~critical");
|
||||
this.checkbox.classList.remove("~positive");
|
||||
this.verified = false;
|
||||
}
|
||||
callback();
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
generate = () => _get("/captcha/gen/"+this.code+(this.isPWR ? "?pwr=true" : ""), null, (req: XMLHttpRequest) => {
|
||||
if (req.readyState == 4) {
|
||||
if (req.status == 200) {
|
||||
this.captchaID = this.isPWR ? this.code : req.response["id"];
|
||||
// the Math.random() appearance below is used for PWRs, since they don't have a unique captchaID. The parameter is ignored by the server, but tells the browser to reload the image.
|
||||
document.getElementById("captcha-img").innerHTML = `
|
||||
generate = () =>
|
||||
_get("/captcha/gen/" + this.code + (this.isPWR ? "?pwr=true" : ""), null, (req: XMLHttpRequest) => {
|
||||
if (req.readyState == 4) {
|
||||
if (req.status == 200) {
|
||||
this.captchaID = this.isPWR ? this.code : req.response["id"];
|
||||
// the Math.random() appearance below is used for PWRs, since they don't have a unique captchaID. The parameter is ignored by the server, but tells the browser to reload the image.
|
||||
document.getElementById("captcha-img").innerHTML = `
|
||||
<img class="w-full" src="${window.location.toString().substring(0, window.location.toString().lastIndexOf(window.pages.Form))}/captcha/img/${this.code}/${this.isPWR ? Math.random() : this.captchaID}${this.isPWR ? "?pwr=true" : ""}"></img>
|
||||
`;
|
||||
this.input.value = "";
|
||||
this.input.value = "";
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
constructor(code: string, enabled: boolean, reCAPTCHA: boolean, isPWR: boolean) {
|
||||
this.code = code;
|
||||
@@ -69,15 +85,17 @@ export class Captcha {
|
||||
}
|
||||
|
||||
export interface GreCAPTCHA {
|
||||
render: (container: HTMLDivElement, parameters: {
|
||||
sitekey?: string,
|
||||
theme?: string,
|
||||
size?: string,
|
||||
tabindex?: number,
|
||||
"callback"?: () => void,
|
||||
"expired-callback"?: () => void,
|
||||
"error-callback"?: () => void
|
||||
}) => void;
|
||||
render: (
|
||||
container: HTMLDivElement,
|
||||
parameters: {
|
||||
sitekey?: string;
|
||||
theme?: string;
|
||||
size?: string;
|
||||
tabindex?: number;
|
||||
callback?: () => void;
|
||||
"expired-callback"?: () => void;
|
||||
"error-callback"?: () => void;
|
||||
},
|
||||
) => void;
|
||||
getResponse: (opt_widget_id?: HTMLDivElement) => string;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
declare var window: GlobalWindow;
|
||||
import dateParser from "any-date-parser";
|
||||
import { Temporal } from 'temporal-polyfill';
|
||||
import { Temporal } from "temporal-polyfill";
|
||||
|
||||
export function toDateString(date: Date): string {
|
||||
const locale = window.language || (window as any).navigator.userLanguage || window.navigator.language;
|
||||
@@ -9,7 +9,7 @@ export function toDateString(date: Date): string {
|
||||
let args1 = {};
|
||||
let args2: Intl.DateTimeFormatOptions = {
|
||||
hour: "2-digit",
|
||||
minute: "2-digit"
|
||||
minute: "2-digit",
|
||||
};
|
||||
if (t12 && t24) {
|
||||
if (t12.checked) {
|
||||
@@ -29,9 +29,9 @@ export const parseDateString = (value: string): ParsedDate => {
|
||||
// Used just to tell use what fields the user passed.
|
||||
attempt: dateParser.attempt(value),
|
||||
// note Date.fromString is also provided by dateParser.
|
||||
date: (Date as any).fromString(value) as Date
|
||||
date: (Date as any).fromString(value) as Date,
|
||||
};
|
||||
if (("invalid" in (out.date as any))) {
|
||||
if ("invalid" in (out.date as any)) {
|
||||
out.invalid = true;
|
||||
} else {
|
||||
// getTimezoneOffset returns UTC - Timezone, so invert it to get distance from UTC -to- timezone.
|
||||
@@ -40,7 +40,7 @@ export const parseDateString = (value: string): ParsedDate => {
|
||||
// Month in Date objects is 0-based, so make our parsed date that way too
|
||||
if ("month" in out.attempt) out.attempt.month -= 1;
|
||||
return out;
|
||||
}
|
||||
};
|
||||
|
||||
// DateCountdown sets the given el's textContent to the time till the given date (unixSeconds), updating
|
||||
// every minute. It returns the timeout, so it can be later removed with clearTimeout if desired.
|
||||
@@ -53,14 +53,14 @@ export function DateCountdown(el: HTMLElement, unixSeconds: number): ReturnType<
|
||||
let diff = now.until(then).round({
|
||||
largestUnit: "years",
|
||||
smallestUnit: "minutes",
|
||||
relativeTo: nowPlain
|
||||
relativeTo: nowPlain,
|
||||
});
|
||||
// FIXME: I'd really like this to be localized, but don't know of any nice solutions.
|
||||
const fields = [diff.years, diff.months, diff.days, diff.hours, diff.minutes];
|
||||
const abbrevs = ["y", "mo", "d", "h", "m"];
|
||||
for (let i = 0; i < fields.length; i++) {
|
||||
if (fields[i]) {
|
||||
out += ""+fields[i] + abbrevs[i] + " ";
|
||||
out += "" + fields[i] + abbrevs[i] + " ";
|
||||
}
|
||||
}
|
||||
return out.slice(0, -1);
|
||||
@@ -72,13 +72,20 @@ export function DateCountdown(el: HTMLElement, unixSeconds: number): ReturnType<
|
||||
return setTimeout(update, 60000);
|
||||
}
|
||||
|
||||
export const _get = (url: string, data: Object, onreadystatechange: (req: XMLHttpRequest) => void, noConnectionError: boolean = false): void => {
|
||||
export const _get = (
|
||||
url: string,
|
||||
data: Object,
|
||||
onreadystatechange: (req: XMLHttpRequest) => void,
|
||||
noConnectionError: boolean = false,
|
||||
): void => {
|
||||
let req = new XMLHttpRequest();
|
||||
if (window.pages) { url = window.pages.Base + url; }
|
||||
if (window.pages) {
|
||||
url = window.pages.Base + url;
|
||||
}
|
||||
req.open("GET", url, true);
|
||||
req.responseType = 'json';
|
||||
req.responseType = "json";
|
||||
req.setRequestHeader("Authorization", "Bearer " + window.token);
|
||||
req.setRequestHeader('Content-Type', 'application/json; charset=UTF-8');
|
||||
req.setRequestHeader("Content-Type", "application/json; charset=UTF-8");
|
||||
req.onreadystatechange = () => {
|
||||
if (req.status == 0) {
|
||||
if (!noConnectionError) window.notifications.connectionError();
|
||||
@@ -93,11 +100,13 @@ export const _get = (url: string, data: Object, onreadystatechange: (req: XMLHtt
|
||||
|
||||
export const _download = (url: string, fname: string): void => {
|
||||
let req = new XMLHttpRequest();
|
||||
if (window.pages) { url = window.pages.Base + url; }
|
||||
if (window.pages) {
|
||||
url = window.pages.Base + url;
|
||||
}
|
||||
req.open("GET", url, true);
|
||||
req.responseType = 'blob';
|
||||
req.responseType = "blob";
|
||||
req.setRequestHeader("Authorization", "Bearer " + window.token);
|
||||
req.setRequestHeader('Content-Type', 'application/json; charset=UTF-8');
|
||||
req.setRequestHeader("Content-Type", "application/json; charset=UTF-8");
|
||||
req.onload = (e: Event) => {
|
||||
let link = document.createElement("a") as HTMLAnchorElement;
|
||||
link.href = URL.createObjectURL(req.response);
|
||||
@@ -109,25 +118,38 @@ export const _download = (url: string, fname: string): void => {
|
||||
|
||||
export const _upload = (url: string, formData: FormData): void => {
|
||||
let req = new XMLHttpRequest();
|
||||
if (window.pages) { url = window.pages.Base + url; }
|
||||
if (window.pages) {
|
||||
url = window.pages.Base + url;
|
||||
}
|
||||
req.open("POST", url, true);
|
||||
req.setRequestHeader("Authorization", "Bearer " + window.token);
|
||||
// req.setRequestHeader('Content-Type', 'multipart/form-data');
|
||||
req.send(formData);
|
||||
};
|
||||
|
||||
export const _req = (method: string, url: string, data: Object, onreadystatechange: (req: XMLHttpRequest) => void, response?: boolean, statusHandler?: (req: XMLHttpRequest) => void, noConnectionError: boolean = false): void => {
|
||||
export const _req = (
|
||||
method: string,
|
||||
url: string,
|
||||
data: Object,
|
||||
onreadystatechange: (req: XMLHttpRequest) => void,
|
||||
response?: boolean,
|
||||
statusHandler?: (req: XMLHttpRequest) => void,
|
||||
noConnectionError: boolean = false,
|
||||
): void => {
|
||||
let req = new XMLHttpRequest();
|
||||
if (window.pages) { url = window.pages.Base + url; }
|
||||
if (window.pages) {
|
||||
url = window.pages.Base + url;
|
||||
}
|
||||
req.open(method, url, true);
|
||||
if (response) {
|
||||
req.responseType = 'json';
|
||||
req.responseType = "json";
|
||||
}
|
||||
req.setRequestHeader("Authorization", "Bearer " + window.token);
|
||||
req.setRequestHeader('Content-Type', 'application/json; charset=UTF-8');
|
||||
req.setRequestHeader("Content-Type", "application/json; charset=UTF-8");
|
||||
req.onreadystatechange = () => {
|
||||
if (statusHandler) { statusHandler(req); }
|
||||
else if (req.status == 0) {
|
||||
if (statusHandler) {
|
||||
statusHandler(req);
|
||||
} else if (req.status == 0) {
|
||||
if (!noConnectionError) window.notifications.connectionError();
|
||||
return;
|
||||
} else if (req.status == 401) {
|
||||
@@ -138,18 +160,46 @@ export const _req = (method: string, url: string, data: Object, onreadystatechan
|
||||
req.send(JSON.stringify(data));
|
||||
};
|
||||
|
||||
export const _post = (url: string, data: Object, onreadystatechange: (req: XMLHttpRequest) => void, response?: boolean, statusHandler?: (req: XMLHttpRequest) => void, noConnectionError: boolean = false): void => _req("POST", url, data, onreadystatechange, response, statusHandler, noConnectionError);
|
||||
export const _post = (
|
||||
url: string,
|
||||
data: Object,
|
||||
onreadystatechange: (req: XMLHttpRequest) => void,
|
||||
response?: boolean,
|
||||
statusHandler?: (req: XMLHttpRequest) => void,
|
||||
noConnectionError: boolean = false,
|
||||
): void => _req("POST", url, data, onreadystatechange, response, statusHandler, noConnectionError);
|
||||
|
||||
export const _put = (url: string, data: Object, onreadystatechange: (req: XMLHttpRequest) => void, response?: boolean, statusHandler?: (req: XMLHttpRequest) => void, noConnectionError: boolean = false): void => _req("PUT", url, data, onreadystatechange, response, statusHandler, noConnectionError);
|
||||
export const _put = (
|
||||
url: string,
|
||||
data: Object,
|
||||
onreadystatechange: (req: XMLHttpRequest) => void,
|
||||
response?: boolean,
|
||||
statusHandler?: (req: XMLHttpRequest) => void,
|
||||
noConnectionError: boolean = false,
|
||||
): void => _req("PUT", url, data, onreadystatechange, response, statusHandler, noConnectionError);
|
||||
|
||||
export const _patch = (url: string, data: Object, onreadystatechange: (req: XMLHttpRequest) => void, response?: boolean, statusHandler?: (req: XMLHttpRequest) => void, noConnectionError: boolean = false): void => _req("PATCH", url, data, onreadystatechange, response, statusHandler, noConnectionError);
|
||||
export const _patch = (
|
||||
url: string,
|
||||
data: Object,
|
||||
onreadystatechange: (req: XMLHttpRequest) => void,
|
||||
response?: boolean,
|
||||
statusHandler?: (req: XMLHttpRequest) => void,
|
||||
noConnectionError: boolean = false,
|
||||
): void => _req("PATCH", url, data, onreadystatechange, response, statusHandler, noConnectionError);
|
||||
|
||||
export function _delete(url: string, data: Object, onreadystatechange: (req: XMLHttpRequest) => void, noConnectionError: boolean = false): void {
|
||||
export function _delete(
|
||||
url: string,
|
||||
data: Object,
|
||||
onreadystatechange: (req: XMLHttpRequest) => void,
|
||||
noConnectionError: boolean = false,
|
||||
): void {
|
||||
let req = new XMLHttpRequest();
|
||||
if (window.pages) { url = window.pages.Base + url; }
|
||||
if (window.pages) {
|
||||
url = window.pages.Base + url;
|
||||
}
|
||||
req.open("DELETE", url, true);
|
||||
req.setRequestHeader("Authorization", "Bearer " + window.token);
|
||||
req.setRequestHeader('Content-Type', 'application/json; charset=UTF-8');
|
||||
req.setRequestHeader("Content-Type", "application/json; charset=UTF-8");
|
||||
req.onreadystatechange = () => {
|
||||
if (req.status == 0) {
|
||||
if (!noConnectionError) window.notifications.connectionError();
|
||||
@@ -162,8 +212,8 @@ export function _delete(url: string, data: Object, onreadystatechange: (req: XML
|
||||
req.send(JSON.stringify(data));
|
||||
}
|
||||
|
||||
export function toClipboard (str: string) {
|
||||
const el = document.createElement('textarea') as HTMLTextAreaElement;
|
||||
export function toClipboard(str: string) {
|
||||
const el = document.createElement("textarea") as HTMLTextAreaElement;
|
||||
el.value = str;
|
||||
el.readOnly = true;
|
||||
el.style.position = "absolute";
|
||||
@@ -193,45 +243,50 @@ export class notificationBox implements NotificationBox {
|
||||
static baseClasses = ["aside", "flex", "flex-row", "justify-between", "gap-4"];
|
||||
|
||||
private _error = (message: string): HTMLElement => {
|
||||
const noti = document.createElement('aside');
|
||||
const noti = document.createElement("aside");
|
||||
noti.classList.add(...notificationBox.baseClasses, "~critical", "@low", "notification-error");
|
||||
let error = "";
|
||||
if (window.lang) {
|
||||
error = window.lang.strings("error") + ":"
|
||||
error = window.lang.strings("error") + ":";
|
||||
}
|
||||
noti.innerHTML = `<div><strong>${error}</strong> ${message}</div>`;
|
||||
const closeButton = document.createElement('span') as HTMLSpanElement;
|
||||
const closeButton = document.createElement("span") as HTMLSpanElement;
|
||||
closeButton.classList.add("button", "~critical", "@low");
|
||||
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');
|
||||
const noti = document.createElement("aside");
|
||||
noti.classList.add(...notificationBox.baseClasses, "~positive", "@low", "notification-positive");
|
||||
noti.innerHTML = `<div><strong>${bold}</strong> ${message}</div>`;
|
||||
const closeButton = document.createElement('span') as HTMLSpanElement;
|
||||
const closeButton = document.createElement("span") as HTMLSpanElement;
|
||||
closeButton.classList.add("button", "~positive", "@low");
|
||||
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);
|
||||
}
|
||||
noti.addEventListener(
|
||||
window.animationEvent,
|
||||
() => {
|
||||
this._box.removeChild(noti);
|
||||
},
|
||||
false,
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
connectionError = () => { this.customError("connectionError", window.lang.notif("errorConnection")); }
|
||||
connectionError = () => {
|
||||
this.customError("connectionError", window.lang.notif("errorConnection"));
|
||||
};
|
||||
|
||||
customError = (type: string, message: string) => {
|
||||
this._errorTypes[type] = this._errorTypes[type] || false;
|
||||
@@ -245,8 +300,13 @@ export class notificationBox implements NotificationBox {
|
||||
}
|
||||
this._box.appendChild(noti);
|
||||
this._errorTypes[type] = true;
|
||||
setTimeout(() => { if (this._box.contains(noti)) { this._close(noti); this._errorTypes[type] = false; } }, this.timeout*1000);
|
||||
}
|
||||
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;
|
||||
@@ -260,10 +320,16 @@ export class notificationBox implements NotificationBox {
|
||||
}
|
||||
this._box.appendChild(noti);
|
||||
this._positiveTypes[type] = true;
|
||||
setTimeout(() => { if (this._box.contains(noti)) { this._close(noti); this._positiveTypes[type] = false; } }, this.timeout*1000);
|
||||
}
|
||||
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)
|
||||
customSuccess = (type: string, message: string) =>
|
||||
this.customPositive(type, window.lang.strings("success") + ":", message);
|
||||
}
|
||||
|
||||
export const whichAnimationEvent = () => {
|
||||
@@ -272,19 +338,23 @@ export const whichAnimationEvent = () => {
|
||||
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(); }
|
||||
if (dot) {
|
||||
dot.remove();
|
||||
}
|
||||
} else {
|
||||
el.classList.add("loader");
|
||||
if (small) { el.classList.add("loader-sm"); }
|
||||
if (small) {
|
||||
el.classList.add("loader-sm");
|
||||
}
|
||||
const dot = document.createElement("span") as HTMLSpanElement;
|
||||
dot.classList.add("dot")
|
||||
dot.classList.add("dot");
|
||||
el.appendChild(dot);
|
||||
}
|
||||
}
|
||||
@@ -293,9 +363,11 @@ export function addLoader(el: HTMLElement, small: boolean = true, relative: bool
|
||||
if (el.classList.contains("loader")) return;
|
||||
el.classList.add("loader");
|
||||
if (relative) el.classList.add("rel");
|
||||
if (small) { el.classList.add("loader-sm"); }
|
||||
if (small) {
|
||||
el.classList.add("loader-sm");
|
||||
}
|
||||
const dot = document.createElement("span") as HTMLSpanElement;
|
||||
dot.classList.add("dot")
|
||||
dot.classList.add("dot");
|
||||
el.appendChild(dot);
|
||||
}
|
||||
|
||||
@@ -305,7 +377,9 @@ export function removeLoader(el: HTMLElement, small: boolean = true) {
|
||||
el.classList.remove("loader-sm");
|
||||
el.classList.remove("rel");
|
||||
const dot = el.querySelector("span.dot");
|
||||
if (dot) { dot.remove(); }
|
||||
if (dot) {
|
||||
dot.remove();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -329,7 +403,9 @@ export function insertText(textarea: HTMLTextAreaElement, text: string) {
|
||||
}
|
||||
|
||||
export function bindManualDropdowns() {
|
||||
const buttons = Array.from(document.getElementsByClassName("dropdown-manual-toggle") as HTMLCollectionOf<HTMLSpanElement>);
|
||||
const buttons = Array.from(
|
||||
document.getElementsByClassName("dropdown-manual-toggle") as HTMLCollectionOf<HTMLSpanElement>,
|
||||
);
|
||||
for (let button of buttons) {
|
||||
const parent = button.closest(".dropdown.manual");
|
||||
const display = parent.querySelector(".dropdown-display");
|
||||
@@ -337,7 +413,7 @@ export function bindManualDropdowns() {
|
||||
const mouseout = () => parent.classList.remove("selected");
|
||||
button.addEventListener("mouseover", mousein);
|
||||
button.addEventListener("mouseout", mouseout);
|
||||
display.addEventListener("mouseover", mousein);
|
||||
display.addEventListener("mouseover", mousein);
|
||||
display.addEventListener("mouseout", mouseout);
|
||||
button.onclick = () => {
|
||||
parent.classList.add("selected");
|
||||
@@ -346,7 +422,12 @@ export function bindManualDropdowns() {
|
||||
display.removeEventListener("mouseout", mouseout);
|
||||
};
|
||||
const outerClickListener = (event: Event) => {
|
||||
if (!(event.target instanceof HTMLElement && (display.contains(event.target) || button.contains(event.target)))) {
|
||||
if (
|
||||
!(
|
||||
event.target instanceof HTMLElement &&
|
||||
(display.contains(event.target) || button.contains(event.target))
|
||||
)
|
||||
) {
|
||||
parent.classList.remove("selected");
|
||||
document.removeEventListener("click", outerClickListener);
|
||||
button.addEventListener("mouseout", mouseout);
|
||||
@@ -372,20 +453,28 @@ export function unicodeB64Encode(s: string): string {
|
||||
// Only allow running a function every n milliseconds.
|
||||
// Source: Clément Prévost at https://stackoverflow.com/questions/27078285/simple-throttle-in-javascript
|
||||
// function foo<T>(bar: T): T {
|
||||
export function throttle (callback: () => void, limitMilliseconds: number): () => void {
|
||||
var waiting = false; // Initially, we're not waiting
|
||||
return function () { // We return a throttled function
|
||||
if (!waiting) { // If we're not waiting
|
||||
callback.apply(this, arguments); // Execute users function
|
||||
waiting = true; // Prevent future invocations
|
||||
setTimeout(function () { // After a period of time
|
||||
waiting = false; // And allow future invocations
|
||||
export function throttle(callback: () => void, limitMilliseconds: number): () => void {
|
||||
var waiting = false; // Initially, we're not waiting
|
||||
return function () {
|
||||
// We return a throttled function
|
||||
if (!waiting) {
|
||||
// If we're not waiting
|
||||
callback.apply(this, arguments); // Execute users function
|
||||
waiting = true; // Prevent future invocations
|
||||
setTimeout(function () {
|
||||
// After a period of time
|
||||
waiting = false; // And allow future invocations
|
||||
}, limitMilliseconds);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export function SetupCopyButton(button: HTMLButtonElement, text: string | (() => string), baseClass?: string, notif?: string) {
|
||||
export function SetupCopyButton(
|
||||
button: HTMLButtonElement,
|
||||
text: string | (() => string),
|
||||
baseClass?: string,
|
||||
notif?: string,
|
||||
) {
|
||||
if (!notif) notif = window.lang.strings("copied");
|
||||
if (!baseClass) baseClass = "~info";
|
||||
// script will probably turn this into multiple
|
||||
@@ -395,7 +484,7 @@ export function SetupCopyButton(button: HTMLButtonElement, text: string | (() =>
|
||||
button.title = window.lang.strings("copy");
|
||||
const icon = document.createElement("i");
|
||||
icon.classList.add("icon", "ri-file-copy-line");
|
||||
button.appendChild(icon)
|
||||
button.appendChild(icon);
|
||||
button.onclick = () => {
|
||||
if (typeof text === "string") {
|
||||
toClipboard(text);
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import {addLoader, removeLoader, _get} from "../modules/common.js";
|
||||
import { addLoader, removeLoader, _get } from "../modules/common.js";
|
||||
|
||||
declare var window: GlobalWindow;
|
||||
|
||||
@@ -12,7 +12,12 @@ var listeners: { [buttonText: string]: (event: CustomEvent) => void } = {};
|
||||
|
||||
export type DiscordSearch = (passData: string) => void;
|
||||
|
||||
export function newDiscordSearch(title: string, description: string, buttonText: string, buttonFunction: (user: DiscordUser, passData: string) => void): DiscordSearch {
|
||||
export function newDiscordSearch(
|
||||
title: string,
|
||||
description: string,
|
||||
buttonText: string,
|
||||
buttonFunction: (user: DiscordUser, passData: string) => void,
|
||||
): DiscordSearch {
|
||||
if (!window.discordEnabled) {
|
||||
return () => {};
|
||||
}
|
||||
@@ -62,7 +67,7 @@ export function newDiscordSearch(title: string, description: string, buttonText:
|
||||
}
|
||||
});
|
||||
}, 750);
|
||||
}
|
||||
};
|
||||
|
||||
return (passData: string) => {
|
||||
const input = document.getElementById("discord-search") as HTMLInputElement;
|
||||
@@ -79,5 +84,5 @@ export function newDiscordSearch(title: string, description: string, buttonText:
|
||||
input.addEventListener("keyup", listeners[buttonText].bind(null, { detail: passData }));
|
||||
|
||||
window.modals.discord.show();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -24,37 +24,45 @@ export class lang implements Lang {
|
||||
}
|
||||
|
||||
get = (sect: string, key: string): string => {
|
||||
if (sect == "quantityStrings" || sect == "meta") { return ""; }
|
||||
if (sect == "quantityStrings" || sect == "meta") {
|
||||
return "";
|
||||
}
|
||||
return this._lang[sect][key];
|
||||
}
|
||||
};
|
||||
|
||||
strings = (key: string): string => this.get("strings", key)
|
||||
notif = (key: string): string => this.get("notifications", key)
|
||||
strings = (key: string): string => this.get("strings", key);
|
||||
notif = (key: string): string => this.get("notifications", key);
|
||||
|
||||
var = (sect: string, key: string, ...subs: string[]): string => {
|
||||
if (sect == "quantityStrings" || sect == "meta") { return ""; }
|
||||
if (sect == "quantityStrings" || sect == "meta") {
|
||||
return "";
|
||||
}
|
||||
let str = this._lang[sect][key];
|
||||
for (let sub of subs) {
|
||||
str = str.replace("{n}", sub);
|
||||
}
|
||||
return str;
|
||||
}
|
||||
};
|
||||
|
||||
template = (sect: string, key: string, subs: { [key: string]: any }): string => {
|
||||
if (sect == "quantityStrings" || sect == "meta") { return ""; }
|
||||
if (sect == "quantityStrings" || sect == "meta") {
|
||||
return "";
|
||||
}
|
||||
const map = new Map<string, any>();
|
||||
for (let key of Object.keys(subs)) { map.set(key, subs[key]); }
|
||||
for (let key of Object.keys(subs)) {
|
||||
map.set(key, subs[key]);
|
||||
}
|
||||
const [out, err] = Template(this._lang[sect][key], map);
|
||||
if (err != null) throw err;
|
||||
return out;
|
||||
}
|
||||
};
|
||||
|
||||
quantity = (key: string, number: number): string => {
|
||||
if (number == 1) {
|
||||
return this._lang.quantityStrings[key].singular.replace("{n}", ""+number)
|
||||
return this._lang.quantityStrings[key].singular.replace("{n}", "" + number);
|
||||
}
|
||||
return this._lang.quantityStrings[key].plural.replace("{n}", ""+number);
|
||||
}
|
||||
return this._lang.quantityStrings[key].plural.replace("{n}", "" + number);
|
||||
};
|
||||
}
|
||||
|
||||
export var TimeFmtChange = new CustomEvent("timefmt-change");
|
||||
@@ -66,7 +74,7 @@ export const loadLangSelector = (page: string) => {
|
||||
localStorage.setItem("timefmt", fmt);
|
||||
};
|
||||
const t12 = document.getElementById("lang-12h") as HTMLInputElement;
|
||||
if (typeof(t12) !== "undefined" && t12 != null) {
|
||||
if (typeof t12 !== "undefined" && t12 != null) {
|
||||
t12.onchange = () => setTimefmt("12h");
|
||||
const t24 = document.getElementById("lang-24h") as HTMLInputElement;
|
||||
t24.onchange = () => setTimefmt("24h");
|
||||
@@ -83,21 +91,26 @@ export const loadLangSelector = (page: string) => {
|
||||
}
|
||||
let queryString = new URLSearchParams(window.location.search);
|
||||
if (queryString.has("lang")) queryString.delete("lang");
|
||||
_get("/lang/" + page, null, (req: XMLHttpRequest) => {
|
||||
if (req.readyState == 4) {
|
||||
if (req.status != 200) {
|
||||
document.getElementById("lang-dropdown").remove();
|
||||
return;
|
||||
_get(
|
||||
"/lang/" + page,
|
||||
null,
|
||||
(req: XMLHttpRequest) => {
|
||||
if (req.readyState == 4) {
|
||||
if (req.status != 200) {
|
||||
document.getElementById("lang-dropdown").remove();
|
||||
return;
|
||||
}
|
||||
const list = document.getElementById("lang-list") as HTMLDivElement;
|
||||
let innerHTML = "";
|
||||
for (let code in req.response) {
|
||||
if (!code || !req.response[code]) continue;
|
||||
queryString.set("lang", code);
|
||||
innerHTML += `<a href="?${queryString.toString()}" class="button w-full text-left justify-start ~neutral lang-link">${req.response[code]}</a>`;
|
||||
queryString.delete("lang");
|
||||
}
|
||||
list.innerHTML = innerHTML;
|
||||
}
|
||||
const list = document.getElementById("lang-list") as HTMLDivElement;
|
||||
let innerHTML = '';
|
||||
for (let code in req.response) {
|
||||
if (!code || !(req.response[code])) continue;
|
||||
queryString.set("lang", code);
|
||||
innerHTML += `<a href="?${queryString.toString()}" class="button w-full text-left justify-start ~neutral lang-link">${req.response[code]}</a>`;
|
||||
queryString.delete("lang");
|
||||
}
|
||||
list.innerHTML = innerHTML;
|
||||
}
|
||||
}, true);
|
||||
},
|
||||
true,
|
||||
);
|
||||
};
|
||||
|
||||
@@ -6,7 +6,7 @@ declare var window: GlobalWindow;
|
||||
|
||||
export interface ListItem {
|
||||
asElement: () => HTMLElement;
|
||||
};
|
||||
}
|
||||
|
||||
export class RecordCounter {
|
||||
private _container: HTMLElement;
|
||||
@@ -50,25 +50,33 @@ export class RecordCounter {
|
||||
});
|
||||
}
|
||||
|
||||
get total(): number { return this._total; }
|
||||
get total(): number {
|
||||
return this._total;
|
||||
}
|
||||
set total(v: number) {
|
||||
this._total = v;
|
||||
this._totalRecords.textContent = window.lang.var("strings", "totalRecords", `${v}`);
|
||||
}
|
||||
|
||||
get loaded(): number { return this._loaded; }
|
||||
get loaded(): number {
|
||||
return this._loaded;
|
||||
}
|
||||
set loaded(v: number) {
|
||||
this._loaded = v;
|
||||
this._loadedRecords.textContent = window.lang.var("strings", "loadedRecords", `${v}`);
|
||||
}
|
||||
|
||||
get shown(): number { return this._shown; }
|
||||
get shown(): number {
|
||||
return this._shown;
|
||||
}
|
||||
set shown(v: number) {
|
||||
this._shown = v;
|
||||
this._shownRecords.textContent = window.lang.var("strings", "shownRecords", `${v}`);
|
||||
}
|
||||
|
||||
get selected(): number { return this._selected; }
|
||||
get selected(): number {
|
||||
return this._selected;
|
||||
}
|
||||
set selected(v: number) {
|
||||
this._selected = v;
|
||||
if (v == 0) this._selectedRecords.textContent = ``;
|
||||
@@ -126,7 +134,9 @@ export abstract class PaginatedList {
|
||||
protected _lastLoad: number;
|
||||
protected _page: number = 0;
|
||||
protected _lastPage: boolean;
|
||||
get lastPage(): boolean { return this._lastPage };
|
||||
get lastPage(): boolean {
|
||||
return this._lastPage;
|
||||
}
|
||||
set lastPage(v: boolean) {
|
||||
this._lastPage = v;
|
||||
if (v) {
|
||||
@@ -156,9 +166,9 @@ export abstract class PaginatedList {
|
||||
limit: 0,
|
||||
page: 0,
|
||||
sortByField: "",
|
||||
ascending: false
|
||||
ascending: false,
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
constructor(c: PaginatedListConfig) {
|
||||
this._c = c;
|
||||
@@ -180,7 +190,10 @@ export abstract class PaginatedList {
|
||||
}
|
||||
|
||||
autoSetServerSearchButtonsDisabled = () => {
|
||||
const serverSearchSortChanged = this._search.inServerSearch && (this._searchParams.sortByField != this._search.sortField || this._searchParams.ascending != this._search.ascending);
|
||||
const serverSearchSortChanged =
|
||||
this._search.inServerSearch &&
|
||||
(this._searchParams.sortByField != this._search.sortField ||
|
||||
this._searchParams.ascending != this._search.ascending);
|
||||
if (this._search.inServerSearch) {
|
||||
if (serverSearchSortChanged) {
|
||||
this._search.setServerSearchButtonsDisabled(false);
|
||||
@@ -189,28 +202,42 @@ export abstract class PaginatedList {
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (!this._search.inSearch && this._search.sortField == this._c.defaultSortField && this._search.ascending == this._c.defaultSortAscending) {
|
||||
if (
|
||||
!this._search.inSearch &&
|
||||
this._search.sortField == this._c.defaultSortField &&
|
||||
this._search.ascending == this._c.defaultSortAscending
|
||||
) {
|
||||
this._search.setServerSearchButtonsDisabled(true);
|
||||
return;
|
||||
}
|
||||
this._search.setServerSearchButtonsDisabled(false);
|
||||
}
|
||||
};
|
||||
|
||||
initSearch = (searchConfig: SearchConfiguration) => {
|
||||
const previousCallback = searchConfig.onSearchCallback;
|
||||
searchConfig.onSearchCallback = (newItems: boolean, loadAll: boolean, callback?: (resp: paginatedDTO) => void) => {
|
||||
searchConfig.onSearchCallback = (
|
||||
newItems: boolean,
|
||||
loadAll: boolean,
|
||||
callback?: (resp: paginatedDTO) => void,
|
||||
) => {
|
||||
// if (this._search.inSearch && !this.lastPage) this._c.loadAllButton.classList.remove("unfocused");
|
||||
// else this._c.loadAllButton.classList.add("unfocused");
|
||||
|
||||
this.autoSetServerSearchButtonsDisabled();
|
||||
|
||||
// FIXME: Figure out why this makes sense and make it clearer.
|
||||
if ((this._visible.length < this._c.itemsPerPage && this._counter.loaded < this._c.maxItemsLoadedForSearch && !this.lastPage) || loadAll) {
|
||||
if (!newItems ||
|
||||
if (
|
||||
(this._visible.length < this._c.itemsPerPage &&
|
||||
this._counter.loaded < this._c.maxItemsLoadedForSearch &&
|
||||
!this.lastPage) ||
|
||||
loadAll
|
||||
) {
|
||||
if (
|
||||
!newItems ||
|
||||
this._previousVisibleItemCount != this._visible.length ||
|
||||
(this._visible.length == 0 && !this.lastPage) ||
|
||||
loadAll
|
||||
) {
|
||||
) {
|
||||
this.loadMore(loadAll, callback);
|
||||
}
|
||||
}
|
||||
@@ -229,7 +256,7 @@ export abstract class PaginatedList {
|
||||
console.trace("Clearing server search");
|
||||
this._page = 0;
|
||||
this.reload();
|
||||
}
|
||||
};
|
||||
searchConfig.setVisibility = this.setVisibility;
|
||||
this._search = new Search(searchConfig);
|
||||
this._search.generateFilterList();
|
||||
@@ -258,7 +285,7 @@ export abstract class PaginatedList {
|
||||
setVisibility = (elements: string[], visible: boolean, appendedItems: boolean = false) => {
|
||||
let timer = this._search.timeSearches ? performance.now() : null;
|
||||
if (visible) this._visible = elements;
|
||||
else this._visible = this._search.ordering.filter(v => !elements.includes(v));
|
||||
else this._visible = this._search.ordering.filter((v) => !elements.includes(v));
|
||||
// console.log(elements.length, visible, this._visible.length);
|
||||
this._counter.shown = this._visible.length;
|
||||
if (this._visible.length == 0) {
|
||||
@@ -268,26 +295,29 @@ export abstract class PaginatedList {
|
||||
|
||||
if (!appendedItems) {
|
||||
// Wipe old elements and render 1 new one, so we can take the element height.
|
||||
this._container.replaceChildren(this._search.items[this._visible[0]].asElement())
|
||||
this._container.replaceChildren(this._search.items[this._visible[0]].asElement());
|
||||
}
|
||||
|
||||
this._computeScrollInfo();
|
||||
|
||||
// Initial render of min(_visible.length, max(rowsOnPage*renderNExtraScreensWorth, itemsPerPage)), skipping 1 as we already did it.
|
||||
this._scroll.initialRenderCount = Math.floor(Math.min(
|
||||
this._visible.length,
|
||||
Math.max(
|
||||
((this._scroll.renderNExtraScreensWorth+1)*this._scroll.screenHeight)/this._scroll.rowHeight,
|
||||
this._c.itemsPerPage)
|
||||
));
|
||||
this._scroll.initialRenderCount = Math.floor(
|
||||
Math.min(
|
||||
this._visible.length,
|
||||
Math.max(
|
||||
((this._scroll.renderNExtraScreensWorth + 1) * this._scroll.screenHeight) / this._scroll.rowHeight,
|
||||
this._c.itemsPerPage,
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
let baseIndex = 1;
|
||||
if (appendedItems) {
|
||||
baseIndex = this._scroll.rendered;
|
||||
}
|
||||
const frag = document.createDocumentFragment()
|
||||
const frag = document.createDocumentFragment();
|
||||
for (let i = baseIndex; i < this._scroll.initialRenderCount; i++) {
|
||||
frag.appendChild(this._search.items[this._visible[i]].asElement())
|
||||
frag.appendChild(this._search.items[this._visible[i]].asElement());
|
||||
}
|
||||
this._scroll.rendered = Math.max(baseIndex, this._scroll.initialRenderCount);
|
||||
// appendChild over replaceChildren because there's already elements on the DOM
|
||||
@@ -297,27 +327,24 @@ export abstract class PaginatedList {
|
||||
const totalTime = performance.now() - timer;
|
||||
console.debug(`setVisibility took ${totalTime}ms`);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Computes required scroll info, requiring one on-DOM item. Should be computed on page resize and this._visible change.
|
||||
_computeScrollInfo = () => {
|
||||
if (this._visible.length == 0) return;
|
||||
|
||||
this._scroll.screenHeight = Math.max(
|
||||
document.documentElement.clientHeight,
|
||||
window.innerHeight || 0
|
||||
);
|
||||
this._scroll.screenHeight = Math.max(document.documentElement.clientHeight, window.innerHeight || 0);
|
||||
|
||||
this._scroll.rowHeight = this._search.items[this._visible[0]].asElement().offsetHeight;
|
||||
}
|
||||
};
|
||||
|
||||
// returns the item index to render up to for the given scroll position.
|
||||
// might return a value greater than this._visible.length, indicating a need for a page load.
|
||||
maximumItemsToRender = (scrollY: number): number => {
|
||||
const bottomScroll = scrollY + ((this._scroll.renderNExtraScreensWorth+1)*this._scroll.screenHeight);
|
||||
const bottomScroll = scrollY + (this._scroll.renderNExtraScreensWorth + 1) * this._scroll.screenHeight;
|
||||
const bottomIdx = Math.floor(bottomScroll / this._scroll.rowHeight);
|
||||
return bottomIdx;
|
||||
}
|
||||
};
|
||||
|
||||
private _load = (
|
||||
itemLimit: number,
|
||||
@@ -325,7 +352,7 @@ export abstract class PaginatedList {
|
||||
appendFunc: (resp: paginatedDTO) => void, // Function to append/put items in storage.
|
||||
pre?: (resp: paginatedDTO) => void,
|
||||
post?: (resp: paginatedDTO) => void,
|
||||
failCallback?: (req: XMLHttpRequest) => void
|
||||
failCallback?: (req: XMLHttpRequest) => void,
|
||||
) => {
|
||||
this._lastLoad = Date.now();
|
||||
let params = this._search.inServerSearch ? this._searchParams : this.defaultParams();
|
||||
@@ -336,29 +363,34 @@ export abstract class PaginatedList {
|
||||
params.ascending = this._c.defaultSortAscending;
|
||||
}
|
||||
|
||||
_post(this._c.getPageEndpoint, params, (req: XMLHttpRequest) => {
|
||||
if (req.readyState != 4) return;
|
||||
if (req.status != 200) {
|
||||
_post(
|
||||
this._c.getPageEndpoint,
|
||||
params,
|
||||
(req: XMLHttpRequest) => {
|
||||
if (req.readyState != 4) return;
|
||||
if (req.status != 200) {
|
||||
if (this._c.pageLoadCallback) this._c.pageLoadCallback(req);
|
||||
if (failCallback) failCallback(req);
|
||||
return;
|
||||
}
|
||||
this._hasLoaded = true;
|
||||
|
||||
let resp = req.response as paginatedDTO;
|
||||
if (pre) pre(resp);
|
||||
|
||||
this.lastPage = resp.last_page;
|
||||
|
||||
appendFunc(resp);
|
||||
|
||||
this._counter.loaded = this._search.ordering.length;
|
||||
|
||||
if (post) post(resp);
|
||||
|
||||
if (this._c.pageLoadCallback) this._c.pageLoadCallback(req);
|
||||
if (failCallback) failCallback(req);
|
||||
return;
|
||||
}
|
||||
this._hasLoaded = true;
|
||||
|
||||
let resp = req.response as paginatedDTO;
|
||||
if (pre) pre(resp);
|
||||
|
||||
this.lastPage = resp.last_page;
|
||||
|
||||
appendFunc(resp);
|
||||
|
||||
this._counter.loaded = this._search.ordering.length;
|
||||
|
||||
if (post) post(resp);
|
||||
|
||||
if (this._c.pageLoadCallback) this._c.pageLoadCallback(req);
|
||||
}, true);
|
||||
}
|
||||
},
|
||||
true,
|
||||
);
|
||||
};
|
||||
|
||||
// Removes all elements, and reloads the first page.
|
||||
public abstract reload: (callback?: (resp: paginatedDTO) => void) => void;
|
||||
@@ -369,7 +401,7 @@ export abstract class PaginatedList {
|
||||
// Reload all currently visible elements, i.e. Load a new page of size (limit*(page+1)).
|
||||
let limit = this._c.itemsPerPage;
|
||||
if (this._page != 0) {
|
||||
limit *= this._page+1;
|
||||
limit *= this._page + 1;
|
||||
}
|
||||
this._load(
|
||||
limit,
|
||||
@@ -378,7 +410,7 @@ export abstract class PaginatedList {
|
||||
(_0: paginatedDTO) => {
|
||||
// Allow refreshes every 15s
|
||||
this._c.refreshButton.disabled = true;
|
||||
setTimeout(() => this._c.refreshButton.disabled = false, 15000);
|
||||
setTimeout(() => (this._c.refreshButton.disabled = false), 15000);
|
||||
},
|
||||
(resp: paginatedDTO) => {
|
||||
this._search.onSearchBoxChange(true, false, false);
|
||||
@@ -392,14 +424,14 @@ export abstract class PaginatedList {
|
||||
if (callback) callback(resp);
|
||||
},
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
// Loads the next page. If "loadAll", all pages will be loaded until the last is reached.
|
||||
public abstract loadMore: (loadAll?: boolean, callback?: (resp?: paginatedDTO) => void) => void;
|
||||
protected _loadMore = (loadAll: boolean = false, callback?: (resp: paginatedDTO) => void) => {
|
||||
this._c.loadMoreButtons.forEach((v) => v.disabled = true);
|
||||
this._c.loadMoreButtons.forEach((v) => (v.disabled = true));
|
||||
const timeout = setTimeout(() => {
|
||||
this._c.loadMoreButtons.forEach((v) => v.disabled = false);
|
||||
this._c.loadMoreButtons.forEach((v) => (v.disabled = false));
|
||||
}, 1000);
|
||||
this._page += 1;
|
||||
|
||||
@@ -430,11 +462,13 @@ export abstract class PaginatedList {
|
||||
if (callback) callback(resp);
|
||||
},
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
public abstract loadAll: (callback?: (resp?: paginatedDTO) => void) => void;
|
||||
protected _loadAll = (callback?: (resp?: paginatedDTO) => void) => {
|
||||
this._c.loadAllButtons.forEach((v) => { addLoader(v, true); });
|
||||
protected _loadAll = (callback?: (resp?: paginatedDTO) => void) => {
|
||||
this._c.loadAllButtons.forEach((v) => {
|
||||
addLoader(v, true);
|
||||
});
|
||||
this.loadMore(true, callback);
|
||||
};
|
||||
|
||||
@@ -442,17 +476,16 @@ export abstract class PaginatedList {
|
||||
const cb = () => {
|
||||
if (this._counter.loaded > n) return;
|
||||
this.loadMore(false, cb);
|
||||
}
|
||||
};
|
||||
cb();
|
||||
}
|
||||
};
|
||||
|
||||
// As reloading can disrupt long-scrolling, this function will only do it if you're at the top of the page, essentially.
|
||||
public reloadIfNotInScroll = () => {
|
||||
if (this._visible.length == 0 || this.maximumItemsToRender(window.scrollY) < this._scroll.initialRenderCount) {
|
||||
return this.reload();
|
||||
}
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
_detectScroll = () => {
|
||||
if (!this._hasLoaded || this._scroll.scrollLoading || this._visible.length == 0) return;
|
||||
@@ -467,9 +500,9 @@ export abstract class PaginatedList {
|
||||
// so we calculate the scroll speed (in rows/call) from the previous scrollY value.
|
||||
// This still might not be enough, so hackily we'll just scale it up.
|
||||
// With onscrollend, this is less necessary, but with both I wasn't able to hit the bottom of the page on my mouse.
|
||||
const rowsPerScroll = Math.round((scrollSpeed / this._scroll.rowHeight));
|
||||
const rowsPerScroll = Math.round(scrollSpeed / this._scroll.rowHeight);
|
||||
// Render extra pages depending on scroll speed
|
||||
endIdx += rowsPerScroll*2;
|
||||
endIdx += rowsPerScroll * 2;
|
||||
|
||||
const realEndIdx = Math.min(endIdx, this._visible.length);
|
||||
const frag = document.createDocumentFragment();
|
||||
@@ -495,7 +528,7 @@ export abstract class PaginatedList {
|
||||
cb();
|
||||
return;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
detectScroll = throttle(this._detectScroll, 200);
|
||||
|
||||
@@ -515,7 +548,5 @@ export abstract class PaginatedList {
|
||||
window.removeEventListener("scroll", this.detectScroll);
|
||||
window.removeEventListener("scrollend", this.detectScroll);
|
||||
window.removeEventListener("resize", this.redrawScroll);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -17,7 +17,7 @@ export class Login {
|
||||
constructor(modal: Modal, endpoint: string, appearance: string) {
|
||||
this._endpoint = endpoint;
|
||||
this._url = window.pages.Base + endpoint;
|
||||
if (this._url[this._url.length-1] != '/') this._url += "/";
|
||||
if (this._url[this._url.length - 1] != "/") this._url += "/";
|
||||
|
||||
this._modal = modal;
|
||||
if (appearance == "opaque") {
|
||||
@@ -45,29 +45,39 @@ export class Login {
|
||||
this._logoutButton = button;
|
||||
this._logoutButton.classList.add("unfocused");
|
||||
const logoutFunc = (url: string, tryAgain: boolean) => {
|
||||
_post(url + "logout", null, (req: XMLHttpRequest): boolean => {
|
||||
if (req.readyState == 4 && req.status == 200) {
|
||||
window.token = "";
|
||||
location.reload();
|
||||
return false;
|
||||
}
|
||||
}, false, (req: XMLHttpRequest) => {
|
||||
if (req.readyState == 4 && req.status == 404 && tryAgain) {
|
||||
console.warn("logout failed, trying without URL Base...");
|
||||
logoutFunc(this._endpoint, false);
|
||||
}
|
||||
});
|
||||
_post(
|
||||
url + "logout",
|
||||
null,
|
||||
(req: XMLHttpRequest): boolean => {
|
||||
if (req.readyState == 4 && req.status == 200) {
|
||||
window.token = "";
|
||||
location.reload();
|
||||
return false;
|
||||
}
|
||||
},
|
||||
false,
|
||||
(req: XMLHttpRequest) => {
|
||||
if (req.readyState == 4 && req.status == 404 && tryAgain) {
|
||||
console.warn("logout failed, trying without URL Base...");
|
||||
logoutFunc(this._endpoint, false);
|
||||
}
|
||||
},
|
||||
);
|
||||
};
|
||||
this._logoutButton.onclick = () => logoutFunc(this._url, true);
|
||||
};
|
||||
|
||||
get onLogin() { return this._onLogin; }
|
||||
set onLogin(f: (username: string, password: string) => void) { this._onLogin = f; }
|
||||
get onLogin() {
|
||||
return this._onLogin;
|
||||
}
|
||||
set onLogin(f: (username: string, password: string) => void) {
|
||||
this._onLogin = f;
|
||||
}
|
||||
|
||||
login = (username: string, password: string, run?: (state?: number) => void) => {
|
||||
const req = new XMLHttpRequest();
|
||||
req.responseType = 'json';
|
||||
const refresh = (username == "" && password == "");
|
||||
req.responseType = "json";
|
||||
const refresh = username == "" && password == "";
|
||||
req.open("GET", this._url + (refresh ? "token/refresh" : "token/login"), true);
|
||||
if (!refresh) {
|
||||
req.setRequestHeader("Authorization", "Basic " + unicodeB64Encode(username + ":" + password));
|
||||
@@ -100,13 +110,13 @@ export class Login {
|
||||
}
|
||||
if (this._hasOpacityWall) this._wall.remove();
|
||||
this._modal.close();
|
||||
if (this._logoutButton != null)
|
||||
this._logoutButton.classList.remove("unfocused");
|
||||
if (this._logoutButton != null) this._logoutButton.classList.remove("unfocused");
|
||||
}
|
||||
if (run) {
|
||||
run(+req.status);
|
||||
}
|
||||
if (run) { run(+req.status); }
|
||||
}
|
||||
}).bind(this, req);
|
||||
req.send();
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -9,14 +9,16 @@ export class Modal implements Modal {
|
||||
this.modal = modal;
|
||||
this.openEvent = new CustomEvent("modal-open-" + modal.id);
|
||||
this.closeEvent = new CustomEvent("modal-close-" + modal.id);
|
||||
const closeButton = this.modal.querySelector('span.modal-close');
|
||||
const closeButton = this.modal.querySelector("span.modal-close");
|
||||
if (closeButton !== null) {
|
||||
this.closeButton = closeButton as HTMLSpanElement;
|
||||
this.closeButton.onclick = this.close;
|
||||
}
|
||||
if (!important) {
|
||||
window.addEventListener('click', (event: Event) => {
|
||||
if (event.target == this.modal) { this.close(); }
|
||||
window.addEventListener("click", (event: Event) => {
|
||||
if (event.target == this.modal) {
|
||||
this.close();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -26,36 +28,38 @@ export class Modal implements Modal {
|
||||
if (event) {
|
||||
event.preventDefault();
|
||||
}
|
||||
this.modal.classList.add('animate-fade-out');
|
||||
this.modal.classList.add("animate-fade-out");
|
||||
this.modal.classList.remove("animate-fade-in");
|
||||
const modal = this.modal;
|
||||
const listenerFunc = () => {
|
||||
modal.classList.remove('block');
|
||||
modal.classList.remove('animate-fade-out');
|
||||
modal.removeEventListener(window.animationEvent, listenerFunc)
|
||||
modal.classList.remove("block");
|
||||
modal.classList.remove("animate-fade-out");
|
||||
modal.removeEventListener(window.animationEvent, listenerFunc);
|
||||
if (!noDispatch) document.dispatchEvent(this.closeEvent);
|
||||
};
|
||||
this.modal.addEventListener(window.animationEvent, listenerFunc, false);
|
||||
}
|
||||
};
|
||||
|
||||
set onopen(f: () => void) {
|
||||
document.addEventListener("modal-open-"+this.modal.id, f);
|
||||
document.addEventListener("modal-open-" + this.modal.id, f);
|
||||
}
|
||||
set onclose(f: () => void) {
|
||||
document.addEventListener("modal-close-"+this.modal.id, f);
|
||||
document.addEventListener("modal-close-" + this.modal.id, f);
|
||||
}
|
||||
|
||||
show = () => {
|
||||
this.modal.classList.add('block', 'animate-fade-in');
|
||||
this.modal.classList.add("block", "animate-fade-in");
|
||||
document.dispatchEvent(this.openEvent);
|
||||
}
|
||||
};
|
||||
toggle = () => {
|
||||
if (this.modal.classList.contains('animate-fade-in')) {
|
||||
if (this.modal.classList.contains("animate-fade-in")) {
|
||||
this.close();
|
||||
} else {
|
||||
this.show();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
asElement = () => { return this.modal; }
|
||||
asElement = () => {
|
||||
return this.modal;
|
||||
};
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@ export interface Page {
|
||||
hide: () => boolean;
|
||||
shouldSkip: () => boolean;
|
||||
index?: number;
|
||||
};
|
||||
}
|
||||
|
||||
export interface PageConfig {
|
||||
hideOthersOnPageShow: boolean;
|
||||
@@ -29,7 +29,7 @@ export class PageManager {
|
||||
let ev = { state: data as string } as PopStateEvent;
|
||||
window.onpopstate(ev);
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
private _onpopstate = (event: PopStateEvent) => {
|
||||
let name = event.state;
|
||||
@@ -42,13 +42,13 @@ export class PageManager {
|
||||
}
|
||||
}
|
||||
if (!this.pages.has(name)) {
|
||||
name = this.pageList[0]
|
||||
name = this.pageList[0];
|
||||
}
|
||||
let success = this.pages.get(name).show();
|
||||
if (!success) {
|
||||
return;
|
||||
}
|
||||
if (!(this.hideOthers)) {
|
||||
if (!this.hideOthers) {
|
||||
return;
|
||||
}
|
||||
for (let k of this.pageList) {
|
||||
@@ -56,10 +56,10 @@ export class PageManager {
|
||||
this.pages.get(k).hide();
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
constructor(c: PageConfig) {
|
||||
this.pages = new Map<string, Page>;
|
||||
this.pages = new Map<string, Page>();
|
||||
this.pageList = [];
|
||||
this.hideOthers = c.hideOthersOnPageShow;
|
||||
this.defaultName = c.defaultName;
|
||||
@@ -77,12 +77,12 @@ export class PageManager {
|
||||
|
||||
load(name: string = "") {
|
||||
name = decodeURI(name);
|
||||
if (!this.pages.has(name)) return window.history.pushState(name || this.defaultName, this.defaultTitle, "")
|
||||
if (!this.pages.has(name)) return window.history.pushState(name || this.defaultName, this.defaultTitle, "");
|
||||
const p = this.pages.get(name);
|
||||
this.loadPage(p);
|
||||
}
|
||||
|
||||
loadPage (p: Page) {
|
||||
loadPage(p: Page) {
|
||||
let url = p.url;
|
||||
// Fix ordering of query params and hash
|
||||
if (url.includes("#")) {
|
||||
@@ -99,7 +99,7 @@ export class PageManager {
|
||||
let p = this.pages.get(name);
|
||||
let shouldSkip = true;
|
||||
while (shouldSkip && p.index > 0) {
|
||||
p = this.pages.get(this.pageList[p.index-1]);
|
||||
p = this.pages.get(this.pageList[p.index - 1]);
|
||||
shouldSkip = p.shouldSkip();
|
||||
}
|
||||
this.loadPage(p);
|
||||
@@ -110,9 +110,9 @@ export class PageManager {
|
||||
let p = this.pages.get(name);
|
||||
let shouldSkip = true;
|
||||
while (shouldSkip && p.index < this.pageList.length) {
|
||||
p = this.pages.get(this.pageList[p.index+1]);
|
||||
p = this.pages.get(this.pageList[p.index + 1]);
|
||||
shouldSkip = p.shouldSkip();
|
||||
}
|
||||
this.loadPage(p);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,24 +1,23 @@
|
||||
import { _get, _post, _delete, toggleLoader, _put } from "../modules/common.js";
|
||||
import hljs from "highlight.js/lib/core";
|
||||
import json from 'highlight.js/lib/languages/json';
|
||||
import json from "highlight.js/lib/languages/json";
|
||||
import codeInput, { CodeInput } from "@webcoder49/code-input/code-input.mjs";
|
||||
import Template from "@webcoder49/code-input/templates/hljs.mjs";
|
||||
import Indent from "@webcoder49/code-input/plugins/indent.mjs";
|
||||
|
||||
hljs.registerLanguage("json", json);
|
||||
codeInput.registerTemplate("json-highlighted",
|
||||
new Template(hljs, [new Indent()])
|
||||
);
|
||||
codeInput.registerTemplate("json-highlighted", new Template(hljs, [new Indent()]));
|
||||
|
||||
declare var window: GlobalWindow;
|
||||
|
||||
export const profileLoadEvent = new CustomEvent("profileLoadEvent");
|
||||
export const reloadProfileNames = (then?: () => void) => _get("/profiles/names", null, (req: XMLHttpRequest) => {
|
||||
if (req.readyState != 4) return;
|
||||
window.availableProfiles = req.response["profiles"];
|
||||
document.dispatchEvent(profileLoadEvent);
|
||||
if (then) then();
|
||||
});
|
||||
export const reloadProfileNames = (then?: () => void) =>
|
||||
_get("/profiles/names", null, (req: XMLHttpRequest) => {
|
||||
if (req.readyState != 4) return;
|
||||
window.availableProfiles = req.response["profiles"];
|
||||
document.dispatchEvent(profileLoadEvent);
|
||||
if (then) then();
|
||||
});
|
||||
|
||||
interface Profile {
|
||||
admin: boolean;
|
||||
@@ -44,10 +43,16 @@ class profile implements Profile {
|
||||
private _referralsEnabled: boolean;
|
||||
private _editButton: HTMLButtonElement;
|
||||
|
||||
get name(): string { return this._name.textContent; }
|
||||
set name(v: string) { this._name.textContent = v; }
|
||||
get name(): string {
|
||||
return this._name.textContent;
|
||||
}
|
||||
set name(v: string) {
|
||||
this._name.textContent = v;
|
||||
}
|
||||
|
||||
get admin(): boolean { return this._adminChip.classList.contains("chip"); }
|
||||
get admin(): boolean {
|
||||
return this._adminChip.classList.contains("chip");
|
||||
}
|
||||
set admin(state: boolean) {
|
||||
if (state) {
|
||||
this._adminChip.classList.remove("unfocused");
|
||||
@@ -60,10 +65,16 @@ class profile implements Profile {
|
||||
}
|
||||
}
|
||||
|
||||
get libraries(): string { return this._libraries.textContent; }
|
||||
set libraries(v: string) { this._libraries.textContent = v; }
|
||||
get libraries(): string {
|
||||
return this._libraries.textContent;
|
||||
}
|
||||
set libraries(v: string) {
|
||||
this._libraries.textContent = v;
|
||||
}
|
||||
|
||||
get ombi(): boolean { return this._ombi; }
|
||||
get ombi(): boolean {
|
||||
return this._ombi;
|
||||
}
|
||||
set ombi(v: boolean) {
|
||||
if (!window.ombiEnabled) return;
|
||||
this._ombi = v;
|
||||
@@ -78,7 +89,9 @@ class profile implements Profile {
|
||||
}
|
||||
}
|
||||
|
||||
get jellyseerr(): boolean { return this._jellyseerr; }
|
||||
get jellyseerr(): boolean {
|
||||
return this._jellyseerr;
|
||||
}
|
||||
set jellyseerr(v: boolean) {
|
||||
if (!window.jellyseerrEnabled) return;
|
||||
this._jellyseerr = v;
|
||||
@@ -93,10 +106,16 @@ class profile implements Profile {
|
||||
}
|
||||
}
|
||||
|
||||
get fromUser(): string { return this._fromUser.textContent; }
|
||||
set fromUser(v: string) { this._fromUser.textContent = v; }
|
||||
get fromUser(): string {
|
||||
return this._fromUser.textContent;
|
||||
}
|
||||
set fromUser(v: string) {
|
||||
this._fromUser.textContent = v;
|
||||
}
|
||||
|
||||
get referrals_enabled(): boolean { return this._referralsEnabled; }
|
||||
get referrals_enabled(): boolean {
|
||||
return this._referralsEnabled;
|
||||
}
|
||||
set referrals_enabled(v: boolean) {
|
||||
if (!window.referralsEnabled) return;
|
||||
this._referralsEnabled = v;
|
||||
@@ -111,8 +130,12 @@ class profile implements Profile {
|
||||
}
|
||||
}
|
||||
|
||||
get default(): boolean { return this._defaultRadio.checked; }
|
||||
set default(v: boolean) { this._defaultRadio.checked = v; }
|
||||
get default(): boolean {
|
||||
return this._defaultRadio.checked;
|
||||
}
|
||||
set default(v: boolean) {
|
||||
this._defaultRadio.checked = v;
|
||||
}
|
||||
|
||||
constructor(name: string, p: Profile) {
|
||||
this._row = document.createElement("tr") as HTMLTableRowElement;
|
||||
@@ -120,13 +143,16 @@ class profile implements Profile {
|
||||
<td><div class="flex flex-row items-baseline gap-2"><b class="profile-name"></b> <span class="profile-admin"></span></div></td>
|
||||
<td><input type="radio" name="profile-default"></td>
|
||||
`;
|
||||
if (window.ombiEnabled) innerHTML += `
|
||||
if (window.ombiEnabled)
|
||||
innerHTML += `
|
||||
<td><span class="button @low profile-ombi"></span></td>
|
||||
`;
|
||||
if (window.jellyseerrEnabled) innerHTML += `
|
||||
if (window.jellyseerrEnabled)
|
||||
innerHTML += `
|
||||
<td><span class="button @low profile-jellyseerr"></span></td>
|
||||
`;
|
||||
if (window.referralsEnabled) innerHTML += `
|
||||
if (window.referralsEnabled)
|
||||
innerHTML += `
|
||||
<td><span class="button @low profile-referrals"></span></td>
|
||||
`;
|
||||
innerHTML += `
|
||||
@@ -139,8 +165,7 @@ class profile implements Profile {
|
||||
this._name = this._row.querySelector("b.profile-name");
|
||||
this._adminChip = this._row.querySelector("span.profile-admin") as HTMLSpanElement;
|
||||
this._libraries = this._row.querySelector("td.profile-libraries") as HTMLTableDataCellElement;
|
||||
if (window.ombiEnabled)
|
||||
this._ombiButton = this._row.querySelector("span.profile-ombi") as HTMLSpanElement;
|
||||
if (window.ombiEnabled) this._ombiButton = this._row.querySelector("span.profile-ombi") as HTMLSpanElement;
|
||||
if (window.jellyseerrEnabled)
|
||||
this._jellyseerrButton = this._row.querySelector("span.profile-jellyseerr") as HTMLSpanElement;
|
||||
if (window.referralsEnabled)
|
||||
@@ -148,7 +173,8 @@ class profile implements Profile {
|
||||
this._fromUser = this._row.querySelector("td.profile-from") as HTMLTableDataCellElement;
|
||||
this._editButton = this._row.querySelector(".profile-edit") as HTMLButtonElement;
|
||||
this._defaultRadio = this._row.querySelector("input[type=radio]") as HTMLInputElement;
|
||||
this._defaultRadio.onclick = () => document.dispatchEvent(new CustomEvent("profiles-default", { detail: this.name }));
|
||||
this._defaultRadio.onclick = () =>
|
||||
document.dispatchEvent(new CustomEvent("profiles-default", { detail: this.name }));
|
||||
(this._row.querySelector("span.\\~critical") as HTMLSpanElement).onclick = this.delete;
|
||||
|
||||
this.update(name, p);
|
||||
@@ -162,26 +188,43 @@ class profile implements Profile {
|
||||
this.ombi = p.ombi;
|
||||
this.jellyseerr = p.jellyseerr;
|
||||
this.referrals_enabled = p.referrals_enabled;
|
||||
}
|
||||
};
|
||||
|
||||
setOmbiFunc = (ombiFunc: (ombi: boolean) => void) => { this._ombiButton.onclick = () => ombiFunc(this._ombi); }
|
||||
setJellyseerrFunc = (jellyseerrFunc: (jellyseerr: boolean) => void) => { this._jellyseerrButton.onclick = () => jellyseerrFunc(this._jellyseerr); }
|
||||
setReferralFunc = (referralFunc: (enabled: boolean) => void) => { this._referralsButton.onclick = () => referralFunc(this._referralsEnabled); }
|
||||
setEditFunc = (editFunc: (name: string) => void) => { this._editButton.onclick = () => editFunc(this.name); }
|
||||
setOmbiFunc = (ombiFunc: (ombi: boolean) => void) => {
|
||||
this._ombiButton.onclick = () => ombiFunc(this._ombi);
|
||||
};
|
||||
setJellyseerrFunc = (jellyseerrFunc: (jellyseerr: boolean) => void) => {
|
||||
this._jellyseerrButton.onclick = () => jellyseerrFunc(this._jellyseerr);
|
||||
};
|
||||
setReferralFunc = (referralFunc: (enabled: boolean) => void) => {
|
||||
this._referralsButton.onclick = () => referralFunc(this._referralsEnabled);
|
||||
};
|
||||
setEditFunc = (editFunc: (name: string) => void) => {
|
||||
this._editButton.onclick = () => editFunc(this.name);
|
||||
};
|
||||
|
||||
remove = () => { document.dispatchEvent(new CustomEvent("profiles-delete", { detail: this._name })); this._row.remove(); }
|
||||
remove = () => {
|
||||
document.dispatchEvent(new CustomEvent("profiles-delete", { detail: this._name }));
|
||||
this._row.remove();
|
||||
};
|
||||
|
||||
delete = () => _delete("/profiles", { "name": this.name }, (req: XMLHttpRequest) => {
|
||||
if (req.readyState == 4) {
|
||||
if (req.status == 200 || req.status == 204) {
|
||||
this.remove();
|
||||
} else {
|
||||
window.notifications.customError("profileDelete", window.lang.var("notifications", "errorDeleteProfile", `"${this.name}"`));
|
||||
delete = () =>
|
||||
_delete("/profiles", { name: this.name }, (req: XMLHttpRequest) => {
|
||||
if (req.readyState == 4) {
|
||||
if (req.status == 200 || req.status == 204) {
|
||||
this.remove();
|
||||
} else {
|
||||
window.notifications.customError(
|
||||
"profileDelete",
|
||||
window.lang.var("notifications", "errorDeleteProfile", `"${this.name}"`),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
asElement = (): HTMLTableRowElement => { return this._row; }
|
||||
asElement = (): HTMLTableRowElement => {
|
||||
return this._row;
|
||||
};
|
||||
}
|
||||
|
||||
interface profileResp {
|
||||
@@ -201,101 +244,119 @@ export class ProfileEditor {
|
||||
private _profileName = document.getElementById("add-profile-name") as HTMLInputElement;
|
||||
private _userSelect = document.getElementById("add-profile-user") as HTMLSelectElement;
|
||||
private _storeHomescreen = document.getElementById("add-profile-homescreen") as HTMLInputElement;
|
||||
private _createJellyseerrProfile = window.jellyseerrEnabled ? document.getElementById("add-profile-jellyseerr") as HTMLInputElement : null;
|
||||
private _createJellyseerrProfile = window.jellyseerrEnabled
|
||||
? (document.getElementById("add-profile-jellyseerr") as HTMLInputElement)
|
||||
: null;
|
||||
|
||||
get empty(): boolean { return (Object.keys(this._table.children).length == 0) }
|
||||
get empty(): boolean {
|
||||
return Object.keys(this._table.children).length == 0;
|
||||
}
|
||||
set empty(state: boolean) {
|
||||
if (state) {
|
||||
this._table.innerHTML = `<tr><td class="empty">${window.lang.strings("inviteNoInvites")}</td></tr>`
|
||||
this._table.innerHTML = `<tr><td class="empty">${window.lang.strings("inviteNoInvites")}</td></tr>`;
|
||||
} else if (this._table.querySelector("td.empty")) {
|
||||
this._table.textContent = ``;
|
||||
}
|
||||
}
|
||||
|
||||
get default(): string { return this._default; }
|
||||
get default(): string {
|
||||
return this._default;
|
||||
}
|
||||
set default(v: string) {
|
||||
this._default = v;
|
||||
if (v != "") { this._profiles[v].default = true; }
|
||||
if (v != "") {
|
||||
this._profiles[v].default = true;
|
||||
}
|
||||
for (let name in this._profiles) {
|
||||
if (name != v) { this._profiles[name].default = false; }
|
||||
if (name != v) {
|
||||
this._profiles[name].default = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
load = () => _get("/profiles", null, (req: XMLHttpRequest) => {
|
||||
if (req.readyState == 4) {
|
||||
if (req.status == 200) {
|
||||
let resp = req.response as profileResp;
|
||||
if (Object.keys(resp.profiles).length == 0) {
|
||||
this.empty = true;
|
||||
} else {
|
||||
this.empty = false;
|
||||
for (let name in resp.profiles) {
|
||||
if (name in this._profiles) {
|
||||
this._profiles[name].update(name, resp.profiles[name]);
|
||||
} else {
|
||||
this._profiles[name] = new profile(name, resp.profiles[name]);
|
||||
if (window.ombiEnabled) {
|
||||
this._profiles[name].setOmbiFunc((ombi: boolean) => {
|
||||
if (ombi) {
|
||||
this._ombiProfiles.delete(name, (req: XMLHttpRequest) => {
|
||||
if (req.readyState == 4) {
|
||||
if (req.status != 204) {
|
||||
window.notifications.customError("errorDeleteOmbi", window.lang.notif("errorUnknown"));
|
||||
return;
|
||||
load = () =>
|
||||
_get("/profiles", null, (req: XMLHttpRequest) => {
|
||||
if (req.readyState == 4) {
|
||||
if (req.status == 200) {
|
||||
let resp = req.response as profileResp;
|
||||
if (Object.keys(resp.profiles).length == 0) {
|
||||
this.empty = true;
|
||||
} else {
|
||||
this.empty = false;
|
||||
for (let name in resp.profiles) {
|
||||
if (name in this._profiles) {
|
||||
this._profiles[name].update(name, resp.profiles[name]);
|
||||
} else {
|
||||
this._profiles[name] = new profile(name, resp.profiles[name]);
|
||||
if (window.ombiEnabled) {
|
||||
this._profiles[name].setOmbiFunc((ombi: boolean) => {
|
||||
if (ombi) {
|
||||
this._ombiProfiles.delete(name, (req: XMLHttpRequest) => {
|
||||
if (req.readyState == 4) {
|
||||
if (req.status != 204) {
|
||||
window.notifications.customError(
|
||||
"errorDeleteOmbi",
|
||||
window.lang.notif("errorUnknown"),
|
||||
);
|
||||
return;
|
||||
}
|
||||
this._profiles[name].ombi = false;
|
||||
}
|
||||
this._profiles[name].ombi = false;
|
||||
}
|
||||
});
|
||||
} else {
|
||||
window.modals.profiles.close();
|
||||
this._ombiProfiles.load(name);
|
||||
}
|
||||
});
|
||||
}
|
||||
if (window.jellyseerrEnabled) {
|
||||
this._profiles[name].setJellyseerrFunc((jellyseerr: boolean) => {
|
||||
if (jellyseerr) {
|
||||
this._jellyseerrProfiles.delete(name, (req: XMLHttpRequest) => {
|
||||
if (req.readyState == 4) {
|
||||
if (req.status != 204) {
|
||||
window.notifications.customError("errorDeleteJellyseerr", window.lang.notif("errorUnknown"));
|
||||
return;
|
||||
});
|
||||
} else {
|
||||
window.modals.profiles.close();
|
||||
this._ombiProfiles.load(name);
|
||||
}
|
||||
});
|
||||
}
|
||||
if (window.jellyseerrEnabled) {
|
||||
this._profiles[name].setJellyseerrFunc((jellyseerr: boolean) => {
|
||||
if (jellyseerr) {
|
||||
this._jellyseerrProfiles.delete(name, (req: XMLHttpRequest) => {
|
||||
if (req.readyState == 4) {
|
||||
if (req.status != 204) {
|
||||
window.notifications.customError(
|
||||
"errorDeleteJellyseerr",
|
||||
window.lang.notif("errorUnknown"),
|
||||
);
|
||||
return;
|
||||
}
|
||||
this._profiles[name].jellyseerr = false;
|
||||
}
|
||||
this._profiles[name].jellyseerr = false;
|
||||
}
|
||||
});
|
||||
} else {
|
||||
window.modals.profiles.close();
|
||||
this._jellyseerrProfiles.load(name);
|
||||
}
|
||||
});
|
||||
});
|
||||
} else {
|
||||
window.modals.profiles.close();
|
||||
this._jellyseerrProfiles.load(name);
|
||||
}
|
||||
});
|
||||
}
|
||||
if (window.referralsEnabled) {
|
||||
this._profiles[name].setReferralFunc((enabled: boolean) => {
|
||||
if (enabled) {
|
||||
this.disableReferrals(name);
|
||||
} else {
|
||||
this.enableReferrals(name);
|
||||
}
|
||||
});
|
||||
}
|
||||
this._profiles[name].setEditFunc(this._loadProfileEditor);
|
||||
this._table.appendChild(this._profiles[name].asElement());
|
||||
}
|
||||
if (window.referralsEnabled) {
|
||||
this._profiles[name].setReferralFunc((enabled: boolean) => {
|
||||
if (enabled) {
|
||||
this.disableReferrals(name);
|
||||
} else {
|
||||
this.enableReferrals(name);
|
||||
}
|
||||
});
|
||||
}
|
||||
this._profiles[name].setEditFunc(this._loadProfileEditor);
|
||||
this._table.appendChild(this._profiles[name].asElement());
|
||||
}
|
||||
}
|
||||
this.default = resp.default_profile;
|
||||
window.modals.profiles.show();
|
||||
} else {
|
||||
window.notifications.customError("profileEditor", window.lang.notif("errorLoadProfiles"));
|
||||
}
|
||||
this.default = resp.default_profile;
|
||||
window.modals.profiles.show();
|
||||
} else {
|
||||
window.notifications.customError("profileEditor", window.lang.notif("errorLoadProfiles"));
|
||||
}
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
disableReferrals = (name: string) => _delete("/profiles/referral/" + name, null, (req: XMLHttpRequest) => {
|
||||
if (req.readyState != 4) return;
|
||||
this.load();
|
||||
});
|
||||
disableReferrals = (name: string) =>
|
||||
_delete("/profiles/referral/" + name, null, (req: XMLHttpRequest) => {
|
||||
if (req.readyState != 4) return;
|
||||
this.load();
|
||||
});
|
||||
|
||||
enableReferrals = (name: string) => {
|
||||
const referralsInviteSelect = document.getElementById("enable-referrals-profile-invites") as HTMLSelectElement;
|
||||
@@ -327,22 +388,34 @@ export class ProfileEditor {
|
||||
toggleLoader(button);
|
||||
|
||||
let send = {
|
||||
"profile": name,
|
||||
"invite": referralsInviteSelect.value
|
||||
profile: name,
|
||||
invite: referralsInviteSelect.value,
|
||||
};
|
||||
|
||||
_post("/profiles/referral/" + send["profile"] + "/" + send["invite"] + "/" + (referralsExpiry.checked ? "with-expiry" : "none"), send, (req: XMLHttpRequest) => {
|
||||
if (req.readyState == 4) {
|
||||
toggleLoader(button);
|
||||
if (req.status == 400) {
|
||||
window.notifications.customError("unknownError", window.lang.notif("errorUnknown"));
|
||||
} else if (req.status == 200 || req.status == 204) {
|
||||
window.notifications.customSuccess("enableReferralsSuccess", window.lang.notif("referralsEnabled"));
|
||||
_post(
|
||||
"/profiles/referral/" +
|
||||
send["profile"] +
|
||||
"/" +
|
||||
send["invite"] +
|
||||
"/" +
|
||||
(referralsExpiry.checked ? "with-expiry" : "none"),
|
||||
send,
|
||||
(req: XMLHttpRequest) => {
|
||||
if (req.readyState == 4) {
|
||||
toggleLoader(button);
|
||||
if (req.status == 400) {
|
||||
window.notifications.customError("unknownError", window.lang.notif("errorUnknown"));
|
||||
} else if (req.status == 200 || req.status == 204) {
|
||||
window.notifications.customSuccess(
|
||||
"enableReferralsSuccess",
|
||||
window.lang.notif("referralsEnabled"),
|
||||
);
|
||||
}
|
||||
window.modals.enableReferralsProfile.close();
|
||||
this.load();
|
||||
}
|
||||
window.modals.enableReferralsProfile.close();
|
||||
this.load();
|
||||
}
|
||||
});
|
||||
},
|
||||
);
|
||||
};
|
||||
referralsExpiry.checked = false;
|
||||
window.modals.profiles.close();
|
||||
@@ -372,7 +445,7 @@ export class ProfileEditor {
|
||||
let send: any;
|
||||
try {
|
||||
send = JSON.parse(editor.value);
|
||||
} catch(e: any) {
|
||||
} catch (e: any) {
|
||||
submit.classList.add("~critical");
|
||||
submit.classList.remove("~urge");
|
||||
window.notifications.customError("errorInvalidJSON", window.lang.notif("errorInvalidJSON"));
|
||||
@@ -389,7 +462,7 @@ export class ProfileEditor {
|
||||
// a 201 implies the profile was renamed. Since reloading profiles doesn't delete missing ones,
|
||||
// we should delete the old one ourselves.
|
||||
if (req.status == 201) {
|
||||
this._profiles[name].remove()
|
||||
this._profiles[name].remove();
|
||||
delete this._profiles[name];
|
||||
}
|
||||
} else {
|
||||
@@ -403,16 +476,15 @@ export class ProfileEditor {
|
||||
|
||||
window.modals.profiles.close();
|
||||
window.modals.editProfile.show();
|
||||
})
|
||||
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
constructor() {
|
||||
(document.getElementById('setting-profiles') as HTMLSpanElement).onclick = this.load;
|
||||
(document.getElementById("setting-profiles") as HTMLSpanElement).onclick = this.load;
|
||||
document.addEventListener("profiles-default", (event: CustomEvent) => {
|
||||
const prevDefault = this.default;
|
||||
const newDefault = event.detail;
|
||||
_post("/profiles/default", { "name": newDefault }, (req: XMLHttpRequest) => {
|
||||
_post("/profiles/default", { name: newDefault }, (req: XMLHttpRequest) => {
|
||||
if (req.readyState == 4) {
|
||||
if (req.status == 200 || req.status == 204) {
|
||||
this.default = newDefault;
|
||||
@@ -428,38 +500,37 @@ export class ProfileEditor {
|
||||
this.load();
|
||||
});
|
||||
|
||||
if (window.ombiEnabled)
|
||||
this._ombiProfiles = new ombiProfiles();
|
||||
if (window.jellyseerrEnabled)
|
||||
this._jellyseerrProfiles = new jellyseerrProfiles();
|
||||
if (window.ombiEnabled) this._ombiProfiles = new ombiProfiles();
|
||||
if (window.jellyseerrEnabled) this._jellyseerrProfiles = new jellyseerrProfiles();
|
||||
|
||||
this._createButton.onclick = () => _get("/users", null, (req: XMLHttpRequest) => {
|
||||
if (req.readyState == 4) {
|
||||
if (req.status == 200 || req.status == 204) {
|
||||
let innerHTML = ``;
|
||||
for (let user of req.response["users"]) {
|
||||
innerHTML += `<option value="${user['id']}">${user['name']}</option>`;
|
||||
this._createButton.onclick = () =>
|
||||
_get("/users", null, (req: XMLHttpRequest) => {
|
||||
if (req.readyState == 4) {
|
||||
if (req.status == 200 || req.status == 204) {
|
||||
let innerHTML = ``;
|
||||
for (let user of req.response["users"]) {
|
||||
innerHTML += `<option value="${user["id"]}">${user["name"]}</option>`;
|
||||
}
|
||||
this._userSelect.innerHTML = innerHTML;
|
||||
this._storeHomescreen.checked = true;
|
||||
if (this._createJellyseerrProfile) this._createJellyseerrProfile.checked = true;
|
||||
window.modals.profiles.close();
|
||||
window.modals.addProfile.show();
|
||||
} else {
|
||||
window.notifications.customError("loadUsers", window.lang.notif("errorLoadUsers"));
|
||||
}
|
||||
this._userSelect.innerHTML = innerHTML;
|
||||
this._storeHomescreen.checked = true;
|
||||
if (this._createJellyseerrProfile) this._createJellyseerrProfile.checked = true;
|
||||
window.modals.profiles.close();
|
||||
window.modals.addProfile.show();
|
||||
} else {
|
||||
window.notifications.customError("loadUsers", window.lang.notif("errorLoadUsers"));
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
this._createForm.onsubmit = (event: SubmitEvent) => {
|
||||
event.preventDefault();
|
||||
const button = this._createForm.querySelector("span.submit") as HTMLSpanElement;
|
||||
toggleLoader(button);
|
||||
let send = {
|
||||
"homescreen": this._storeHomescreen.checked,
|
||||
"id": this._userSelect.value,
|
||||
"name": this._profileName.value
|
||||
}
|
||||
homescreen: this._storeHomescreen.checked,
|
||||
id: this._userSelect.value,
|
||||
name: this._profileName.value,
|
||||
};
|
||||
if (this._createJellyseerrProfile) send["jellyseerr"] = this._createJellyseerrProfile.checked;
|
||||
_post("/profiles", send, (req: XMLHttpRequest) => {
|
||||
if (req.readyState == 4) {
|
||||
@@ -467,15 +538,20 @@ export class ProfileEditor {
|
||||
window.modals.addProfile.close();
|
||||
if (req.status == 200 || req.status == 204) {
|
||||
this.load();
|
||||
window.notifications.customSuccess("createProfile", window.lang.var("notifications", "createProfile", `"${send['name']}"`));
|
||||
window.notifications.customSuccess(
|
||||
"createProfile",
|
||||
window.lang.var("notifications", "createProfile", `"${send["name"]}"`),
|
||||
);
|
||||
} else {
|
||||
window.notifications.customError("createProfile", window.lang.var("notifications", "errorCreateProfile", `"${send['name']}"`));
|
||||
window.notifications.customError(
|
||||
"createProfile",
|
||||
window.lang.var("notifications", "errorCreateProfile", `"${send["name"]}"`),
|
||||
);
|
||||
}
|
||||
window.modals.profiles.show();
|
||||
}
|
||||
})
|
||||
});
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -501,20 +577,25 @@ export class ombiProfiles {
|
||||
let resp = {} as ombiUser;
|
||||
resp.id = this._select.value;
|
||||
resp.name = this._users[resp.id];
|
||||
_post("/profiles/ombi/" + encodeURIComponent(encodeURIComponent(this._currentProfile)), resp, (req: XMLHttpRequest) => {
|
||||
if (req.readyState == 4) {
|
||||
toggleLoader(button);
|
||||
if (req.status == 200 || req.status == 204) {
|
||||
window.notifications.customSuccess("ombiDefaults", window.lang.notif("setOmbiProfile"));
|
||||
} else {
|
||||
window.notifications.customError("ombiDefaults", window.lang.notif("errorSetOmbiProfile"));
|
||||
_post(
|
||||
"/profiles/ombi/" + encodeURIComponent(encodeURIComponent(this._currentProfile)),
|
||||
resp,
|
||||
(req: XMLHttpRequest) => {
|
||||
if (req.readyState == 4) {
|
||||
toggleLoader(button);
|
||||
if (req.status == 200 || req.status == 204) {
|
||||
window.notifications.customSuccess("ombiDefaults", window.lang.notif("setOmbiProfile"));
|
||||
} else {
|
||||
window.notifications.customError("ombiDefaults", window.lang.notif("errorSetOmbiProfile"));
|
||||
}
|
||||
window.modals.ombiProfile.close();
|
||||
}
|
||||
window.modals.ombiProfile.close();
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
);
|
||||
};
|
||||
|
||||
delete = (profile: string, post?: (req: XMLHttpRequest) => void) => _delete("/profiles/ombi/" + encodeURIComponent(encodeURIComponent(profile)), null, post);
|
||||
delete = (profile: string, post?: (req: XMLHttpRequest) => void) =>
|
||||
_delete("/profiles/ombi/" + encodeURIComponent(encodeURIComponent(profile)), null, post);
|
||||
|
||||
load = (profile: string) => {
|
||||
this._currentProfile = profile;
|
||||
@@ -530,11 +611,11 @@ export class ombiProfiles {
|
||||
this._select.innerHTML = innerHTML;
|
||||
window.modals.ombiProfile.show();
|
||||
} else {
|
||||
window.notifications.customError("ombiLoadError", window.lang.notif("errorLoadOmbiUsers"))
|
||||
window.notifications.customError("ombiLoadError", window.lang.notif("errorLoadOmbiUsers"));
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export class jellyseerrProfiles {
|
||||
@@ -563,9 +644,10 @@ export class jellyseerrProfiles {
|
||||
window.modals.jellyseerrProfile.close();
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
delete = (profile: string, post?: (req: XMLHttpRequest) => void) => _delete("/profiles/jellyseerr/" + encodeURIComponent(encodeURIComponent(profile)), null, post);
|
||||
delete = (profile: string, post?: (req: XMLHttpRequest) => void) =>
|
||||
_delete("/profiles/jellyseerr/" + encodeURIComponent(encodeURIComponent(profile)), null, post);
|
||||
|
||||
load = (profile: string) => {
|
||||
this._currentProfile = profile;
|
||||
@@ -581,9 +663,9 @@ export class jellyseerrProfiles {
|
||||
this._select.innerHTML = innerHTML;
|
||||
window.modals.jellyseerrProfile.show();
|
||||
} else {
|
||||
window.notifications.customError("jellyseerrLoadError", window.lang.notif("errorLoadUsers"))
|
||||
window.notifications.customError("jellyseerrLoadError", window.lang.notif("errorLoadUsers"));
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@ declare var window: GlobalWindow;
|
||||
export enum QueryOperator {
|
||||
Greater = ">",
|
||||
Lower = "<",
|
||||
Equal = "="
|
||||
Equal = "=",
|
||||
}
|
||||
|
||||
export function QueryOperatorToDateText(op: QueryOperator): string {
|
||||
@@ -29,7 +29,7 @@ export interface QueryType {
|
||||
date: boolean;
|
||||
dependsOnElement?: string; // Format for querySelector
|
||||
show?: boolean;
|
||||
localOnly?: boolean // Indicates can't be performed server-side.
|
||||
localOnly?: boolean; // Indicates can't be performed server-side.
|
||||
}
|
||||
|
||||
export interface SearchConfiguration {
|
||||
@@ -62,7 +62,7 @@ export interface QueryDTO {
|
||||
field: string;
|
||||
operator: QueryOperator;
|
||||
value: boolean | string | DateAttempt;
|
||||
};
|
||||
}
|
||||
|
||||
export abstract class Query {
|
||||
protected _subject: QueryType;
|
||||
@@ -83,7 +83,9 @@ export abstract class Query {
|
||||
this._card.addEventListener("click", v);
|
||||
}
|
||||
|
||||
asElement(): HTMLElement { return this._card; }
|
||||
asElement(): HTMLElement {
|
||||
return this._card;
|
||||
}
|
||||
|
||||
public abstract compare(subjectValue: any): boolean;
|
||||
|
||||
@@ -95,7 +97,9 @@ export abstract class Query {
|
||||
return out;
|
||||
}
|
||||
|
||||
get subject(): QueryType { return this._subject; }
|
||||
get subject(): QueryType {
|
||||
return this._subject;
|
||||
}
|
||||
|
||||
getValueFromItem(item: SearchableItem): any {
|
||||
return Object.getOwnPropertyDescriptor(Object.getPrototypeOf(item), this.subject.getter).get.call(item);
|
||||
@@ -105,7 +109,9 @@ export abstract class Query {
|
||||
return this.compare(this.getValueFromItem(item));
|
||||
}
|
||||
|
||||
get localOnly(): boolean { return this._subject.localOnly ? true : false; }
|
||||
get localOnly(): boolean {
|
||||
return this._subject.localOnly ? true : false;
|
||||
}
|
||||
}
|
||||
|
||||
export class BoolQuery extends Query {
|
||||
@@ -122,7 +128,7 @@ export class BoolQuery extends Query {
|
||||
}
|
||||
this._card.innerHTML = `
|
||||
<span class="font-bold">${subject.name}</span>
|
||||
<i class="text-2xl ri-${this._value? "checkbox" : "close"}-circle-fill"></i>
|
||||
<i class="text-2xl ri-${this._value ? "checkbox" : "close"}-circle-fill"></i>
|
||||
`;
|
||||
}
|
||||
|
||||
@@ -136,14 +142,16 @@ export class BoolQuery extends Query {
|
||||
isBool = true;
|
||||
boolState = false;
|
||||
}
|
||||
return [boolState, isBool]
|
||||
return [boolState, isBool];
|
||||
}
|
||||
|
||||
get value(): boolean { return this._value; }
|
||||
get value(): boolean {
|
||||
return this._value;
|
||||
}
|
||||
|
||||
// Ripped from old code. Why it's like this, I don't know
|
||||
public compare(subjectBool: boolean): boolean {
|
||||
return ((subjectBool && this._value) || (!subjectBool && !this._value))
|
||||
return (subjectBool && this._value) || (!subjectBool && !this._value);
|
||||
}
|
||||
|
||||
asDTO(): QueryDTO | null {
|
||||
@@ -167,7 +175,9 @@ export class StringQuery extends Query {
|
||||
`;
|
||||
}
|
||||
|
||||
get value(): string { return this._value; }
|
||||
get value(): string {
|
||||
return this._value;
|
||||
}
|
||||
|
||||
public compare(subjectString: string): boolean {
|
||||
return subjectString.toLowerCase().includes(this._value);
|
||||
@@ -211,7 +221,7 @@ export class DateQuery extends Query {
|
||||
this._card.classList.add("button", "~neutral", "@low", "center", "flex", "flex-row", "gap-2");
|
||||
let dateText = QueryOperatorToDateText(operator);
|
||||
this._card.innerHTML = `
|
||||
<span class="font-bold">${subject.name}:</span> ${dateText != "" ? dateText+" " : ""}${value.text}
|
||||
<span class="font-bold">${subject.name}:</span> ${dateText != "" ? dateText + " " : ""}${value.text}
|
||||
`;
|
||||
}
|
||||
|
||||
@@ -229,7 +239,9 @@ export class DateQuery extends Query {
|
||||
return [out, op, isValid];
|
||||
}
|
||||
|
||||
get value(): ParsedDate { return this._value; }
|
||||
get value(): ParsedDate {
|
||||
return this._value;
|
||||
}
|
||||
|
||||
public compare(subjectDate: Date): boolean {
|
||||
// We want to compare only the fields given in this._value,
|
||||
@@ -237,10 +249,7 @@ export class DateQuery extends Query {
|
||||
const temp = new Date(subjectDate.valueOf());
|
||||
for (let [field] of dateGetters) {
|
||||
if (field in this._value.attempt) {
|
||||
dateSetters.get(field).call(
|
||||
temp,
|
||||
dateGetters.get(field).call(this._value.date)
|
||||
);
|
||||
dateSetters.get(field).call(temp, dateGetters.get(field).call(this._value.date));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -281,7 +290,9 @@ export class Search {
|
||||
private _searchTerms: string[] = [];
|
||||
inSearch: boolean = false;
|
||||
private _inServerSearch: boolean = false;
|
||||
get inServerSearch(): boolean { return this._inServerSearch; }
|
||||
get inServerSearch(): boolean {
|
||||
return this._inServerSearch;
|
||||
}
|
||||
set inServerSearch(v: boolean) {
|
||||
const previous = this._inServerSearch;
|
||||
this._inServerSearch = v;
|
||||
@@ -316,14 +327,14 @@ export class Search {
|
||||
}
|
||||
}
|
||||
|
||||
if (query[i] == " " || i == query.length-1) {
|
||||
if (query[i] == " " || i == query.length - 1) {
|
||||
if (lastQuote != -1) {
|
||||
continue;
|
||||
} else {
|
||||
let end = i+1;
|
||||
let end = i + 1;
|
||||
if (query[i] == " ") {
|
||||
end = i;
|
||||
while (i+1 < query.length && query[i+1] == " ") {
|
||||
while (i + 1 < query.length && query[i + 1] == " ") {
|
||||
i += 1;
|
||||
}
|
||||
}
|
||||
@@ -333,7 +344,7 @@ export class Search {
|
||||
}
|
||||
}
|
||||
return words;
|
||||
}
|
||||
};
|
||||
|
||||
parseTokens = (tokens: string[]): [string[], Query[]] => {
|
||||
let queries: Query[] = [];
|
||||
@@ -346,7 +357,7 @@ export class Search {
|
||||
continue;
|
||||
}
|
||||
// 2. A filter query of some sort.
|
||||
const split = [word.substring(0, word.indexOf(":")), word.substring(word.indexOf(":")+1)];
|
||||
const split = [word.substring(0, word.indexOf(":")), word.substring(word.indexOf(":") + 1)];
|
||||
|
||||
if (!(split[0] in this._c.queries)) continue;
|
||||
|
||||
@@ -360,9 +371,12 @@ export class Search {
|
||||
q = new BoolQuery(queryFormat, boolState);
|
||||
q.onclick = () => {
|
||||
for (let quote of [`"`, `'`, ``]) {
|
||||
this._c.search.value = this._c.search.value.replace(split[0] + ":" + quote + split[1] + quote, "");
|
||||
this._c.search.value = this._c.search.value.replace(
|
||||
split[0] + ":" + quote + split[1] + quote,
|
||||
"",
|
||||
);
|
||||
}
|
||||
this._c.search.oninput((null as Event));
|
||||
this._c.search.oninput(null as Event);
|
||||
};
|
||||
queries.push(q);
|
||||
continue;
|
||||
@@ -376,8 +390,8 @@ export class Search {
|
||||
let regex = new RegExp(split[0] + ":" + quote + split[1] + quote, "ig");
|
||||
this._c.search.value = this._c.search.value.replace(regex, "");
|
||||
}
|
||||
this._c.search.oninput((null as Event));
|
||||
}
|
||||
this._c.search.oninput(null as Event);
|
||||
};
|
||||
queries.push(q);
|
||||
continue;
|
||||
}
|
||||
@@ -392,15 +406,15 @@ export class Search {
|
||||
this._c.search.value = this._c.search.value.replace(regex, "");
|
||||
}
|
||||
|
||||
this._c.search.oninput((null as Event));
|
||||
}
|
||||
this._c.search.oninput(null as Event);
|
||||
};
|
||||
queries.push(q);
|
||||
continue;
|
||||
}
|
||||
// if (q != null) queries.push(q);
|
||||
}
|
||||
return [searchTerms, queries];
|
||||
}
|
||||
};
|
||||
|
||||
// Returns a list of identifiers (used as keys in items, values in ordering).
|
||||
searchParsed = (searchTerms: string[], queries: Query[]): string[] => {
|
||||
@@ -432,7 +446,7 @@ export class Search {
|
||||
for (let q of queries) {
|
||||
this._c.filterArea.appendChild(q.asElement());
|
||||
// Skip if this query has already been performed by the server.
|
||||
if (this.inServerSearch && !(q.localOnly)) continue;
|
||||
if (this.inServerSearch && !q.localOnly) continue;
|
||||
|
||||
let cachedResult = [...result];
|
||||
if (q.type == "bool") {
|
||||
@@ -463,7 +477,7 @@ export class Search {
|
||||
result.splice(result.indexOf(id), 1);
|
||||
continue;
|
||||
}
|
||||
let value = new Date(unixValue*1000);
|
||||
let value = new Date(unixValue * 1000);
|
||||
|
||||
if (!q.compare(value)) {
|
||||
result.splice(result.indexOf(id), 1);
|
||||
@@ -472,7 +486,7 @@ export class Search {
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
};
|
||||
|
||||
// Returns a list of identifiers (used as keys in items, values in ordering).
|
||||
search = (query: string): string[] => {
|
||||
@@ -491,7 +505,7 @@ export class Search {
|
||||
console.debug(`Search took ${totalTime}ms`);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
};
|
||||
|
||||
// postServerSearch performs local-only queries after a server search if necessary.
|
||||
postServerSearch = () => {
|
||||
@@ -500,35 +514,48 @@ export class Search {
|
||||
|
||||
showHideSearchOptionsHeader = () => {
|
||||
let sortingBy = false;
|
||||
if (this._c.sortingByButton) sortingBy = !(this._c.sortingByButton.classList.contains("hidden"));
|
||||
if (this._c.sortingByButton) sortingBy = !this._c.sortingByButton.classList.contains("hidden");
|
||||
const hasFilters = this._c.filterArea.textContent != "";
|
||||
if (sortingBy || hasFilters) {
|
||||
this._c.searchOptionsHeader.classList.remove("hidden");
|
||||
} else {
|
||||
this._c.searchOptionsHeader.classList.add("hidden");
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// -all- elements.
|
||||
get items(): { [id: string]: SearchableItem } { return this._items; }
|
||||
get items(): { [id: string]: SearchableItem } {
|
||||
return this._items;
|
||||
}
|
||||
// set items(v: { [id: string]: SearchableItem }) {
|
||||
// this._items = v;
|
||||
// }
|
||||
|
||||
// The order of -all- elements (even those hidden), by their identifier.
|
||||
get ordering(): string[] { return this._ordering; }
|
||||
get ordering(): string[] {
|
||||
return this._ordering;
|
||||
}
|
||||
// Specifically dis-allow setting ordering itself, so that setOrdering is used instead (for the field and ascending params).
|
||||
// set ordering(v: string[]) { this._ordering = v; }
|
||||
setOrdering = (v: string[], field: string, ascending: boolean) => {
|
||||
this._ordering = v;
|
||||
this._sortField = field;
|
||||
this._ascending = ascending;
|
||||
};
|
||||
|
||||
get sortField(): string {
|
||||
return this._sortField;
|
||||
}
|
||||
get ascending(): boolean {
|
||||
return this._ascending;
|
||||
}
|
||||
|
||||
get sortField(): string { return this._sortField; }
|
||||
get ascending(): boolean { return this._ascending; }
|
||||
|
||||
onSearchBoxChange = (newItems: boolean = false, appendedItems: boolean = false, loadAll: boolean = false, callback?: (resp: paginatedDTO) => void) => {
|
||||
onSearchBoxChange = (
|
||||
newItems: boolean = false,
|
||||
appendedItems: boolean = false,
|
||||
loadAll: boolean = false,
|
||||
callback?: (resp: paginatedDTO) => void,
|
||||
) => {
|
||||
const query = this._c.search.value;
|
||||
if (!query) {
|
||||
this.inSearch = false;
|
||||
@@ -554,7 +581,7 @@ export class Search {
|
||||
this.showHideSearchOptionsHeader();
|
||||
this.setNotFoundPanelVisibility(results.length == 0);
|
||||
if (this._c.notFoundCallback) this._c.notFoundCallback(results.length == 0);
|
||||
}
|
||||
};
|
||||
|
||||
setNotFoundPanelVisibility = (visible: boolean) => {
|
||||
if (this._inServerSearch || !this.inSearch) {
|
||||
@@ -567,14 +594,13 @@ export class Search {
|
||||
} else {
|
||||
this._c.notFoundPanel.classList.add("unfocused");
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
fillInFilter = (name: string, value: string, offset?: number) => {
|
||||
this._c.search.value = name + ":" + value + " " + this._c.search.value;
|
||||
this._c.search.focus();
|
||||
let newPos = name.length + 1 + value.length;
|
||||
if (typeof offset !== 'undefined')
|
||||
newPos += offset;
|
||||
if (typeof offset !== "undefined") newPos += offset;
|
||||
this._c.search.setSelectionRange(newPos, newPos);
|
||||
this._c.search.oninput(null as any);
|
||||
};
|
||||
@@ -592,7 +618,16 @@ export class Search {
|
||||
}
|
||||
|
||||
const container = document.createElement("span") as HTMLSpanElement;
|
||||
container.classList.add("button", "button-xl", "~neutral", "@low", "align-bottom", "flex", "flex-row", "gap-2");
|
||||
container.classList.add(
|
||||
"button",
|
||||
"button-xl",
|
||||
"~neutral",
|
||||
"@low",
|
||||
"align-bottom",
|
||||
"flex",
|
||||
"flex-row",
|
||||
"gap-2",
|
||||
);
|
||||
container.innerHTML = `
|
||||
<div class="flex flex-col">
|
||||
<span>${query.name}</span>
|
||||
@@ -653,19 +688,18 @@ export class Search {
|
||||
|
||||
filterListContainer.appendChild(container);
|
||||
}
|
||||
this._c.filterList.appendChild(filterListContainer)
|
||||
}
|
||||
this._c.filterList.appendChild(filterListContainer);
|
||||
};
|
||||
|
||||
onServerSearch = () => {
|
||||
const newServerSearch = !this.inServerSearch;
|
||||
this.inServerSearch = true;
|
||||
this.searchServer(newServerSearch);
|
||||
}
|
||||
};
|
||||
|
||||
searchServer = (newServerSearch: boolean) => {
|
||||
this._c.searchServer(this.serverSearchParams(this._searchTerms, this._queries), newServerSearch);
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
serverSearchParams = (searchTerms: string[], queries: Query[]): PaginatedReqDTO => {
|
||||
let req: ServerSearchReqDTO = {
|
||||
@@ -674,18 +708,18 @@ export class Search {
|
||||
limit: -1,
|
||||
page: 0,
|
||||
sortByField: this.sortField,
|
||||
ascending: this.ascending
|
||||
ascending: this.ascending,
|
||||
};
|
||||
for (const q of queries) {
|
||||
const dto = q.asDTO();
|
||||
if (dto !== null) req.queries.push(dto);
|
||||
}
|
||||
return req;
|
||||
}
|
||||
};
|
||||
|
||||
setServerSearchButtonsDisabled = (disabled: boolean) => {
|
||||
this._serverSearchButtons.forEach((v: HTMLButtonElement) => v.disabled = disabled);
|
||||
}
|
||||
this._serverSearchButtons.forEach((v: HTMLButtonElement) => (v.disabled = disabled));
|
||||
};
|
||||
|
||||
constructor(c: SearchConfiguration) {
|
||||
this._c = c;
|
||||
@@ -693,14 +727,16 @@ export class Search {
|
||||
this._c.search.oninput = () => {
|
||||
this.inServerSearch = false;
|
||||
this.onSearchBoxChange();
|
||||
}
|
||||
};
|
||||
this._c.search.addEventListener("keyup", (ev: KeyboardEvent) => {
|
||||
if (ev.key == "Enter") {
|
||||
this.onServerSearch();
|
||||
}
|
||||
});
|
||||
|
||||
const clearSearchButtons = Array.from(document.querySelectorAll(this._c.clearSearchButtonSelector)) as Array<HTMLSpanElement>;
|
||||
const clearSearchButtons = Array.from(
|
||||
document.querySelectorAll(this._c.clearSearchButtonSelector),
|
||||
) as Array<HTMLSpanElement>;
|
||||
for (let b of clearSearchButtons) {
|
||||
b.addEventListener("click", () => {
|
||||
this._c.search.value = "";
|
||||
@@ -709,7 +745,9 @@ export class Search {
|
||||
});
|
||||
}
|
||||
|
||||
this._serverSearchButtons = Array.from(document.querySelectorAll(this._c.serverSearchButtonSelector)) as Array<HTMLSpanElement>;
|
||||
this._serverSearchButtons = Array.from(
|
||||
document.querySelectorAll(this._c.serverSearchButtonSelector),
|
||||
) as Array<HTMLSpanElement>;
|
||||
for (let b of this._serverSearchButtons) {
|
||||
b.addEventListener("click", () => {
|
||||
this.onServerSearch();
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,38 +1,38 @@
|
||||
const removeMd = require("remove-markdown");
|
||||
|
||||
function stripAltText(md: string): string {
|
||||
let altStart = -1; // Start of alt text (between '[' & ']')
|
||||
let urlStart = -1; // Start of url (between '(' & ')')
|
||||
let urlEnd = -1;
|
||||
let prevURLEnd = -2;
|
||||
let out = "";
|
||||
let altStart = -1; // Start of alt text (between '[' & ']')
|
||||
let urlStart = -1; // Start of url (between '(' & ')')
|
||||
let urlEnd = -1;
|
||||
let prevURLEnd = -2;
|
||||
let out = "";
|
||||
for (let i = 0; i < md.length; i++) {
|
||||
if (altStart != -1 && urlStart != -1 && md.charAt(i) == ')') {
|
||||
urlEnd = i - 1;
|
||||
out += md.substring(prevURLEnd+2, altStart-1) + md.substring(urlStart, urlEnd+1);
|
||||
prevURLEnd = urlEnd;
|
||||
altStart = -1;
|
||||
if (altStart != -1 && urlStart != -1 && md.charAt(i) == ")") {
|
||||
urlEnd = i - 1;
|
||||
out += md.substring(prevURLEnd + 2, altStart - 1) + md.substring(urlStart, urlEnd + 1);
|
||||
prevURLEnd = urlEnd;
|
||||
altStart = -1;
|
||||
urlStart = -1;
|
||||
urlEnd = -1;
|
||||
continue;
|
||||
}
|
||||
if (md.charAt(i) == '[' && altStart == -1) {
|
||||
altStart = i + 1
|
||||
if (i > 0 && md.charAt(i-1) == '!') {
|
||||
altStart--
|
||||
}
|
||||
}
|
||||
if (i > 0 && md.charAt(i-1) == ']' && md.charAt(i) == '(' && urlStart == -1) {
|
||||
urlStart = i + 1
|
||||
}
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if (md.charAt(i) == "[" && altStart == -1) {
|
||||
altStart = i + 1;
|
||||
if (i > 0 && md.charAt(i - 1) == "!") {
|
||||
altStart--;
|
||||
}
|
||||
}
|
||||
if (i > 0 && md.charAt(i - 1) == "]" && md.charAt(i) == "(" && urlStart == -1) {
|
||||
urlStart = i + 1;
|
||||
}
|
||||
}
|
||||
if (prevURLEnd + 1 != md.length - 1) {
|
||||
out += md.substring(prevURLEnd+2)
|
||||
out += md.substring(prevURLEnd + 2);
|
||||
}
|
||||
if (out == "") {
|
||||
return md
|
||||
return md;
|
||||
}
|
||||
return out
|
||||
return out;
|
||||
}
|
||||
|
||||
export function stripMarkdown(md: string): string {
|
||||
|
||||
@@ -8,7 +8,6 @@ export interface Tab {
|
||||
postFunc?: () => void;
|
||||
}
|
||||
|
||||
|
||||
export class Tabs implements Tabs {
|
||||
private _current: string = "";
|
||||
private _baseOffset = -1;
|
||||
@@ -16,7 +15,7 @@ export class Tabs implements Tabs {
|
||||
pages: PageManager;
|
||||
|
||||
constructor() {
|
||||
this.tabs = new Map<string, Tab>;
|
||||
this.tabs = new Map<string, Tab>();
|
||||
this.pages = new PageManager({
|
||||
hideOthersOnPageShow: true,
|
||||
defaultName: "invites",
|
||||
@@ -24,7 +23,13 @@ export class Tabs implements Tabs {
|
||||
});
|
||||
}
|
||||
|
||||
addTab = (tabID: string, url: string, preFunc = () => void {}, postFunc = () => void {}, unloadFunc = () => void {}) => {
|
||||
addTab = (
|
||||
tabID: string,
|
||||
url: string,
|
||||
preFunc = () => void {},
|
||||
postFunc = () => void {},
|
||||
unloadFunc = () => void {},
|
||||
) => {
|
||||
let tab: Tab = {
|
||||
page: null,
|
||||
tabEl: document.getElementById("tab-" + tabID) as HTMLDivElement,
|
||||
@@ -37,12 +42,12 @@ export class Tabs implements Tabs {
|
||||
}
|
||||
tab.page = {
|
||||
name: tabID,
|
||||
title: document.title, /*FIXME: Get actual names from translations*/
|
||||
title: document.title /*FIXME: Get actual names from translations*/,
|
||||
url: url,
|
||||
show: () => {
|
||||
tab.buttonEl.classList.add("active", "~urge");
|
||||
tab.tabEl.classList.remove("unfocused");
|
||||
tab.buttonEl.parentElement.scrollTo(tab.buttonEl.offsetLeft-this._baseOffset, 0);
|
||||
tab.buttonEl.parentElement.scrollTo(tab.buttonEl.offsetLeft - this._baseOffset, 0);
|
||||
document.dispatchEvent(new CustomEvent("tab-change", { detail: tabID }));
|
||||
return true;
|
||||
},
|
||||
@@ -56,12 +61,18 @@ export class Tabs implements Tabs {
|
||||
shouldSkip: () => false,
|
||||
};
|
||||
this.pages.setPage(tab.page);
|
||||
tab.buttonEl.onclick = () => { this.switch(tabID); };
|
||||
tab.buttonEl.onclick = () => {
|
||||
this.switch(tabID);
|
||||
};
|
||||
this.tabs.set(tabID, tab);
|
||||
}
|
||||
};
|
||||
|
||||
get current(): string { return this._current; }
|
||||
set current(tabID: string) { this.switch(tabID); }
|
||||
get current(): string {
|
||||
return this._current;
|
||||
}
|
||||
set current(tabID: string) {
|
||||
this.switch(tabID);
|
||||
}
|
||||
|
||||
switch = (tabID: string, noRun: boolean = false) => {
|
||||
let t = this.tabs.get(tabID);
|
||||
@@ -71,8 +82,12 @@ export class Tabs implements Tabs {
|
||||
|
||||
this._current = t.page.name;
|
||||
|
||||
if (t.preFunc && !noRun) { t.preFunc(); }
|
||||
if (t.preFunc && !noRun) {
|
||||
t.preFunc();
|
||||
}
|
||||
this.pages.load(tabID);
|
||||
if (t.postFunc && !noRun) { t.postFunc(); }
|
||||
}
|
||||
if (t.postFunc && !noRun) {
|
||||
t.postFunc();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,21 +1,19 @@
|
||||
export class ThemeManager {
|
||||
|
||||
private _themeButton: HTMLElement = null;
|
||||
private _metaTag: HTMLMetaElement;
|
||||
|
||||
private _cssLightFiles: HTMLLinkElement[];
|
||||
private _cssDarkFiles: HTMLLinkElement[];
|
||||
|
||||
|
||||
private _beforeTransition = () => {
|
||||
const doc = document.documentElement;
|
||||
const onTransitionDone = () => {
|
||||
doc.classList.remove('nightwind');
|
||||
doc.removeEventListener('transitionend', onTransitionDone);
|
||||
}
|
||||
doc.addEventListener('transitionend', onTransitionDone);
|
||||
if (!doc.classList.contains('nightwind')) {
|
||||
doc.classList.add('nightwind');
|
||||
doc.classList.remove("nightwind");
|
||||
doc.removeEventListener("transitionend", onTransitionDone);
|
||||
};
|
||||
doc.addEventListener("transitionend", onTransitionDone);
|
||||
if (!doc.classList.contains("nightwind")) {
|
||||
doc.classList.add("nightwind");
|
||||
}
|
||||
};
|
||||
|
||||
@@ -40,20 +38,24 @@ export class ThemeManager {
|
||||
this._themeButton = button;
|
||||
this._themeButton.onclick = this.toggle;
|
||||
this._updateThemeIcon();
|
||||
}
|
||||
};
|
||||
|
||||
toggle = () => {
|
||||
this._toggle();
|
||||
if (this._themeButton) {
|
||||
this._updateThemeIcon();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
constructor(button?: HTMLElement) {
|
||||
this._metaTag = document.querySelector("meta[name=color-scheme]") as HTMLMetaElement;
|
||||
|
||||
this._cssLightFiles = Array.from(document.head.querySelectorAll("link[data-theme=light]")) as Array<HTMLLinkElement>;
|
||||
this._cssDarkFiles = Array.from(document.head.querySelectorAll("link[data-theme=dark]")) as Array<HTMLLinkElement>;
|
||||
this._cssLightFiles = Array.from(
|
||||
document.head.querySelectorAll("link[data-theme=light]"),
|
||||
) as Array<HTMLLinkElement>;
|
||||
this._cssDarkFiles = Array.from(
|
||||
document.head.querySelectorAll("link[data-theme=dark]"),
|
||||
) as Array<HTMLLinkElement>;
|
||||
this._cssLightFiles.forEach((el) => el.remove());
|
||||
this._cssDarkFiles.forEach((el) => el.remove());
|
||||
const theme = localStorage.getItem("theme");
|
||||
@@ -61,12 +63,11 @@ export class ThemeManager {
|
||||
this._enable(true);
|
||||
} else if (theme == "light") {
|
||||
this._enable(false);
|
||||
} else if (window.matchMedia('(prefers-color-scheme: dark)').media !== 'not all') {
|
||||
} else if (window.matchMedia("(prefers-color-scheme: dark)").media !== "not all") {
|
||||
this._enable(true);
|
||||
}
|
||||
|
||||
if (button)
|
||||
this.bindButton(button);
|
||||
if (button) this.bindButton(button);
|
||||
}
|
||||
|
||||
private _toggle = () => {
|
||||
@@ -74,16 +75,16 @@ export class ThemeManager {
|
||||
this._beforeTransition();
|
||||
const dark = !document.documentElement.classList.contains("dark");
|
||||
if (dark) {
|
||||
document.documentElement.classList.add('dark');
|
||||
document.documentElement.classList.add("dark");
|
||||
metaValue = "dark light";
|
||||
this._cssLightFiles.forEach((el) => el.remove());
|
||||
this._cssDarkFiles.forEach((el) => document.head.appendChild(el));
|
||||
} else {
|
||||
document.documentElement.classList.remove('dark');
|
||||
document.documentElement.classList.remove("dark");
|
||||
this._cssDarkFiles.forEach((el) => el.remove());
|
||||
this._cssLightFiles.forEach((el) => document.head.appendChild(el));
|
||||
}
|
||||
localStorage.setItem('theme', document.documentElement.classList.contains('dark') ? "dark" : "light");
|
||||
localStorage.setItem("theme", document.documentElement.classList.contains("dark") ? "dark" : "light");
|
||||
|
||||
// this._metaTag.setAttribute("content", metaValue);
|
||||
};
|
||||
@@ -92,7 +93,7 @@ export class ThemeManager {
|
||||
const mode = dark ? "dark" : "light";
|
||||
const opposite = dark ? "light" : "dark";
|
||||
|
||||
localStorage.setItem('theme', dark ? "dark" : "light");
|
||||
localStorage.setItem("theme", dark ? "dark" : "light");
|
||||
|
||||
this._beforeTransition();
|
||||
|
||||
@@ -117,4 +118,4 @@ export class ThemeManager {
|
||||
this._updateThemeIcon();
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@ export interface HiddenInputConf {
|
||||
customContainerHTML?: string;
|
||||
input?: string;
|
||||
clickAwayShouldSave?: boolean;
|
||||
};
|
||||
}
|
||||
|
||||
export class HiddenInputField {
|
||||
public static editClass = "ri-edit-line";
|
||||
@@ -13,17 +13,17 @@ export class HiddenInputField {
|
||||
|
||||
private _c: HiddenInputConf;
|
||||
private _input: HTMLInputElement;
|
||||
private _content: HTMLElement
|
||||
private _content: HTMLElement;
|
||||
private _toggle: HTMLElement;
|
||||
|
||||
previous: string;
|
||||
|
||||
constructor(c: HiddenInputConf) {
|
||||
this._c = c;
|
||||
if (!(this._c.customContainerHTML)) {
|
||||
if (!this._c.customContainerHTML) {
|
||||
this._c.customContainerHTML = `<span class="hidden-input-content"></span>`;
|
||||
}
|
||||
if (!(this._c.input)) {
|
||||
if (!this._c.input) {
|
||||
this._c.input = `<input type="text" class="field ~neutral @low max-w-24 hidden-input-input">`;
|
||||
}
|
||||
this._c.container.innerHTML = `
|
||||
@@ -49,22 +49,29 @@ export class HiddenInputField {
|
||||
e.preventDefault();
|
||||
this._toggle.click();
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
});
|
||||
|
||||
this.setEditing(false, true);
|
||||
}
|
||||
|
||||
// FIXME: not working
|
||||
outerClickListener = ((event: Event) => {
|
||||
if (!(event.target instanceof HTMLElement && (this._input.contains(event.target) || this._toggle.contains(event.target)))) {
|
||||
this.toggle(!(this._c.clickAwayShouldSave));
|
||||
if (
|
||||
!(
|
||||
event.target instanceof HTMLElement &&
|
||||
(this._input.contains(event.target) || this._toggle.contains(event.target))
|
||||
)
|
||||
) {
|
||||
this.toggle(!this._c.clickAwayShouldSave);
|
||||
}
|
||||
}).bind(this);
|
||||
|
||||
get editing(): boolean { return this._toggle.classList.contains(HiddenInputField.saveClass); }
|
||||
set editing(e: boolean) { this.setEditing(e); }
|
||||
get editing(): boolean {
|
||||
return this._toggle.classList.contains(HiddenInputField.saveClass);
|
||||
}
|
||||
set editing(e: boolean) {
|
||||
this.setEditing(e);
|
||||
}
|
||||
|
||||
setEditing(e: boolean, noEvent: boolean = false, noSave: boolean = false) {
|
||||
if (e) {
|
||||
@@ -84,11 +91,13 @@ export class HiddenInputField {
|
||||
// done by set value()
|
||||
// this._content.classList.remove("hidden");
|
||||
this._input.classList.add("hidden");
|
||||
if (this.value != this.previous && !noEvent && !noSave) this._c.onSet()
|
||||
if (this.value != this.previous && !noEvent && !noSave) this._c.onSet();
|
||||
}
|
||||
}
|
||||
|
||||
get value(): string { return this._content.textContent; };
|
||||
get value(): string {
|
||||
return this._content.textContent;
|
||||
}
|
||||
set value(v: string) {
|
||||
this._content.textContent = v;
|
||||
this._input.value = v;
|
||||
@@ -96,5 +105,7 @@ export class HiddenInputField {
|
||||
else this._content.classList.remove("hidden");
|
||||
}
|
||||
|
||||
toggle(noSave: boolean = false) { this.setEditing(!this.editing, false, noSave); }
|
||||
toggle(noSave: boolean = false) {
|
||||
this.setEditing(!this.editing, false, noSave);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,28 +13,35 @@ export class Updater implements updater {
|
||||
private _date: number;
|
||||
updateAvailable = false;
|
||||
|
||||
checkForUpdates = (run?: (req: XMLHttpRequest) => void) => _get("/config/update", null, (req: XMLHttpRequest) => {
|
||||
if (req.readyState == 4) {
|
||||
if (req.status != 200) {
|
||||
window.notifications.customError("errorCheckUpdate", window.lang.notif("errorCheckUpdate"));
|
||||
return
|
||||
checkForUpdates = (run?: (req: XMLHttpRequest) => void) =>
|
||||
_get("/config/update", null, (req: XMLHttpRequest) => {
|
||||
if (req.readyState == 4) {
|
||||
if (req.status != 200) {
|
||||
window.notifications.customError("errorCheckUpdate", window.lang.notif("errorCheckUpdate"));
|
||||
return;
|
||||
}
|
||||
let resp = req.response as updateDTO;
|
||||
if (resp.new) {
|
||||
this.update = resp.update;
|
||||
if (run) {
|
||||
run(req);
|
||||
}
|
||||
// } else {
|
||||
// window.notifications.customPositive("noUpdatesAvailable", "", window.lang.notif("noUpdatesAvailable"));
|
||||
}
|
||||
}
|
||||
let resp = req.response as updateDTO;
|
||||
if (resp.new) {
|
||||
this.update = resp.update;
|
||||
if (run) { run(req); }
|
||||
// } else {
|
||||
// window.notifications.customPositive("noUpdatesAvailable", "", window.lang.notif("noUpdatesAvailable"));
|
||||
}
|
||||
}
|
||||
});
|
||||
get date(): number { return this._date; }
|
||||
});
|
||||
get date(): number {
|
||||
return this._date;
|
||||
}
|
||||
set date(unix: number) {
|
||||
this._date = unix;
|
||||
document.getElementById("update-date").textContent = toDateString(new Date(this._date * 1000));
|
||||
}
|
||||
|
||||
get description(): string { return this._update.description; }
|
||||
get description(): string {
|
||||
return this._update.description;
|
||||
}
|
||||
set description(description: string) {
|
||||
this._update.description = description;
|
||||
const el = document.getElementById("update-description") as HTMLParagraphElement;
|
||||
@@ -46,35 +53,49 @@ export class Updater implements updater {
|
||||
}
|
||||
}
|
||||
|
||||
get changelog(): string { return this._update.changelog; }
|
||||
get changelog(): string {
|
||||
return this._update.changelog;
|
||||
}
|
||||
set changelog(changelog: string) {
|
||||
this._update.changelog = changelog;
|
||||
|
||||
document.getElementById("update-changelog").innerHTML = Marked.parse(changelog);
|
||||
}
|
||||
|
||||
get version(): string { return this._update.version; }
|
||||
get version(): string {
|
||||
return this._update.version;
|
||||
}
|
||||
set version(version: string) {
|
||||
this._update.version = version;
|
||||
document.getElementById("update-version").textContent = version;
|
||||
}
|
||||
|
||||
get commit(): string { return this._update.commit; }
|
||||
get commit(): string {
|
||||
return this._update.commit;
|
||||
}
|
||||
set commit(commit: string) {
|
||||
this._update.commit = commit;
|
||||
document.getElementById("update-commit").textContent = commit.slice(0, 7);
|
||||
}
|
||||
|
||||
get link(): string { return this._update.link; }
|
||||
get link(): string {
|
||||
return this._update.link;
|
||||
}
|
||||
set link(link: string) {
|
||||
this._update.link = link;
|
||||
(document.getElementById("update-version") as HTMLAnchorElement).href = link;
|
||||
}
|
||||
|
||||
get download_link(): string { return this._update.download_link; }
|
||||
set download_link(link: string) { this._update.download_link = link; }
|
||||
get download_link(): string {
|
||||
return this._update.download_link;
|
||||
}
|
||||
set download_link(link: string) {
|
||||
this._update.download_link = link;
|
||||
}
|
||||
|
||||
get can_update(): boolean { return this._update.can_update; }
|
||||
get can_update(): boolean {
|
||||
return this._update.can_update;
|
||||
}
|
||||
set can_update(can: boolean) {
|
||||
this._update.can_update = can;
|
||||
const download = document.getElementById("update-download") as HTMLSpanElement;
|
||||
@@ -89,7 +110,9 @@ export class Updater implements updater {
|
||||
}
|
||||
}
|
||||
|
||||
get update(): Update { return this._update; }
|
||||
get update(): Update {
|
||||
return this._update;
|
||||
}
|
||||
set update(update: Update) {
|
||||
this._update = update;
|
||||
this.version = update.version;
|
||||
@@ -106,24 +129,36 @@ export class Updater implements updater {
|
||||
const update = document.getElementById("update-update") as HTMLSpanElement;
|
||||
update.onclick = () => {
|
||||
toggleLoader(update);
|
||||
_post("/config/update", null, (req: XMLHttpRequest) => {
|
||||
if (req.readyState == 4) {
|
||||
toggleLoader(update);
|
||||
const success = req.response["success"] as Boolean;
|
||||
if (req.status == 500 && success) {
|
||||
window.notifications.customSuccess("applyUpdate", window.lang.notif("updateAppliedRefresh"));
|
||||
} else if (req.status != 200) {
|
||||
window.notifications.customError("applyUpdateError", window.lang.notif("errorApplyUpdate"));
|
||||
} else {
|
||||
_post(
|
||||
"/config/update",
|
||||
null,
|
||||
(req: XMLHttpRequest) => {
|
||||
if (req.readyState == 4) {
|
||||
toggleLoader(update);
|
||||
const success = req.response["success"] as Boolean;
|
||||
if (req.status == 500 && success) {
|
||||
window.notifications.customSuccess(
|
||||
"applyUpdate",
|
||||
window.lang.notif("updateAppliedRefresh"),
|
||||
);
|
||||
} else if (req.status != 200) {
|
||||
window.notifications.customError("applyUpdateError", window.lang.notif("errorApplyUpdate"));
|
||||
} else {
|
||||
window.notifications.customSuccess(
|
||||
"applyUpdate",
|
||||
window.lang.notif("updateAppliedRefresh"),
|
||||
);
|
||||
}
|
||||
window.modals.updateInfo.close();
|
||||
}
|
||||
},
|
||||
true,
|
||||
(req: XMLHttpRequest) => {
|
||||
if (req.status == 0) {
|
||||
window.notifications.customSuccess("applyUpdate", window.lang.notif("updateAppliedRefresh"));
|
||||
}
|
||||
window.modals.updateInfo.close();
|
||||
}
|
||||
}, true, (req: XMLHttpRequest) => {
|
||||
if (req.status == 0) {
|
||||
window.notifications.customSuccess("applyUpdate", window.lang.notif("updateAppliedRefresh"));
|
||||
}
|
||||
});
|
||||
},
|
||||
);
|
||||
};
|
||||
this.checkForUpdates(() => {
|
||||
this.updateAvailable = true;
|
||||
|
||||
@@ -20,7 +20,7 @@ interface pwValStrings {
|
||||
lowercase: pwValString;
|
||||
number: pwValString;
|
||||
special: pwValString;
|
||||
[ type: string ]: pwValString;
|
||||
[type: string]: pwValString;
|
||||
}
|
||||
|
||||
declare var window: valWindow;
|
||||
@@ -32,7 +32,9 @@ class Requirement {
|
||||
private _valid: HTMLSpanElement;
|
||||
private _li: HTMLLIElement;
|
||||
|
||||
get valid(): boolean { return this._valid.classList.contains("~positive"); }
|
||||
get valid(): boolean {
|
||||
return this._valid.classList.contains("~positive");
|
||||
}
|
||||
set valid(state: boolean) {
|
||||
if (state) {
|
||||
this._valid.classList.add("~positive");
|
||||
@@ -57,12 +59,14 @@ class Requirement {
|
||||
if (this._minCount == 1) {
|
||||
text = window.validationStrings[this._name].singular.replace("{n}", "1");
|
||||
} else {
|
||||
text = window.validationStrings[this._name].plural.replace("{n}", ""+this._minCount);
|
||||
text = window.validationStrings[this._name].plural.replace("{n}", "" + this._minCount);
|
||||
}
|
||||
this._content.textContent = text;
|
||||
}
|
||||
|
||||
validate = (count: number) => { this.valid = (count >= this._minCount); }
|
||||
validate = (count: number) => {
|
||||
this.valid = count >= this._minCount;
|
||||
};
|
||||
}
|
||||
|
||||
export interface ValidatorConf {
|
||||
@@ -73,8 +77,12 @@ export interface ValidatorConf {
|
||||
validatorFunc?: (oncomplete: (valid: boolean) => void) => void;
|
||||
}
|
||||
|
||||
export interface Validation { [name: string]: number }
|
||||
export interface Requirements { [category: string]: Requirement };
|
||||
export interface Validation {
|
||||
[name: string]: number;
|
||||
}
|
||||
export interface Requirements {
|
||||
[category: string]: Requirement;
|
||||
}
|
||||
|
||||
export class Validator {
|
||||
private _conf: ValidatorConf;
|
||||
@@ -82,29 +90,29 @@ export class Validator {
|
||||
private _defaultPwValStrings: pwValStrings = {
|
||||
length: {
|
||||
singular: "Must have at least {n} character",
|
||||
plural: "Must have at least {n} characters"
|
||||
plural: "Must have at least {n} characters",
|
||||
},
|
||||
uppercase: {
|
||||
singular: "Must have at least {n} uppercase character",
|
||||
plural: "Must have at least {n} uppercase characters"
|
||||
plural: "Must have at least {n} uppercase characters",
|
||||
},
|
||||
lowercase: {
|
||||
singular: "Must have at least {n} lowercase character",
|
||||
plural: "Must have at least {n} lowercase characters"
|
||||
plural: "Must have at least {n} lowercase characters",
|
||||
},
|
||||
number: {
|
||||
singular: "Must have at least {n} number",
|
||||
plural: "Must have at least {n} numbers"
|
||||
plural: "Must have at least {n} numbers",
|
||||
},
|
||||
special: {
|
||||
singular: "Must have at least {n} special character",
|
||||
plural: "Must have at least {n} special characters"
|
||||
}
|
||||
plural: "Must have at least {n} special characters",
|
||||
},
|
||||
};
|
||||
|
||||
private _checkPasswords = () => {
|
||||
return this._conf.passwordField.value == this._conf.rePasswordField.value;
|
||||
}
|
||||
};
|
||||
|
||||
validate = () => {
|
||||
const pw = this._checkPasswords();
|
||||
@@ -125,33 +133,44 @@ export class Validator {
|
||||
});
|
||||
};
|
||||
|
||||
private _isInt = (s: string): boolean => { return (s >= '0' && s <= '9'); }
|
||||
private _isInt = (s: string): boolean => {
|
||||
return s >= "0" && s <= "9";
|
||||
};
|
||||
|
||||
private _testStrings = (f: pwValString): boolean => {
|
||||
const testString = (s: string): boolean => {
|
||||
if (s == "" || !s.includes("{n}")) { return false; }
|
||||
if (s == "" || !s.includes("{n}")) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
};
|
||||
return testString(f.singular) && testString(f.plural);
|
||||
}
|
||||
};
|
||||
|
||||
private _validate = (s: string): Validation => {
|
||||
let v: Validation = {};
|
||||
for (let criteria of ["length", "lowercase", "uppercase", "number", "special"]) { v[criteria] = 0; }
|
||||
for (let criteria of ["length", "lowercase", "uppercase", "number", "special"]) {
|
||||
v[criteria] = 0;
|
||||
}
|
||||
v["length"] = s.length;
|
||||
for (let c of s) {
|
||||
if (this._isInt(c)) { v["number"]++; }
|
||||
else {
|
||||
if (this._isInt(c)) {
|
||||
v["number"]++;
|
||||
} else {
|
||||
const upper = c.toUpperCase();
|
||||
if (upper == c.toLowerCase()) { v["special"]++; }
|
||||
else {
|
||||
if (upper == c) { v["uppercase"]++; }
|
||||
else if (upper != c) { v["lowercase"]++; }
|
||||
if (upper == c.toLowerCase()) {
|
||||
v["special"]++;
|
||||
} else {
|
||||
if (upper == c) {
|
||||
v["uppercase"]++;
|
||||
} else if (upper != c) {
|
||||
v["lowercase"]++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return v
|
||||
}
|
||||
return v;
|
||||
};
|
||||
|
||||
private _bindRequirements = () => {
|
||||
for (let category in window.validationStrings) {
|
||||
@@ -159,17 +178,21 @@ export class Validator {
|
||||
window.validationStrings[category] = this._defaultPwValStrings[category];
|
||||
}
|
||||
const el = document.getElementById("requirement-" + category);
|
||||
if (typeof(el) === 'undefined' || el == null) continue;
|
||||
if (typeof el === "undefined" || el == null) continue;
|
||||
this._requirements[category] = new Requirement(category, el as HTMLLIElement);
|
||||
}
|
||||
};
|
||||
|
||||
get requirements(): Requirements { return this._requirements };
|
||||
get requirements(): Requirements {
|
||||
return this._requirements;
|
||||
}
|
||||
|
||||
constructor(conf: ValidatorConf) {
|
||||
this._conf = conf;
|
||||
if (!(this._conf.validatorFunc)) {
|
||||
this._conf.validatorFunc = (oncomplete: (valid: boolean) => void) => { oncomplete(true); };
|
||||
if (!this._conf.validatorFunc) {
|
||||
this._conf.validatorFunc = (oncomplete: (valid: boolean) => void) => {
|
||||
oncomplete(true);
|
||||
};
|
||||
}
|
||||
this._conf.rePasswordField.addEventListener("keyup", this.validate);
|
||||
this._conf.passwordField.addEventListener("keyup", this.validate);
|
||||
|
||||
61
ts/pwr.ts
61
ts/pwr.ts
@@ -10,7 +10,7 @@ interface formWindow extends Window {
|
||||
telegramModal: Modal;
|
||||
discordModal: Modal;
|
||||
matrixModal: Modal;
|
||||
confirmationModal: Modal
|
||||
confirmationModal: Modal;
|
||||
code: string;
|
||||
messages: { [key: string]: string };
|
||||
confirmation: boolean;
|
||||
@@ -66,7 +66,7 @@ let validatorConf: ValidatorConf = {
|
||||
rePasswordField: rePasswordField,
|
||||
submitInput: submitInput,
|
||||
submitButton: submitSpan,
|
||||
validatorFunc: baseValidator
|
||||
validatorFunc: baseValidator,
|
||||
};
|
||||
|
||||
var validator = new Validator(validatorConf);
|
||||
@@ -90,7 +90,7 @@ form.onsubmit = (event: Event) => {
|
||||
const params = new URLSearchParams(window.location.search);
|
||||
let send: sendDTO = {
|
||||
pin: params.get("pin"),
|
||||
password: passwordField.value
|
||||
password: passwordField.value,
|
||||
};
|
||||
if (window.captcha) {
|
||||
if (window.reCAPTCHA) {
|
||||
@@ -99,13 +99,34 @@ form.onsubmit = (event: Event) => {
|
||||
send.captcha_text = captcha.input.value;
|
||||
}
|
||||
}
|
||||
_post("/reset", send, (req: XMLHttpRequest) => {
|
||||
if (req.readyState == 4) {
|
||||
removeLoader(submitSpan);
|
||||
if (req.status == 400) {
|
||||
if (req.response["error"] as string) {
|
||||
_post(
|
||||
"/reset",
|
||||
send,
|
||||
(req: XMLHttpRequest) => {
|
||||
if (req.readyState == 4) {
|
||||
removeLoader(submitSpan);
|
||||
if (req.status == 400) {
|
||||
if (req.response["error"] as string) {
|
||||
const old = submitSpan.textContent;
|
||||
submitSpan.textContent = window.messages[req.response["error"]];
|
||||
submitSpan.classList.add("~critical");
|
||||
submitSpan.classList.remove("~urge");
|
||||
setTimeout(() => {
|
||||
submitSpan.classList.add("~urge");
|
||||
submitSpan.classList.remove("~critical");
|
||||
submitSpan.textContent = old;
|
||||
}, 2000);
|
||||
} else {
|
||||
for (let type in req.response) {
|
||||
if (requirements[type]) {
|
||||
requirements[type].valid = req.response[type] as boolean;
|
||||
}
|
||||
}
|
||||
}
|
||||
return;
|
||||
} else if (req.status != 200) {
|
||||
const old = submitSpan.textContent;
|
||||
submitSpan.textContent = window.messages[req.response["error"]];
|
||||
submitSpan.textContent = window.messages["errorUnknown"];
|
||||
submitSpan.classList.add("~critical");
|
||||
submitSpan.classList.remove("~urge");
|
||||
setTimeout(() => {
|
||||
@@ -114,26 +135,12 @@ form.onsubmit = (event: Event) => {
|
||||
submitSpan.textContent = old;
|
||||
}, 2000);
|
||||
} else {
|
||||
for (let type in req.response) {
|
||||
if (requirements[type]) { requirements[type].valid = req.response[type] as boolean; }
|
||||
}
|
||||
window.successModal.show();
|
||||
}
|
||||
return;
|
||||
} else if (req.status != 200) {
|
||||
const old = submitSpan.textContent;
|
||||
submitSpan.textContent = window.messages["errorUnknown"];
|
||||
submitSpan.classList.add("~critical");
|
||||
submitSpan.classList.remove("~urge");
|
||||
setTimeout(() => {
|
||||
submitSpan.classList.add("~urge");
|
||||
submitSpan.classList.remove("~critical");
|
||||
submitSpan.textContent = old;
|
||||
}, 2000);
|
||||
} else {
|
||||
window.successModal.show();
|
||||
}
|
||||
}
|
||||
}, true);
|
||||
},
|
||||
true,
|
||||
);
|
||||
};
|
||||
|
||||
validator.validate();
|
||||
|
||||
552
ts/setup.ts
552
ts/setup.ts
@@ -11,13 +11,15 @@ declare var window: sWindow;
|
||||
|
||||
const theme = new ThemeManager(document.getElementById("button-theme"));
|
||||
|
||||
window.notifications = new notificationBox(document.getElementById('notification-box') as HTMLDivElement, 5);
|
||||
|
||||
window.notifications = new notificationBox(document.getElementById("notification-box") as HTMLDivElement, 5);
|
||||
|
||||
const get = (id: string): HTMLElement => document.getElementById(id);
|
||||
const text = (id: string, val: string) => { document.getElementById(id).textContent = val; };
|
||||
const html = (id: string, val: string) => { document.getElementById(id).innerHTML = val; };
|
||||
|
||||
const text = (id: string, val: string) => {
|
||||
document.getElementById(id).textContent = val;
|
||||
};
|
||||
const html = (id: string, val: string) => {
|
||||
document.getElementById(id).innerHTML = val;
|
||||
};
|
||||
|
||||
// FIXME: Reuse setting types from ts/modules/settings.ts
|
||||
interface boolEvent extends Event {
|
||||
@@ -26,18 +28,35 @@ interface boolEvent extends Event {
|
||||
|
||||
class Input {
|
||||
private _el: HTMLInputElement;
|
||||
get value(): string { return ""+this._el.value; }
|
||||
set value(v: string) { this._el.value = v; }
|
||||
get value(): string {
|
||||
return "" + this._el.value;
|
||||
}
|
||||
set value(v: string) {
|
||||
this._el.value = v;
|
||||
}
|
||||
// Nothing depends on input, but we add an empty broadcast function so we can just loop over all settings to fix dependents on start.
|
||||
broadcast = () => {}
|
||||
constructor(el: HTMLElement, placeholder?: any, value?: any, depends?: string, dependsTrue?: boolean, section?: string) {
|
||||
broadcast = () => {};
|
||||
constructor(
|
||||
el: HTMLElement,
|
||||
placeholder?: any,
|
||||
value?: any,
|
||||
depends?: string,
|
||||
dependsTrue?: boolean,
|
||||
section?: string,
|
||||
) {
|
||||
this._el = el as HTMLInputElement;
|
||||
if (placeholder) { this._el.placeholder = placeholder; }
|
||||
if (value) { this.value = value; }
|
||||
if (placeholder) {
|
||||
this._el.placeholder = placeholder;
|
||||
}
|
||||
if (value) {
|
||||
this.value = value;
|
||||
}
|
||||
if (depends) {
|
||||
document.addEventListener(`settings-${section}-${depends}`, (event: boolEvent) => {
|
||||
let el = this._el as HTMLElement;
|
||||
if (el.parentElement.tagName == "LABEL") { el = el.parentElement; }
|
||||
if (el.parentElement.tagName == "LABEL") {
|
||||
el = el.parentElement;
|
||||
}
|
||||
if (event.detail !== dependsTrue) {
|
||||
el.classList.add("unfocused");
|
||||
} else {
|
||||
@@ -51,8 +70,12 @@ class Input {
|
||||
class Checkbox {
|
||||
private _el: HTMLInputElement;
|
||||
private _hideEl: HTMLElement;
|
||||
get value(): string { return this._el.checked ? "true" : "false"; }
|
||||
set value(v: string) { this._el.checked = (v == "true") ? true : false; }
|
||||
get value(): string {
|
||||
return this._el.checked ? "true" : "false";
|
||||
}
|
||||
set value(v: string) {
|
||||
this._el.checked = v == "true" ? true : false;
|
||||
}
|
||||
|
||||
private _section: string;
|
||||
private _setting: string;
|
||||
@@ -62,10 +85,10 @@ class Checkbox {
|
||||
state = false;
|
||||
}
|
||||
if (this._section && this._setting) {
|
||||
const ev = new CustomEvent(`settings-${this._section}-${this._setting}`, { "detail": state })
|
||||
const ev = new CustomEvent(`settings-${this._section}-${this._setting}`, { detail: state });
|
||||
document.dispatchEvent(ev);
|
||||
}
|
||||
}
|
||||
};
|
||||
set onchange(f: () => void) {
|
||||
this._el.addEventListener("change", f);
|
||||
}
|
||||
@@ -110,9 +133,11 @@ class Checkbox {
|
||||
|
||||
class BoolRadios {
|
||||
private _els: NodeListOf<HTMLInputElement>;
|
||||
get value(): string { return this._els[0].checked ? "true" : "false" }
|
||||
get value(): string {
|
||||
return this._els[0].checked ? "true" : "false";
|
||||
}
|
||||
set value(v: string) {
|
||||
const bool = (v == "true") ? true : false;
|
||||
const bool = v == "true" ? true : false;
|
||||
this._els[0].checked = bool;
|
||||
this._els[1].checked = !bool;
|
||||
}
|
||||
@@ -121,10 +146,10 @@ class BoolRadios {
|
||||
private _setting: string;
|
||||
broadcast = () => {
|
||||
if (this._section && this._setting) {
|
||||
const ev = new CustomEvent(`settings-${this._section}-${this._setting}`, { "detail": this._els[0].checked })
|
||||
const ev = new CustomEvent(`settings-${this._section}-${this._setting}`, { detail: this._els[0].checked });
|
||||
document.dispatchEvent(ev);
|
||||
}
|
||||
}
|
||||
};
|
||||
constructor(name: string, depends?: string, dependsTrue?: boolean, section?: string, setting?: string) {
|
||||
this._els = document.getElementsByName(name) as NodeListOf<HTMLInputElement>;
|
||||
if (section && setting) {
|
||||
@@ -177,14 +202,18 @@ class BoolRadios {
|
||||
|
||||
class Select {
|
||||
private _el: HTMLSelectElement;
|
||||
get value(): string { return this._el.value; }
|
||||
set value(v: string) { this._el.value = v; }
|
||||
get value(): string {
|
||||
return this._el.value;
|
||||
}
|
||||
set value(v: string) {
|
||||
this._el.value = v;
|
||||
}
|
||||
add = (val: string, label: string) => {
|
||||
const item = document.createElement("option") as HTMLOptionElement;
|
||||
item.value = val;
|
||||
item.textContent = label;
|
||||
this._el.appendChild(item);
|
||||
}
|
||||
};
|
||||
set onchange(f: () => void) {
|
||||
this._el.addEventListener("change", f);
|
||||
}
|
||||
@@ -193,10 +222,12 @@ class Select {
|
||||
private _setting: string;
|
||||
broadcast = () => {
|
||||
if (this._section && this._setting) {
|
||||
const ev = new CustomEvent(`settings-${this._section}-${this._setting}`, { "detail": this.value ? true : false })
|
||||
const ev = new CustomEvent(`settings-${this._section}-${this._setting}`, {
|
||||
detail: this.value ? true : false,
|
||||
});
|
||||
document.dispatchEvent(ev);
|
||||
}
|
||||
}
|
||||
};
|
||||
constructor(el: HTMLElement, depends?: string, dependsTrue?: boolean, section?: string, setting?: string) {
|
||||
this._el = el as HTMLSelectElement;
|
||||
if (section && setting) {
|
||||
@@ -221,137 +252,194 @@ class Select {
|
||||
}
|
||||
|
||||
class LangSelect extends Select {
|
||||
constructor(page: string, el: HTMLElement, depends?: string, dependsTrue?: boolean, section?: string, setting?: string) {
|
||||
constructor(
|
||||
page: string,
|
||||
el: HTMLElement,
|
||||
depends?: string,
|
||||
dependsTrue?: boolean,
|
||||
section?: string,
|
||||
setting?: string,
|
||||
) {
|
||||
super(el, depends, dependsTrue, section, setting);
|
||||
_get("/lang/" + page, null, (req: XMLHttpRequest) => {
|
||||
if (req.readyState == 4 && req.status == 200) {
|
||||
for (let code in req.response) {
|
||||
this.add(code, req.response[code]);
|
||||
_get(
|
||||
"/lang/" + page,
|
||||
null,
|
||||
(req: XMLHttpRequest) => {
|
||||
if (req.readyState == 4 && req.status == 200) {
|
||||
for (let code in req.response) {
|
||||
this.add(code, req.response[code]);
|
||||
}
|
||||
this.value = "en-us";
|
||||
}
|
||||
this.value = "en-us";
|
||||
}
|
||||
}, true);
|
||||
},
|
||||
true,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const replaceLink = (elName: string, sect: string, name: string, url: string, text: string) => html(elName, window.lang.var(sect, name, `<a class="underline" target="_blank" href="${url}">${text}</a>`));
|
||||
const replaceLink = (elName: string, sect: string, name: string, url: string, text: string) =>
|
||||
html(elName, window.lang.var(sect, name, `<a class="underline" target="_blank" href="${url}">${text}</a>`));
|
||||
|
||||
window.lang = new lang(window.langFile as LangFile);
|
||||
replaceLink("language-description", "language", "description", "https://weblate.jfa-go.com", "Weblate");
|
||||
replaceLink("email-description", "email", "description", "https://mailgun.com", "Mailgun");
|
||||
replaceLink("email-dateformat-notice", "email", "dateFormatNotice", "https://strftime.timpetricola.com/", "strftime.timpetricola.com");
|
||||
replaceLink(
|
||||
"email-dateformat-notice",
|
||||
"email",
|
||||
"dateFormatNotice",
|
||||
"https://strftime.timpetricola.com/",
|
||||
"strftime.timpetricola.com",
|
||||
);
|
||||
replaceLink("updates-description", "updates", "description", "https://builds.hrfee.dev/view/hrfee/jfa-go", "buildrone");
|
||||
replaceLink("messages-description", "messages", "description", "https://wiki.jfa-go.com", "Wiki");
|
||||
replaceLink("password_resets-more-info", "passwordResets", "moreInfo", "https://wiki.jfa-go.com/docs/pwr/", "wiki.jfa-go.com");
|
||||
replaceLink("ombi-stability-warning", "ombi", "stabilityWarning", "https://wiki.jfa-go.com/docs/ombi/", "wiki.jfa-go.com");
|
||||
replaceLink(
|
||||
"password_resets-more-info",
|
||||
"passwordResets",
|
||||
"moreInfo",
|
||||
"https://wiki.jfa-go.com/docs/pwr/",
|
||||
"wiki.jfa-go.com",
|
||||
);
|
||||
replaceLink(
|
||||
"ombi-stability-warning",
|
||||
"ombi",
|
||||
"stabilityWarning",
|
||||
"https://wiki.jfa-go.com/docs/ombi/",
|
||||
"wiki.jfa-go.com",
|
||||
);
|
||||
|
||||
const settings = {
|
||||
"jellyfin": {
|
||||
"type": new Select(get("jellyfin-type")),
|
||||
"server": new Input(get("jellyfin-server")),
|
||||
"public_server": new Input(get("jellyfin-public_server")),
|
||||
"username": new Input(get("jellyfin-username")),
|
||||
"password": new Input(get("jellyfin-password")),
|
||||
"substitute_jellyfin_strings": new Input(get("jellyfin-substitute_jellyfin_strings"))
|
||||
jellyfin: {
|
||||
type: new Select(get("jellyfin-type")),
|
||||
server: new Input(get("jellyfin-server")),
|
||||
public_server: new Input(get("jellyfin-public_server")),
|
||||
username: new Input(get("jellyfin-username")),
|
||||
password: new Input(get("jellyfin-password")),
|
||||
substitute_jellyfin_strings: new Input(get("jellyfin-substitute_jellyfin_strings")),
|
||||
},
|
||||
"updates": {
|
||||
"enabled": new Checkbox(get("updates-enabled"), "", false, "updates", "enabled"),
|
||||
"channel": new Select(get("updates-channel"), "enabled", true, "updates")
|
||||
updates: {
|
||||
enabled: new Checkbox(get("updates-enabled"), "", false, "updates", "enabled"),
|
||||
channel: new Select(get("updates-channel"), "enabled", true, "updates"),
|
||||
},
|
||||
"ui": {
|
||||
"host": new Input(get("ui-host")),
|
||||
"port": new Input(get("ui-port")),
|
||||
"url_base": new Input(get("ui-url_base")),
|
||||
"jfa_url": new Input(get("ui-jfa_url")),
|
||||
"theme": new Select(get("ui-theme")),
|
||||
ui: {
|
||||
host: new Input(get("ui-host")),
|
||||
port: new Input(get("ui-port")),
|
||||
url_base: new Input(get("ui-url_base")),
|
||||
jfa_url: new Input(get("ui-jfa_url")),
|
||||
theme: new Select(get("ui-theme")),
|
||||
"language-form": new LangSelect("form", get("ui-language-form")),
|
||||
"language-admin": new LangSelect("admin", get("ui-language-admin")),
|
||||
"jellyfin_login": new BoolRadios("ui-jellyfin_login", "", false, "ui", "jellyfin_login"),
|
||||
"admin_only": new Checkbox(get("ui-admin_only"), "jellyfin_login", true, "ui"),
|
||||
"allow_all": new Checkbox(get("ui-allow_all"), "jellyfin_login", true, "ui"),
|
||||
"username": new Input(get("ui-username"), "", "", "jellyfin_login", false, "ui"),
|
||||
"password": new Input(get("ui-password"), "", "", "jellyfin_login", false, "ui"),
|
||||
"email": new Input(get("ui-email"), "", "", "jellyfin_login", false, "ui"),
|
||||
"contact_message": new Input(get("ui-contact_message"), window.messages["ui"]["contact_message"]),
|
||||
"help_message": new Input(get("ui-help_message"), window.messages["ui"]["help_message"]),
|
||||
"success_message": new Input(get("ui-success_message"), window.messages["ui"]["success_message"])
|
||||
jellyfin_login: new BoolRadios("ui-jellyfin_login", "", false, "ui", "jellyfin_login"),
|
||||
admin_only: new Checkbox(get("ui-admin_only"), "jellyfin_login", true, "ui"),
|
||||
allow_all: new Checkbox(get("ui-allow_all"), "jellyfin_login", true, "ui"),
|
||||
username: new Input(get("ui-username"), "", "", "jellyfin_login", false, "ui"),
|
||||
password: new Input(get("ui-password"), "", "", "jellyfin_login", false, "ui"),
|
||||
email: new Input(get("ui-email"), "", "", "jellyfin_login", false, "ui"),
|
||||
contact_message: new Input(get("ui-contact_message"), window.messages["ui"]["contact_message"]),
|
||||
help_message: new Input(get("ui-help_message"), window.messages["ui"]["help_message"]),
|
||||
success_message: new Input(get("ui-success_message"), window.messages["ui"]["success_message"]),
|
||||
},
|
||||
"password_validation": {
|
||||
"enabled": new Checkbox(get("password_validation-enabled"), "", false, "password_validation", "enabled"),
|
||||
"min_length": new Input(get("password_validation-min_length"), "", 8, "enabled", true, "password_validation"),
|
||||
"upper": new Input(get("password_validation-upper"), "", 1, "enabled", true, "password_validation"),
|
||||
"lower": new Input(get("password_validation-lower"), "", 0, "enabled", true, "password_validation"),
|
||||
"number": new Input(get("password_validation-number"), "", 1, "enabled", true, "password_validation"),
|
||||
"special": new Input(get("password_validation-special"), "", 0, "enabled", true, "password_validation")
|
||||
password_validation: {
|
||||
enabled: new Checkbox(get("password_validation-enabled"), "", false, "password_validation", "enabled"),
|
||||
min_length: new Input(get("password_validation-min_length"), "", 8, "enabled", true, "password_validation"),
|
||||
upper: new Input(get("password_validation-upper"), "", 1, "enabled", true, "password_validation"),
|
||||
lower: new Input(get("password_validation-lower"), "", 0, "enabled", true, "password_validation"),
|
||||
number: new Input(get("password_validation-number"), "", 1, "enabled", true, "password_validation"),
|
||||
special: new Input(get("password_validation-special"), "", 0, "enabled", true, "password_validation"),
|
||||
},
|
||||
"messages": {
|
||||
"enabled": new Checkbox(get("messages-enabled"), "", false, "messages", "enabled"),
|
||||
"use_24h": new BoolRadios("email-24h", "enabled", true, "messages"),
|
||||
"date_format": new Input(get("email-date_format"), "", "%d/%m/%y", "enabled", true, "messages"),
|
||||
"message": new Input(get("email-message"), window.messages["messages"]["message"], "", "enabled", true, "messages")
|
||||
messages: {
|
||||
enabled: new Checkbox(get("messages-enabled"), "", false, "messages", "enabled"),
|
||||
use_24h: new BoolRadios("email-24h", "enabled", true, "messages"),
|
||||
date_format: new Input(get("email-date_format"), "", "%d/%m/%y", "enabled", true, "messages"),
|
||||
message: new Input(
|
||||
get("email-message"),
|
||||
window.messages["messages"]["message"],
|
||||
"",
|
||||
"enabled",
|
||||
true,
|
||||
"messages",
|
||||
),
|
||||
},
|
||||
"email": {
|
||||
"language": new LangSelect("email", get("email-language")),
|
||||
"no_username": new Checkbox(get("email-no_username"), "method", true, "email"),
|
||||
"method": new Select(get("email-method"), "", false, "email", "method"),
|
||||
"address": new Input(get("email-address"), "jellyfin@jellyf.in", "", "method", true, "email"),
|
||||
"from": new Input(get("email-from"), "", "Jellyfin", "method", true, "email")
|
||||
email: {
|
||||
language: new LangSelect("email", get("email-language")),
|
||||
no_username: new Checkbox(get("email-no_username"), "method", true, "email"),
|
||||
method: new Select(get("email-method"), "", false, "email", "method"),
|
||||
address: new Input(get("email-address"), "jellyfin@jellyf.in", "", "method", true, "email"),
|
||||
from: new Input(get("email-from"), "", "Jellyfin", "method", true, "email"),
|
||||
},
|
||||
"password_resets": {
|
||||
"enabled": new Checkbox(get("password_resets-enabled"), "", false, "password_resets", "enabled"),
|
||||
"watch_directory": new Input(get("password_resets-watch_directory"), "", "", "enabled", true, "password_resets"),
|
||||
"subject": new Input(get("password_resets-subject"), "", "", "enabled", true, "password_resets"),
|
||||
"link_reset": new Checkbox(get("password_resets-link_reset"), "enabled", true, "password_resets", "link_reset"),
|
||||
"language": new LangSelect("pwr", get("password_resets-language"), "link_reset", true, "password_resets", "language"),
|
||||
"set_password": new Checkbox(get("password_resets-set_password"), "link_reset", true, "password_resets", "set_password")
|
||||
password_resets: {
|
||||
enabled: new Checkbox(get("password_resets-enabled"), "", false, "password_resets", "enabled"),
|
||||
watch_directory: new Input(get("password_resets-watch_directory"), "", "", "enabled", true, "password_resets"),
|
||||
subject: new Input(get("password_resets-subject"), "", "", "enabled", true, "password_resets"),
|
||||
link_reset: new Checkbox(get("password_resets-link_reset"), "enabled", true, "password_resets", "link_reset"),
|
||||
language: new LangSelect(
|
||||
"pwr",
|
||||
get("password_resets-language"),
|
||||
"link_reset",
|
||||
true,
|
||||
"password_resets",
|
||||
"language",
|
||||
),
|
||||
set_password: new Checkbox(
|
||||
get("password_resets-set_password"),
|
||||
"link_reset",
|
||||
true,
|
||||
"password_resets",
|
||||
"set_password",
|
||||
),
|
||||
},
|
||||
"notifications": {
|
||||
"enabled": new Checkbox(get("notifications-enabled"))
|
||||
notifications: {
|
||||
enabled: new Checkbox(get("notifications-enabled")),
|
||||
},
|
||||
"user_page": {
|
||||
"enabled": new Checkbox(get("userpage-enabled"))
|
||||
user_page: {
|
||||
enabled: new Checkbox(get("userpage-enabled")),
|
||||
},
|
||||
"welcome_email": {
|
||||
"enabled": new Checkbox(get("welcome_email-enabled"), "", false, "welcome_email", "enabled"),
|
||||
"subject": new Input(get("welcome_email-subject"), "", "", "enabled", true, "welcome_email")
|
||||
welcome_email: {
|
||||
enabled: new Checkbox(get("welcome_email-enabled"), "", false, "welcome_email", "enabled"),
|
||||
subject: new Input(get("welcome_email-subject"), "", "", "enabled", true, "welcome_email"),
|
||||
},
|
||||
"invite_emails": {
|
||||
"enabled": new Checkbox(get("invite_emails-enabled"), "", false, "invite_emails", "enabled"),
|
||||
"subject": new Input(get("invite_emails-subject"), "", "", "enabled", true, "invite_emails"),
|
||||
invite_emails: {
|
||||
enabled: new Checkbox(get("invite_emails-enabled"), "", false, "invite_emails", "enabled"),
|
||||
subject: new Input(get("invite_emails-subject"), "", "", "enabled", true, "invite_emails"),
|
||||
},
|
||||
"mailgun": {
|
||||
"api_url": new Input(get("mailgun-api_url")),
|
||||
"api_key": new Input(get("mailgun-api_key"))
|
||||
mailgun: {
|
||||
api_url: new Input(get("mailgun-api_url")),
|
||||
api_key: new Input(get("mailgun-api_key")),
|
||||
},
|
||||
"smtp": {
|
||||
"username": new Input(get("smtp-username")),
|
||||
"encryption": new Select(get("smtp-encryption")),
|
||||
"server": new Input(get("smtp-server")),
|
||||
"port": new Input(get("smtp-port")),
|
||||
"password": new Input(get("smtp-password"))
|
||||
smtp: {
|
||||
username: new Input(get("smtp-username")),
|
||||
encryption: new Select(get("smtp-encryption")),
|
||||
server: new Input(get("smtp-server")),
|
||||
port: new Input(get("smtp-port")),
|
||||
password: new Input(get("smtp-password")),
|
||||
},
|
||||
"ombi": {
|
||||
"enabled": new Checkbox(get("ombi-enabled"), "", false, "ombi", "enabled"),
|
||||
"server": new Input(get("ombi-server"), "", "", "enabled", true, "ombi"),
|
||||
"api_key": new Input(get("ombi-api_key"), "", "", "enabled", true, "ombi")
|
||||
ombi: {
|
||||
enabled: new Checkbox(get("ombi-enabled"), "", false, "ombi", "enabled"),
|
||||
server: new Input(get("ombi-server"), "", "", "enabled", true, "ombi"),
|
||||
api_key: new Input(get("ombi-api_key"), "", "", "enabled", true, "ombi"),
|
||||
},
|
||||
"jellyseerr": {
|
||||
"enabled": new Checkbox(get("jellyseerr-enabled"), "", false, "jellyseerr", "enabled"),
|
||||
"server": new Input(get("jellyseerr-server"), "", "", "enabled", true, "jellyseerr"),
|
||||
"api_key": new Input(get("jellyseerr-api_key"), "", "", "enabled", true, "jellyseerr"),
|
||||
"import_existing": new Checkbox(get("jellyseerr-import_existing"), "enabled", true, "jellyseerr", "import_existing")
|
||||
jellyseerr: {
|
||||
enabled: new Checkbox(get("jellyseerr-enabled"), "", false, "jellyseerr", "enabled"),
|
||||
server: new Input(get("jellyseerr-server"), "", "", "enabled", true, "jellyseerr"),
|
||||
api_key: new Input(get("jellyseerr-api_key"), "", "", "enabled", true, "jellyseerr"),
|
||||
import_existing: new Checkbox(
|
||||
get("jellyseerr-import_existing"),
|
||||
"enabled",
|
||||
true,
|
||||
"jellyseerr",
|
||||
"import_existing",
|
||||
),
|
||||
},
|
||||
advanced: {
|
||||
tls: new Checkbox(get("advanced-tls"), "", false, "advanced", "tls"),
|
||||
tls_port: new Input(get("advanced-tls_port"), "", "", "tls", true, "advanced"),
|
||||
tls_cert: new Input(get("advanced-tls_cert"), "", "", "tls", true, "advanced"),
|
||||
tls_key: new Input(get("advanced-tls_key"), "", "", "tls", true, "advanced"),
|
||||
proxy: new Checkbox(get("advanced-proxy"), "", false, "advanced", "proxy"),
|
||||
proxy_protocol: new Select(get("advanced-proxy_protocol"), "proxy", true, "advanced"),
|
||||
proxy_address: new Input(get("advanced-proxy_address"), "", "", "proxy", true, "advanced"),
|
||||
proxy_user: new Input(get("advanced-proxy_user"), "", "", "proxy", true, "advanced"),
|
||||
proxy_password: new Input(get("advanced-proxy_password"), "", "", "proxy", true, "advanced"),
|
||||
},
|
||||
"advanced": {
|
||||
"tls": new Checkbox(get("advanced-tls"), "", false, "advanced", "tls"),
|
||||
"tls_port": new Input(get("advanced-tls_port"), "", "", "tls", true, "advanced"),
|
||||
"tls_cert": new Input(get("advanced-tls_cert"), "", "", "tls", true, "advanced"),
|
||||
"tls_key": new Input(get("advanced-tls_key"), "", "", "tls", true, "advanced"),
|
||||
"proxy": new Checkbox(get("advanced-proxy"), "", false, "advanced", "proxy"),
|
||||
"proxy_protocol": new Select(get("advanced-proxy_protocol"), "proxy", true, "advanced"),
|
||||
"proxy_address": new Input(get("advanced-proxy_address"), "", "", "proxy", true, "advanced"),
|
||||
"proxy_user": new Input(get("advanced-proxy_user"), "", "", "proxy", true, "advanced"),
|
||||
"proxy_password": new Input(get("advanced-proxy_password"), "", "", "proxy", true, "advanced")
|
||||
}
|
||||
};
|
||||
const checkTheme = () => {
|
||||
if (settings["ui"]["theme"].value.includes("Dark")) {
|
||||
@@ -366,7 +454,7 @@ settings["ui"]["theme"].onchange = checkTheme;
|
||||
checkTheme();
|
||||
|
||||
const fixFullURL = (v: string): string => {
|
||||
if (!(v.startsWith("http://")) && !(v.startsWith("https://"))) {
|
||||
if (!v.startsWith("http://") && !v.startsWith("https://")) {
|
||||
v = "http://" + v;
|
||||
}
|
||||
return v;
|
||||
@@ -374,9 +462,11 @@ const fixFullURL = (v: string): string => {
|
||||
|
||||
const formatSubpath = (v: string): string => {
|
||||
if (v == "/") return "";
|
||||
if (v.charAt(-1) == "/") { v = v.slice(0, -1); }
|
||||
if (v.charAt(-1) == "/") {
|
||||
v = v.slice(0, -1);
|
||||
}
|
||||
return v;
|
||||
}
|
||||
};
|
||||
|
||||
const constructNewURLs = (): string[] => {
|
||||
let local = settings["ui"]["host"].value + ":" + settings["ui"]["port"].value;
|
||||
@@ -390,7 +480,7 @@ const constructNewURLs = (): string[] => {
|
||||
}
|
||||
remote = fixFullURL(remote);
|
||||
return [local, remote];
|
||||
}
|
||||
};
|
||||
|
||||
const restartButton = document.getElementById("restart") as HTMLSpanElement;
|
||||
const serialize = () => {
|
||||
@@ -405,54 +495,63 @@ const serialize = () => {
|
||||
}
|
||||
}
|
||||
config["restart-program"] = true;
|
||||
_post("/config", config, (req: XMLHttpRequest) => {
|
||||
if (req.readyState == 4) {
|
||||
toggleLoader(restartButton);
|
||||
if (req.status == 500) {
|
||||
if (req.response == null) {
|
||||
const old = restartButton.textContent;
|
||||
restartButton.classList.add("~critical");
|
||||
restartButton.classList.remove("~urge");
|
||||
restartButton.textContent = window.lang.strings("errorUnknown");
|
||||
setTimeout(() => {
|
||||
restartButton.classList.add("~urge");
|
||||
restartButton.classList.remove("~critical");
|
||||
restartButton.textContent = old;
|
||||
}, 5000);
|
||||
return;
|
||||
}
|
||||
if (req.response["error"] as string) {
|
||||
const old = restartButton.textContent;
|
||||
restartButton.classList.add("~critical");
|
||||
restartButton.classList.remove("~urge");
|
||||
restartButton.textContent = req.response["error"];
|
||||
setTimeout(() => {
|
||||
restartButton.classList.add("~urge");
|
||||
restartButton.classList.remove("~critical");
|
||||
restartButton.textContent = old;
|
||||
}, 5000);
|
||||
return;
|
||||
_post(
|
||||
"/config",
|
||||
config,
|
||||
(req: XMLHttpRequest) => {
|
||||
if (req.readyState == 4) {
|
||||
toggleLoader(restartButton);
|
||||
if (req.status == 500) {
|
||||
if (req.response == null) {
|
||||
const old = restartButton.textContent;
|
||||
restartButton.classList.add("~critical");
|
||||
restartButton.classList.remove("~urge");
|
||||
restartButton.textContent = window.lang.strings("errorUnknown");
|
||||
setTimeout(() => {
|
||||
restartButton.classList.add("~urge");
|
||||
restartButton.classList.remove("~critical");
|
||||
restartButton.textContent = old;
|
||||
}, 5000);
|
||||
return;
|
||||
}
|
||||
if (req.response["error"] as string) {
|
||||
const old = restartButton.textContent;
|
||||
restartButton.classList.add("~critical");
|
||||
restartButton.classList.remove("~urge");
|
||||
restartButton.textContent = req.response["error"];
|
||||
setTimeout(() => {
|
||||
restartButton.classList.add("~urge");
|
||||
restartButton.classList.remove("~critical");
|
||||
restartButton.textContent = old;
|
||||
}, 5000);
|
||||
return;
|
||||
}
|
||||
}
|
||||
restartButton.parentElement.querySelector("span.back").classList.add("unfocused");
|
||||
restartButton.classList.add("unfocused");
|
||||
const refreshURLs = constructNewURLs();
|
||||
const refreshButtons = [
|
||||
document.getElementById("refresh-internal") as HTMLAnchorElement,
|
||||
document.getElementById("refresh-external") as HTMLAnchorElement,
|
||||
];
|
||||
["internal", "external"].forEach((urltype, i) => {
|
||||
const button = refreshButtons[i];
|
||||
button.classList.remove("unfocused");
|
||||
button.href = refreshURLs[i];
|
||||
button.innerHTML = `<span>${urltype.charAt(0).toUpperCase() + urltype.slice(1)}:</span><i class="italic underline">${button.href}</i>`;
|
||||
// skip external if it isn't set
|
||||
if (refreshURLs.length == 1) return;
|
||||
});
|
||||
}
|
||||
restartButton.parentElement.querySelector("span.back").classList.add("unfocused");
|
||||
restartButton.classList.add("unfocused");
|
||||
const refreshURLs = constructNewURLs();
|
||||
const refreshButtons = [document.getElementById("refresh-internal") as HTMLAnchorElement, document.getElementById("refresh-external") as HTMLAnchorElement];
|
||||
["internal", "external"].forEach((urltype, i) => {
|
||||
const button = refreshButtons[i];
|
||||
button.classList.remove("unfocused");
|
||||
button.href = refreshURLs[i];
|
||||
button.innerHTML = `<span>${urltype.charAt(0).toUpperCase() + urltype.slice(1)}:</span><i class="italic underline">${button.href}</i>`;
|
||||
// skip external if it isn't set
|
||||
if (refreshURLs.length == 1) return;
|
||||
});
|
||||
}
|
||||
}, true, (req: XMLHttpRequest) => {
|
||||
if (req.status == 0) {
|
||||
window.notifications.customError("connectionError", window.lang.strings("errorConnectionRefused"));
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
true,
|
||||
(req: XMLHttpRequest) => {
|
||||
if (req.status == 0) {
|
||||
window.notifications.customError("connectionError", window.lang.strings("errorConnectionRefused"));
|
||||
}
|
||||
},
|
||||
);
|
||||
};
|
||||
restartButton.onclick = serialize;
|
||||
|
||||
const relatedToEmail = Array.from(document.getElementsByClassName("related-to-email"));
|
||||
@@ -503,7 +602,7 @@ const getParentCard = (el: HTMLElement): HTMLDivElement => {
|
||||
|
||||
const jellyfinLoginAccessChange = () => {
|
||||
const adminOnly = settings["ui"]["admin_only"].value == "true";
|
||||
const allowAll = settings["ui"]["allow_all"].value == "true";
|
||||
const allowAll = settings["ui"]["allow_all"].value == "true";
|
||||
const adminOnlyEl = document.getElementById("ui-admin_only") as HTMLInputElement;
|
||||
const allowAllEl = document.getElementById("ui-allow_all") as HTMLInputElement;
|
||||
const nextButton = getParentCard(adminOnlyEl).querySelector("span.next") as HTMLSpanElement;
|
||||
@@ -518,7 +617,7 @@ const jellyfinLoginAccessChange = () => {
|
||||
} else {
|
||||
adminOnlyEl.disabled = false;
|
||||
allowAllEl.disabled = false;
|
||||
nextButton.setAttribute("disabled", "true")
|
||||
nextButton.setAttribute("disabled", "true");
|
||||
}
|
||||
};
|
||||
|
||||
@@ -534,7 +633,7 @@ const embyHidePWR = () => {
|
||||
} else if (val == "emby") {
|
||||
pwr.classList.add("hidden");
|
||||
}
|
||||
}
|
||||
};
|
||||
settings["jellyfin"]["type"].onchange = embyHidePWR;
|
||||
embyHidePWR();
|
||||
|
||||
@@ -552,7 +651,9 @@ let pages = new PageManager({
|
||||
defaultTitle: "Setup - jfa-go",
|
||||
});
|
||||
|
||||
const cards = Array.from(document.getElementsByClassName("page-container")[0].querySelectorAll(".card.sectioned")) as Array<HTMLDivElement>;
|
||||
const cards = Array.from(
|
||||
document.getElementsByClassName("page-container")[0].querySelectorAll(".card.sectioned"),
|
||||
) as Array<HTMLDivElement>;
|
||||
(window as any).cards = cards;
|
||||
|
||||
(() => {
|
||||
@@ -582,10 +683,11 @@ const cards = Array.from(document.getElementsByClassName("page-container")[0].qu
|
||||
},
|
||||
});
|
||||
if (back) back.addEventListener("click", () => pages.prev(title));
|
||||
if (next) next.addEventListener("click", () => {
|
||||
if (next.hasAttribute("disabled")) return;
|
||||
pages.next(title);
|
||||
});
|
||||
if (next)
|
||||
next.addEventListener("click", () => {
|
||||
if (next.hasAttribute("disabled")) return;
|
||||
pages.next(title);
|
||||
});
|
||||
}
|
||||
})();
|
||||
|
||||
@@ -596,51 +698,57 @@ const cards = Array.from(document.getElementsByClassName("page-container")[0].qu
|
||||
button.onclick = () => {
|
||||
toggleLoader(button);
|
||||
let send = {
|
||||
"type": settings["jellyfin"]["type"].value,
|
||||
"server": settings["jellyfin"]["server"].value,
|
||||
"username": settings["jellyfin"]["username"].value,
|
||||
"password": settings["jellyfin"]["password"].value,
|
||||
"proxy": settings["advanced"]["proxy"].value == "true",
|
||||
"proxy_protocol": settings["advanced"]["proxy_protocol"].value,
|
||||
"proxy_address": settings["advanced"]["proxy_address"].value,
|
||||
"proxy_user": settings["advanced"]["proxy_user"].value,
|
||||
"proxy_password": settings["advanced"]["proxy_password"].value
|
||||
type: settings["jellyfin"]["type"].value,
|
||||
server: settings["jellyfin"]["server"].value,
|
||||
username: settings["jellyfin"]["username"].value,
|
||||
password: settings["jellyfin"]["password"].value,
|
||||
proxy: settings["advanced"]["proxy"].value == "true",
|
||||
proxy_protocol: settings["advanced"]["proxy_protocol"].value,
|
||||
proxy_address: settings["advanced"]["proxy_address"].value,
|
||||
proxy_user: settings["advanced"]["proxy_user"].value,
|
||||
proxy_password: settings["advanced"]["proxy_password"].value,
|
||||
};
|
||||
_post("/jellyfin/test", send, (req: XMLHttpRequest) => {
|
||||
if (req.readyState == 4) {
|
||||
toggleLoader(button);
|
||||
if (req.status != 200) {
|
||||
nextButton.setAttribute("disabled", "");
|
||||
button.classList.add("~critical");
|
||||
_post(
|
||||
"/jellyfin/test",
|
||||
send,
|
||||
(req: XMLHttpRequest) => {
|
||||
if (req.readyState == 4) {
|
||||
toggleLoader(button);
|
||||
if (req.status != 200) {
|
||||
nextButton.setAttribute("disabled", "");
|
||||
button.classList.add("~critical");
|
||||
button.classList.remove("~urge");
|
||||
setTimeout(() => {
|
||||
button.textContent = ogText;
|
||||
button.classList.add("~urge");
|
||||
button.classList.remove("~critical");
|
||||
}, 5000);
|
||||
const errorMsg = req.response["error"] as string;
|
||||
if (!errorMsg) {
|
||||
button.textContent = window.lang.strings("error");
|
||||
} else {
|
||||
button.textContent = window.lang.strings(errorMsg);
|
||||
}
|
||||
return;
|
||||
}
|
||||
nextButton.removeAttribute("disabled");
|
||||
button.textContent = window.lang.strings("success");
|
||||
button.classList.add("~positive");
|
||||
button.classList.remove("~urge");
|
||||
setTimeout(() => {
|
||||
button.textContent = ogText;
|
||||
button.classList.add("~urge");
|
||||
button.classList.remove("~critical");
|
||||
button.classList.remove("~positive");
|
||||
}, 5000);
|
||||
const errorMsg = req.response["error"] as string;
|
||||
if (!errorMsg) {
|
||||
button.textContent = window.lang.strings("error");
|
||||
} else {
|
||||
button.textContent = window.lang.strings(errorMsg);
|
||||
}
|
||||
return;
|
||||
}
|
||||
nextButton.removeAttribute("disabled");
|
||||
button.textContent = window.lang.strings("success");
|
||||
button.classList.add("~positive");
|
||||
button.classList.remove("~urge");
|
||||
setTimeout(() => {
|
||||
button.textContent = ogText;
|
||||
button.classList.add("~urge");
|
||||
button.classList.remove("~positive");
|
||||
}, 5000);
|
||||
}
|
||||
}, true, (req: XMLHttpRequest) => {
|
||||
if (req.status == 0) {
|
||||
window.notifications.customError("connectionError", window.lang.strings("errorConnectionRefused"));
|
||||
}
|
||||
});
|
||||
},
|
||||
true,
|
||||
(req: XMLHttpRequest) => {
|
||||
if (req.status == 0) {
|
||||
window.notifications.customError("connectionError", window.lang.strings("errorConnectionRefused"));
|
||||
}
|
||||
},
|
||||
);
|
||||
};
|
||||
})();
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
declare interface Modal {
|
||||
modal: HTMLElement;
|
||||
closeButton: HTMLSpanElement
|
||||
closeButton: HTMLSpanElement;
|
||||
show: () => void;
|
||||
close: (event?: Event, noDispatch?: boolean) => void;
|
||||
toggle: () => void;
|
||||
@@ -63,7 +63,7 @@ declare interface GlobalWindow extends Window {
|
||||
|
||||
declare interface InviteList {
|
||||
empty: boolean;
|
||||
invites: { [code: string]: Invite }
|
||||
invites: { [code: string]: Invite };
|
||||
add: (invite: Invite) => void;
|
||||
reload: (callback?: () => void) => void;
|
||||
isInviteURL: () => boolean;
|
||||
@@ -71,25 +71,25 @@ declare interface InviteList {
|
||||
}
|
||||
|
||||
declare interface Invite {
|
||||
code: string; // Invite code
|
||||
code: string; // Invite code
|
||||
valid_till: number; // Unix timestamp of expiry
|
||||
user_expiry: boolean; // Whether or not user expiry is enabled
|
||||
user_months?: number; // Number of months till user expiry
|
||||
user_days?: number; // Number of days till user expiry
|
||||
user_hours?: number; // Number of hours till user expiry
|
||||
user_minutes?: number; // Number of minutes till user expiry
|
||||
created: number; // Date of creation (unix timestamp)
|
||||
profile: string; // Profile used on this invite
|
||||
used_by?: { [user: string]: number }; // Users who have used this invite mapped to their creation time in Epoch/Unix time
|
||||
no_limit: boolean; // If true, invite can be used any number of times
|
||||
remaining_uses?: number; // Remaining number of uses (if applicable)
|
||||
send_to?: string; // DEPRECATED Email/Discord username the invite was sent to (if applicable)
|
||||
sent_to?: SentToList; // Email/Discord usernames attempts were made to send this invite to, and a failure reason if failed.
|
||||
user_expiry: boolean; // Whether or not user expiry is enabled
|
||||
user_months?: number; // Number of months till user expiry
|
||||
user_days?: number; // Number of days till user expiry
|
||||
user_hours?: number; // Number of hours till user expiry
|
||||
user_minutes?: number; // Number of minutes till user expiry
|
||||
created: number; // Date of creation (unix timestamp)
|
||||
profile: string; // Profile used on this invite
|
||||
used_by?: { [user: string]: number }; // Users who have used this invite mapped to their creation time in Epoch/Unix time
|
||||
no_limit: boolean; // If true, invite can be used any number of times
|
||||
remaining_uses?: number; // Remaining number of uses (if applicable)
|
||||
send_to?: string; // DEPRECATED Email/Discord username the invite was sent to (if applicable)
|
||||
sent_to?: SentToList; // Email/Discord usernames attempts were made to send this invite to, and a failure reason if failed.
|
||||
|
||||
notify_expiry?: boolean; // Whether to notify the requesting user of expiry or not
|
||||
notify_creation?: boolean; // Whether to notify the requesting user of account creation or not
|
||||
label?: string; // Optional label for the invite
|
||||
user_label?: string; // Label to apply to users created w/ this invite.
|
||||
notify_expiry?: boolean; // Whether to notify the requesting user of expiry or not
|
||||
notify_creation?: boolean; // Whether to notify the requesting user of account creation or not
|
||||
label?: string; // Optional label for the invite
|
||||
user_label?: string; // Label to apply to users created w/ this invite.
|
||||
}
|
||||
|
||||
declare interface SendFailure {
|
||||
@@ -103,9 +103,9 @@ declare interface SentToList {
|
||||
}
|
||||
|
||||
declare interface Update {
|
||||
version: string;
|
||||
version: string;
|
||||
commit: string;
|
||||
date: number;
|
||||
date: number;
|
||||
description: string;
|
||||
changelog: string;
|
||||
link: string;
|
||||
@@ -131,7 +131,7 @@ declare interface Lang {
|
||||
declare interface NotificationBox {
|
||||
connectionError: () => void;
|
||||
customError: (type: string, message: string) => void;
|
||||
customPositive: (type: string, bold: string, message: string) => void;
|
||||
customPositive: (type: string, bold: string, message: string) => void;
|
||||
customSuccess: (type: string, message: string) => void;
|
||||
}
|
||||
|
||||
@@ -182,7 +182,7 @@ interface PaginatedReqDTO {
|
||||
page: number;
|
||||
sortByField: string;
|
||||
ascending: boolean;
|
||||
};
|
||||
}
|
||||
|
||||
interface DateAttempt {
|
||||
year?: number;
|
||||
@@ -198,7 +198,7 @@ interface ParsedDate {
|
||||
date: Date;
|
||||
text: string;
|
||||
invalid?: boolean;
|
||||
};
|
||||
}
|
||||
|
||||
declare var config: Object;
|
||||
declare var modifiedConfig: Object;
|
||||
|
||||
261
ts/user.ts
261
ts/user.ts
@@ -1,7 +1,17 @@
|
||||
import { ThemeManager } from "./modules/theme.js";
|
||||
import { lang, LangFile, loadLangSelector } from "./modules/lang.js";
|
||||
import { Modal } from "./modules/modal.js";
|
||||
import { _get, _post, _delete, notificationBox, whichAnimationEvent, toDateString, addLoader, removeLoader, toClipboard } from "./modules/common.js";
|
||||
import {
|
||||
_get,
|
||||
_post,
|
||||
_delete,
|
||||
notificationBox,
|
||||
whichAnimationEvent,
|
||||
toDateString,
|
||||
addLoader,
|
||||
removeLoader,
|
||||
toClipboard,
|
||||
} from "./modules/common.js";
|
||||
import { Login } from "./modules/login.js";
|
||||
import { Discord, Telegram, Matrix, ServiceConfiguration, MatrixConfiguration } from "./modules/account-linking.js";
|
||||
import { Validator, ValidatorConf, ValidatorRespDTO } from "./modules/validator.js";
|
||||
@@ -77,7 +87,7 @@ pages.setPage({
|
||||
pages.setPage({
|
||||
name: "reset",
|
||||
title: document.title,
|
||||
url: basePath+"/password/reset",
|
||||
url: basePath + "/password/reset",
|
||||
show: () => {
|
||||
const usernameInput = document.getElementById("login-user") as HTMLInputElement;
|
||||
const input = document.getElementById("pwr-address") as HTMLInputElement;
|
||||
@@ -98,11 +108,11 @@ pages.setPage({
|
||||
const resetButton = document.getElementById("modal-login-pwr");
|
||||
resetButton.onclick = () => {
|
||||
pages.load("reset");
|
||||
}
|
||||
};
|
||||
}
|
||||
})();
|
||||
|
||||
window.notifications = new notificationBox(document.getElementById('notification-box') as HTMLDivElement, 5);
|
||||
window.notifications = new notificationBox(document.getElementById("notification-box") as HTMLDivElement, 5);
|
||||
|
||||
if (window.pwrEnabled && window.linkResetEnabled) {
|
||||
const submitButton = document.getElementById("pwr-submit");
|
||||
@@ -113,7 +123,7 @@ if (window.pwrEnabled && window.linkResetEnabled) {
|
||||
if (req.readyState != 4) return;
|
||||
removeLoader(submitButton);
|
||||
if (req.status != 204) {
|
||||
window.notifications.customError("unkownError", window.lang.notif("errorUnknown"));;
|
||||
window.notifications.customError("unkownError", window.lang.notif("errorUnknown"));
|
||||
window.modals.pwr.close();
|
||||
return;
|
||||
}
|
||||
@@ -168,9 +178,9 @@ interface ContactDTO {
|
||||
class ContactMethods {
|
||||
private _card: HTMLElement;
|
||||
private _content: HTMLElement;
|
||||
private _buttons: { [name: string]: { element: HTMLElement, details: MyDetailsContactMethod } };
|
||||
private _buttons: { [name: string]: { element: HTMLElement; details: MyDetailsContactMethod } };
|
||||
|
||||
constructor (card: HTMLElement) {
|
||||
constructor(card: HTMLElement) {
|
||||
this._card = card;
|
||||
this._content = this._card.querySelector(".content");
|
||||
this._buttons = {};
|
||||
@@ -179,9 +189,15 @@ class ContactMethods {
|
||||
clear = () => {
|
||||
this._content.textContent = "";
|
||||
this._buttons = {};
|
||||
}
|
||||
};
|
||||
|
||||
append = (name: string, details: MyDetailsContactMethod, icon: string, addEditFunc?: (add: boolean) => void, required?: boolean) => {
|
||||
append = (
|
||||
name: string,
|
||||
details: MyDetailsContactMethod,
|
||||
icon: string,
|
||||
addEditFunc?: (add: boolean) => void,
|
||||
required?: boolean,
|
||||
) => {
|
||||
const row = document.createElement("div");
|
||||
row.classList.add("flex", "flex-row", "justify-between", "gap-2", "flex-nowrap");
|
||||
let innerHTML = `
|
||||
@@ -191,7 +207,7 @@ class ContactMethods {
|
||||
${icon}
|
||||
</span>
|
||||
</span>
|
||||
<span class="font-bold text-ellipsis overflow-hidden">${(details.value == "") ? window.lang.strings("notSet") : details.value}</span>
|
||||
<span class="font-bold text-ellipsis overflow-hidden">${details.value == "" ? window.lang.strings("notSet") : details.value}</span>
|
||||
</div>
|
||||
<div class="flex flex-col justify-center">
|
||||
<div class="flex items-center flex-row gap-2">
|
||||
@@ -227,7 +243,7 @@ class ContactMethods {
|
||||
|
||||
this._buttons[name] = {
|
||||
element: row,
|
||||
details: details
|
||||
details: details,
|
||||
};
|
||||
|
||||
const button = row.querySelector(".user-contact-enabled-disabled") as HTMLButtonElement;
|
||||
@@ -263,10 +279,11 @@ class ContactMethods {
|
||||
|
||||
if (!required && details.value != "") {
|
||||
const deleteButton = row.querySelector(".user-contact-delete") as HTMLButtonElement;
|
||||
deleteButton.onclick = () => _delete("/my/" + name, null, (req: XMLHttpRequest) => {
|
||||
if (req.readyState != 4) return;
|
||||
document.dispatchEvent(new CustomEvent("details-reload"));
|
||||
});
|
||||
deleteButton.onclick = () =>
|
||||
_delete("/my/" + name, null, (req: XMLHttpRequest) => {
|
||||
if (req.readyState != 4) return;
|
||||
document.dispatchEvent(new CustomEvent("details-reload"));
|
||||
});
|
||||
}
|
||||
|
||||
this._content.appendChild(row);
|
||||
@@ -305,11 +322,12 @@ class ReferralCard {
|
||||
private _expiryEl: HTMLSpanElement;
|
||||
private _descriptionEl: HTMLSpanElement;
|
||||
|
||||
get code(): string { return this._code; }
|
||||
get code(): string {
|
||||
return this._code;
|
||||
}
|
||||
set code(c: string) {
|
||||
this._code = c;
|
||||
|
||||
|
||||
// let u = new URL(window.location.href);
|
||||
// const path = window.pages.Base + window.pages.Form + "/" + this._code;
|
||||
//
|
||||
@@ -321,30 +339,35 @@ class ReferralCard {
|
||||
this._url = generateCodeLink(this._code);
|
||||
}
|
||||
|
||||
get remaining_uses(): number { return this._remainingUses; }
|
||||
get remaining_uses(): number {
|
||||
return this._remainingUses;
|
||||
}
|
||||
set remaining_uses(v: number) {
|
||||
this._remainingUses = v;
|
||||
if (v > 0 && !(this._noLimit))
|
||||
this._remainingUsesEl.textContent = `${v}`;
|
||||
if (v > 0 && !this._noLimit) this._remainingUsesEl.textContent = `${v}`;
|
||||
}
|
||||
|
||||
get no_limit(): boolean { return this._noLimit; }
|
||||
get no_limit(): boolean {
|
||||
return this._noLimit;
|
||||
}
|
||||
set no_limit(v: boolean) {
|
||||
this._noLimit = v;
|
||||
if (v)
|
||||
this._remainingUsesEl.textContent = `∞`;
|
||||
else
|
||||
this._remainingUsesEl.textContent = `${this._remainingUses}`;
|
||||
if (v) this._remainingUsesEl.textContent = `∞`;
|
||||
else this._remainingUsesEl.textContent = `${this._remainingUses}`;
|
||||
}
|
||||
|
||||
get expiry(): Date { return this._expiry; };
|
||||
get expiry(): Date {
|
||||
return this._expiry;
|
||||
}
|
||||
set expiry(expiryUnix: number) {
|
||||
this._expiryUnix = expiryUnix;
|
||||
this._expiry = new Date(expiryUnix * 1000);
|
||||
this._expiryEl.textContent = toDateString(this._expiry);
|
||||
}
|
||||
|
||||
get use_expiry(): boolean { return this._useExpiry; }
|
||||
get use_expiry(): boolean {
|
||||
return this._useExpiry;
|
||||
}
|
||||
set use_expiry(v: boolean) {
|
||||
this._useExpiry = v;
|
||||
if (v) {
|
||||
@@ -432,19 +455,19 @@ class ExpiryCard {
|
||||
let ymd = [0, 0, 0];
|
||||
while (now.getFullYear() != this._expiry.getFullYear()) {
|
||||
ymd[0] += 1;
|
||||
now.setFullYear(now.getFullYear()+1);
|
||||
now.setFullYear(now.getFullYear() + 1);
|
||||
}
|
||||
if (now.getMonth() > this._expiry.getMonth()) {
|
||||
ymd[0] -=1;
|
||||
now.setFullYear(now.getFullYear()-1);
|
||||
ymd[0] -= 1;
|
||||
now.setFullYear(now.getFullYear() - 1);
|
||||
}
|
||||
while (now.getMonth() != this._expiry.getMonth()) {
|
||||
ymd[1] += 1;
|
||||
now.setMonth(now.getMonth() + 1);
|
||||
}
|
||||
if (now.getDate() > this._expiry.getDate()) {
|
||||
ymd[1] -=1;
|
||||
now.setMonth(now.getMonth()-1);
|
||||
ymd[1] -= 1;
|
||||
now.setMonth(now.getMonth() - 1);
|
||||
}
|
||||
while (now.getDate() != this._expiry.getDate()) {
|
||||
ymd[2] += 1;
|
||||
@@ -467,7 +490,9 @@ class ExpiryCard {
|
||||
this._countdown.innerHTML = innerHTML;
|
||||
};
|
||||
|
||||
get expiry(): Date { return this._expiry; };
|
||||
get expiry(): Date {
|
||||
return this._expiry;
|
||||
}
|
||||
set expiry(expiryUnix: number) {
|
||||
if (this._interval !== null) {
|
||||
window.clearInterval(this._interval);
|
||||
@@ -479,10 +504,12 @@ class ExpiryCard {
|
||||
return;
|
||||
}
|
||||
this._expiry = new Date(expiryUnix * 1000);
|
||||
this._aside.textContent = window.lang.strings("yourAccountIsValidUntil").replace("{date}", toDateString(this._expiry));
|
||||
this._aside.textContent = window.lang
|
||||
.strings("yourAccountIsValidUntil")
|
||||
.replace("{date}", toDateString(this._expiry));
|
||||
this._card.classList.remove("unfocused");
|
||||
|
||||
this._interval = window.setInterval(this._drawCountdown, 60*1000);
|
||||
this._interval = window.setInterval(this._drawCountdown, 60 * 1000);
|
||||
this._drawCountdown();
|
||||
}
|
||||
}
|
||||
@@ -496,7 +523,9 @@ var contactMethodList = new ContactMethods(contactCard);
|
||||
|
||||
const addEditEmail = (add: boolean): void => {
|
||||
const heading = window.modals.email.modal.querySelector(".heading");
|
||||
heading.innerHTML = (add ? window.lang.strings("addContactMethod") : window.lang.strings("editContactMethod")) + `<span class="modal-close">×</span>`;
|
||||
heading.innerHTML =
|
||||
(add ? window.lang.strings("addContactMethod") : window.lang.strings("editContactMethod")) +
|
||||
`<span class="modal-close">×</span>`;
|
||||
const input = document.getElementById("modal-email-input") as HTMLInputElement;
|
||||
input.value = "";
|
||||
const confirmationRequired = window.modals.email.modal.querySelector(".confirmation-required");
|
||||
@@ -508,25 +537,31 @@ const addEditEmail = (add: boolean): void => {
|
||||
const submit = window.modals.email.modal.querySelector(".modal-submit") as HTMLButtonElement;
|
||||
submit.onclick = () => {
|
||||
addLoader(submit);
|
||||
_post("/my/email", {"email": input.value}, (req: XMLHttpRequest) => {
|
||||
if (req.readyState != 4) return;
|
||||
removeLoader(submit);
|
||||
if (req.status == 303 || req.status == 200) {
|
||||
document.dispatchEvent(new CustomEvent("details-reload"));
|
||||
window.modals.email.close();
|
||||
}
|
||||
}, true, (req: XMLHttpRequest) => {
|
||||
if (req.readyState != 4) return;
|
||||
removeLoader(submit);
|
||||
if (req.status == 401) {
|
||||
content.classList.add("unfocused");
|
||||
confirmationRequired.classList.remove("unfocused");
|
||||
}
|
||||
});
|
||||
}
|
||||
_post(
|
||||
"/my/email",
|
||||
{ email: input.value },
|
||||
(req: XMLHttpRequest) => {
|
||||
if (req.readyState != 4) return;
|
||||
removeLoader(submit);
|
||||
if (req.status == 303 || req.status == 200) {
|
||||
document.dispatchEvent(new CustomEvent("details-reload"));
|
||||
window.modals.email.close();
|
||||
}
|
||||
},
|
||||
true,
|
||||
(req: XMLHttpRequest) => {
|
||||
if (req.readyState != 4) return;
|
||||
removeLoader(submit);
|
||||
if (req.status == 401) {
|
||||
content.classList.add("unfocused");
|
||||
confirmationRequired.classList.remove("unfocused");
|
||||
}
|
||||
},
|
||||
);
|
||||
};
|
||||
|
||||
window.modals.email.show();
|
||||
}
|
||||
};
|
||||
|
||||
const discordConf: ServiceConfiguration = {
|
||||
modal: window.modals.discord as Modal,
|
||||
@@ -539,7 +574,7 @@ const discordConf: ServiceConfiguration = {
|
||||
successError: window.lang.notif("verified"),
|
||||
successFunc: (modalClosed: boolean) => {
|
||||
if (modalClosed) document.dispatchEvent(new CustomEvent("details-reload"));
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
let discord: Discord;
|
||||
@@ -555,7 +590,7 @@ const telegramConf: ServiceConfiguration = {
|
||||
successError: window.lang.notif("verified"),
|
||||
successFunc: (modalClosed: boolean) => {
|
||||
if (modalClosed) document.dispatchEvent(new CustomEvent("details-reload"));
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
let telegram: Telegram;
|
||||
@@ -571,13 +606,12 @@ const matrixConf: MatrixConfiguration = {
|
||||
successError: window.lang.notif("verified"),
|
||||
successFunc: () => {
|
||||
setTimeout(() => document.dispatchEvent(new CustomEvent("details-reload")), 1200);
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
let matrix: Matrix;
|
||||
if (window.matrixEnabled) matrix = new Matrix(matrixConf);
|
||||
|
||||
|
||||
const oldPasswordField = document.getElementById("user-old-password") as HTMLInputElement;
|
||||
const newPasswordField = document.getElementById("user-new-password") as HTMLInputElement;
|
||||
const rePasswordField = document.getElementById("user-reenter-new-password") as HTMLInputElement;
|
||||
@@ -592,7 +626,7 @@ let validatorConf: ValidatorConf = {
|
||||
passwordField: newPasswordField,
|
||||
rePasswordField: rePasswordField,
|
||||
submitButton: changePasswordButton,
|
||||
validatorFunc: baseValidator
|
||||
validatorFunc: baseValidator,
|
||||
};
|
||||
|
||||
let validator = new Validator(validatorConf);
|
||||
@@ -601,25 +635,33 @@ let validator = new Validator(validatorConf);
|
||||
oldPasswordField.addEventListener("keyup", validator.validate);
|
||||
changePasswordButton.addEventListener("click", () => {
|
||||
addLoader(changePasswordButton);
|
||||
_post("/my/password", { old: oldPasswordField.value, new: newPasswordField.value }, (req: XMLHttpRequest) => {
|
||||
if (req.readyState != 4) return;
|
||||
removeLoader(changePasswordButton);
|
||||
if (req.status == 400) {
|
||||
window.notifications.customError("errorPassword", window.lang.notif("errorPassword"));
|
||||
} else if (req.status == 500) {
|
||||
window.notifications.customError("errorUnknown", window.lang.notif("errorUnknown"));
|
||||
} else if (req.status == 204) {
|
||||
window.notifications.customSuccess("passwordChanged", window.lang.notif("passwordChanged"));
|
||||
setTimeout(() => { window.location.reload() }, 2000);
|
||||
}
|
||||
}, true, (req: XMLHttpRequest) => {
|
||||
if (req.readyState != 4) return;
|
||||
removeLoader(changePasswordButton);
|
||||
if (req.status == 401) {
|
||||
window.notifications.customError("oldPasswordError", window.lang.notif("errorOldPassword"));
|
||||
return;
|
||||
}
|
||||
});
|
||||
_post(
|
||||
"/my/password",
|
||||
{ old: oldPasswordField.value, new: newPasswordField.value },
|
||||
(req: XMLHttpRequest) => {
|
||||
if (req.readyState != 4) return;
|
||||
removeLoader(changePasswordButton);
|
||||
if (req.status == 400) {
|
||||
window.notifications.customError("errorPassword", window.lang.notif("errorPassword"));
|
||||
} else if (req.status == 500) {
|
||||
window.notifications.customError("errorUnknown", window.lang.notif("errorUnknown"));
|
||||
} else if (req.status == 204) {
|
||||
window.notifications.customSuccess("passwordChanged", window.lang.notif("passwordChanged"));
|
||||
setTimeout(() => {
|
||||
window.location.reload();
|
||||
}, 2000);
|
||||
}
|
||||
},
|
||||
true,
|
||||
(req: XMLHttpRequest) => {
|
||||
if (req.readyState != 4) return;
|
||||
removeLoader(changePasswordButton);
|
||||
if (req.status == 401) {
|
||||
window.notifications.customError("oldPasswordError", window.lang.notif("errorOldPassword"));
|
||||
return;
|
||||
}
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
document.addEventListener("details-reload", () => {
|
||||
@@ -649,15 +691,51 @@ document.addEventListener("details-reload", () => {
|
||||
// Note the weird format of the functions for discord/telegram:
|
||||
// "this" was being redefined within the onclick() method, so
|
||||
// they had to be wrapped in an anonymous function.
|
||||
const contactMethods: { name: string, icon: string, f: (add: boolean) => void, required: boolean, enabled: boolean }[] = [
|
||||
{name: "email", icon: `<i class="ri-mail-fill ri-lg"></i>`, f: addEditEmail, required: true, enabled: true},
|
||||
{name: "discord", icon: `<i class="ri-discord-fill ri-lg"></i>`, f: (add: boolean) => { discord.onclick(); }, required: window.discordRequired, enabled: window.discordEnabled},
|
||||
{name: "telegram", icon: `<i class="ri-telegram-fill ri-lg"></i>`, f: (add: boolean) => { telegram.onclick() }, required: window.telegramRequired, enabled: window.telegramEnabled},
|
||||
{name: "matrix", icon: `<span class="font-bold">[m]</span>`, f: (add: boolean) => { matrix.show(); }, required: window.matrixRequired, enabled: window.matrixEnabled}
|
||||
const contactMethods: {
|
||||
name: string;
|
||||
icon: string;
|
||||
f: (add: boolean) => void;
|
||||
required: boolean;
|
||||
enabled: boolean;
|
||||
}[] = [
|
||||
{
|
||||
name: "email",
|
||||
icon: `<i class="ri-mail-fill ri-lg"></i>`,
|
||||
f: addEditEmail,
|
||||
required: true,
|
||||
enabled: true,
|
||||
},
|
||||
{
|
||||
name: "discord",
|
||||
icon: `<i class="ri-discord-fill ri-lg"></i>`,
|
||||
f: (add: boolean) => {
|
||||
discord.onclick();
|
||||
},
|
||||
required: window.discordRequired,
|
||||
enabled: window.discordEnabled,
|
||||
},
|
||||
{
|
||||
name: "telegram",
|
||||
icon: `<i class="ri-telegram-fill ri-lg"></i>`,
|
||||
f: (add: boolean) => {
|
||||
telegram.onclick();
|
||||
},
|
||||
required: window.telegramRequired,
|
||||
enabled: window.telegramEnabled,
|
||||
},
|
||||
{
|
||||
name: "matrix",
|
||||
icon: `<span class="font-bold">[m]</span>`,
|
||||
f: (add: boolean) => {
|
||||
matrix.show();
|
||||
},
|
||||
required: window.matrixRequired,
|
||||
enabled: window.matrixEnabled,
|
||||
},
|
||||
];
|
||||
|
||||
for (let method of contactMethods) {
|
||||
if (!(method.enabled)) continue;
|
||||
if (!method.enabled) continue;
|
||||
if (method.name in details) {
|
||||
contactMethodList.append(method.name, details[method.name], method.icon, method.f, method.required);
|
||||
}
|
||||
@@ -671,7 +749,7 @@ document.addEventListener("details-reload", () => {
|
||||
let messageCard = document.getElementById("card-message");
|
||||
if (details.accounts_admin) {
|
||||
adminBackButton.classList.remove("unfocused");
|
||||
if (typeof(messageCard) == "undefined" || messageCard == null) {
|
||||
if (typeof messageCard == "undefined" || messageCard == null) {
|
||||
messageCard = document.createElement("div");
|
||||
messageCard.classList.add("card", "@low", "dark:~d_neutral", "content");
|
||||
messageCard.id = "card-message";
|
||||
@@ -685,7 +763,7 @@ document.addEventListener("details-reload", () => {
|
||||
}
|
||||
}
|
||||
|
||||
if (typeof(messageCard) != "undefined" && messageCard != null) {
|
||||
if (typeof messageCard != "undefined" && messageCard != null) {
|
||||
messageCard.innerHTML = messageCard.innerHTML.replace(new RegExp("{username}", "g"), details.username);
|
||||
// setBestRowSpan(messageCard, false);
|
||||
// contactCard.querySelector(".content").classList.add("h-100");
|
||||
@@ -715,9 +793,9 @@ document.addEventListener("details-reload", () => {
|
||||
const setCardOrder = (messageCard: HTMLElement) => {
|
||||
const cards = document.getElementById("user-cardlist");
|
||||
const children = Array.from(cards.children);
|
||||
const idxs = [...Array(cards.childElementCount).keys()]
|
||||
const idxs = [...Array(cards.childElementCount).keys()];
|
||||
// The message card is the first element and should always be so, so remove it from the list.
|
||||
const hasMessageCard = !(typeof(messageCard) == "undefined" || messageCard == null);
|
||||
const hasMessageCard = !(typeof messageCard == "undefined" || messageCard == null);
|
||||
if (hasMessageCard) idxs.shift();
|
||||
const perms = generatePermutations(idxs);
|
||||
let minHeight = 999999;
|
||||
@@ -781,8 +859,7 @@ const setBestRowSpan = (el: HTMLElement, setOnParent: boolean) => {
|
||||
|
||||
let rowSpan = Math.ceil(computeRealHeight(el) / largestNonMessageCardHeight);
|
||||
|
||||
if (rowSpan > 0)
|
||||
(setOnParent ? el.parentElement : el).style.gridRow = `span ${rowSpan}`;
|
||||
if (rowSpan > 0) (setOnParent ? el.parentElement : el).style.gridRow = `span ${rowSpan}`;
|
||||
};
|
||||
|
||||
const computeRealHeight = (el: HTMLElement): number => {
|
||||
@@ -801,12 +878,12 @@ const computeRealHeight = (el: HTMLElement): number => {
|
||||
}
|
||||
}
|
||||
return total;
|
||||
}
|
||||
};
|
||||
|
||||
const generatePermutations = (xs: number[]): [number[], number[]][] => {
|
||||
const l = xs.length;
|
||||
let out: [number[], number[]][] = [];
|
||||
for (let i = 0; i < (l << 1); i++) {
|
||||
for (let i = 0; i < l << 1; i++) {
|
||||
let incl = [];
|
||||
let excl = [];
|
||||
for (let j = 0; j < l; j++) {
|
||||
@@ -819,7 +896,7 @@ const generatePermutations = (xs: number[]): [number[], number[]][] => {
|
||||
out.push([incl, excl]);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
};
|
||||
|
||||
login.bindLogout(document.getElementById("logout-button"));
|
||||
|
||||
|
||||
Reference in New Issue
Block a user