blog / src /utils /setting-utils.ts
cacode's picture
Upload 434 files
96dd062 verified
import {
DARK_MODE,
DEFAULT_THEME,
LIGHT_MODE,
SYSTEM_MODE,
WALLPAPER_BANNER,
WALLPAPER_NONE,
WALLPAPER_OVERLAY,
} from "@constants/constants";
import type { LIGHT_DARK_MODE, WALLPAPER_MODE } from "@/types/config";
import {
backgroundWallpaper,
expressiveCodeConfig,
siteConfig,
} from "../config";
import { isHomePage as checkIsHomePage } from "./layout-utils";
// Declare global functions
declare global {
interface Window {
initSemifullScrollDetection?: () => void;
semifullScrollHandler?: () => void;
}
}
export function getDefaultHue(): number {
const fallback = "250";
// 检查是否在浏览器环境中
if (typeof document === "undefined") {
return Number.parseInt(fallback, 10);
}
const configCarrier = document.getElementById("config-carrier");
return Number.parseInt(configCarrier?.dataset.hue || fallback, 10);
}
export function getDefaultTheme(): LIGHT_DARK_MODE {
// 如果配置文件中设置了 defaultMode,使用配置的值
// 否则使用 DEFAULT_THEME(向后兼容)
return siteConfig.themeColor.defaultMode ?? DEFAULT_THEME;
}
// 获取系统主题
export function getSystemTheme(): LIGHT_DARK_MODE {
if (typeof window === "undefined") {
return LIGHT_MODE;
}
return window.matchMedia("(prefers-color-scheme: dark)").matches
? DARK_MODE
: LIGHT_MODE;
}
// 解析主题(如果是system模式,则获取系统主题)
export function resolveTheme(theme: LIGHT_DARK_MODE): LIGHT_DARK_MODE {
if (theme === SYSTEM_MODE) {
return getSystemTheme();
}
return theme;
}
export function getHue(): number {
// 先检查全局对象
if (typeof window === "undefined" || !window.localStorage) {
return getDefaultHue();
}
const stored = localStorage.getItem("hue");
return stored ? Number.parseInt(stored, 10) : getDefaultHue();
}
export function setHue(hue: number): void {
// 先检查是否在浏览器环境
if (
typeof window === "undefined" ||
!window.localStorage ||
typeof document === "undefined"
) {
return;
}
localStorage.setItem("hue", String(hue));
const r = document.querySelector(":root") as HTMLElement;
if (!r) {
return;
}
r.style.setProperty("--hue", String(hue));
}
export function applyThemeToDocument(theme: LIGHT_DARK_MODE) {
// 检查是否在浏览器环境中
if (typeof document === "undefined") {
return;
}
// 解析主题
const resolvedTheme = resolveTheme(theme);
// 获取当前主题状态的完整信息
const currentIsDark = document.documentElement.classList.contains("dark");
const currentTheme = document.documentElement.getAttribute("data-theme");
// 计算目标主题状态
let targetIsDark = false; // 初始化默认值
switch (resolvedTheme) {
case LIGHT_MODE:
targetIsDark = false;
break;
case DARK_MODE:
targetIsDark = true;
break;
default:
// 处理默认情况,使用当前主题状态
targetIsDark = currentIsDark;
break;
}
// 检测是否真的需要主题切换:
// 1. dark类状态是否改变
// 2. expressiveCode主题是否需要更新
const needsThemeChange = currentIsDark !== targetIsDark;
const expectedTheme = targetIsDark
? expressiveCodeConfig.darkTheme
: expressiveCodeConfig.lightTheme;
const needsCodeThemeUpdate = currentTheme !== expectedTheme;
// 如果既不需要主题切换也不需要代码主题更新,直接返回
if (!needsThemeChange && !needsCodeThemeUpdate) {
return;
}
// 批量 DOM 操作,减少重绘
if (needsThemeChange) {
// 添加过渡保护类(但会导致大量重绘,所以使用更轻量的方式)
// document.documentElement.classList.add("is-theme-transitioning");
// 直接切换主题,利用 CSS 变量的特性让浏览器优化过渡
if (targetIsDark) {
document.documentElement.classList.add("dark");
} else {
document.documentElement.classList.remove("dark");
}
}
// Set the theme for Expressive Code based on current mode
if (needsCodeThemeUpdate) {
document.documentElement.setAttribute("data-theme", expectedTheme);
}
}
// 系统主题监听器引用
let systemThemeListener:
| ((e: MediaQueryListEvent | MediaQueryList) => void)
| null = null;
export function setTheme(theme: LIGHT_DARK_MODE): void {
// 检查是否在浏览器环境中
if (
typeof localStorage === "undefined" ||
typeof localStorage.setItem !== "function"
) {
return;
}
// 先应用主题
applyThemeToDocument(theme);
// 保存到localStorage
localStorage.setItem("theme", theme);
// 如果切换到 system 模式,需要监听系统主题变化
if (theme === SYSTEM_MODE) {
setupSystemThemeListener();
} else {
// 如果切换其他模式,移除系统主题监听
cleanupSystemThemeListener();
}
}
// 设置系统主题监听器
export function setupSystemThemeListener() {
// 先清理之前的监听器
cleanupSystemThemeListener();
if (typeof window === "undefined") {
return;
}
const mediaQuery = window.matchMedia("(prefers-color-scheme: dark)");
// 处理系统主题变化的回调
const handleSystemThemeChange = (e: MediaQueryListEvent | MediaQueryList) => {
const isDark = e.matches;
const currentIsDark = document.documentElement.classList.contains("dark");
// 如果主题状态没有变化,直接返回
if (currentIsDark === isDark) {
return;
}
// 直接应用系统主题,不使用过渡保护类以避免大量重绘
if (isDark) {
document.documentElement.classList.add("dark");
} else {
document.documentElement.classList.remove("dark");
}
// Set the theme for Expressive Code
const expressiveTheme = isDark
? expressiveCodeConfig.darkTheme
: expressiveCodeConfig.lightTheme;
document.documentElement.setAttribute("data-theme", expressiveTheme);
// 触发自定义事件通知其他组件(仅在真正切换时触发)
window.dispatchEvent(new CustomEvent("theme-change"));
};
// 立即调用一次以设置初始状态
handleSystemThemeChange(mediaQuery);
// 监听系统主题变化(现代浏览器)
if (mediaQuery.addEventListener) {
mediaQuery.addEventListener("change", handleSystemThemeChange);
} else {
// 兼容旧浏览器
mediaQuery.addListener(handleSystemThemeChange);
}
systemThemeListener = handleSystemThemeChange;
}
// 清理系统主题监听器
function cleanupSystemThemeListener() {
if (typeof window === "undefined" || !systemThemeListener) {
return;
}
const mediaQuery = window.matchMedia("(prefers-color-scheme: dark)");
if (mediaQuery.removeEventListener) {
mediaQuery.removeEventListener("change", systemThemeListener);
} else {
// 兼容旧浏览器
mediaQuery.removeListener(systemThemeListener);
}
systemThemeListener = null;
}
export function getStoredTheme(): LIGHT_DARK_MODE {
// 检查是否在浏览器环境中
if (
typeof localStorage === "undefined" ||
typeof localStorage.getItem !== "function"
) {
return getDefaultTheme();
}
return (
(localStorage.getItem("theme") as LIGHT_DARK_MODE) || getDefaultTheme()
);
}
// 初始化主题监听器(用于页面加载后)
export function initThemeListener() {
if (
typeof localStorage === "undefined" ||
typeof localStorage.getItem !== "function"
) {
return;
}
const theme = getStoredTheme();
// 如果主题是 system 模式,需要监听系统主题变化
if (theme === SYSTEM_MODE) {
setupSystemThemeListener();
}
}
// Wallpaper mode functions
export function applyWallpaperModeToDocument(mode: WALLPAPER_MODE) {
// 检查是否允许切换壁纸模式
const isSwitchable = backgroundWallpaper.switchable ?? true;
if (!isSwitchable) {
// 如果不允许切换,直接返回,不执行任何操作
return;
}
// 获取当前的壁纸模式
const currentMode =
(document.documentElement.getAttribute(
"data-wallpaper-mode",
) as WALLPAPER_MODE) || backgroundWallpaper.mode;
// 如果模式没有变化,直接返回
if (currentMode === mode) {
// 即使是相同模式,也要确保UI状态正确
ensureWallpaperState(mode);
return;
}
// 添加过渡保护类
document.documentElement.classList.add("is-wallpaper-transitioning");
// 更新数据属性
document.documentElement.setAttribute("data-wallpaper-mode", mode);
// 使用 requestAnimationFrame 确保在下一帧执行,避免闪屏
requestAnimationFrame(() => {
const body = document.body;
// 移除所有壁纸相关的CSS类
body.classList.remove("enable-banner", "wallpaper-transparent");
// 根据模式添加相应的CSS类
switch (mode) {
case WALLPAPER_BANNER:
body.classList.add("enable-banner");
showBannerMode();
break;
case WALLPAPER_OVERLAY:
body.classList.add("wallpaper-transparent");
showOverlayMode();
break;
case WALLPAPER_NONE:
hideAllWallpapers();
break;
default:
hideAllWallpapers();
break;
}
// 更新导航栏透明模式
updateNavbarTransparency(mode);
// 在下一帧移除过渡保护类
requestAnimationFrame(() => {
document.documentElement.classList.remove("is-wallpaper-transitioning");
});
});
}
// 确保壁纸状态正确
function ensureWallpaperState(mode: WALLPAPER_MODE) {
const body = document.body;
// 移除所有壁纸相关的CSS类
body.classList.remove("enable-banner", "wallpaper-transparent");
// 根据模式添加相应的CSS类
switch (mode) {
case WALLPAPER_BANNER:
body.classList.add("enable-banner");
showBannerMode();
break;
case WALLPAPER_OVERLAY:
body.classList.add("wallpaper-transparent");
showOverlayMode();
break;
case WALLPAPER_NONE:
hideAllWallpapers();
break;
}
// 更新导航栏透明模式
updateNavbarTransparency(mode);
}
function showBannerMode() {
// 隐藏全屏壁纸(通过CSS类和display控制)
const overlayContainer = document.querySelector(
"[data-overlay-wallpaper]",
) as HTMLElement;
if (overlayContainer) {
overlayContainer.style.display = "none";
overlayContainer.classList.add("hidden");
overlayContainer.classList.add("opacity-0");
overlayContainer.classList.remove("opacity-100");
}
// 显示banner壁纸(通过CSS类和display控制)
const bannerWrapper = document.getElementById("banner-wrapper");
if (bannerWrapper) {
// 检查当前是否为首页
const isHomePage = checkIsHomePage(window.location.pathname);
const isMobile = window.innerWidth < 1024;
// 移动端非首页时,不显示banner;桌面端始终显示
if (isMobile && !isHomePage) {
bannerWrapper.style.display = "none";
bannerWrapper.classList.add("mobile-hide-banner");
} else {
// 首页或桌面端:先设置display,然后使用requestAnimationFrame确保渲染
bannerWrapper.style.display = "block";
bannerWrapper.style.setProperty("display", "block", "important");
requestAnimationFrame(() => {
bannerWrapper.classList.remove("hidden");
bannerWrapper.classList.remove("opacity-0");
bannerWrapper.classList.add("opacity-100");
bannerWrapper.classList.remove("mobile-hide-banner");
});
}
}
// 显示横幅图片来源文本
const creditDesktop = document.getElementById("banner-credit-desktop");
const creditMobile = document.getElementById("banner-credit-mobile");
if (creditDesktop) creditDesktop.style.display = "";
if (creditMobile) creditMobile.style.display = "";
// 显示横幅首页文本(如果启用且是首页)
const bannerTextOverlay = document.querySelector(".banner-text-overlay");
if (bannerTextOverlay) {
// 检查是否启用 homeText
const homeTextEnabled = backgroundWallpaper.banner?.homeText?.enable;
// 检查当前是否为首页
const isHomePage = checkIsHomePage(window.location.pathname);
// 只有在启用且在首页时才显示
if (homeTextEnabled && isHomePage) {
bannerTextOverlay.classList.remove("hidden");
} else {
bannerTextOverlay.classList.add("hidden");
}
}
// 调整主内容位置
adjustMainContentPosition("banner");
// 处理移动端非首页主内容区域位置
const mainContentWrapper = document.querySelector(".absolute.w-full.z-30");
if (mainContentWrapper) {
const isHomePage = checkIsHomePage(window.location.pathname);
const isMobile = window.innerWidth < 1024;
// 只在移动端非首页时调整主内容位置
if (isMobile && !isHomePage) {
mainContentWrapper.classList.add("mobile-main-no-banner");
} else {
mainContentWrapper.classList.remove("mobile-main-no-banner");
}
}
// 移除透明效果(横幅模式不使用半透明)
adjustMainContentTransparency(false);
// 调整导航栏透明度
const navbar = document.getElementById("navbar");
if (navbar) {
// 获取导航栏透明模式配置(banner模式)
const transparentMode =
backgroundWallpaper.banner?.navbar?.transparentMode || "semi";
navbar.setAttribute("data-transparent-mode", transparentMode);
// 重新初始化半透明模式滚动检测(如果需要)
if (
transparentMode === "semifull" &&
typeof window.initSemifullScrollDetection === "function"
) {
window.initSemifullScrollDetection();
}
}
}
function showOverlayMode() {
// 显示全屏壁纸(通过CSS类和display控制)
const overlayContainer = document.querySelector(
"[data-overlay-wallpaper]",
) as HTMLElement;
if (overlayContainer) {
// 先设置display,然后使用requestAnimationFrame确保渲染
overlayContainer.style.display = "block";
overlayContainer.style.setProperty("display", "block", "important");
requestAnimationFrame(() => {
overlayContainer.classList.remove("hidden");
overlayContainer.classList.remove("opacity-0");
overlayContainer.classList.add("opacity-100");
});
}
// 隐藏banner壁纸(通过CSS类和display控制)
const bannerWrapper = document.getElementById("banner-wrapper");
if (bannerWrapper) {
bannerWrapper.style.display = "none";
bannerWrapper.classList.add("hidden");
bannerWrapper.classList.add("opacity-0");
bannerWrapper.classList.remove("opacity-100");
}
// 隐藏横幅图片来源文本
const creditDesktop = document.getElementById("banner-credit-desktop");
const creditMobile = document.getElementById("banner-credit-mobile");
if (creditDesktop) creditDesktop.style.display = "none";
if (creditMobile) creditMobile.style.display = "none";
// 隐藏横幅首页文本
const bannerTextOverlay = document.querySelector(".banner-text-overlay");
if (bannerTextOverlay) {
bannerTextOverlay.classList.add("hidden");
}
// 调整主内容透明度
adjustMainContentTransparency(true);
// 调整布局为紧凑模式
adjustMainContentPosition("overlay");
}
function hideAllWallpapers() {
// 隐藏所有壁纸(通过CSS类和display控制)
const bannerWrapper = document.getElementById("banner-wrapper");
const overlayContainer = document.querySelector(
"[data-overlay-wallpaper]",
) as HTMLElement;
if (bannerWrapper) {
bannerWrapper.style.display = "none";
bannerWrapper.classList.add("hidden");
bannerWrapper.classList.add("opacity-0");
}
if (overlayContainer) {
overlayContainer.style.display = "none";
overlayContainer.classList.add("hidden");
overlayContainer.classList.add("opacity-0");
overlayContainer.classList.remove("opacity-100");
}
// 隐藏横幅图片来源文本
const creditDesktop = document.getElementById("banner-credit-desktop");
const creditMobile = document.getElementById("banner-credit-mobile");
if (creditDesktop) creditDesktop.style.display = "none";
if (creditMobile) creditMobile.style.display = "none";
// 隐藏横幅首页文本
const bannerTextOverlay = document.querySelector(".banner-text-overlay");
if (bannerTextOverlay) {
bannerTextOverlay.classList.add("hidden");
}
// 调整主内容位置和透明度
adjustMainContentPosition("none");
adjustMainContentTransparency(false);
}
function updateNavbarTransparency(mode: WALLPAPER_MODE) {
const navbar = document.getElementById("navbar");
if (!navbar) return;
let transparentMode: string;
let enableBlur: boolean;
// 根据当前壁纸模式设置导航栏透明模式和模糊效果
if (mode === WALLPAPER_OVERLAY) {
// 全屏壁纸模式
transparentMode = "none";
enableBlur = false;
} else if (mode === WALLPAPER_NONE) {
// 纯色背景模式
transparentMode = "none";
enableBlur = false;
} else {
// Banner模式:使用配置的透明模式和模糊效果
transparentMode =
backgroundWallpaper.banner?.navbar?.transparentMode || "semi";
enableBlur = backgroundWallpaper.banner?.navbar?.enableBlur ?? true;
}
// 更新导航栏的透明模式属性
navbar.setAttribute("data-transparent-mode", transparentMode);
navbar.setAttribute("data-enable-blur", String(enableBlur));
// 移除现有的透明模式类
navbar.classList.remove(
"navbar-transparent-semi",
"navbar-transparent-full",
"navbar-transparent-semifull",
);
// 移除scrolled类
navbar.classList.remove("scrolled");
// 滚动检测功能
if (
transparentMode === "semifull" &&
mode === WALLPAPER_BANNER &&
typeof window.initSemifullScrollDetection === "function"
) {
// 仅在Banner模式的semifull下启用滚动检测
window.initSemifullScrollDetection();
} else if (window.semifullScrollHandler) {
// 移除滚动监听器
window.removeEventListener("scroll", window.semifullScrollHandler);
delete window.semifullScrollHandler;
}
}
function adjustMainContentPosition(
mode: WALLPAPER_MODE | "banner" | "none" | "overlay",
) {
const mainContent = document.querySelector(
".absolute.w-full.z-30",
) as HTMLElement;
if (!mainContent) return;
// 移除现有的位置类
mainContent.classList.remove("mobile-main-no-banner", "no-banner-layout");
switch (mode) {
case "banner":
// Banner模式:主内容在banner下方
mainContent.style.top = "calc(var(--banner-height) - 3rem)";
break;
case "overlay":
// Overlay模式:使用紧凑布局,主内容从导航栏下方开始
mainContent.classList.add("no-banner-layout");
mainContent.style.top = "5.5rem";
break;
case "none":
// 无壁纸模式:主内容从导航栏下方开始
mainContent.classList.add("no-banner-layout");
mainContent.style.top = "5.5rem";
break;
default:
mainContent.style.top = "5.5rem";
break;
}
}
function adjustMainContentTransparency(enable: boolean) {
const mainContent = document.querySelector(".absolute.w-full.z-30");
const body = document.body;
if (!mainContent || !body) return;
if (enable) {
mainContent.classList.add("wallpaper-transparent");
body.classList.add("wallpaper-transparent");
} else {
mainContent.classList.remove("wallpaper-transparent");
body.classList.remove("wallpaper-transparent");
}
}
export function setWallpaperMode(mode: WALLPAPER_MODE): void {
// 检查是否在浏览器环境中
if (
typeof localStorage === "undefined" ||
typeof localStorage.setItem !== "function"
) {
return;
}
localStorage.setItem("wallpaperMode", mode);
applyWallpaperModeToDocument(mode);
}
export function initWallpaperMode(): void {
const storedMode = getStoredWallpaperMode();
applyWallpaperModeToDocument(storedMode);
}
export function getStoredWallpaperMode(): WALLPAPER_MODE {
// 检查是否在浏览器环境中
if (
typeof localStorage === "undefined" ||
typeof localStorage.getItem !== "function"
) {
return backgroundWallpaper.mode;
}
return (
(localStorage.getItem("wallpaperMode") as WALLPAPER_MODE) ||
backgroundWallpaper.mode
);
}