feat(highlight): replace Prism with highlight.js and Atom One Dark

This commit is contained in:
eezd committed 2026-10-24 18:12:49 +08:00
1 parent 634ef9fc11
commit b123ef36db
10 files changed
+72 -134

No files matched your search

+1 -1
View File
@@ -34,7 +34,7 @@ docker run -d --name rawmark \
- 笔记以普通 `.md` 文件保存在 `数据目录/vault/`,粘贴、上传和导入的图片统一存放在笔记同级的 `assets/`,链接使用 `./assets/文件名`,同目录相同内容去重。笔记跨目录移动时图片跟随,仍被其他笔记引用的原图保留;删除单篇笔记不会删除共享图片。旧 `笔记名.assets/` 仍可读取、移动与恢复;外部修改会实时同步到网页。 - 笔记以普通 `.md` 文件保存在 `数据目录/vault/`,粘贴、上传和导入的图片统一存放在笔记同级的 `assets/`,链接使用 `./assets/文件名`,同目录相同内容去重。笔记跨目录移动时图片跟随,仍被其他笔记引用的原图保留;删除单篇笔记不会删除共享图片。旧 `笔记名.assets/` 仍可读取、移动与恢复;外部修改会实时同步到网页。
- 知识工具:标准 Markdown 链接构成图谱,支持正文 `#标签` 与 YAML `tags`。笔记或文件夹在应用内改名、移动时维护入链和相对出链;公式和代码示例不参与关系提取。 - 知识工具:标准 Markdown 链接构成图谱,支持正文 `#标签` 与 YAML `tags`。笔记或文件夹在应用内改名、移动时维护入链和相对出链;公式和代码示例不参与关系提取。
- 日记与模板:默认日记路径为 `Daily/YYYY-MM-DD.md`(本地日期),已有日记直接打开;模板为 `Templates/` 下的普通 Markdown,支持 `{{date}}`、`{{time}}`、`{{title}}`,资源链接按新笔记位置调整。可在设置中修改目录和日记模板。 - 日记与模板:默认日记路径为 `Daily/YYYY-MM-DD.md`(本地日期),已有日记直接打开;模板为 `Templates/` 下的普通 Markdown,支持 `{{date}}`、`{{time}}`、`{{title}}`,资源链接按新笔记位置调整。可在设置中修改目录和日记模板。
- 外观:浅色玻璃工作台与深色模式,字体资源随应用本地提供;界面字号默认 14px,可输入 14–20px 并单独重置,与编辑器及阅读字号独立;排版、玻璃透明度、选中文本工具栏均可在设置中调整并即时保存。 - 外观:浅色玻璃工作台与深色模式,字体资源随应用本地提供;代码块使用 highlight.js,固定采用官网的 Atom One Dark 配色,与页面明暗模式独立;界面字号默认 14px,可输入 14–20px 并单独重置,与编辑器及阅读字号独立;排版、玻璃透明度、选中文本工具栏均可在设置中调整并即时保存。
- 选区格式:电脑与触屏选中文字后提供外观、加粗、斜体、下划线、删除线、高亮和链接。外观可设置配色、字体、局部字号与效果;下划线及外观以受限的标准 HTML 行内标签保存在 Markdown 中,源码与导出保留标签,其他软件的显示取决于其 HTML 支持。清除样式只重置局部外观;底部不再显示格式栏。 - 选区格式:电脑与触屏选中文字后提供外观、加粗、斜体、下划线、删除线、高亮和链接。外观可设置配色、字体、局部字号与效果;下划线及外观以受限的标准 HTML 行内标签保存在 Markdown 中,源码与导出保留标签,其他软件的显示取决于其 HTML 支持。清除样式只重置局部外观;底部不再显示格式栏。
- 图片管理:侧栏默认隐藏 `assets` 和 `笔记名.assets`;右键文件夹选择「图片管理」,查看当前目录及直属附件目录的图片,支持检索、排序、网格/列表、批量上传、引用查看及预览、重命名、移动、替换。可切换整个知识库;清理孤立图片须确认候选,服务端再次核对全库引用后移入回收站,可恢复。上传至所选目录的 `assets/`,相同内容自动去重。根目录可在侧栏空白处右键或通过命令面板打开。图片改名/移动会更新笔记中的本地引用;替换保留路径,选择相同扩展名的图片(最大 50 MB),旧图片不进回收站。 - 图片管理:侧栏默认隐藏 `assets` 和 `笔记名.assets`;右键文件夹选择「图片管理」,查看当前目录及直属附件目录的图片,支持检索、排序、网格/列表、批量上传、引用查看及预览、重命名、移动、替换。可切换整个知识库;清理孤立图片须确认候选,服务端再次核对全库引用后移入回收站,可恢复。上传至所选目录的 `assets/`,相同内容自动去重。根目录可在侧栏空白处右键或通过命令面板打开。图片改名/移动会更新笔记中的本地引用;替换保留路径,选择相同扩展名的图片(最大 50 MB),旧图片不进回收站。
- `--user` 让容器以宿主机用户身份读写数据目录;不指定时容器以 UID 65532 运行,需要该用户对目录有写权限。 - `--user` 让容器以宿主机用户身份读写数据目录;不指定时容器以 UID 65532 运行,需要该用户对目录有写权限。
-2
View File
@@ -16,8 +16,6 @@
--rm-shadow: 0 12px 36px rgba(0, 0, 0, 0.6); --rm-shadow: 0 12px 36px rgba(0, 0, 0, 0.6);
--rm-inline-code-bg: #202127; --rm-inline-code-bg: #202127;
--rm-inline-code-text: #f19a58; --rm-inline-code-text: #f19a58;
--rm-code-bg: #2d2d2d;
--rm-code-text: #ccc;
--rm-highlight-bg: rgba(232, 196, 119, 0.25); --rm-highlight-bg: rgba(232, 196, 119, 0.25);
} }
-6
View File
@@ -16,12 +16,6 @@
--rm-shadow: 0 12px 40px -12px rgba(19, 27, 46, 0.18), 0 2px 8px rgba(19, 27, 46, 0.03); --rm-shadow: 0 12px 40px -12px rgba(19, 27, 46, 0.18), 0 2px 8px rgba(19, 27, 46, 0.03);
--rm-inline-code-bg: #eaedff; --rm-inline-code-bg: #eaedff;
--rm-inline-code-text: #3525cd; --rm-inline-code-text: #3525cd;
--rm-code-bg: #ffffff;
--rm-code-text: #334155;
--rm-code-font-size: 14px; --rm-code-font-size: 14px;
--rm-highlight-bg: rgba(255, 213, 0, 0.35); --rm-highlight-bg: rgba(255, 213, 0, 0.35);
} }
@media (max-width: 767px) {
:root { --rm-code-bg: #283044; --rm-code-text: #eef0ff; }
}
+10 -18
View File
@@ -15,14 +15,13 @@
"@lezer/highlight": "^1.2.5", "@lezer/highlight": "^1.2.5",
"@lezer/markdown": "^1.8.0", "@lezer/markdown": "^1.8.0",
"gemoji": "^8.1.0", "gemoji": "^8.1.0",
"highlight.js": "^11.12.0",
"katex": "^0.18.11", "katex": "^0.18.11",
"mermaid": "^12.2.0", "mermaid": "^12.2.0",
"prismjs": "^1.30.0",
"turndown": "^7.2.4", "turndown": "^7.2.4",
"vue": "^3.5.44" "vue": "^3.5.44"
}, },
"devDependencies": { "devDependencies": {
"@types/prismjs": "^1.26.6",
"@types/turndown": "^5.0.6", "@types/turndown": "^5.0.6",
"@vitejs/plugin-vue": "^6.0.10", "@vitejs/plugin-vue": "^6.0.10",
"commonmark-spec": "^0.31.2", "commonmark-spec": "^0.31.2",
@@ -991,13 +990,6 @@
"undici-types": "~8.9.0" "undici-types": "~8.9.0"
} }
}, },
"node_modules/@types/prismjs": {
"version": "1.26.6",
"resolved": "https://registry.npmjs.org/@types/prismjs/-/prismjs-1.26.6.tgz",
"integrity": "sha512-vqlvI7qlMvcCBbVe0AKAb4f97//Hy0EBTaiW8AalRnG/xAN5zOiWWyrNqNXeq8+KAuvRewjCVY1+IPxk4RdNYw==",
"dev": true,
"license": "MIT"
},
"node_modules/@types/trusted-types": { "node_modules/@types/trusted-types": {
"version": "2.0.7", "version": "2.0.7",
"resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.7.tgz", "resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.7.tgz",
@@ -2018,6 +2010,15 @@
"node": ">=20.0.0" "node": ">=20.0.0"
} }
}, },
"node_modules/highlight.js": {
"version": "11.12.0",
"resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-11.12.0.tgz",
"integrity": "sha512-nbfWpyRMcMrPMmDwJB+dhX/eiaPKtc2RB+0QZskqJ3WjRA/FDS0e9hZrx8EC/lbEv8gXy98FcDbNa/dspAaJMg==",
"license": "BSD-3-Clause",
"engines": {
"node": ">=12.0.0"
}
},
"node_modules/iconv-lite": { "node_modules/iconv-lite": {
"version": "0.6.3", "version": "0.6.3",
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz",
@@ -2533,15 +2534,6 @@
"node": "^10 || ^12 || >=14" "node": "^10 || ^12 || >=14"
} }
}, },
"node_modules/prismjs": {
"version": "1.30.0",
"resolved": "https://registry.npmjs.org/prismjs/-/prismjs-1.30.0.tgz",
"integrity": "sha512-DEvV2ZF2r2/63V+tK8hQvrR2ZGn10srHbXviTlcv7Kpzw8jWiNTqbVgjO3IY8RxrrOUF8VPMQQFysYYYv0YZxw==",
"license": "MIT",
"engines": {
"node": ">=6"
}
},
"node_modules/robust-predicates": { "node_modules/robust-predicates": {
"version": "3.0.3", "version": "3.0.3",
"resolved": "https://registry.npmjs.org/robust-predicates/-/robust-predicates-3.0.3.tgz", "resolved": "https://registry.npmjs.org/robust-predicates/-/robust-predicates-3.0.3.tgz",
+1 -2
View File
@@ -18,14 +18,13 @@
"@lezer/highlight": "^1.2.5", "@lezer/highlight": "^1.2.5",
"@lezer/markdown": "^1.8.0", "@lezer/markdown": "^1.8.0",
"gemoji": "^8.1.0", "gemoji": "^8.1.0",
"highlight.js": "^11.12.0",
"katex": "^0.18.11", "katex": "^0.18.11",
"mermaid": "^12.2.0", "mermaid": "^12.2.0",
"prismjs": "^1.30.0",
"turndown": "^7.2.4", "turndown": "^7.2.4",
"vue": "^3.5.44" "vue": "^3.5.44"
}, },
"devDependencies": { "devDependencies": {
"@types/prismjs": "^1.26.6",
"@types/turndown": "^5.0.6", "@types/turndown": "^5.0.6",
"@vitejs/plugin-vue": "^6.0.10", "@vitejs/plugin-vue": "^6.0.10",
"commonmark-spec": "^0.31.2", "commonmark-spec": "^0.31.2",
+2 -2
View File
@@ -34,6 +34,6 @@ it('启动和运行失败均结束请求,失败不会伪装成缓存结果且
await crashed await crashed
const recovered = requestTokens('python', 'print(1)') const recovered = requestTokens('python', 'print(1)')
const replacement = control.workers[1]! const replacement = control.workers[1]!
replacement.dispatchEvent(new MessageEvent('message', { data: { id: replacement.request!.id, tokens: [{ classes: 'token function', content: ['print'] }] } })) replacement.dispatchEvent(new MessageEvent('message', { data: { id: replacement.request!.id, tokens: [{ classes: 'hljs-title function_', content: ['print'] }] } }))
await expect(recovered).resolves.toEqual([{ classes: 'token function', content: ['print'] }]) await expect(recovered).resolves.toEqual([{ classes: 'hljs-title function_', content: ['print'] }])
}) })
+1 -1
View File
@@ -6,7 +6,7 @@ export interface HighlightRequest {
code: string code: string
} }
// 保留 Prism 的嵌套 token 与原文,编辑器使用官方 token 类样式。 // 保留 highlight.js 的嵌套 token 与原文,编辑器使用官方 token 类样式。
export type TokenRun = (string | { classes: string; content: TokenRun })[] export type TokenRun = (string | { classes: string; content: TokenRun })[]
// 语言不受支持时 tokens 为 null;实际失败必须与纯文本结果区分。 // 语言不受支持时 tokens 为 null;实际失败必须与纯文本结果区分。
+32 -18
View File
@@ -9,7 +9,6 @@ let nextId = 1
beforeAll(async () => { beforeAll(async () => {
vi.stubGlobal('self', worker) vi.stubGlobal('self', worker)
vi.stubGlobal('Prism', {})
await import('./worker') await import('./worker')
}) })
afterAll(() => { vi.restoreAllMocks(); vi.unstubAllGlobals() }) afterAll(() => { vi.restoreAllMocks(); vi.unstubAllGlobals() })
@@ -34,31 +33,31 @@ it('首次高亮 HTML 就包含内嵌脚本和样式,不依赖其他代码块
const tokens = await highlight('html', source) const tokens = await highlight('html', source)
expect(tokens).not.toBeNull() expect(tokens).not.toBeNull()
expect(text(tokens!)).toBe(source) expect(text(tokens!)).toBe(source)
expect(hasToken(tokens!, 'token keyword', 'const')).toBe(true) expect(hasToken(tokens!, 'hljs-keyword', 'const')).toBe(true)
expect(hasToken(tokens!, 'token property', 'color')).toBe(true) expect(hasToken(tokens!, 'hljs-attribute', 'color')).toBe(true)
}) })
it('Prism token 保留原文、语言别名与嵌套语法,不再返回旧颜色', async () => { it('highlight.js token 保留原文、语言别名与嵌套语法,不再返回旧颜色', async () => {
const source = 'const value = `中文😀 ${name}` // comment\n' const source = 'const value = `中文😀 ${name}` // comment\n'
const tokens = await highlight('js', source) const tokens = await highlight('js', source)
expect(tokens).not.toBeNull() expect(tokens).not.toBeNull()
expect(text(tokens!)).toBe(source) expect(text(tokens!)).toBe(source)
expect(hasToken(tokens!, 'token keyword', 'const')).toBe(true) expect(hasToken(tokens!, 'hljs-keyword', 'const')).toBe(true)
expect(hasToken(tokens!, 'token template-string', '`中文😀 ${name}`')).toBe(true) expect(hasToken(tokens!, 'hljs-string', '`中文😀 ${name}`')).toBe(true)
expect(hasToken(tokens!, 'token interpolation', '${name}')).toBe(true) expect(hasToken(tokens!, 'hljs-subst', '${name}')).toBe(true)
expect(hasToken(tokens!, 'token comment', '// comment')).toBe(true) expect(hasToken(tokens!, 'hljs-comment', '// comment')).toBe(true)
expect(await highlight('javascript', source)).toEqual(tokens) expect(await highlight('javascript', source)).toEqual(tokens)
}) })
it('按需加载语言依赖,并发请求和常见别名得到实际高亮', async () => { it('并发请求和常见别名得到实际高亮', async () => {
const samples = [ const samples = [
['python', 'print("hello")', 'token string', '"hello"'], ['python', 'print("hello")', 'hljs-string', '"hello"'],
['tsx', 'const App = () => <div title="hello">你好</div>', 'token attr-value', '="hello"'], ['tsx', 'const App = () => <div title="hello">你好</div>', 'hljs-string', '"hello"'],
['sh', 'echo "$HOME"', 'token environment constant', '$HOME'], ['sh', 'echo "$HOME"', 'hljs-variable', '$HOME'],
['yaml', 'name: hello', 'token key atrule', 'name'], ['yaml', 'name: hello', 'hljs-attr', 'name:'],
['json', '{"value": true}', 'token boolean', 'true'], ['json', '{"value": true}', 'hljs-keyword', 'true'],
['go', 'package main\nfunc main() {}', 'token keyword', 'package'], ['go', 'package main\nfunc main() {}', 'hljs-keyword', 'package'],
['php', '<?php echo $value; ?><div>中文😀</div>', 'token keyword', 'echo'], ['php', '<?php echo $value; ?><div>中文😀</div>', 'hljs-keyword', 'echo'],
] ]
await Promise.all(samples.map(async ([lang, source, classes, content]) => { await Promise.all(samples.map(async ([lang, source, classes, content]) => {
const tokens = await highlight(lang, source) const tokens = await highlight(lang, source)
@@ -70,9 +69,24 @@ it('按需加载语言依赖,并发请求和常见别名得到实际高亮', a
it('未知语言保持纯文本,真实分词失败返回原始原因且不阻断下一次请求', async () => { it('未知语言保持纯文本,真实分词失败返回原始原因且不阻断下一次请求', async () => {
for (const lang of ['unknown-language', 'constructor', '__proto__']) expect(await highlight(lang, 'text')).toBeNull() for (const lang of ['unknown-language', 'constructor', '__proto__']) expect(await highlight(lang, 'text')).toBeNull()
const { default: prism } = await import('prismjs') const { default: hljs } = await import('highlight.js')
const cause = new Error('分词失败') const cause = new Error('分词失败')
vi.spyOn(prism, 'tokenize').mockImplementationOnce(() => { throw cause }) vi.spyOn(hljs, 'highlight').mockImplementationOnce(() => { throw cause })
await expect(highlight('python', 'print(1)')).rejects.toMatchObject({ cause }) await expect(highlight('python', 'print(1)')).rejects.toMatchObject({ cause })
expect(await highlight('python', 'print(1)')).not.toBeNull() expect(await highlight('python', 'print(1)')).not.toBeNull()
}) })
it('HTML 特殊字符、转义实体和伪造 span 原样保留,不作为标记解析', async () => {
const source = `const value = "&lt; &amp; &quot; &#x27; <span class='hljs-keyword'>中文😀</span>"; // > & '\n`
const tokens = await highlight('js', source)
expect(text(tokens!)).toBe(source)
expect(hasToken(tokens!, 'hljs-keyword', '中文😀')).toBe(false)
})
it('库安全模式返回的实际异常不伪装成成功的纯文本结果', async () => {
const { default: hljs } = await import('highlight.js')
const cause = new Error('语法运行失败')
const original = hljs.highlight('value', { language: 'js' })
vi.spyOn(hljs, 'highlight').mockReturnValueOnce({ ...original, errorRaised: cause })
await expect(highlight('js', 'value')).rejects.toMatchObject({ cause })
})
+22 -64
View File
@@ -1,76 +1,34 @@
import type { Grammar, Token } from 'prismjs' import hljs from 'highlight.js'
import components from 'prismjs/components.json'
import getLoader, { type ComponentCategory } from 'prismjs/dependencies'
import type { HighlightRequest, HighlightResponse, TokenRun } from './protocol' import type { HighlightRequest, HighlightResponse, TokenRun } from './protocol'
// 复用应用自己的 Worker 协议,禁止 Prism 注册另一套消息处理器。 // 只转换 highlight.js 生成的 span 和转义文本,不向编辑器插入 HTML。
Object.assign(globalThis, { Prism: { manual: true, disableWorkerMessageHandler: true } }) function serialize(html: string): TokenRun {
const prismReady = import('prismjs').then((mod) => mod.default) const root: TokenRun = []
const imports = import.meta.glob(['../../node_modules/prismjs/components/prism-*.js', '!../../node_modules/prismjs/components/*.min.js', '!../../node_modules/prismjs/components/prism-core.js']) const stack = [root]
const { meta, ...entries } = components.languages const entities: Record<string, string> = { '&amp;': '&', '&lt;': '<', '&gt;': '>', '&quot;': '"', '&#x27;': "'" }
const languages: ComponentCategory = entries for (const match of html.matchAll(/<span class="([^"]+)">|(<\/span>)|([^<]+)/g)) {
const aliases = new Map<string, string>() if (match[1]) {
for (const [id, entry] of Object.entries(languages)) { const content: TokenRun = []
aliases.set(id, id) stack[stack.length - 1].push({ classes: match[1], content })
if (typeof entry !== 'string') { stack.push(content)
for (const alias of typeof entry.alias === 'string' ? [entry.alias] : entry.alias ?? []) aliases.set(alias, id) } else if (match[2]) stack.pop()
else stack[stack.length - 1].push(match[3].replace(/&(?:amp|lt|gt|quot|#x27);/g, entity => entities[entity]))
} }
return root
} }
// Prism 默认包预载这四种基础语法,确保 HTML 的样式和脚本嵌套高亮不依赖请求顺序。 function highlight(req: HighlightRequest): TokenRun | null {
const loaded = new Set(['markup', 'css', 'clike', 'javascript']) if (!hljs.getLanguage(req.lang)) return null
let loadTail = Promise.resolve() const result = hljs.highlight(req.code, { language: req.lang, ignoreIllegals: true })
// 库的安全模式会随纯文本结果返回异常,仍需传递真实失败原因。
function loadLanguage(id: string): Promise<void> { if (result.errorRaised) throw result.errorRaised
const ready = loadTail.then(async () => { return serialize(result.value)
const wanted = new Set<string>()
function include(lang: string) {
if (wanted.has(lang) || loaded.has(lang)) return
wanted.add(lang)
const entry = languages[lang]
if (typeof entry !== 'string') {
// 首次加载时也包含可选嵌套语法,避免后续依赖 ESM 无法重复执行的组件。
for (const refs of [entry.require, entry.optional, entry.modify]) {
for (const dep of typeof refs === 'string' ? [refs] : refs ?? []) include(dep)
}
}
}
include(id)
for (const lang of getLoader({ languages }, [...wanted]).getIds()) {
if (loaded.has(lang)) continue
await imports[`../../node_modules/prismjs/components/prism-${lang}.js`]()
loaded.add(lang)
}
})
// 语法组件修改共享注册表,按顺序加载;单次失败不能阻塞后续独立请求。
loadTail = ready.catch(() => {})
return ready
} }
function serialize(content: string | Token | (string | Token)[]): TokenRun { self.addEventListener('message', (e: MessageEvent<HighlightRequest>) => {
return (Array.isArray(content) ? content : [content]).map((token) => {
if (typeof token === 'string') return token
const aliases = typeof token.alias === 'string' ? [token.alias] : token.alias ?? []
return { classes: ['token', token.type, ...aliases].join(' '), content: serialize(token.content) }
})
}
async function highlight(req: HighlightRequest): Promise<TokenRun | null> {
const id = aliases.get(req.lang)
if (!id) return null
const prism = await prismReady
await loadLanguage(id)
const env: { code: string; grammar: Grammar; language: string; tokens?: (string | Token)[] } = { code: req.code, grammar: prism.languages[id], language: id }
prism.hooks.run('before-tokenize', env)
env.tokens = prism.tokenize(env.code, env.grammar)
prism.hooks.run('after-tokenize', env)
return serialize(env.tokens)
}
self.addEventListener('message', async (e: MessageEvent<HighlightRequest>) => {
let res: HighlightResponse let res: HighlightResponse
try { try {
res = { id: e.data.id, tokens: await highlight(e.data) } res = { id: e.data.id, tokens: highlight(e.data) }
} catch (cause) { } catch (cause) {
res = { id: e.data.id, error: new Error('代码高亮失败', { cause }) } res = { id: e.data.id, error: new Error('代码高亮失败', { cause }) }
} }
+3 -20
View File
@@ -1,5 +1,5 @@
@import './fonts.css'; @import './fonts.css';
@import 'prismjs/themes/prism-tomorrow.css'; @import 'highlight.js/styles/atom-one-dark.css';
/* 基础主题。--rm-* 变量继续作为主题接口,字体在本地加载。 */ /* 基础主题。--rm-* 变量继续作为主题接口,字体在本地加载。 */
:root { :root {
@@ -47,8 +47,8 @@
--rm-shadow: 0 12px 40px -12px rgba(19, 27, 46, 0.18), 0 2px 8px rgba(19, 27, 46, 0.03); --rm-shadow: 0 12px 40px -12px rgba(19, 27, 46, 0.18), 0 2px 8px rgba(19, 27, 46, 0.03);
--rm-inline-code-bg: #eaedff; --rm-inline-code-bg: #eaedff;
--rm-inline-code-text: #3525cd; --rm-inline-code-text: #3525cd;
--rm-code-bg: #ffffff; --rm-code-bg: #282c34;
--rm-code-text: #334155; --rm-code-text: #abb2bf;
--rm-code-font-size: 14px; --rm-code-font-size: 14px;
--rm-highlight-bg: rgba(255, 213, 0, 0.35); --rm-highlight-bg: rgba(255, 213, 0, 0.35);
} }
@@ -63,8 +63,6 @@
--rm-accent-strong: #42b883; --rm-accent-strong: #42b883;
--rm-inline-code-bg: #2f2f2f; --rm-inline-code-bg: #2f2f2f;
--rm-inline-code-text: #f19a58; --rm-inline-code-text: #f19a58;
--rm-code-bg: #2d2d2d;
--rm-code-text: #ccc;
--rm-highlight-bg: rgba(255, 213, 0, 0.28); --rm-highlight-bg: rgba(255, 213, 0, 0.28);
--rm-sidebar-bg: #191b1d; --rm-sidebar-bg: #191b1d;
@@ -86,8 +84,6 @@
--rm-accent-strong: #42b883; --rm-accent-strong: #42b883;
--rm-inline-code-bg: #2f2f2f; --rm-inline-code-bg: #2f2f2f;
--rm-inline-code-text: #f19a58; --rm-inline-code-text: #f19a58;
--rm-code-bg: #2d2d2d;
--rm-code-text: #ccc;
--rm-highlight-bg: rgba(255, 213, 0, 0.28); --rm-highlight-bg: rgba(255, 213, 0, 0.28);
--rm-sidebar-bg: #191b1d; --rm-sidebar-bg: #191b1d;
@@ -343,14 +339,6 @@ button, input, select, textarea { font: inherit; }
.cm-line.cm-rm-frontmatter-begin { border-radius: 6px 6px 0 0; padding-top: 4px; } .cm-line.cm-rm-frontmatter-begin { border-radius: 6px 6px 0 0; padding-top: 4px; }
.cm-line.cm-rm-frontmatter-end { border-radius: 0 0 6px 6px; padding-bottom: 4px; } .cm-line.cm-rm-frontmatter-end { border-radius: 0 0 6px 6px; padding-bottom: 4px; }
/* 浅色编辑器的 Prism token 颜色。 */
:root:not([data-rm-color-mode="dark"]) .cm-rm-code .token { text-shadow: none; background: none; }
:root:not([data-rm-color-mode="dark"]) .cm-rm-code :is(.comment,.prolog,.doctype,.cdata) { color: #94a3b8; }
:root:not([data-rm-color-mode="dark"]) .cm-rm-code :is(.punctuation,.operator) { color: #64748b; }
:root:not([data-rm-color-mode="dark"]) .cm-rm-code :is(.keyword,.atrule,.tag) { color: #7c3aed; }
:root:not([data-rm-color-mode="dark"]) .cm-rm-code :is(.string,.char,.attr-value,.builtin) { color: #059669; }
:root:not([data-rm-color-mode="dark"]) .cm-rm-code :is(.function,.class-name) { color: #2563eb; }
:root:not([data-rm-color-mode="dark"]) .cm-rm-code :is(.number,.boolean,.constant) { color: #d97706; }
.cm-line:is(.cm-rm-h1,.cm-rm-h2,.cm-rm-h3,.cm-rm-h4,.cm-rm-h5,.cm-rm-h6) { font-family: var(--rm-font-heading); font-weight: 600; } .cm-line:is(.cm-rm-h1,.cm-rm-h2,.cm-rm-h3,.cm-rm-h4,.cm-rm-h5,.cm-rm-h6) { font-family: var(--rm-font-heading); font-weight: 600; }
.cm-line.cm-rm-h1 { font-size: 2.125em; line-height: 1.4; } .cm-line.cm-rm-h1 { font-size: 2.125em; line-height: 1.4; }
.cm-line.cm-rm-h2 { font-size: 1.5em; line-height: 1.5; } .cm-line.cm-rm-h2 { font-size: 1.5em; line-height: 1.5; }
@@ -361,10 +349,5 @@ button, input, select, textarea { font: inherit; }
.cm-line.cm-rm-code-end::before { border-bottom: 1px solid var(--rm-divider); } .cm-line.cm-rm-code-end::before { border-bottom: 1px solid var(--rm-divider); }
.cm-line.cm-rm-paragraph-end { padding-bottom: var(--rm-paragraph-spacing); } .cm-line.cm-rm-paragraph-end { padding-bottom: var(--rm-paragraph-spacing); }
@media (max-width: 767px) { @media (max-width: 767px) {
:root:not([data-rm-color-mode="dark"]) { --rm-code-bg: #283044; --rm-code-text: #eef0ff; }
:root:not([data-rm-color-mode="dark"]) .cm-rm-code :is(.keyword,.atrule,.tag) { color: #c4b5fd; }
:root:not([data-rm-color-mode="dark"]) .cm-rm-code :is(.string,.char,.attr-value,.builtin) { color: #6ee7b7; }
:root:not([data-rm-color-mode="dark"]) .cm-rm-code :is(.function,.class-name) { color: #93c5fd; }
:root:not([data-rm-color-mode="dark"]) .cm-rm-code :is(.number,.boolean,.constant) { color: #fcd34d; }
.cm-line.cm-rm-h1 { font-size: 1.75em; } .cm-line.cm-rm-h1 { font-size: 1.75em; }
} }