141 lines
4.3 KiB
JavaScript

const API_BASE = "http://127.0.0.1:5000/api";
const THEME_STORAGE_KEY = "journey-theme";
const DEFAULT_THEME = "dark";
const VALID_THEMES = ["dark", "light", "system"];
let currentUser = null;
function getThemePreference() {
const storedTheme = localStorage.getItem(THEME_STORAGE_KEY);
return VALID_THEMES.includes(storedTheme) ? storedTheme : DEFAULT_THEME;
}
function applyThemePreference(theme = getThemePreference()) {
const selectedTheme = VALID_THEMES.includes(theme) ? theme : DEFAULT_THEME;
document.documentElement.dataset.theme = selectedTheme;
}
function setThemePreference(theme) {
const selectedTheme = VALID_THEMES.includes(theme) ? theme : DEFAULT_THEME;
localStorage.setItem(THEME_STORAGE_KEY, selectedTheme);
applyThemePreference(selectedTheme);
}
function getThemeControlHtml() {
return `
<div class="theme-switcher">
<label for="theme-select">Theme</label>
<select id="theme-select" class="theme-select" aria-label="Theme">
<option value="dark">Dark</option>
<option value="light">Light</option>
<option value="system">System</option>
</select>
</div>
`;
}
function attachThemeControl(root = document) {
const select = root.querySelector("#theme-select");
if (!select) return;
select.value = getThemePreference();
select.addEventListener("change", () => setThemePreference(select.value));
}
function ensureStandaloneThemeControl() {
if (document.getElementById("user-menu") || document.getElementById("theme-switcher-floating")) return;
const container = document.createElement("div");
container.id = "theme-switcher-floating";
container.className = "theme-switcher-floating";
container.innerHTML = getThemeControlHtml();
document.body.appendChild(container);
attachThemeControl(container);
}
applyThemePreference();
async function checkAuth() {
try {
const res = await fetch(`${API_BASE}/me`, { credentials: "include" });
if (res.ok) {
currentUser = await res.json();
return true;
}
return false;
} catch (err) {
return false;
}
}
async function checkAuthAndRedirect() {
const ok = await checkAuth();
if (!ok) {
window.location.href = "login.html";
return false;
}
return true;
}
function updateUserMenu() {
const container = document.getElementById("user-menu");
if (!container) return;
if (currentUser) {
container.innerHTML = `
${getThemeControlHtml()}
<span class="username"><i class="fas fa-user"></i> ${escapeHtml(currentUser.username)}</span>
<button id="logout-btn" class="logout-btn"><i class="fas fa-sign-out-alt"></i> Logout</button>
`;
attachThemeControl(container);
document.getElementById("logout-btn")?.addEventListener("click", logout);
} else {
container.innerHTML = `
${getThemeControlHtml()}
<button id="login-open-btn" class="login-btn"><i class="fas fa-sign-in-alt"></i> Login</button>
`;
attachThemeControl(container);
document.getElementById("login-open-btn")?.addEventListener("click", () => {
window.location.href = "login.html";
});
}
}
async function logout() {
await fetch(`${API_BASE}/logout`, { method: "POST", credentials: "include" });
window.location.href = "login.html";
}
function escapeHtml(str) {
if (!str) return "";
return String(str).replace(/[&<>"']/g, function (m) {
if (m === "&") return "&amp;";
if (m === "<") return "&lt;";
if (m === ">") return "&gt;";
if (m === '"') return "&quot;";
if (m === "'") return "&#39;";
return m;
});
}
function escapeAttribute(str) {
return escapeHtml(str);
}
function sanitizeDisplayUrl(url) {
const value = String(url || "").trim().replace(/[\u0000-\u001f\u007f\s]+/g, "");
if (/^(https?:\/\/|\/uploads\/|uploads\/)/i.test(value)) {
return value;
}
return "";
}
function showToast(msg, isError = false) {
const toast = document.getElementById("toast");
if (!toast) return;
toast.textContent = msg;
toast.style.backgroundColor = isError ? "var(--red-7)" : "var(--green-7)";
toast.style.display = "block";
setTimeout(() => {
toast.style.display = "none";
}, 3000);
}
document.addEventListener("DOMContentLoaded", ensureStandaloneThemeControl);