feat(theme): implement comprehensive theme management system
Add full theme support with light, dark, and system modes, including: - Theme store using Pinia for state management - useTheme composable for reactive theme handling - Theme setting UI in settings page - Enhanced CSS variable system with Tailwind integration - IPC communication for theme persistence - Internationalization support for theme texts - System theme detection and auto-switching The implementation follows ClawX's architecture while adapting to Vue 3 and zn-ai's existing infrastructure.
This commit is contained in:
11
src/App.vue
11
src/App.vue
@@ -6,4 +6,13 @@
|
||||
</router-view>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts"></script>
|
||||
<script setup lang="ts">
|
||||
import { onMounted } from 'vue'
|
||||
import { useThemeStore } from '@src/stores/theme'
|
||||
|
||||
// 初始化主题设置
|
||||
const themeStore = useThemeStore()
|
||||
onMounted(() => {
|
||||
themeStore.init().catch(console.error)
|
||||
})
|
||||
</script>
|
||||
|
||||
139
src/composables/useTheme.ts
Normal file
139
src/composables/useTheme.ts
Normal file
@@ -0,0 +1,139 @@
|
||||
/**
|
||||
* 主题管理 composable
|
||||
* 提供响应式的主题状态和操作方法
|
||||
*/
|
||||
import { computed, onMounted, onUnmounted } from 'vue';
|
||||
import { useThemeStore, type Theme } from '@src/stores/theme';
|
||||
|
||||
/**
|
||||
* 主题管理 composable
|
||||
* 封装了主题状态管理、切换和系统主题检测
|
||||
*/
|
||||
export function useTheme() {
|
||||
const themeStore = useThemeStore();
|
||||
|
||||
// 响应式状态
|
||||
const theme = computed(() => themeStore.theme);
|
||||
const isDark = computed(() => themeStore.isDark);
|
||||
const systemTheme = computed(() => themeStore.systemTheme);
|
||||
const initialized = computed(() => themeStore.initialized);
|
||||
|
||||
// 计算实际应用的主题(考虑 system 模式)
|
||||
const appliedTheme = computed(() => {
|
||||
if (theme.value === 'system') return systemTheme.value;
|
||||
return theme.value;
|
||||
});
|
||||
|
||||
// 主题选项
|
||||
const themeOptions = computed(() => [
|
||||
{ value: 'light' as Theme, label: '浅色' },
|
||||
{ value: 'dark' as Theme, label: '深色' },
|
||||
{ value: 'system' as Theme, label: '跟随系统' },
|
||||
]);
|
||||
|
||||
// 获取主题对应的标签
|
||||
const getThemeLabel = (themeValue: Theme): string => {
|
||||
const option = themeOptions.value.find(opt => opt.value === themeValue);
|
||||
return option?.label || themeValue;
|
||||
};
|
||||
|
||||
// 当前主题标签
|
||||
const currentThemeLabel = computed(() => getThemeLabel(theme.value));
|
||||
|
||||
/**
|
||||
* 初始化主题设置
|
||||
* 应该在应用启动时调用一次
|
||||
*/
|
||||
const initTheme = async () => {
|
||||
await themeStore.init();
|
||||
};
|
||||
|
||||
/**
|
||||
* 切换主题
|
||||
* @param newTheme 新的主题模式
|
||||
*/
|
||||
const setTheme = async (newTheme: Theme) => {
|
||||
await themeStore.setTheme(newTheme);
|
||||
};
|
||||
|
||||
/**
|
||||
* 切换主题(轮换)
|
||||
*/
|
||||
const toggleTheme = async () => {
|
||||
const themes: Theme[] = ['light', 'dark', 'system'];
|
||||
const currentIndex = themes.indexOf(theme.value);
|
||||
const nextIndex = (currentIndex + 1) % themes.length;
|
||||
await setTheme(themes[nextIndex]);
|
||||
};
|
||||
|
||||
/**
|
||||
* 重置为系统主题
|
||||
*/
|
||||
const resetToSystemTheme = async () => {
|
||||
await setTheme('system');
|
||||
};
|
||||
|
||||
/**
|
||||
* 检测当前系统主题
|
||||
*/
|
||||
const detectSystemTheme = () => {
|
||||
return themeStore.detectSystemTheme();
|
||||
};
|
||||
|
||||
// 监听系统主题变化(store 内部已处理,这里提供额外的事件监听)
|
||||
const onSystemThemeChange = (callback: (theme: 'light' | 'dark') => void) => {
|
||||
if (typeof window === 'undefined') return () => {};
|
||||
|
||||
const mediaQuery = window.matchMedia('(prefers-color-scheme: dark)');
|
||||
const handleChange = (e: MediaQueryListEvent) => {
|
||||
callback(e.matches ? 'dark' : 'light');
|
||||
};
|
||||
|
||||
// 现代浏览器支持 addEventListener
|
||||
if (mediaQuery.addEventListener) {
|
||||
mediaQuery.addEventListener('change', handleChange);
|
||||
return () => mediaQuery.removeEventListener('change', handleChange);
|
||||
} else {
|
||||
// 旧浏览器支持 addListener
|
||||
mediaQuery.addListener(handleChange);
|
||||
return () => mediaQuery.removeListener(handleChange);
|
||||
}
|
||||
};
|
||||
|
||||
// 自动初始化(可选)
|
||||
onMounted(() => {
|
||||
if (!themeStore.initialized) {
|
||||
initTheme().catch(console.error);
|
||||
}
|
||||
});
|
||||
|
||||
return {
|
||||
// 响应式状态
|
||||
theme,
|
||||
isDark,
|
||||
systemTheme,
|
||||
appliedTheme,
|
||||
initialized,
|
||||
|
||||
// 主题选项
|
||||
themeOptions,
|
||||
currentThemeLabel,
|
||||
|
||||
// 操作方法
|
||||
initTheme,
|
||||
setTheme,
|
||||
toggleTheme,
|
||||
resetToSystemTheme,
|
||||
detectSystemTheme,
|
||||
getThemeLabel,
|
||||
|
||||
// 事件监听
|
||||
onSystemThemeChange,
|
||||
|
||||
// store 引用(谨慎使用)
|
||||
themeStore,
|
||||
};
|
||||
}
|
||||
|
||||
// 导出类型
|
||||
export type { Theme };
|
||||
@@ -9,7 +9,24 @@
|
||||
"dark": "Dark Theme",
|
||||
"light": "Light Theme",
|
||||
"system": "System Theme",
|
||||
"primaryColor": "Primary Color"
|
||||
"primaryColor": "Primary Color",
|
||||
"description": "Customize the look and feel of the application",
|
||||
"themeMode": "Theme Mode",
|
||||
"themeModeDescription": "Choose your preferred theme mode, or follow system settings to switch automatically.",
|
||||
"lightLabel": "Light",
|
||||
"lightDescription": "Bright and clear",
|
||||
"darkLabel": "Dark",
|
||||
"darkDescription": "Eye comfort",
|
||||
"systemLabel": "Follow system",
|
||||
"systemDescription": "Auto switch",
|
||||
"currentTheme": "Current theme",
|
||||
"followingSystem": "Following system theme ({{theme}})",
|
||||
"setToLight": "Set to light theme",
|
||||
"setToDark": "Set to dark theme",
|
||||
"preview": "Preview",
|
||||
"previewDescription": "View the current theme effect",
|
||||
"lightPreview": "Light preview",
|
||||
"darkPreview": "Dark preview"
|
||||
},
|
||||
"appearance": {
|
||||
"fontSize": "Font Size",
|
||||
|
||||
@@ -9,7 +9,24 @@
|
||||
"dark": "深色主题",
|
||||
"light": "浅色主题",
|
||||
"system": "跟随系统",
|
||||
"primaryColor": "主题颜色"
|
||||
"primaryColor": "主题颜色",
|
||||
"description": "自定义应用程序的外观和风格",
|
||||
"themeMode": "主题模式",
|
||||
"themeModeDescription": "选择您偏好的主题模式,或跟随系统设置自动切换。",
|
||||
"lightLabel": "浅色",
|
||||
"lightDescription": "明亮清晰",
|
||||
"darkLabel": "深色",
|
||||
"darkDescription": "护眼舒适",
|
||||
"systemLabel": "跟随系统",
|
||||
"systemDescription": "自动切换",
|
||||
"currentTheme": "当前主题",
|
||||
"followingSystem": "跟随系统主题 ({{theme}})",
|
||||
"setToLight": "已设置为浅色主题",
|
||||
"setToDark": "已设置为深色主题",
|
||||
"preview": "预览",
|
||||
"previewDescription": "查看当前主题效果",
|
||||
"lightPreview": "浅色预览",
|
||||
"darkPreview": "深色预览"
|
||||
},
|
||||
"appearance": {
|
||||
"fontSize": "字体大小",
|
||||
|
||||
@@ -1,18 +1,58 @@
|
||||
<template>
|
||||
<div class="flex-1 h-full p-[20px] select-none">
|
||||
<TitleSection title="账号设置" desc="请关联PMS和渠道房型名称,可使用智能对标" />
|
||||
<TitleSection title="通用设置" desc="配置应用程序的外观、语言和版本信息" />
|
||||
|
||||
<!-- 主题设置 -->
|
||||
<div
|
||||
class="w-full flex items-center mt-[20px] py-[20px] box-border border-b-[1px] border-dashed border-b-[#E5E8EE]">
|
||||
<div class="label w-[64px] text-[16px] font-medium text-[#171717] mr-[24px]">当前版本</div>
|
||||
<div class="value text-[16px] font-medium text-[#171717]">1.0.0</div>
|
||||
<el-button type="text" class="ml-auto">检查更新</el-button>
|
||||
class="w-full flex items-center mt-[20px] py-[20px] box-border border-b-[1px] border-dashed border-b-[#E5E8EE] dark:border-gray-700">
|
||||
<div class="label w-[64px] text-[16px] font-medium text-[#171717] dark:text-gray-100 mr-[24px]">主题设置</div>
|
||||
<div class="value flex-1">
|
||||
<div class="space-y-4">
|
||||
<div class="flex flex-wrap gap-3">
|
||||
<!-- 浅色主题 -->
|
||||
<button
|
||||
class="theme-button px-5 py-2.5 rounded-full border text-[14px] font-medium transition-all duration-200 flex items-center gap-2"
|
||||
:class="[
|
||||
currentTheme === 'light'
|
||||
? 'bg-primary-50 dark:bg-primary-900/20 border-primary-500 dark:border-primary-400 text-primary-700 dark:text-primary-300'
|
||||
: 'bg-white dark:bg-gray-800 border-gray-300 dark:border-gray-600 text-gray-700 dark:text-gray-300 hover:bg-gray-50 dark:hover:bg-gray-700'
|
||||
]" @click="handleThemeChange('light')">
|
||||
<RiSunLine class="w-4 h-4" />
|
||||
浅色
|
||||
</button>
|
||||
|
||||
<!-- 深色主题 -->
|
||||
<button
|
||||
class="theme-button px-5 py-2.5 rounded-full border text-[14px] font-medium transition-all duration-200 flex items-center gap-2"
|
||||
:class="[
|
||||
currentTheme === 'dark'
|
||||
? 'bg-primary-50 dark:bg-primary-900/20 border-primary-500 dark:border-primary-400 text-primary-700 dark:text-primary-300'
|
||||
: 'bg-white dark:bg-gray-800 border-gray-300 dark:border-gray-600 text-gray-700 dark:text-gray-300 hover:bg-gray-50 dark:hover:bg-gray-700'
|
||||
]" @click="handleThemeChange('dark')">
|
||||
<RiMoonLine class="w-4 h-4" />
|
||||
深色
|
||||
</button>
|
||||
|
||||
<!-- 跟随系统 -->
|
||||
<button
|
||||
class="theme-button px-5 py-2.5 rounded-full border text-[14px] font-medium transition-all duration-200 flex items-center gap-2"
|
||||
:class="[
|
||||
currentTheme === 'system'
|
||||
? 'bg-primary-50 dark:bg-primary-900/20 border-primary-500 dark:border-primary-400 text-primary-700 dark:text-primary-300'
|
||||
: 'bg-white dark:bg-gray-800 border-gray-300 dark:border-gray-600 text-gray-700 dark:text-gray-300 hover:bg-gray-50 dark:hover:bg-gray-700'
|
||||
]" @click="handleThemeChange('system')">
|
||||
<RiComputerLine class="w-4 h-4" />
|
||||
跟随系统
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 语言设置 -->
|
||||
<div
|
||||
class="w-full flex items-center mt-[20px] py-[20px] box-border border-b-[1px] border-dashed border-b-[#E5E8EE]">
|
||||
<div class="label w-[64px] text-[16px] font-medium text-[#171717] mr-[24px]">语言设置</div>
|
||||
class="w-full flex items-center mt-[20px] py-[20px] box-border border-b-[1px] border-dashed border-b-[#E5E8EE] dark:border-gray-700">
|
||||
<div class="label w-[64px] text-[16px] font-medium text-[#171717] dark:text-gray-100 mr-[24px]">语言设置</div>
|
||||
<div class="value flex gap-2">
|
||||
<el-button v-for="lang in supportedLanguages" :key="lang.code"
|
||||
:type="currentLanguage === lang.code ? 'primary' : 'text'" @click="handleLanguageChange(lang.code)"
|
||||
@@ -21,14 +61,40 @@
|
||||
</el-button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 版本信息 -->
|
||||
<div
|
||||
class="w-full flex items-center mt-[20px] py-[20px] box-border border-b-[1px] border-dashed border-b-[#E5E8EE] dark:border-gray-700">
|
||||
<div class="label w-[64px] text-[16px] font-medium text-[#171717] dark:text-gray-100 mr-[24px]">当前版本</div>
|
||||
<div class="value text-[16px] font-medium text-[#171717] dark:text-gray-100">1.0.0</div>
|
||||
<el-button type="text" class="ml-auto">检查更新</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted } from 'vue'
|
||||
import { ref, onMounted, computed } from 'vue'
|
||||
import TitleSection from '@src/components/TitleSection/index.vue'
|
||||
import { setLanguage, getLanguage, type LanguageType } from '@src/i18n'
|
||||
import { useThemeStore } from '@src/stores/theme'
|
||||
import { RiSunLine, RiMoonLine, RiComputerLine } from '@remixicon/vue'
|
||||
|
||||
// 主题状态管理
|
||||
const themeStore = useThemeStore()
|
||||
const currentTheme = computed(() => themeStore.theme)
|
||||
|
||||
// 主题切换处理
|
||||
const handleThemeChange = async (theme: 'light' | 'dark' | 'system') => {
|
||||
try {
|
||||
console.log('切换主题到:', theme)
|
||||
await themeStore.setTheme(theme)
|
||||
console.log('主题切换成功:', theme)
|
||||
} catch (error) {
|
||||
console.error('主题切换失败:', error)
|
||||
}
|
||||
}
|
||||
|
||||
// 语言设置
|
||||
const supportedLanguages = [
|
||||
{ code: 'zh', label: '中文' },
|
||||
{ code: 'en', label: 'English' },
|
||||
@@ -46,4 +112,19 @@ const handleLanguageChange = async (langCode: LanguageType) => {
|
||||
await setLanguage(langCode)
|
||||
currentLanguage.value = langCode
|
||||
}
|
||||
</script>
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.theme-button {
|
||||
cursor: pointer;
|
||||
outline: none;
|
||||
transition-property: background-color, border-color, color, transform;
|
||||
transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
|
||||
transition-duration: 150ms;
|
||||
}
|
||||
|
||||
.theme-button:focus-visible {
|
||||
outline: 2px solid var(--primary-color);
|
||||
outline-offset: 2px;
|
||||
}
|
||||
</style>
|
||||
182
src/stores/theme.ts
Normal file
182
src/stores/theme.ts
Normal file
@@ -0,0 +1,182 @@
|
||||
/**
|
||||
* 主题状态管理 store
|
||||
* 采用 Vue 推荐的 Pinia 实现,功能和架构与 ClawX 的 Zustand 实现保持一致
|
||||
* 集成 zn-ai 现有 IPC 通信
|
||||
*/
|
||||
import { defineStore } from 'pinia';
|
||||
|
||||
export type Theme = 'light' | 'dark' | 'system';
|
||||
|
||||
interface ThemeState {
|
||||
// 主题状态
|
||||
theme: Theme;
|
||||
isDark: boolean;
|
||||
systemTheme: 'light' | 'dark';
|
||||
|
||||
// 初始化状态
|
||||
initialized: boolean;
|
||||
}
|
||||
|
||||
// 检测系统主题
|
||||
const detectSystemTheme = (): 'light' | 'dark' => {
|
||||
if (typeof window === 'undefined') return 'light';
|
||||
return window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';
|
||||
};
|
||||
|
||||
// 计算实际应用的主题(考虑 system 模式)
|
||||
const getAppliedTheme = (theme: Theme, systemTheme: 'light' | 'dark'): 'light' | 'dark' => {
|
||||
if (theme === 'system') return systemTheme;
|
||||
return theme;
|
||||
};
|
||||
|
||||
// 应用主题到 DOM(通过 CSS 类)
|
||||
const applyThemeToDom = (theme: 'light' | 'dark') => {
|
||||
if (typeof document === 'undefined') return;
|
||||
|
||||
const root = document.documentElement;
|
||||
if (theme === 'dark') {
|
||||
root.classList.add('dark');
|
||||
} else {
|
||||
root.classList.remove('dark');
|
||||
}
|
||||
};
|
||||
|
||||
export const useThemeStore = defineStore('zn-ai-theme', {
|
||||
state: (): ThemeState => {
|
||||
// 从 localStorage 恢复缓存的主题,确保在初始化前 UI 不会闪烁
|
||||
const cachedTheme = typeof localStorage !== 'undefined' ? localStorage.getItem('zn-ai-theme-cache') as Theme : null;
|
||||
const initialTheme = (cachedTheme === 'light' || cachedTheme === 'dark' || cachedTheme === 'system') ? cachedTheme : 'system';
|
||||
|
||||
return {
|
||||
theme: initialTheme,
|
||||
isDark: false,
|
||||
systemTheme: 'light',
|
||||
initialized: false,
|
||||
};
|
||||
},
|
||||
|
||||
actions: {
|
||||
async init() {
|
||||
if (this.initialized) return;
|
||||
|
||||
try {
|
||||
// 1. 检测系统主题
|
||||
const systemTheme = detectSystemTheme();
|
||||
|
||||
// 2. 从主进程获取持久化的主题设置
|
||||
let savedTheme: Theme = 'system';
|
||||
try {
|
||||
savedTheme = await window.api.invoke<Theme>('get-theme-mode');
|
||||
} catch (error) {
|
||||
console.warn('Failed to get theme from main process, using default:', error);
|
||||
}
|
||||
|
||||
// 3. 计算实际主题和 isDark 状态
|
||||
const appliedTheme = getAppliedTheme(savedTheme, systemTheme);
|
||||
const isDark = appliedTheme === 'dark';
|
||||
|
||||
// 4. 应用主题到 DOM
|
||||
applyThemeToDom(appliedTheme);
|
||||
|
||||
// 5. 更新 store 状态
|
||||
this.theme = savedTheme;
|
||||
this.isDark = isDark;
|
||||
this.systemTheme = systemTheme;
|
||||
this.initialized = true;
|
||||
|
||||
// 缓存到 localStorage
|
||||
if (typeof localStorage !== 'undefined') {
|
||||
localStorage.setItem('zn-ai-theme-cache', savedTheme);
|
||||
}
|
||||
|
||||
// 6. 监听系统主题变化
|
||||
if (typeof window !== 'undefined') {
|
||||
const mediaQuery = window.matchMedia('(prefers-color-scheme: dark)');
|
||||
const handleChange = (e: MediaQueryListEvent) => {
|
||||
const newSystemTheme = e.matches ? 'dark' : 'light';
|
||||
|
||||
if (this.theme === 'system') {
|
||||
const appliedTheme = getAppliedTheme('system', newSystemTheme);
|
||||
const isDark = appliedTheme === 'dark';
|
||||
applyThemeToDom(appliedTheme);
|
||||
this.systemTheme = newSystemTheme;
|
||||
this.isDark = isDark;
|
||||
} else {
|
||||
this.systemTheme = newSystemTheme;
|
||||
}
|
||||
};
|
||||
|
||||
if (mediaQuery.addEventListener) {
|
||||
mediaQuery.addEventListener('change', handleChange);
|
||||
} else {
|
||||
mediaQuery.addListener(handleChange);
|
||||
}
|
||||
|
||||
// 7. 监听主进程主题更新事件
|
||||
window.api.on('theme-mode-updated', (isDarkUpdate: boolean) => {
|
||||
const newIsDark = Boolean(isDarkUpdate);
|
||||
const newTheme = newIsDark ? 'dark' : 'light';
|
||||
if (this.theme === 'system') {
|
||||
this.isDark = newIsDark;
|
||||
} else {
|
||||
const actualTheme = newIsDark ? 'dark' : 'light';
|
||||
if (this.theme !== actualTheme) {
|
||||
this.theme = actualTheme;
|
||||
this.isDark = newIsDark;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
console.log('Theme store initialized:', { theme: savedTheme, isDark, systemTheme });
|
||||
} catch (error) {
|
||||
console.error('Failed to initialize theme store:', error);
|
||||
const systemTheme = detectSystemTheme();
|
||||
const appliedTheme = getAppliedTheme('system', systemTheme);
|
||||
const isDark = appliedTheme === 'dark';
|
||||
applyThemeToDom(appliedTheme);
|
||||
this.theme = 'system';
|
||||
this.isDark = isDark;
|
||||
this.systemTheme = systemTheme;
|
||||
this.initialized = true;
|
||||
}
|
||||
},
|
||||
|
||||
async setTheme(theme: Theme) {
|
||||
if (theme === this.theme) return;
|
||||
|
||||
try {
|
||||
// 1. 保存到主进程
|
||||
await window.api.invoke('set-theme-mode', theme);
|
||||
|
||||
// 2. 计算实际应用的主题
|
||||
const appliedTheme = getAppliedTheme(theme, this.systemTheme);
|
||||
const isDark = appliedTheme === 'dark';
|
||||
|
||||
// 3. 应用主题到 DOM
|
||||
applyThemeToDom(appliedTheme);
|
||||
|
||||
// 4. 更新 store 状态
|
||||
this.theme = theme;
|
||||
this.isDark = isDark;
|
||||
|
||||
// 5. 缓存到 localStorage
|
||||
if (typeof localStorage !== 'undefined') {
|
||||
localStorage.setItem('zn-ai-theme-cache', theme);
|
||||
}
|
||||
|
||||
console.log('Theme changed:', { theme, appliedTheme, isDark });
|
||||
} catch (error) {
|
||||
console.error('Failed to set theme:', error);
|
||||
throw error;
|
||||
}
|
||||
},
|
||||
|
||||
detectSystemTheme,
|
||||
|
||||
updateIsDark() {
|
||||
const appliedTheme = getAppliedTheme(this.theme, this.systemTheme);
|
||||
this.isDark = appliedTheme === 'dark';
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -1,4 +1,8 @@
|
||||
@import "tailwindcss";
|
||||
|
||||
/* 启用 dark 变体,基于 .dark 类 */
|
||||
@custom-variant dark (&:where(.dark, .dark *));
|
||||
|
||||
@import "./theme/index.css";
|
||||
@import "./tailwind.css";
|
||||
@plugin "@tailwindcss/typography";
|
||||
|
||||
@@ -1,126 +1,111 @@
|
||||
@media (prefers-color-scheme: dark) {
|
||||
:root {
|
||||
--primary-color: #07C160;
|
||||
--bg-color: #1E1E1E;
|
||||
--bg-secondary: #2C2C2C;
|
||||
/* 深色主题变量定义在 .dark 类下 */
|
||||
.dark {
|
||||
--primary-color: #07C160;
|
||||
--bg-color: #1E1E1E;
|
||||
--bg-secondary: #2C2C2C;
|
||||
--text-primary: #E0E0E0;
|
||||
--text-secondary: #A0A0A0;
|
||||
|
||||
--text-primary: #E0E0E0;
|
||||
--text-secondary: #A0A0A0;
|
||||
|
||||
--bubble-self: var(--primary-color);
|
||||
--bubble-others: #3A3A3A;
|
||||
--input-bg: #333333;
|
||||
--ripple-color: var(--text-secondary);
|
||||
--ripple-opacity: 0.2;
|
||||
}
|
||||
|
||||
pre code.hljs {
|
||||
display: block;
|
||||
overflow-x: auto;
|
||||
padding: 1em
|
||||
}
|
||||
code.hljs {
|
||||
padding: 3px 5px
|
||||
}
|
||||
/*!
|
||||
Theme: GitHub Dark
|
||||
Description: Dark theme as seen on github.com
|
||||
Author: github.com
|
||||
Maintainer: @Hirse
|
||||
Updated: 2021-05-15
|
||||
|
||||
Outdated base version: https://github.com/primer/github-syntax-dark
|
||||
Current colors taken from GitHub's CSS
|
||||
*/
|
||||
.hljs {
|
||||
color: var(--text-primary);
|
||||
background: var(--input-bg);
|
||||
}
|
||||
.hljs-doctag,
|
||||
.hljs-keyword,
|
||||
.hljs-meta .hljs-keyword,
|
||||
.hljs-template-tag,
|
||||
.hljs-template-variable,
|
||||
.hljs-type,
|
||||
.hljs-variable.language_ {
|
||||
/* prettylights-syntax-keyword */
|
||||
color: #ff7b72
|
||||
}
|
||||
.hljs-title,
|
||||
.hljs-title.class_,
|
||||
.hljs-title.class_.inherited__,
|
||||
.hljs-title.function_ {
|
||||
/* prettylights-syntax-entity */
|
||||
color: #d2a8ff
|
||||
}
|
||||
.hljs-attr,
|
||||
.hljs-attribute,
|
||||
.hljs-literal,
|
||||
.hljs-meta,
|
||||
.hljs-number,
|
||||
.hljs-operator,
|
||||
.hljs-variable,
|
||||
.hljs-selector-attr,
|
||||
.hljs-selector-class,
|
||||
.hljs-selector-id {
|
||||
/* prettylights-syntax-constant */
|
||||
color: #79c0ff
|
||||
}
|
||||
.hljs-regexp,
|
||||
.hljs-string,
|
||||
.hljs-meta .hljs-string {
|
||||
/* prettylights-syntax-string */
|
||||
color: #a5d6ff
|
||||
}
|
||||
.hljs-built_in,
|
||||
.hljs-symbol {
|
||||
/* prettylights-syntax-variable */
|
||||
color: #ffa657
|
||||
}
|
||||
.hljs-comment,
|
||||
.hljs-code,
|
||||
.hljs-formula {
|
||||
/* prettylights-syntax-comment */
|
||||
color: #8b949e
|
||||
}
|
||||
.hljs-name,
|
||||
.hljs-quote,
|
||||
.hljs-selector-tag,
|
||||
.hljs-selector-pseudo {
|
||||
/* prettylights-syntax-entity-tag */
|
||||
color: #7ee787
|
||||
}
|
||||
.hljs-subst {
|
||||
/* prettylights-syntax-storage-modifier-import */
|
||||
color: #c9d1d9
|
||||
}
|
||||
.hljs-section {
|
||||
/* prettylights-syntax-markup-heading */
|
||||
color: #1f6feb;
|
||||
font-weight: bold
|
||||
}
|
||||
.hljs-bullet {
|
||||
/* prettylights-syntax-markup-list */
|
||||
color: #f2cc60
|
||||
}
|
||||
.hljs-emphasis {
|
||||
/* prettylights-syntax-markup-italic */
|
||||
color: #c9d1d9;
|
||||
font-style: italic
|
||||
}
|
||||
.hljs-strong {
|
||||
/* prettylights-syntax-markup-bold */
|
||||
color: #c9d1d9;
|
||||
font-weight: bold
|
||||
}
|
||||
.hljs-addition {
|
||||
/* prettylights-syntax-markup-inserted */
|
||||
color: #aff5b4;
|
||||
background-color: #033a16
|
||||
}
|
||||
.hljs-deletion {
|
||||
/* prettylights-syntax-markup-deleted */
|
||||
color: #ffdcd7;
|
||||
background-color: #67060c
|
||||
}
|
||||
--bubble-self: var(--primary-color);
|
||||
--bubble-others: #3A3A3A;
|
||||
--input-bg: #333333;
|
||||
--ripple-color: var(--text-secondary);
|
||||
--ripple-opacity: 0.2;
|
||||
}
|
||||
|
||||
/* 代码高亮样式 */
|
||||
pre code.hljs {
|
||||
display: block;
|
||||
overflow-x: auto;
|
||||
padding: 1em
|
||||
}
|
||||
code.hljs {
|
||||
padding: 3px 5px
|
||||
}
|
||||
/*!
|
||||
Theme: GitHub Dark
|
||||
Description: Dark theme as seen on github.com
|
||||
Author: github.com
|
||||
Maintainer: @Hirse
|
||||
Updated: 2021-05-15
|
||||
|
||||
Outdated base version: https://github.com/primer/github-syntax-dark
|
||||
Current colors taken from GitHub's CSS
|
||||
*/
|
||||
.dark .hljs {
|
||||
color: var(--text-primary);
|
||||
background: var(--input-bg);
|
||||
}
|
||||
.dark .hljs-doctag,
|
||||
.dark .hljs-keyword,
|
||||
.dark .hljs-meta .hljs-keyword,
|
||||
.dark .hljs-template-tag,
|
||||
.dark .hljs-template-variable,
|
||||
.dark .hljs-type,
|
||||
.dark .hljs-variable.language_ {
|
||||
color: #ff7b72
|
||||
}
|
||||
.dark .hljs-title,
|
||||
.dark .hljs-title.class_,
|
||||
.dark .hljs-title.class_.inherited__,
|
||||
.dark .hljs-title.function_ {
|
||||
color: #d2a8ff
|
||||
}
|
||||
.dark .hljs-attr,
|
||||
.dark .hljs-attribute,
|
||||
.dark .hljs-literal,
|
||||
.dark .hljs-meta,
|
||||
.dark .hljs-number,
|
||||
.dark .hljs-operator,
|
||||
.dark .hljs-variable,
|
||||
.dark .hljs-selector-attr,
|
||||
.dark .hljs-selector-class,
|
||||
.dark .hljs-selector-id {
|
||||
color: #79c0ff
|
||||
}
|
||||
.dark .hljs-regexp,
|
||||
.dark .hljs-string,
|
||||
.dark .hljs-meta .hljs-string {
|
||||
color: #a5d6ff
|
||||
}
|
||||
.dark .hljs-built_in,
|
||||
.dark .hljs-symbol {
|
||||
color: #ffa657
|
||||
}
|
||||
.dark .hljs-comment,
|
||||
.dark .hljs-code,
|
||||
.dark .hljs-formula {
|
||||
color: #8b949e
|
||||
}
|
||||
.dark .hljs-name,
|
||||
.dark .hljs-quote,
|
||||
.dark .hljs-selector-tag,
|
||||
.dark .hljs-selector-pseudo {
|
||||
color: #7ee787
|
||||
}
|
||||
.dark .hljs-subst {
|
||||
color: #c9d1d9
|
||||
}
|
||||
.dark .hljs-section {
|
||||
color: #1f6feb;
|
||||
font-weight: bold
|
||||
}
|
||||
.dark .hljs-bullet {
|
||||
color: #f2cc60
|
||||
}
|
||||
.dark .hljs-emphasis {
|
||||
color: #c9d1d9;
|
||||
font-style: italic
|
||||
}
|
||||
.dark .hljs-strong {
|
||||
color: #c9d1d9;
|
||||
font-weight: bold
|
||||
}
|
||||
.dark .hljs-addition {
|
||||
color: #aff5b4;
|
||||
background-color: #033a16
|
||||
}
|
||||
.dark .hljs-deletion {
|
||||
color: #ffdcd7;
|
||||
background-color: #67060c
|
||||
}
|
||||
@@ -1,129 +1,112 @@
|
||||
@media (prefers-color-scheme: light) {
|
||||
:root {
|
||||
--primary-color: #07C160;
|
||||
--bg-color: #FFFFFF;
|
||||
--bg-secondary: #F5F5F5;
|
||||
--text-primary: #000000;
|
||||
--text-secondary: #7F7F7F;
|
||||
/* 浅色主题变量定义在 :root 中 */
|
||||
:root {
|
||||
--primary-color: #07C160;
|
||||
--bg-color: #FFFFFF;
|
||||
--bg-secondary: #F5F5F5;
|
||||
--text-primary: #000000;
|
||||
--text-secondary: #7F7F7F;
|
||||
|
||||
--header-bg: var(--primary-color);
|
||||
--bubble-self: var(--primary-color);
|
||||
--bubble-others: #FFFFFF;
|
||||
--input-bg: #F0F0F0;
|
||||
--ripple-color: var(--text-secondary);
|
||||
--ripple-opacity: 0.2;
|
||||
}
|
||||
|
||||
pre code.hljs {
|
||||
display: block;
|
||||
overflow-x: auto;
|
||||
padding: 1em
|
||||
}
|
||||
code.hljs {
|
||||
padding: 3px 5px
|
||||
}
|
||||
/*!
|
||||
Theme: GitHub
|
||||
Description: Light theme as seen on github.com
|
||||
Author: github.com
|
||||
Maintainer: @Hirse
|
||||
Updated: 2021-05-15
|
||||
--header-bg: var(--primary-color);
|
||||
--bubble-self: var(--primary-color);
|
||||
--bubble-others: #FFFFFF;
|
||||
--input-bg: #F0F0F0;
|
||||
--ripple-color: var(--text-secondary);
|
||||
--ripple-opacity: 0.2;
|
||||
}
|
||||
|
||||
Outdated base version: https://github.com/primer/github-syntax-light
|
||||
Current colors taken from GitHub's CSS
|
||||
*/
|
||||
.hljs {
|
||||
/* color: #24292e;
|
||||
background: #ffffff */
|
||||
/* 代码高亮样式 */
|
||||
pre code.hljs {
|
||||
display: block;
|
||||
overflow-x: auto;
|
||||
padding: 1em
|
||||
}
|
||||
code.hljs {
|
||||
padding: 3px 5px
|
||||
}
|
||||
/*!
|
||||
Theme: GitHub
|
||||
Description: Light theme as seen on github.com
|
||||
Author: github.com
|
||||
Maintainer: @Hirse
|
||||
Updated: 2021-05-15
|
||||
|
||||
color: var(--text-primary);
|
||||
background: var(--input-bg);
|
||||
}
|
||||
.hljs-doctag,
|
||||
.hljs-keyword,
|
||||
.hljs-meta .hljs-keyword,
|
||||
.hljs-template-tag,
|
||||
.hljs-template-variable,
|
||||
.hljs-type,
|
||||
.hljs-variable.language_ {
|
||||
/* prettylights-syntax-keyword */
|
||||
color: #d73a49
|
||||
}
|
||||
.hljs-title,
|
||||
.hljs-title.class_,
|
||||
.hljs-title.class_.inherited__,
|
||||
.hljs-title.function_ {
|
||||
/* prettylights-syntax-entity */
|
||||
color: #6f42c1
|
||||
}
|
||||
.hljs-attr,
|
||||
.hljs-attribute,
|
||||
.hljs-literal,
|
||||
.hljs-meta,
|
||||
.hljs-number,
|
||||
.hljs-operator,
|
||||
.hljs-variable,
|
||||
.hljs-selector-attr,
|
||||
.hljs-selector-class,
|
||||
.hljs-selector-id {
|
||||
/* prettylights-syntax-constant */
|
||||
color: #005cc5
|
||||
}
|
||||
.hljs-regexp,
|
||||
.hljs-string,
|
||||
.hljs-meta .hljs-string {
|
||||
/* prettylights-syntax-string */
|
||||
color: #032f62
|
||||
}
|
||||
.hljs-built_in,
|
||||
.hljs-symbol {
|
||||
/* prettylights-syntax-variable */
|
||||
color: #e36209
|
||||
}
|
||||
.hljs-comment,
|
||||
.hljs-code,
|
||||
.hljs-formula {
|
||||
/* prettylights-syntax-comment */
|
||||
color: #6a737d
|
||||
}
|
||||
.hljs-name,
|
||||
.hljs-quote,
|
||||
.hljs-selector-tag,
|
||||
.hljs-selector-pseudo {
|
||||
/* prettylights-syntax-entity-tag */
|
||||
color: #22863a
|
||||
}
|
||||
.hljs-subst {
|
||||
/* prettylights-syntax-storage-modifier-import */
|
||||
color: #24292e
|
||||
}
|
||||
.hljs-section {
|
||||
/* prettylights-syntax-markup-heading */
|
||||
color: #005cc5;
|
||||
font-weight: bold
|
||||
}
|
||||
.hljs-bullet {
|
||||
/* prettylights-syntax-markup-list */
|
||||
color: #735c0f
|
||||
}
|
||||
.hljs-emphasis {
|
||||
/* prettylights-syntax-markup-italic */
|
||||
color: #24292e;
|
||||
font-style: italic
|
||||
}
|
||||
.hljs-strong {
|
||||
/* prettylights-syntax-markup-bold */
|
||||
color: #24292e;
|
||||
font-weight: bold
|
||||
}
|
||||
.hljs-addition {
|
||||
/* prettylights-syntax-markup-inserted */
|
||||
color: #22863a;
|
||||
background-color: #f0fff4
|
||||
}
|
||||
.hljs-deletion {
|
||||
/* prettylights-syntax-markup-deleted */
|
||||
color: #b31d28;
|
||||
background-color: #ffeef0
|
||||
}
|
||||
Outdated base version: https://github.com/primer/github-syntax-light
|
||||
Current colors taken from GitHub's CSS
|
||||
*/
|
||||
.hljs {
|
||||
color: var(--text-primary);
|
||||
background: var(--input-bg);
|
||||
}
|
||||
.hljs-doctag,
|
||||
.hljs-keyword,
|
||||
.hljs-meta .hljs-keyword,
|
||||
.hljs-template-tag,
|
||||
.hljs-template-variable,
|
||||
.hljs-type,
|
||||
.hljs-variable.language_ {
|
||||
color: #d73a49
|
||||
}
|
||||
.hljs-title,
|
||||
.hljs-title.class_,
|
||||
.hljs-title.class_.inherited__,
|
||||
.hljs-title.function_ {
|
||||
color: #6f42c1
|
||||
}
|
||||
.hljs-attr,
|
||||
.hljs-attribute,
|
||||
.hljs-literal,
|
||||
.hljs-meta,
|
||||
.hljs-number,
|
||||
.hljs-operator,
|
||||
.hljs-variable,
|
||||
.hljs-selector-attr,
|
||||
.hljs-selector-class,
|
||||
.hljs-selector-id {
|
||||
color: #005cc5
|
||||
}
|
||||
.hljs-regexp,
|
||||
.hljs-string,
|
||||
.hljs-meta .hljs-string {
|
||||
color: #032f62
|
||||
}
|
||||
.hljs-built_in,
|
||||
.hljs-symbol {
|
||||
color: #e36209
|
||||
}
|
||||
.hljs-comment,
|
||||
.hljs-code,
|
||||
.hljs-formula {
|
||||
color: #6a737d
|
||||
}
|
||||
.hljs-name,
|
||||
.hljs-quote,
|
||||
.hljs-selector-tag,
|
||||
.hljs-selector-pseudo {
|
||||
color: #22863a
|
||||
}
|
||||
.hljs-subst {
|
||||
color: #24292e
|
||||
}
|
||||
.hljs-section {
|
||||
color: #005cc5;
|
||||
font-weight: bold
|
||||
}
|
||||
.hljs-bullet {
|
||||
color: #735c0f
|
||||
}
|
||||
.hljs-emphasis {
|
||||
color: #24292e;
|
||||
font-style: italic
|
||||
}
|
||||
.hljs-strong {
|
||||
color: #24292e;
|
||||
font-weight: bold
|
||||
}
|
||||
.hljs-addition {
|
||||
color: #22863a;
|
||||
background-color: #f0fff4
|
||||
}
|
||||
.hljs-deletion {
|
||||
color: #b31d28;
|
||||
background-color: #ffeef0
|
||||
}
|
||||
Reference in New Issue
Block a user