d14c629778
Co-authored-by: Cursor <cursoragent@cursor.com>
142 lines
3.8 KiB
JavaScript
142 lines
3.8 KiB
JavaScript
/**
|
|
* 四所实例主题:默认暗色;单独登录用 instance-theme;中控 iframe/SSO 随 hub-theme 联动。
|
|
*/
|
|
(function (global) {
|
|
const STANDALONE_KEY = "instance-theme";
|
|
const META = { dark: "#0b0d14", light: "#d8e2ec" };
|
|
|
|
function normalize(theme) {
|
|
return theme === "light" ? "light" : "dark";
|
|
}
|
|
|
|
function isHubLinked() {
|
|
try {
|
|
const p = new URLSearchParams(location.search);
|
|
if (p.get("embed") === "1") return true;
|
|
const ht = p.get("hub_theme");
|
|
if (ht === "light" || ht === "dark") return true;
|
|
} catch (_) {}
|
|
try {
|
|
if (window.self !== window.top) return true;
|
|
} catch (_) {
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
function themeFromUrl() {
|
|
try {
|
|
const t = new URLSearchParams(location.search).get("hub_theme");
|
|
if (t === "light" || t === "dark") return t;
|
|
} catch (_) {}
|
|
return null;
|
|
}
|
|
|
|
function getStandalone() {
|
|
try {
|
|
return normalize(localStorage.getItem(STANDALONE_KEY));
|
|
} catch (_) {
|
|
return "dark";
|
|
}
|
|
}
|
|
|
|
function setStandalone(theme) {
|
|
try {
|
|
localStorage.setItem(STANDALONE_KEY, normalize(theme));
|
|
} catch (_) {}
|
|
}
|
|
|
|
function get() {
|
|
if (isHubLinked()) {
|
|
return themeFromUrl() || _linkedTheme || "dark";
|
|
}
|
|
return getStandalone();
|
|
}
|
|
|
|
let _linkedTheme = null;
|
|
|
|
function apply(theme, opts) {
|
|
const options = opts || {};
|
|
const linked = isHubLinked();
|
|
const t = normalize(theme);
|
|
if (linked) {
|
|
_linkedTheme = t;
|
|
} else if (!options.skipStore) {
|
|
setStandalone(t);
|
|
}
|
|
const root = document.documentElement;
|
|
root.setAttribute("data-theme", t);
|
|
const meta = document.querySelector('meta[name="theme-color"]');
|
|
if (meta) meta.setAttribute("content", META[t]);
|
|
root.style.colorScheme = t;
|
|
syncToggleUI();
|
|
document.dispatchEvent(
|
|
new CustomEvent("instance-theme-change", { detail: { theme: t, hubLinked: linked } })
|
|
);
|
|
return t;
|
|
}
|
|
|
|
function syncToggleUI(root) {
|
|
const scope = root || document;
|
|
const linked = isHubLinked();
|
|
const toggle = scope.querySelector(".instance-theme-toggle");
|
|
if (toggle) {
|
|
toggle.classList.toggle("is-hub-linked", linked);
|
|
toggle.setAttribute("aria-hidden", linked ? "true" : "false");
|
|
}
|
|
if (linked) return;
|
|
scope.querySelectorAll(".theme-toggle-btn[data-theme-value]").forEach((btn) => {
|
|
const on = btn.getAttribute("data-theme-value") === getStandalone();
|
|
btn.classList.toggle("is-active", on);
|
|
btn.setAttribute("aria-pressed", on ? "true" : "false");
|
|
});
|
|
}
|
|
|
|
function initToggleUI(root) {
|
|
const scope = root || document;
|
|
syncToggleUI(scope);
|
|
scope.querySelectorAll(".theme-toggle-btn[data-theme-value]").forEach((btn) => {
|
|
if (btn.dataset.themeBound === "1") return;
|
|
btn.dataset.themeBound = "1";
|
|
btn.addEventListener("click", () => {
|
|
if (isHubLinked()) return;
|
|
apply(btn.getAttribute("data-theme-value"));
|
|
});
|
|
});
|
|
}
|
|
|
|
function initFromHubMessage(data) {
|
|
if (!data || data.type !== "hub-theme-sync") return;
|
|
if (!isHubLinked()) return;
|
|
apply(data.theme, { skipStore: true });
|
|
}
|
|
|
|
function boot() {
|
|
if (isHubLinked()) {
|
|
apply(themeFromUrl() || "dark", { skipStore: true });
|
|
window.addEventListener("message", (ev) => initFromHubMessage(ev.data));
|
|
try {
|
|
window.parent.postMessage({ type: "instance-theme-ready" }, "*");
|
|
} catch (_) {}
|
|
} else {
|
|
apply(getStandalone());
|
|
}
|
|
if (document.readyState === "loading") {
|
|
document.addEventListener("DOMContentLoaded", () => initToggleUI());
|
|
} else {
|
|
initToggleUI();
|
|
}
|
|
}
|
|
|
|
boot();
|
|
|
|
global.InstanceTheme = {
|
|
STANDALONE_KEY,
|
|
isHubLinked,
|
|
get,
|
|
apply,
|
|
initToggleUI,
|
|
syncToggleUI,
|
|
};
|
|
})(typeof window !== "undefined" ? window : globalThis);
|