Add synced themes, color modes and custom CSS snippets

This commit is contained in:
eezd committed 2026-10-20 10:06:58 +08:00
1 parent 1562519f08
commit 7f65162115
17 files changed
+766 -36

No files matched your search

+75
View File
@@ -0,0 +1,75 @@
export interface CssSnippet {
id: string
name: string
css: string
enabled: boolean
}
export interface Appearance {
colorMode: 'system' | 'light' | 'dark'
theme: string
cssSnippets: CssSnippet[]
}
export const appearancePaused = new URLSearchParams(location.search).has('reset-appearance')
export function sanitizeAppearance(raw: Record<string, unknown>): Appearance {
return {
colorMode: raw.colorMode === 'light' || raw.colorMode === 'dark' ? raw.colorMode : 'system',
theme: typeof raw.theme === 'string' && /^[a-z0-9][a-z0-9_-]{0,63}$/.test(raw.theme) ? raw.theme : 'default',
cssSnippets: Array.isArray(raw.cssSnippets) ? raw.cssSnippets.filter((s): s is CssSnippet =>
s !== null && typeof s === 'object' && typeof s.id === 'string' && typeof s.name === 'string' &&
typeof s.css === 'string' && typeof s.enabled === 'boolean',
).map((s) => ({ id: s.id, name: s.name, css: s.css, enabled: s.enabled })) : [],
}
}
let current: Appearance = { colorMode: 'system', theme: 'default', cssSnippets: [] }
let themeCSS = ''
const darkMode = matchMedia('(prefers-color-scheme: dark)')
let rendered = ''
function style(id: string, css: string) {
let el = document.getElementById(id) as HTMLStyleElement | null
if (!el) {
el = document.createElement('style')
el.id = id
document.head.append(el)
}
if (el.textContent !== css) el.textContent = css
}
function render() {
const mode = appearancePaused ? 'system' : current.colorMode
const dark = mode === 'system' ? darkMode.matches : mode === 'dark'
const signature = JSON.stringify([dark, appearancePaused ? '' : [current.theme, themeCSS, current.cssSnippets]])
if (signature === rendered) return
rendered = signature
const root = document.documentElement
root.dataset.rmColorMode = dark ? 'dark' : 'light'
root.dataset.rmTheme = appearancePaused ? 'default' : current.theme
style('rm-theme', appearancePaused ? '' : themeCSS)
// 独立样式元素避免某个片段的未闭合语法吞掉其他片段。
document.querySelectorAll('style[data-rm-snippet]').forEach((el) => el.remove())
if (!appearancePaused) for (const snippet of current.cssSnippets) {
if (!snippet.enabled) continue
const el = document.createElement('style')
el.dataset.rmSnippet = snippet.id
el.textContent = snippet.css
document.head.append(el)
}
const bg = getComputedStyle(root).getPropertyValue('--rm-bg').trim()
for (const meta of document.querySelectorAll<HTMLMetaElement>('meta[name="theme-color"]')) {
meta.removeAttribute('media')
meta.content = bg
}
window.dispatchEvent(new Event('rawmark-appearance'))
}
darkMode.addEventListener('change', render)
export function applyAppearance(next: Appearance, css: string) {
current = next
themeCSS = css
render()
}
+40 -1
View File
@@ -3,6 +3,16 @@
* 代码块与行内代码配色取自 vuejs.org(@vue/theme + Shiki github-dark)。
*/
:root {
color-scheme: light;
--rm-sidebar-bg: #f9f9f9;
--rm-sidebar-width: 260px;
--rm-hover: rgba(0, 0, 0, 0.045);
--rm-active: rgba(66, 184, 131, 0.14);
--rm-danger: #e5484d;
--rm-warn-bg: #fff8e6;
--rm-warn-text: #8a5a00;
--rm-shadow: 0 8px 24px rgba(0, 0, 0, 0.12);
--rm-font-text: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'PingFang SC', 'Hiragino Sans GB',
'Microsoft YaHei', 'Noto Sans CJK SC', 'Helvetica Neue', sans-serif;
--rm-font-mono: ui-monospace, 'SF Mono', 'SFMono-Regular', Menlo, Monaco, 'Cascadia Mono', Consolas,
@@ -27,8 +37,30 @@
--rm-highlight-bg: rgba(255, 213, 0, 0.35);
}
:root:where([data-rm-color-mode="dark"]) {
color-scheme: dark;
--rm-bg: #1a1a1a;
--rm-text: rgba(255, 255, 255, 0.87);
--rm-text-muted: rgba(235, 235, 235, 0.6);
--rm-divider: rgba(84, 84, 84, 0.48);
--rm-accent: #42d392;
--rm-accent-strong: #42b883;
--rm-inline-code-bg: #2f2f2f;
--rm-inline-code-text: #aac8e4;
--rm-code-bg: #242424;
--rm-highlight-bg: rgba(255, 213, 0, 0.28);
--rm-sidebar-bg: #161618;
--rm-hover: rgba(255, 255, 255, 0.06);
--rm-active: rgba(66, 211, 146, 0.16);
--rm-warn-bg: #2f2716;
--rm-warn-text: #f0c060;
--rm-shadow: 0 8px 24px rgba(0, 0, 0, 0.5);
}
@media (prefers-color-scheme: dark) {
:root {
:root:where(:not([data-rm-color-mode])) {
color-scheme: dark;
--rm-bg: #1a1a1a;
--rm-text: rgba(255, 255, 255, 0.87);
--rm-text-muted: rgba(235, 235, 235, 0.6);
@@ -39,6 +71,13 @@
--rm-inline-code-text: #aac8e4;
--rm-code-bg: #242424;
--rm-highlight-bg: rgba(255, 213, 0, 0.28);
--rm-sidebar-bg: #161618;
--rm-hover: rgba(255, 255, 255, 0.06);
--rm-active: rgba(66, 211, 146, 0.16);
--rm-warn-bg: #2f2716;
--rm-warn-text: #f0c060;
--rm-shadow: 0 8px 24px rgba(0, 0, 0, 0.5);
}
}