custom branding
This commit is contained in:
@@ -11,6 +11,9 @@ const loginTitle = document.getElementById("login-title");
|
||||
const loginSubtitle = document.getElementById("login-subtitle");
|
||||
const loginIcon = document.getElementById("login-icon");
|
||||
const loginLogo = document.getElementById("login-logo");
|
||||
const loginHeroImage = document.getElementById("login-hero-image");
|
||||
const loginBrandFooter = document.getElementById("login-brand-footer");
|
||||
const loginSupportLink = document.getElementById("login-support-link");
|
||||
|
||||
(() => {
|
||||
const params = new URLSearchParams(window.location.search);
|
||||
@@ -31,6 +34,7 @@ const loginLogo = document.getElementById("login-logo");
|
||||
if (loginTitle && data.title) loginTitle.textContent = data.title;
|
||||
if (loginSubtitle && data.subtitle) loginSubtitle.textContent = data.subtitle;
|
||||
if (data.productName) document.title = `${data.productName} Login`;
|
||||
if (data.accentColor) document.documentElement.style.setProperty("--brand-accent", data.accentColor);
|
||||
|
||||
if (loginIcon && data.iconClass) {
|
||||
loginIcon.className = `${data.iconClass} login-crown`;
|
||||
@@ -48,6 +52,34 @@ const loginLogo = document.getElementById("login-logo");
|
||||
};
|
||||
loginLogo.src = data.logoUrl;
|
||||
}
|
||||
|
||||
if (loginHeroImage && data.heroImageUrl) {
|
||||
loginHeroImage.alt = data.heroImageAlt || "";
|
||||
loginHeroImage.onload = () => {
|
||||
loginHeroImage.style.display = "";
|
||||
document.body.classList.add("login-has-hero-image");
|
||||
};
|
||||
loginHeroImage.onerror = () => {
|
||||
loginHeroImage.style.display = "none";
|
||||
document.body.classList.remove("login-has-hero-image");
|
||||
};
|
||||
loginHeroImage.src = data.heroImageUrl;
|
||||
}
|
||||
|
||||
if (loginBrandFooter && data.footerText) {
|
||||
loginBrandFooter.textContent = data.footerText;
|
||||
loginBrandFooter.style.display = "";
|
||||
}
|
||||
|
||||
if (loginSupportLink && data.supportText && data.supportUrl) {
|
||||
loginSupportLink.textContent = data.supportText;
|
||||
loginSupportLink.href = data.supportUrl;
|
||||
if (!data.supportUrl.startsWith("/")) {
|
||||
loginSupportLink.target = "_blank";
|
||||
loginSupportLink.rel = "noopener noreferrer";
|
||||
}
|
||||
loginSupportLink.style.display = "";
|
||||
}
|
||||
} catch {}
|
||||
})();
|
||||
|
||||
|
||||
@@ -340,6 +340,27 @@ a {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.login-hero-image {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
opacity: 0.38;
|
||||
filter: saturate(0.9) contrast(1.05);
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.login-body.login-has-hero-image::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
background:
|
||||
linear-gradient(90deg, rgba(5, 7, 15, 0.88), rgba(5, 7, 15, 0.64)),
|
||||
radial-gradient(75% 75% at 50% 25%, transparent, rgba(5, 7, 15, 0.62));
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.login-body::before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
@@ -984,6 +1005,7 @@ article.card[data-bookmarked="true"] {
|
||||
inset: 0;
|
||||
pointer-events: none;
|
||||
overflow: hidden;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.particle {
|
||||
@@ -1031,6 +1053,7 @@ article.card[data-bookmarked="true"] {
|
||||
0 0 100px rgba(92, 145, 255, 0.1);
|
||||
position: relative;
|
||||
opacity: 0;
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
.login-card::before {
|
||||
@@ -1056,8 +1079,8 @@ article.card[data-bookmarked="true"] {
|
||||
|
||||
.login-crown {
|
||||
font-size: 48px;
|
||||
color: #7a5bff;
|
||||
filter: drop-shadow(0 4px 12px rgba(122, 91, 255, 0.5));
|
||||
color: var(--brand-accent, #7a5bff);
|
||||
filter: drop-shadow(0 4px 12px color-mix(in srgb, var(--brand-accent, #7a5bff) 50%, transparent));
|
||||
}
|
||||
|
||||
.login-logo {
|
||||
@@ -1065,7 +1088,7 @@ article.card[data-bookmarked="true"] {
|
||||
height: 56px;
|
||||
object-fit: contain;
|
||||
border-radius: 12px;
|
||||
filter: drop-shadow(0 4px 12px rgba(122, 91, 255, 0.35));
|
||||
filter: drop-shadow(0 4px 12px color-mix(in srgb, var(--brand-accent, #7a5bff) 35%, transparent));
|
||||
}
|
||||
|
||||
.login-brand {
|
||||
@@ -1074,7 +1097,7 @@ article.card[data-bookmarked="true"] {
|
||||
font-weight: 700;
|
||||
letter-spacing: 1px;
|
||||
margin-bottom: 12px;
|
||||
background: linear-gradient(135deg, #7a5bff, #5672ff);
|
||||
background: linear-gradient(135deg, var(--brand-accent, #7a5bff), #7dd3fc);
|
||||
-webkit-background-clip: text;
|
||||
-webkit-text-fill-color: transparent;
|
||||
background-clip: text;
|
||||
@@ -1097,6 +1120,24 @@ article.card[data-bookmarked="true"] {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.login-enterprise-footer,
|
||||
.login-support-link {
|
||||
display: block;
|
||||
text-align: center;
|
||||
font-size: 0.78rem;
|
||||
color: #94a3b8;
|
||||
margin-top: 12px;
|
||||
}
|
||||
|
||||
.login-support-link {
|
||||
color: #c4b5fd;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.login-support-link:hover {
|
||||
color: #e0e7ff;
|
||||
}
|
||||
|
||||
.form-group {
|
||||
margin-bottom: 20px;
|
||||
opacity: 0;
|
||||
@@ -4456,6 +4497,33 @@ body.sb-collapsed .sb-link .sb-text {
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.nav-brand-link {
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.nav-brand-logo {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
object-fit: contain;
|
||||
border-radius: 6px;
|
||||
flex: 0 0 auto;
|
||||
filter: drop-shadow(0 2px 8px color-mix(in srgb, var(--brand-accent, #7a5bff) 28%, transparent));
|
||||
}
|
||||
|
||||
.sb-logo .nav-brand-logo,
|
||||
.sb-mobile-brand .nav-brand-logo {
|
||||
width: 22px;
|
||||
height: 22px;
|
||||
}
|
||||
|
||||
#nav-brand-name,
|
||||
#nav-mobile-brand-name {
|
||||
min-width: 0;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
/* ---- Mobile backdrop ---- */
|
||||
#sb-backdrop {
|
||||
display: none;
|
||||
|
||||
@@ -22,6 +22,7 @@ import {
|
||||
const host = document.getElementById("top-nav");
|
||||
if (host) {
|
||||
const refs = mountNav(host);
|
||||
applyBranding();
|
||||
initCommandPalette();
|
||||
import("./cert-banner.js").then(({ showCertBannerIfNeeded }) => {
|
||||
runWithoutPageTracking(() => showCertBannerIfNeeded(document.getElementById("sb-mobile-bar") || host));
|
||||
@@ -246,3 +247,44 @@ if (host) {
|
||||
setupSoftNavigation(host, { onPathChange: applyActivePath });
|
||||
startPageTracking();
|
||||
}
|
||||
|
||||
async function applyBranding() {
|
||||
try {
|
||||
const res = await fetch("/api/login/branding");
|
||||
if (!res.ok) return;
|
||||
const brand = await res.json();
|
||||
if (brand.accentColor) {
|
||||
document.documentElement.style.setProperty("--brand-accent", brand.accentColor);
|
||||
}
|
||||
|
||||
const navName = brand.navName || brand.productName || "Overlord";
|
||||
for (const el of document.querySelectorAll("#nav-brand-name, #nav-mobile-brand-name")) {
|
||||
el.textContent = navName;
|
||||
}
|
||||
|
||||
const iconClass = brand.iconClass || "fa-solid fa-crown";
|
||||
for (const icon of document.querySelectorAll("#nav-brand-icon, #nav-mobile-brand-icon")) {
|
||||
const sidebarIcon = Boolean(icon.closest(".sb-logo"));
|
||||
icon.className = `${iconClass} header-crown${sidebarIcon ? " sb-icon" : ""}`;
|
||||
}
|
||||
|
||||
const logoUrl = brand.navLogoUrl || brand.logoUrl || "";
|
||||
if (!logoUrl) return;
|
||||
|
||||
for (const logo of document.querySelectorAll("#nav-brand-logo, #nav-mobile-brand-logo")) {
|
||||
const pairedIcon = logo.id === "nav-mobile-brand-logo"
|
||||
? document.getElementById("nav-mobile-brand-icon")
|
||||
: document.getElementById("nav-brand-icon");
|
||||
logo.alt = brand.navLogoAlt || brand.logoAlt || `${navName} logo`;
|
||||
logo.onload = () => {
|
||||
logo.style.display = "";
|
||||
if (pairedIcon) pairedIcon.style.display = "none";
|
||||
};
|
||||
logo.onerror = () => {
|
||||
logo.style.display = "none";
|
||||
if (pairedIcon) pairedIcon.style.display = "";
|
||||
};
|
||||
logo.src = logoUrl;
|
||||
}
|
||||
} catch {}
|
||||
}
|
||||
|
||||
@@ -133,8 +133,10 @@ function mountTopbar(host) {
|
||||
host.innerHTML = `
|
||||
<div class="topbar-grid">
|
||||
<div class="topbar-left">
|
||||
<a href="/" class="flex items-center gap-2 font-semibold tracking-wide">
|
||||
<i class="fa-solid fa-crown header-crown"></i> Overlord
|
||||
<a href="/" class="nav-brand-link flex items-center gap-2 font-semibold tracking-wide">
|
||||
<img id="nav-brand-logo" class="nav-brand-logo" alt="Overlord logo" style="display: none" />
|
||||
<i id="nav-brand-icon" class="fa-solid fa-crown header-crown"></i>
|
||||
<span id="nav-brand-name">Overlord</span>
|
||||
</a>
|
||||
<button id="topbar-nav-toggle" class="topbar-nav-toggle" aria-label="Open navigation menu" aria-expanded="false" type="button">
|
||||
<i class="fa-solid fa-layer-group"></i>
|
||||
@@ -281,9 +283,10 @@ function mountSidebar(host) {
|
||||
|
||||
host.innerHTML = `
|
||||
<div class="sb-header">
|
||||
<a href="/" class="sb-logo">
|
||||
<i class="fa-solid fa-crown header-crown sb-icon"></i>
|
||||
<span class="sb-text">Overlord</span>
|
||||
<a href="/" class="sb-logo nav-brand-link">
|
||||
<img id="nav-brand-logo" class="nav-brand-logo sb-icon" alt="Overlord logo" style="display: none" />
|
||||
<i id="nav-brand-icon" class="fa-solid fa-crown header-crown sb-icon"></i>
|
||||
<span id="nav-brand-name" class="sb-text">Overlord</span>
|
||||
</a>
|
||||
<button id="sb-collapse-btn" class="sb-collapse-btn" title="Toggle sidebar" aria-label="Toggle sidebar">
|
||||
<i class="fa-solid fa-chevron-left"></i>
|
||||
@@ -332,8 +335,9 @@ function mountSidebar(host) {
|
||||
<i class="fa-solid fa-bars"></i>
|
||||
</button>
|
||||
<a href="/" class="sb-mobile-brand">
|
||||
<i class="fa-solid fa-crown header-crown" style="font-size:0.85rem"></i>
|
||||
<span>Overlord</span>
|
||||
<img id="nav-mobile-brand-logo" class="nav-brand-logo" alt="Overlord logo" style="display: none" />
|
||||
<i id="nav-mobile-brand-icon" class="fa-solid fa-crown header-crown" style="font-size:0.85rem"></i>
|
||||
<span id="nav-mobile-brand-name">Overlord</span>
|
||||
</a>
|
||||
`;
|
||||
host.insertAdjacentElement("afterend", mobileBar);
|
||||
|
||||
@@ -108,6 +108,24 @@ const appearanceForm = document.getElementById("appearance-form");
|
||||
const appearancePermissionNote = document.getElementById("appearance-permission-note");
|
||||
const appearanceSaveBtn = document.getElementById("appearance-save-btn");
|
||||
const appearanceCustomCssInput = document.getElementById("appearance-custom-css");
|
||||
const brandProductNameInput = document.getElementById("brand-product-name");
|
||||
const brandNavNameInput = document.getElementById("brand-nav-name");
|
||||
const brandAccentColorInput = document.getElementById("brand-accent-color");
|
||||
const brandIconClassInput = document.getElementById("brand-icon-class");
|
||||
const brandNavLogoUrlInput = document.getElementById("brand-nav-logo-url");
|
||||
const brandNavLogoFileInput = document.getElementById("brand-nav-logo-file");
|
||||
const brandNavLogoAltInput = document.getElementById("brand-nav-logo-alt");
|
||||
const brandLoginLogoUrlInput = document.getElementById("brand-login-logo-url");
|
||||
const brandLoginLogoFileInput = document.getElementById("brand-login-logo-file");
|
||||
const brandLoginLogoAltInput = document.getElementById("brand-login-logo-alt");
|
||||
const brandLoginTitleInput = document.getElementById("brand-login-title");
|
||||
const brandLoginSubtitleInput = document.getElementById("brand-login-subtitle");
|
||||
const brandHeroImageUrlInput = document.getElementById("brand-hero-image-url");
|
||||
const brandHeroImageFileInput = document.getElementById("brand-hero-image-file");
|
||||
const brandHeroImageAltInput = document.getElementById("brand-hero-image-alt");
|
||||
const brandFooterTextInput = document.getElementById("brand-footer-text");
|
||||
const brandSupportTextInput = document.getElementById("brand-support-text");
|
||||
const brandSupportUrlInput = document.getElementById("brand-support-url");
|
||||
|
||||
const exportImportSection = document.getElementById("export-import-section");
|
||||
const exportSettingsBtn = document.getElementById("export-settings-btn");
|
||||
@@ -1354,13 +1372,146 @@ function initSettingsSidebar() {
|
||||
}, { passive: true });
|
||||
}
|
||||
|
||||
const brandInputs = [
|
||||
brandProductNameInput,
|
||||
brandNavNameInput,
|
||||
brandAccentColorInput,
|
||||
brandIconClassInput,
|
||||
brandNavLogoUrlInput,
|
||||
brandNavLogoFileInput,
|
||||
brandNavLogoAltInput,
|
||||
brandLoginLogoUrlInput,
|
||||
brandLoginLogoFileInput,
|
||||
brandLoginLogoAltInput,
|
||||
brandLoginTitleInput,
|
||||
brandLoginSubtitleInput,
|
||||
brandHeroImageUrlInput,
|
||||
brandHeroImageFileInput,
|
||||
brandHeroImageAltInput,
|
||||
brandFooterTextInput,
|
||||
brandSupportTextInput,
|
||||
brandSupportUrlInput,
|
||||
];
|
||||
|
||||
function setAppearanceFormDisabled(disabled) {
|
||||
for (const input of [...brandInputs, appearanceCustomCssInput, appearanceSaveBtn]) {
|
||||
if (input) input.disabled = disabled;
|
||||
}
|
||||
}
|
||||
|
||||
function applyBrandingForm(loginBranding = {}) {
|
||||
if (brandProductNameInput) brandProductNameInput.value = loginBranding.productName || "Overlord";
|
||||
if (brandNavNameInput) brandNavNameInput.value = loginBranding.navName || loginBranding.productName || "Overlord";
|
||||
if (brandAccentColorInput) brandAccentColorInput.value = /^#[0-9a-fA-F]{6}$/.test(loginBranding.accentColor || "") ? loginBranding.accentColor : "#7a5bff";
|
||||
if (brandIconClassInput) brandIconClassInput.value = loginBranding.iconClass || "fa-solid fa-crown";
|
||||
if (brandNavLogoUrlInput) brandNavLogoUrlInput.value = loginBranding.navLogoUrl || "";
|
||||
if (brandNavLogoAltInput) brandNavLogoAltInput.value = loginBranding.navLogoAlt || "";
|
||||
if (brandLoginLogoUrlInput) brandLoginLogoUrlInput.value = loginBranding.logoUrl || "";
|
||||
if (brandLoginLogoAltInput) brandLoginLogoAltInput.value = loginBranding.logoAlt || "";
|
||||
if (brandLoginTitleInput) brandLoginTitleInput.value = loginBranding.title || "Welcome back";
|
||||
if (brandLoginSubtitleInput) brandLoginSubtitleInput.value = loginBranding.subtitle || "Sign in to your control plane";
|
||||
if (brandHeroImageUrlInput) brandHeroImageUrlInput.value = loginBranding.heroImageUrl || "";
|
||||
if (brandHeroImageAltInput) brandHeroImageAltInput.value = loginBranding.heroImageAlt || "";
|
||||
if (brandFooterTextInput) brandFooterTextInput.value = loginBranding.footerText || "";
|
||||
if (brandSupportTextInput) brandSupportTextInput.value = loginBranding.supportText || "";
|
||||
if (brandSupportUrlInput) brandSupportUrlInput.value = loginBranding.supportUrl || "";
|
||||
}
|
||||
|
||||
function collectBrandingForm() {
|
||||
return {
|
||||
productName: String(brandProductNameInput?.value || "").trim(),
|
||||
navName: String(brandNavNameInput?.value || "").trim(),
|
||||
accentColor: String(brandAccentColorInput?.value || "").trim(),
|
||||
iconClass: String(brandIconClassInput?.value || "").trim(),
|
||||
navLogoUrl: String(brandNavLogoUrlInput?.value || "").trim(),
|
||||
navLogoAlt: String(brandNavLogoAltInput?.value || "").trim(),
|
||||
logoUrl: String(brandLoginLogoUrlInput?.value || "").trim(),
|
||||
logoAlt: String(brandLoginLogoAltInput?.value || "").trim(),
|
||||
title: String(brandLoginTitleInput?.value || "").trim(),
|
||||
subtitle: String(brandLoginSubtitleInput?.value || "").trim(),
|
||||
heroImageUrl: String(brandHeroImageUrlInput?.value || "").trim(),
|
||||
heroImageAlt: String(brandHeroImageAltInput?.value || "").trim(),
|
||||
footerText: String(brandFooterTextInput?.value || "").trim(),
|
||||
supportText: String(brandSupportTextInput?.value || "").trim(),
|
||||
supportUrl: String(brandSupportUrlInput?.value || "").trim(),
|
||||
};
|
||||
}
|
||||
|
||||
function setBrandingUploadName(fileInput) {
|
||||
const label = document.getElementById(`${fileInput.id}-name`);
|
||||
if (label) label.textContent = fileInput.files?.[0]?.name || "No file chosen";
|
||||
}
|
||||
|
||||
async function uploadBrandingImage(fileInput, targetInput, kind, label) {
|
||||
const file = fileInput?.files?.[0];
|
||||
if (!file || !targetInput) return;
|
||||
|
||||
if (!requireUiPermission("system:appearance", "Appearance settings permission required.")) {
|
||||
fileInput.value = "";
|
||||
return;
|
||||
}
|
||||
|
||||
if (file.size > 5 * 1024 * 1024) {
|
||||
showMessage("Branding images must be 5 MB or smaller.", "error");
|
||||
fileInput.value = "";
|
||||
return;
|
||||
}
|
||||
|
||||
const form = new FormData();
|
||||
form.append("kind", kind);
|
||||
form.append("file", file);
|
||||
|
||||
fileInput.disabled = true;
|
||||
showMessage(`Uploading ${label}...`);
|
||||
try {
|
||||
const res = await fetch("/api/settings/appearance/image", {
|
||||
method: "POST",
|
||||
credentials: "include",
|
||||
body: form,
|
||||
});
|
||||
const data = await res.json().catch(() => ({}));
|
||||
if (!res.ok) {
|
||||
showMessage(data.error || `Failed to upload ${label}.`, "error");
|
||||
return;
|
||||
}
|
||||
targetInput.value = data.url || "";
|
||||
showMessage(`${label} uploaded. Save Branding to apply it.`);
|
||||
} catch {
|
||||
showMessage(`Failed to upload ${label}.`, "error");
|
||||
} finally {
|
||||
fileInput.disabled = false;
|
||||
fileInput.value = "";
|
||||
setBrandingUploadName(fileInput);
|
||||
}
|
||||
}
|
||||
|
||||
function initBrandingUploads() {
|
||||
if (brandNavLogoFileInput) {
|
||||
brandNavLogoFileInput.addEventListener("change", () => {
|
||||
setBrandingUploadName(brandNavLogoFileInput);
|
||||
uploadBrandingImage(brandNavLogoFileInput, brandNavLogoUrlInput, "nav-logo", "Navigation logo");
|
||||
});
|
||||
}
|
||||
if (brandLoginLogoFileInput) {
|
||||
brandLoginLogoFileInput.addEventListener("change", () => {
|
||||
setBrandingUploadName(brandLoginLogoFileInput);
|
||||
uploadBrandingImage(brandLoginLogoFileInput, brandLoginLogoUrlInput, "login-logo", "Sign-in logo");
|
||||
});
|
||||
}
|
||||
if (brandHeroImageFileInput) {
|
||||
brandHeroImageFileInput.addEventListener("change", () => {
|
||||
setBrandingUploadName(brandHeroImageFileInput);
|
||||
uploadBrandingImage(brandHeroImageFileInput, brandHeroImageUrlInput, "hero-image", "Sign-in background image");
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
async function loadAppearanceSettings() {
|
||||
if (!currentUser) return;
|
||||
|
||||
if (!userHas("system:appearance")) {
|
||||
if (appearancePermissionNote) appearancePermissionNote.classList.remove("hidden");
|
||||
if (appearanceCustomCssInput) appearanceCustomCssInput.disabled = true;
|
||||
if (appearanceSaveBtn) appearanceSaveBtn.disabled = true;
|
||||
setAppearanceFormDisabled(true);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1369,14 +1520,15 @@ async function loadAppearanceSettings() {
|
||||
try {
|
||||
const res = await fetch("/api/settings/appearance", { credentials: "include" });
|
||||
if (!res.ok) {
|
||||
showMessage("Failed to load custom CSS settings.", "error");
|
||||
showMessage("Failed to load branding settings.", "error");
|
||||
return;
|
||||
}
|
||||
const data = await res.json().catch(() => ({}));
|
||||
if (appearanceCustomCssInput) appearanceCustomCssInput.value = data.customCSS || "";
|
||||
if (appearanceSaveBtn) appearanceSaveBtn.disabled = false;
|
||||
applyBrandingForm(data.loginBranding || {});
|
||||
setAppearanceFormDisabled(false);
|
||||
} catch {
|
||||
showMessage("Failed to load custom CSS settings.", "error");
|
||||
showMessage("Failed to load branding settings.", "error");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1396,16 +1548,17 @@ async function saveAppearanceSettings(event) {
|
||||
method: "PUT",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
credentials: "include",
|
||||
body: JSON.stringify({ customCSS }),
|
||||
body: JSON.stringify({ customCSS, loginBranding: collectBrandingForm() }),
|
||||
});
|
||||
|
||||
const data = await res.json().catch(() => ({}));
|
||||
if (!res.ok) {
|
||||
showMessage(data.error || "Failed to save custom CSS.", "error");
|
||||
showMessage(data.error || "Failed to save branding.", "error");
|
||||
return;
|
||||
}
|
||||
|
||||
showMessage("Custom CSS saved. Reload the page to apply the new styles.");
|
||||
applyBrandingForm(data.loginBranding || collectBrandingForm());
|
||||
showMessage("Branding saved. Reload any open page to apply it everywhere.");
|
||||
}
|
||||
|
||||
const chatSettingsSection = document.getElementById("chat-settings-section");
|
||||
@@ -2620,6 +2773,7 @@ async function init() {
|
||||
tlsCertbotAutoBtn.addEventListener("click", runCertbotAutoSetup);
|
||||
if (oidcForm) oidcForm.addEventListener("submit", saveOidcSettings);
|
||||
if (appearanceForm) appearanceForm.addEventListener("submit", saveAppearanceSettings);
|
||||
initBrandingUploads();
|
||||
if (chatSettingsForm) chatSettingsForm.addEventListener("submit", saveChatSettings);
|
||||
if (inputArchiveUserForm) inputArchiveUserForm.addEventListener("submit", saveInputArchivePreference);
|
||||
if (inputArchiveAdminForm) inputArchiveAdminForm.addEventListener("submit", saveInputArchiveAdminSettings);
|
||||
|
||||
Reference in New Issue
Block a user