diff --git a/README.md b/README.md index 907466e..dc05298 100644 --- a/README.md +++ b/README.md @@ -172,6 +172,16 @@ docker compose up -d --- +## Dark Mode + +ConvertX-CN 支援亮色與暗色主題。 + +- 可手動切換主題(點擊導覽列的太陽/月亮圖示) +- 偏好設定自動儲存於瀏覽器 +- 預設跟隨系統色彩偏好 + +--- + ## MinerU ConvertX 內建文件轉換引擎。 diff --git a/public/theme.js b/public/theme.js new file mode 100644 index 0000000..41f277d --- /dev/null +++ b/public/theme.js @@ -0,0 +1,102 @@ +// ===== Theme Toggle Script ===== +// Handles dark/light mode switching with localStorage persistence +// and system preference detection + +(function () { + const THEME_KEY = "theme"; + + // Get stored theme or detect system preference + function getPreferredTheme() { + const storedTheme = localStorage.getItem(THEME_KEY); + if (storedTheme) { + return storedTheme; + } + // Return null to let CSS handle system preference + return null; + } + + // Get current effective theme (what's actually being displayed) + function getEffectiveTheme() { + const storedTheme = localStorage.getItem(THEME_KEY); + if (storedTheme) { + return storedTheme; + } + // Check system preference + return window.matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light"; + } + + // Apply theme to document + function applyTheme(theme) { + const root = document.documentElement; + if (theme === "dark") { + root.setAttribute("data-theme", "dark"); + } else if (theme === "light") { + root.setAttribute("data-theme", "light"); + } else { + // Remove attribute to follow system preference + root.removeAttribute("data-theme"); + } + updateThemeIcons(); + } + + // Update theme toggle icons visibility + function updateThemeIcons() { + const effectiveTheme = getEffectiveTheme(); + const lightIcon = document.getElementById("theme-icon-light"); + const darkIcon = document.getElementById("theme-icon-dark"); + + if (lightIcon && darkIcon) { + if (effectiveTheme === "dark") { + // In dark mode, show sun icon (to switch to light) + lightIcon.classList.remove("hidden"); + darkIcon.classList.add("hidden"); + } else { + // In light mode, show moon icon (to switch to dark) + lightIcon.classList.add("hidden"); + darkIcon.classList.remove("hidden"); + } + } + } + + // Toggle theme + function toggleTheme() { + const currentTheme = getEffectiveTheme(); + const newTheme = currentTheme === "dark" ? "light" : "dark"; + localStorage.setItem(THEME_KEY, newTheme); + applyTheme(newTheme); + } + + // Initialize theme on page load + function initTheme() { + const preferredTheme = getPreferredTheme(); + applyTheme(preferredTheme); + + // Listen for system preference changes + window.matchMedia("(prefers-color-scheme: dark)").addEventListener("change", (e) => { + // Only update if no manual preference is set + if (!localStorage.getItem(THEME_KEY)) { + applyTheme(null); + } + }); + } + + // Apply theme immediately to prevent flash + const preferredTheme = getPreferredTheme(); + if (preferredTheme) { + document.documentElement.setAttribute("data-theme", preferredTheme); + } + + // Set up event listener when DOM is ready + document.addEventListener("DOMContentLoaded", function () { + initTheme(); + + const themeToggle = document.getElementById("theme-toggle"); + if (themeToggle) { + themeToggle.addEventListener("click", toggleTheme); + } + }); + + // Expose toggle function globally for programmatic access + window.toggleTheme = toggleTheme; + window.getEffectiveTheme = getEffectiveTheme; +})(); diff --git a/src/components/base.tsx b/src/components/base.tsx index 561c4e2..8862bc3 100644 --- a/src/components/base.tsx +++ b/src/components/base.tsx @@ -26,6 +26,7 @@ export const BaseHtml = ({