(function () { const STORAGE = { token: "learning-demo-token", username: "learning-demo-username", language: "learning-demo-language" }; const TEXT = { zh: { brand: "Spring \u5b66\u4e60\u5de5\u4f5c\u53f0", home: "\u9996\u9875", access: "\u767b\u5f55\u9875", loginReady: "\u5df2\u767b\u5f55\uff0c\u53ef\u8bbf\u95ee\u53d7\u4fdd\u62a4\u5b9e\u9a8c", loginMissing: "\u672a\u767b\u5f55\uff0c\u53d7\u4fdd\u62a4\u5b9e\u9a8c\u4f1a\u8fd4\u56de 401", currentUser: "\u5f53\u524d\u7528\u6237", logout: "\u9000\u51fa\u767b\u5f55", login: "\u53bb\u767b\u5f55", languageToggle: "EN", unauthorized: "\u672a\u767b\u5f55\u6216\u4ee4\u724c\u5df2\u5931\u6548\uff0c\u8bf7\u5148\u6253\u5f00\u767b\u5f55\u9875\u5b8c\u6210\u6f14\u793a\u767b\u5f55\u3002", requestFailed: "\u8bf7\u6c42\u5931\u8d25\uff0c\u8bf7\u7a0d\u540e\u91cd\u8bd5\u3002" }, en: { brand: "Spring Learning Workspace", home: "Home", access: "Login", loginReady: "Authenticated, protected labs are available", loginMissing: "Not logged in, protected labs will return 401", currentUser: "User", logout: "Logout", login: "Login", languageToggle: "\u4e2d\u6587", unauthorized: "Not logged in or token expired. Open the login page first.", requestFailed: "Request failed. Please try again." } }; function normalizeLanguage(language) { return language === "en" ? "en" : "zh"; } function getLanguage() { return normalizeLanguage(localStorage.getItem(STORAGE.language)); } function setLanguage(language) { const normalized = normalizeLanguage(language); localStorage.setItem(STORAGE.language, normalized); document.documentElement.lang = normalized === "zh" ? "zh-CN" : "en"; window.dispatchEvent(new CustomEvent("learning-language-changed", { detail: { language: normalized } })); } function t(key) { const lang = getLanguage(); return (TEXT[lang] && TEXT[lang][key]) || key; } function getToken() { return localStorage.getItem(STORAGE.token) || ""; } function getUsername() { return localStorage.getItem(STORAGE.username) || ""; } function isLoggedIn() { return Boolean(getToken()); } function saveAuth(token, username) { localStorage.setItem(STORAGE.token, token || ""); localStorage.setItem(STORAGE.username, username || ""); window.dispatchEvent(new CustomEvent("learning-auth-changed", { detail: { loggedIn: isLoggedIn(), username: getUsername() } })); } function clearAuth() { localStorage.removeItem(STORAGE.token); localStorage.removeItem(STORAGE.username); window.dispatchEvent(new CustomEvent("learning-auth-changed", { detail: { loggedIn: false, username: "" } })); } function ensureStyle() { if (document.getElementById("learning-shell-style")) { return; } const style = document.createElement("style"); style.id = "learning-shell-style"; style.textContent = [ ".learning-shell {", " max-width: 1380px;", " margin: 18px auto 0;", " padding: 0 24px;", "}", ".learning-shell-card {", " display: flex;", " justify-content: space-between;", " align-items: center;", " gap: 14px;", " flex-wrap: wrap;", " padding: 14px 18px;", " border-radius: 22px;", " border: 1px solid rgba(216, 228, 240, 0.9);", " background: rgba(255, 255, 255, 0.88);", " box-shadow: 0 12px 28px rgba(18, 32, 51, 0.08);", " backdrop-filter: blur(10px);", "}", ".learning-shell-brand {", " display: flex;", " flex-direction: column;", " gap: 4px;", "}", ".learning-shell-brand strong {", " font-size: 15px;", " color: #122033;", "}", ".learning-shell-brand span {", " font-size: 13px;", " color: #5d7288;", "}", ".learning-shell-actions {", " display: flex;", " align-items: center;", " gap: 10px;", " flex-wrap: wrap;", "}", ".learning-shell-link,", ".learning-shell-button,", ".learning-shell-badge {", " display: inline-flex;", " align-items: center;", " justify-content: center;", " min-height: 36px;", " padding: 0 14px;", " border-radius: 999px;", " text-decoration: none;", " font-size: 13px;", " font-weight: 700;", "}", ".learning-shell-link {", " color: #0f67b5;", " background: rgba(15, 103, 181, 0.09);", "}", ".learning-shell-button {", " border: 0;", " cursor: pointer;", " color: #fff;", " background: linear-gradient(135deg, #177245, #35a465);", "}", ".learning-shell-button.alt {", " color: #0f67b5;", " background: rgba(15, 103, 181, 0.09);", "}", ".learning-shell-badge {", " color: #33485e;", " background: rgba(18, 32, 51, 0.06);", "}", "@media (max-width: 780px) {", " .learning-shell {", " padding: 0 14px;", " }", "}" ].join("\n"); document.head.appendChild(style); } function mountShell(options) { ensureStyle(); if (document.querySelector(".learning-shell")) { if (options && typeof options.onLanguageChange === "function") { options.onLanguageChange(getLanguage()); } return; } const shell = document.createElement("div"); shell.className = "learning-shell"; shell.innerHTML = [ '
" ].join(""); const firstPage = document.querySelector(".page"); if (firstPage) { document.body.insertBefore(shell, firstPage); } else { document.body.insertBefore(shell, document.body.firstChild); } const els = { brand: shell.querySelector('[data-role="brand"]'), status: shell.querySelector('[data-role="status"]'), home: shell.querySelector('[data-role="home"]'), access: shell.querySelector('[data-role="access"]'), user: shell.querySelector('[data-role="user"]'), language: shell.querySelector('[data-role="language"]'), login: shell.querySelector('[data-role="login"]'), logout: shell.querySelector('[data-role="logout"]') }; function render() { const loggedIn = isLoggedIn(); const username = getUsername(); els.brand.textContent = t("brand"); els.status.textContent = loggedIn ? t("loginReady") : t("loginMissing"); els.home.textContent = t("home"); els.access.textContent = t("access"); els.user.textContent = loggedIn ? t("currentUser") + ": " + username : t("loginMissing"); els.language.textContent = t("languageToggle"); els.login.textContent = t("login"); els.logout.textContent = t("logout"); els.login.style.display = loggedIn ? "none" : "inline-flex"; els.logout.style.display = loggedIn ? "inline-flex" : "none"; if (options && typeof options.onLanguageChange === "function") { options.onLanguageChange(getLanguage()); } if (options && typeof options.onAuthChange === "function") { options.onAuthChange({ loggedIn: loggedIn, username: username }); } } els.language.addEventListener("click", function () { setLanguage(getLanguage() === "zh" ? "en" : "zh"); }); els.login.addEventListener("click", function () { window.location.href = "/access.html"; }); els.logout.addEventListener("click", function () { clearAuth(); render(); }); window.addEventListener("learning-auth-changed", render); window.addEventListener("learning-language-changed", render); render(); } async function fetchWithAuth(url, options) { const requestOptions = options || {}; const headers = new Headers(requestOptions.headers || {}); const token = getToken(); if (token && !headers.has("Authorization")) { headers.set("Authorization", "Bearer " + token); } return fetch(url, Object.assign({}, requestOptions, { headers: headers })); } async function requestJson(url, options) { const response = await fetchWithAuth(url, options); const contentType = response.headers.get("content-type") || ""; const payload = contentType.includes("application/json") ? await response.json() : await response.text(); if (!response.ok) { const error = new Error( typeof payload === "object" && payload && payload.message ? payload.message : t("requestFailed") ); error.status = response.status; error.payload = payload; error.details = typeof payload === "object" && payload ? payload.data : null; throw error; } if ( typeof payload === "object" && payload && Object.prototype.hasOwnProperty.call(payload, "code") && payload.code !== 0 ) { const error = new Error(payload.message || t("requestFailed")); error.status = payload.code; error.payload = payload; error.details = payload.data; throw error; } return payload; } function describeError(error) { if (error && Number(error.status) === 401) { return t("unauthorized"); } return error && error.message ? error.message : t("requestFailed"); } setLanguage(getLanguage()); window.learningShell = { mountShell: mountShell, getLanguage: getLanguage, setLanguage: setLanguage, getToken: getToken, getUsername: getUsername, isLoggedIn: isLoggedIn, saveAuth: saveAuth, clearAuth: clearAuth, fetchWithAuth: fetchWithAuth, requestJson: requestJson, describeError: describeError }; })();