config: start adding path parameters

to change the urls of the admin page, the my account page and of
invites. Seems to work, but need to check all the code over and test.
This commit is contained in:
Harvey Tindall
2025-05-13 21:10:40 +01:00
parent 2d98c6cff4
commit c52ba2162e
43 changed files with 336 additions and 266 deletions
+5 -3
View File
@@ -11,6 +11,8 @@ import { _get, _post, notificationBox, whichAnimationEvent, bindManualDropdowns
import { Updater } from "./modules/update.js";
import { Login } from "./modules/login.js";
declare var window: GlobalWindow;
const theme = new ThemeManager(document.getElementById("button-theme"));
window.lang = new lang(window.langFile as LangFile);
@@ -165,12 +167,12 @@ const defaultTab = tabs[0];
window.tabs = new Tabs();
for (let tab of tabs) {
window.tabs.addTab(tab.id, tab.url, null, tab.reloader);
window.tabs.addTab(tab.id, window.pages.Admin + "/" + tab.url, null, tab.reloader);
}
let matchedTab = false
for (let tab of tabs) {
if (window.location.pathname.startsWith(window.URLBase + "/" + tab.url)) {
for (const tab of tabs) {
if (window.location.pathname.startsWith(window.pages.Base + window.pages.Current + "/" + tab.url)) {
window.tabs.switch(tab.url, true);
matchedTab = true;
}
+6 -6
View File
@@ -6,7 +6,7 @@ import { Validator, ValidatorConf, ValidatorRespDTO } from "./modules/validator.
import { Discord, Telegram, Matrix, ServiceConfiguration, MatrixConfiguration } from "./modules/account-linking.js";
import { Captcha, GreCAPTCHA } from "./modules/captcha.js";
interface formWindow extends Window {
interface formWindow extends GlobalWindow {
invalidPassword: string;
successModal: Modal;
telegramModal: Modal;
@@ -59,7 +59,7 @@ if (window.telegramEnabled) {
modal: window.telegramModal as Modal,
pin: window.telegramPIN,
pinURL: "",
verifiedURL: "/invite/" + window.code + "/telegram/verified/",
verifiedURL: window.pages.Form + "/" + window.code + "/telegram/verified/",
invalidCodeError: window.messages["errorInvalidPIN"],
accountLinkedError: window.messages["errorAccountLinked"],
successError: window.messages["verified"],
@@ -89,9 +89,9 @@ if (window.discordEnabled) {
const discordConf: ServiceConfiguration = {
modal: window.discordModal as Modal,
pin: window.discordPIN,
inviteURL: window.discordInviteLink ? ("/invite/" + window.code + "/discord/invite") : "",
inviteURL: window.discordInviteLink ? (window.pages.Form + "/" + window.code + "/discord/invite") : "",
pinURL: "",
verifiedURL: "/invite/" + window.code + "/discord/verified/",
verifiedURL: window.pages.Form + "/" + window.code + "/discord/verified/",
invalidCodeError: window.messages["errorInvalidPIN"],
accountLinkedError: window.messages["errorAccountLinked"],
successError: window.messages["verified"],
@@ -121,8 +121,8 @@ if (window.matrixEnabled) {
const matrixConf: MatrixConfiguration = {
modal: window.matrixModal as Modal,
sendMessageURL: "/invite/" + window.code + "/matrix/user",
verifiedURL: "/invite/" + window.code + "/matrix/verified/",
sendMessageURL: window.pages.Form + "/" + window.code + "/matrix/user",
verifiedURL: window.pages.Form + "/" + window.code + "/matrix/verified/",
invalidCodeError: window.messages["errorInvalidPIN"],
accountLinkedError: window.messages["errorAccountLinked"],
unknownError: window.messages["errorUnknown"],
+1 -1
View File
@@ -1,7 +1,7 @@
import { Modal } from "../modules/modal.js";
import { _get, _post, toggleLoader, addLoader, removeLoader } from "../modules/common.js";
interface formWindow extends Window {
interface formWindow extends GlobalWindow {
invalidPassword: string;
successModal: Modal;
telegramModal: Modal;
+2
View File
@@ -6,6 +6,8 @@ import { DiscordUser, newDiscordSearch } from "../modules/discord.js";
import { Search, SearchConfiguration, QueryType, SearchableItem } from "../modules/search.js";
import { HiddenInputField } from "./ui.js";
declare var window: GlobalWindow;
const dateParser = require("any-date-parser");
interface User {
+7 -5
View File
@@ -3,6 +3,8 @@ import { Search, SearchConfiguration, QueryType, SearchableItem } from "../modul
import { accountURLEvent } from "../modules/accounts.js";
import { inviteURLEvent } from "../modules/invites.js";
declare var window: GlobalWindow;
export interface activity {
id: string;
type: string;
@@ -52,8 +54,8 @@ export class Activity implements activity, SearchableItem {
link = link.split(split)[0];
}
if (link.slice(-1) != "/") { link += "/"; }
// FIXME: I should probably just be using window.URLBase, but incase thats not right, i'll put this warning here
if (link != window.URLBase) console.error(`URL Bases don't match: "${link}" != "${window.URLBase}"`);
// FIXME: I should probably just be using window.pages.Base, but incase thats not right, i'll put this warning here
if (link != window.pages.Base) console.error(`URL Bases don't match: "${link}" != "${window.pages.Base}"`);
return link;
})(); */
@@ -66,17 +68,17 @@ export class Activity implements activity, SearchableItem {
}
_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.URLBase}/accounts?user=${this._act.user_id}">${this._genUserText()}</a>`;
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.URLBase}/accounts?user=${this._act.source}">${this._genSrcUserText()}</a>`;
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 _genInvLink = (): string => {
return `<a role="link" tabindex="0" class="hover:underline cursor-pointer activity-pseudo-link-invite" data-id="${this.invite_code}" href="${window.URLBase}/?invite=${this.invite_code}">${this._renderInvText()}</a>`;
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>`;
}
+8 -6
View File
@@ -1,4 +1,4 @@
declare var window: Window;
declare var window: GlobalWindow;
export function toDateString(date: Date): string {
const locale = window.language || (window as any).navigator.userLanguage || window.navigator.language;
@@ -23,7 +23,7 @@ export function toDateString(date: Date): string {
export const _get = (url: string, data: Object, onreadystatechange: (req: XMLHttpRequest) => void, noConnectionError: boolean = false): void => {
let req = new XMLHttpRequest();
if (window.URLBase) { url = window.URLBase + url; }
if (window.pages) { url = window.pages.Base + url; }
req.open("GET", url, true);
req.responseType = 'json';
req.setRequestHeader("Authorization", "Bearer " + window.token);
@@ -42,7 +42,7 @@ export const _get = (url: string, data: Object, onreadystatechange: (req: XMLHtt
export const _download = (url: string, fname: string): void => {
let req = new XMLHttpRequest();
if (window.URLBase) { url = window.URLBase + url; }
if (window.pages) { url = window.pages.Base + url; }
req.open("GET", url, true);
req.responseType = 'blob';
req.setRequestHeader("Authorization", "Bearer " + window.token);
@@ -58,7 +58,7 @@ export const _download = (url: string, fname: string): void => {
export const _upload = (url: string, formData: FormData): void => {
let req = new XMLHttpRequest();
if (window.URLBase) { url = window.URLBase + 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');
@@ -67,7 +67,8 @@ export const _upload = (url: string, formData: FormData): void => {
export const _post = (url: string, data: Object, onreadystatechange: (req: XMLHttpRequest) => void, response?: boolean, statusHandler?: (req: XMLHttpRequest) => void, noConnectionError: boolean = false): void => {
let req = new XMLHttpRequest();
req.open("POST", window.URLBase + url, true);
if (window.pages) { url = window.pages.Base + url; }
req.open("POST", url, true);
if (response) {
req.responseType = 'json';
}
@@ -88,7 +89,8 @@ export const _post = (url: string, data: Object, onreadystatechange: (req: XMLHt
export function _delete(url: string, data: Object, onreadystatechange: (req: XMLHttpRequest) => void, noConnectionError: boolean = false): void {
let req = new XMLHttpRequest();
req.open("DELETE", window.URLBase + url, true);
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.onreadystatechange = () => {
+2
View File
@@ -1,5 +1,7 @@
import {addLoader, removeLoader, _get} from "../modules/common.js";
declare var window: GlobalWindow;
export interface DiscordUser {
name: string;
avatar_url: string;
+3 -1
View File
@@ -2,6 +2,8 @@ import { _get, _post, _delete, toClipboard, toggleLoader, toDateString } from ".
import { DiscordUser, newDiscordSearch } from "../modules/discord.js";
import { reloadProfileNames } from "../modules/profiles.js";
declare var window: GlobalWindow;
class DOMInvite implements Invite {
updateNotify = (checkbox: HTMLInputElement) => {
let state: { [code: string]: { [type: string]: boolean } } = {};
@@ -66,7 +68,7 @@ class DOMInvite implements Invite {
codeLink = codeLink.split(split)[0];
}
if (codeLink.slice(-1) != "/") { codeLink += "/"; }
this._codeLink = codeLink + "invite/" + code;
this._codeLink = codeLink + window.pages.Form + "/" + code;
const linkEl = this._codeArea.querySelector("a") as HTMLAnchorElement;
if (this.label == "") {
linkEl.textContent = code.replace(/-/g, '-');
+3 -1
View File
@@ -1,6 +1,8 @@
import { Modal } from "../modules/modal.js";
import { toggleLoader, _post, unicodeB64Encode } from "../modules/common.js";
declare var window: GlobalWindow;
export class Login {
loggedIn: boolean = false;
private _modal: Modal;
@@ -14,7 +16,7 @@ export class Login {
constructor(modal: Modal, endpoint: string, appearance: string) {
this._endpoint = endpoint;
this._url = window.URLBase + endpoint;
this._url = window.pages.Base + endpoint;
if (this._url[this._url.length-1] != '/') this._url += "/";
this._modal = modal;
+1 -1
View File
@@ -1,4 +1,4 @@
declare var window: Window;
declare var window: GlobalWindow;
export class Modal implements Modal {
modal: HTMLElement;
+1
View File
@@ -73,6 +73,7 @@ export class PageManager {
}
loadPage (p: Page) {
console.log("loading page with", p.name || this.defaultName, p.title, p.url + window.location.search);
window.history.pushState(p.name || this.defaultName, p.title, p.url + window.location.search);
}
+2
View File
@@ -1,5 +1,7 @@
import { _get, _post, _delete, toggleLoader } from "../modules/common.js";
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;
+2
View File
@@ -1,5 +1,7 @@
const dateParser = require("any-date-parser");
declare var window: GlobalWindow;
export interface QueryType {
name: string;
description?: string;
+2
View File
@@ -2,6 +2,8 @@ import { _get, _post, _delete, _download, _upload, toggleLoader, addLoader, remo
import { Marked } from "@ts-stack/markdown";
import { stripMarkdown } from "../modules/stripmd.js";
declare var window: GlobalWindow;
const toBool = (s: string): boolean => {
let b = Boolean(s);
if (s == "false") b = false;
+3 -1
View File
@@ -1,5 +1,7 @@
import { PageManager, Page } from "../modules/pages.js";
declare var window: GlobalWindow;
export interface Tab {
page: Page;
tabEl: HTMLDivElement;
@@ -38,7 +40,7 @@ export class Tabs implements Tabs {
tab.page = {
name: tabID,
title: document.title, /*FIXME: Get actual names from translations*/
url: window.URLBase + "/" + url,
url: url,
show: () => {
tab.buttonEl.classList.add("active", "~urge");
tab.tabEl.classList.remove("unfocused");
+2
View File
@@ -1,6 +1,8 @@
import { _get, _post, toggleLoader, toDateString } from "../modules/common.js";
import { Marked, Renderer } from "@ts-stack/markdown";
declare var window: GlobalWindow;
interface updateDTO {
new: boolean;
update: Update;
+2
View File
@@ -1,5 +1,7 @@
import { toClipboard, notificationBox } from "./modules/common.js";
declare var window: GlobalWindow;
const pin = document.getElementById("pin") as HTMLSpanElement;
if (pin) {
+1 -2
View File
@@ -3,12 +3,11 @@ import { lang, LangFile, loadLangSelector } from "./modules/lang.js";
import { ThemeManager } from "./modules/theme.js";
import { PageManager } from "./modules/pages.js";
interface sWindow extends Window {
interface sWindow extends GlobalWindow {
messages: {};
}
declare var window: sWindow;
window.URLBase = "";
const theme = new ThemeManager(document.getElementById("button-theme"));
+14 -2
View File
@@ -12,8 +12,19 @@ interface ArrayConstructor {
from(arrayLike: any, mapFn?, thisArg?): Array<any>;
}
declare interface Window {
URLBase: string;
declare interface PagePaths {
// The base subfolder the app is hosted on.
Base: string;
// The subdirectory this bit of the app is hosted on (e.g. admin is usually on "/", myacc is usually on "/my/account")
Current: string;
// Those for other pages
Admin: string;
MyAccount: string;
Form: string;
}
declare interface GlobalWindow extends Window {
pages: PagePaths;
modals: Modals;
cssFile: string;
availableProfiles: string[];
@@ -25,6 +36,7 @@ declare interface Window {
matrixEnabled: boolean;
ombiEnabled: boolean;
jellyseerrEnabled: boolean;
pwrEnabled: boolean;
usernameEnabled: boolean;
linkResetEnabled: boolean;
token: string;
+6 -7
View File
@@ -7,7 +7,7 @@ import { Discord, Telegram, Matrix, ServiceConfiguration, MatrixConfiguration }
import { Validator, ValidatorConf, ValidatorRespDTO } from "./modules/validator.js";
import { PageManager } from "./modules/pages.js";
interface userWindow extends Window {
interface userWindow extends GlobalWindow {
jellyfinID: string;
username: string;
emailRequired: boolean;
@@ -18,14 +18,13 @@ interface userWindow extends Window {
discordInviteLink: boolean;
matrixUserID: string;
discordSendPINMessage: string;
pwrEnabled: string;
referralsEnabled: boolean;
}
const basePath = window.location.pathname.replace("/password/reset", "");
declare var window: userWindow;
const basePath = window.location.pathname.replace("/password/reset", "");
const theme = new ThemeManager(document.getElementById("button-theme"));
window.lang = new lang(window.langFile as LangFile);
@@ -38,7 +37,7 @@ window.token = "";
window.modals = {} as Modals;
let pages = new PageManager({
const pages = new PageManager({
hideOthersOnPageShow: true,
defaultName: "",
defaultTitle: document.title,
@@ -311,7 +310,7 @@ class ReferralCard {
path = path.split(split)[0];
}
if (path.slice(-1) != "/") { path += "/"; }
path = path + "invite/" + this._code;
path = path + window.pages.Form + "/" + this._code;
u.pathname = path;
u.hash = "";
@@ -661,7 +660,7 @@ document.addEventListener("details-reload", () => {
expiryCard.expiry = details.expiry;
const adminBackButton = document.getElementById("admin-back-button") as HTMLAnchorElement;
adminBackButton.href = window.location.href.replace("my/account", "");
adminBackButton.href = window.location.href.replace(window.pages.MyAccount, window.pages.Admin);
let messageCard = document.getElementById("card-message");
if (details.accounts_admin) {