style(代码配色): 全站代码换成 Dracula 配色(亮色用官方浅色版 Alucard);编辑器、题面和教程的代码块、练一练、提交详情的颜色一模一样了
Deploy / deploy (push) Canceled after 0s

原来三套配色各管一块:编辑器是 smoothy / One Dark,正文代码块和练一练是仿 GitHub,
提交详情是 Naive 自带的,同一段 printf 在一课里就有两种颜色。现在静态代码也走编辑器
那套 lezer 解析器,颜色只在 shared/highlight.css 写一份,highlight.js 删掉。

填空题顺手修好:空会把语法搅乱,(humidity ____ 40) 被认成类型声明、humidity 染成类型色,
现在空里先填上答案再解析,答案不输出。

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
xuyueandClaude Opus 5.5 committed 2026-10-13 19:49:20 +08:00
1 parent 27210eac1b
commit df3b4f0dfd
18 files changed
+343 -460

No files matched your search

-1
View File
@@ -34,7 +34,6 @@
"copy-text-to-clipboard": "^3.2.2",
"date-fns": "^4.4.0",
"fflate": "^0.8.3",
"highlight.js": "^11.12.0",
"lib0": "0.2.119",
"markdown-it": "^14.3.0",
"mermaid": "^12.1.0",
+3 -1
View File
@@ -2,6 +2,7 @@
import { darkTheme, dateZhCN, zhCN } from "naive-ui"
import "normalize.css"
import "./index.css"
import "shared/highlight.css"
import { useConfigStore } from "shared/store/config"
import { useConfigUpdate } from "shared/composables/configUpdate"
import { useUserStore } from "shared/store/user"
@@ -40,7 +41,8 @@ watch(
{ immediate: true },
)
// n-code(提交详情、看代码的弹框)用的 highlight.js,空闲时再加载,不挡首屏
// n-code(提交详情、看代码的弹框)的高亮,空闲时再加载,不挡首屏。
// 它只认 highlight.js 的接口,shared/highlight 给了一个同名的壳,里面是和编辑器同一套解析器
const hljsInstance = shallowRef<typeof import("shared/highlight").hljs>()
onMounted(() => {
const load = async () => {
@@ -2,7 +2,6 @@
import ExerciseResult from "./ExerciseResult.vue"
import type { Exercise, ExerciseDebugData } from "utils/types"
import { highlightLines } from "shared/highlight"
import "shared/highlight.css"
const props = defineProps<{ exercise: Exercise; lang?: string }>()
const emit = defineEmits<{
@@ -1,8 +1,7 @@
<script setup lang="ts">
import ExerciseResult from "./ExerciseResult.vue"
import type { Exercise, ExerciseFillData } from "utils/types"
import { highlight } from "shared/highlight"
import "shared/highlight.css"
import { highlightWithHoles } from "shared/highlight"
const props = defineProps<{ exercise: Exercise; lang?: string }>()
const emit = defineEmits<{
@@ -16,25 +15,26 @@ type BlankSeg = { type: "blank"; answers: string[]; index: number }
type Segment = CodeSeg | BlankSeg
const segments = computed<Segment[]>(() => {
// 空里先填上第一个答案再解析,免得空把语法搅乱;答案本身不输出,见 highlightWithHoles
const blanks: string[][] = []
const markedCode = data.value.code.replace(/\{\{([^}]+)\}\}/g, (_, inner) => {
blanks.push(inner.split("|"))
return `____${blanks.length - 1}____`
})
const highlighted = highlight(markedCode, props.lang)
const parts = highlighted.split(/____(\d+)____/)
const result: Segment[] = []
for (let i = 0; i < parts.length; i++) {
if (i % 2 === 0) {
if (parts[i]) result.push({ type: "code", html: parts[i] })
} else {
const idx = parseInt(parts[i])
result.push({ type: "blank", answers: blanks[idx], index: idx })
const holes: { from: number; to: number }[] = []
let code = ""
let last = 0
for (const m of data.value.code.matchAll(/\{\{([^}]+)\}\}/g)) {
const answers = m[1].split("|")
code += data.value.code.slice(last, m.index)
holes.push({ from: code.length, to: code.length + answers[0].length })
code += answers[0]
blanks.push(answers)
last = m.index + m[0].length
}
}
return result
code += data.value.code.slice(last)
return highlightWithHoles(code, holes, props.lang).map((part) =>
typeof part === "string"
? { type: "code", html: part }
: { type: "blank", answers: blanks[part], index: part },
)
})
const blankCount = computed(() => segments.value.filter((s) => s.type === "blank").length)
@@ -2,7 +2,6 @@
import ExerciseResult from "./ExerciseResult.vue"
import type { Exercise, ExercisePredictData } from "utils/types"
import { highlight } from "shared/highlight"
import "shared/highlight.css"
const props = defineProps<{ exercise: Exercise; lang?: string }>()
const emit = defineEmits<{
@@ -3,7 +3,6 @@ import ExerciseResult from "./ExerciseResult.vue"
import type { Exercise, ExerciseSortData } from "utils/types"
import { shuffle } from "../composables/useShuffle"
import { highlightLines } from "shared/highlight"
import "shared/highlight.css"
const props = defineProps<{ exercise: Exercise; lang?: string }>()
const emit = defineEmits<{
@@ -11,7 +11,7 @@ import { useDark } from "@vueuse/core"
const props = withDefaults(
defineProps<{
code: string
/** highlight.js 的语言名(LANGUAGE_FORMAT_VALUE) */
/** 高亮用的语言名(LANGUAGE_FORMAT_VALUE),见 shared/highlight.ts */
language: string
/** 要标红的行,1 起 */
markLine?: number | null
@@ -7,8 +7,7 @@ import CodeMirror from "./CodeMirror.vue"
import type { Extension } from "@codemirror/state"
import { autocompletion, closeBrackets, completeAnyWord } from "@codemirror/autocomplete"
import type { LANGUAGE } from "utils/types"
import { oneDark } from "../themes/oneDark"
import { smoothy } from "../themes/smoothy"
import { darkCodeTheme, lightCodeTheme } from "../themes/codeTheme"
import { styleTheme } from "shared/extensions/baseTheme"
import { enhanceCompletion } from "shared/extensions/autocompletion"
@@ -47,7 +46,7 @@ const extensions = computed(() => [
autocompletion({
override: [enhanceCompletion(language), completeAnyWord],
}),
isDark.value ? oneDark : smoothy,
isDark.value ? darkCodeTheme : lightCodeTheme,
...extraExtensions,
])
</script>
@@ -1,7 +1,6 @@
<script setup lang="ts">
import { useThemeVars } from "naive-ui"
import { renderMarkdown } from "shared/markdown"
import "shared/highlight.css"
/**
* 显示一段 Markdown / HTML 正文:题面、教程、AI 的提示和报告。
@@ -6,8 +6,7 @@ import { Icon } from "@iconify/vue"
import CodeMirror from "./CodeMirror.vue"
import { uploadImage } from "../../admin/api"
import { styleTheme } from "../extensions/baseTheme"
import { oneDark } from "../themes/oneDark"
import { smoothy } from "../themes/smoothy"
import { darkCodeTheme, lightCodeTheme } from "../themes/codeTheme"
/**
* 后台教程正文的 Markdown 编辑器:左边写,右边由教程编辑页用学生端同一套组件预览。
@@ -145,7 +144,7 @@ const extensions = computed(() => [
EditorView.lineWrapping,
imageEvents,
shortcuts,
isDark.value ? oneDark : smoothy,
isDark.value ? darkCodeTheme : lightCodeTheme,
])
const tools = computed(() => [
@@ -5,8 +5,7 @@ import { autocompletion, closeBrackets, completeAnyWord } from "@codemirror/auto
import type { EditorView } from "@codemirror/view"
import type { Extension } from "@codemirror/state"
import type { LANGUAGE } from "utils/types"
import { oneDark } from "../themes/oneDark"
import { smoothy } from "../themes/smoothy"
import { darkCodeTheme, lightCodeTheme } from "../themes/codeTheme"
import { styleTheme } from "shared/extensions/baseTheme"
import { enhanceCompletion } from "shared/extensions/autocompletion"
import { languageExtension } from "shared/extensions/language"
@@ -60,7 +59,7 @@ const extensions = computed(() => [
langExtension.value,
bracketMatching(),
closeBrackets(),
isDark.value ? oneDark : smoothy,
isDark.value ? darkCodeTheme : lightCodeTheme,
autocompletion({
override: [enhanceCompletion(language), completeAnyWord],
}),
+66 -42
View File
@@ -1,50 +1,74 @@
/* 代码高亮配色(明 / 暗):练一练的题型组件和 Markdown.vue 的代码块共用,见 shared/highlight.ts */
.hljs-keyword,
.hljs-operator,
.hljs-selector-tag {
color: #d73a49;
}
.hljs-string,
.hljs-regexp,
.hljs-template-literal {
color: #032f62;
}
.hljs-comment,
.hljs-quote {
color: #6a737d;
font-style: italic;
}
.hljs-number,
.hljs-literal {
color: #005cc5;
}
.hljs-built_in,
.hljs-title.function_,
.hljs-class .hljs-title {
color: #6f42c1;
/* 全站代码配色:亮色 Alucard、暗色 Dracula(https://draculatheme.com/spec,Alucard 是它的官方浅色版)。
编辑器(CodeMirror)、正文代码块、练一练、n-code 都用 shared/highlight.ts 的 codeHighlighter
给词打 tok-* 类名,颜色只在这里写一份。App.vue 全局引入。 */
:root {
--code-fg: #1f1f1f;
--code-comment: #6c664b;
--code-cyan: #036a96;
--code-green: #14710a;
--code-orange: #a34d14;
--code-pink: #a3144d;
--code-purple: #644ac9;
--code-red: #cb3a2a;
--code-yellow: #846e15;
}
.dark .hljs-keyword,
.dark .hljs-operator,
.dark .hljs-selector-tag {
color: #c678dd;
html.dark {
--code-fg: #f8f8f2;
--code-comment: #6272a4;
--code-cyan: #8be9fd;
--code-green: #50fa7b;
--code-orange: #ffb86c;
--code-pink: #ff79c6;
--code-purple: #bd93f9;
--code-red: #ff5555;
--code-yellow: #f1fa8c;
}
.dark .hljs-string,
.dark .hljs-regexp,
.dark .hljs-template-literal {
color: #98c379;
.tok-keyword {
color: var(--code-pink);
}
.dark .hljs-comment,
.dark .hljs-quote {
color: #7f848e;
.tok-function {
color: var(--code-green);
}
.tok-string {
color: var(--code-yellow);
}
.tok-number {
color: var(--code-purple);
}
.tok-type {
color: var(--code-cyan);
}
.tok-comment {
color: var(--code-comment);
}
.tok-invalid {
color: var(--code-red);
}
/* 以下只有 Markdown 编辑器用得到 */
.tok-heading {
color: var(--code-purple);
font-weight: bold;
}
.tok-strong {
color: var(--code-orange);
font-weight: bold;
}
.tok-emphasis {
color: var(--code-yellow);
font-style: italic;
}
.dark .hljs-number,
.dark .hljs-literal {
color: #e5c07b;
.tok-link {
color: var(--code-cyan);
}
.dark .hljs-built_in,
.dark .hljs-title.function_,
.dark .hljs-class .hljs-title {
color: #61afef;
.tok-monospace {
color: var(--code-green);
}
.tok-quote {
color: var(--code-yellow);
}
.tok-strikethrough {
text-decoration: line-through;
}
+141 -42
View File
@@ -1,62 +1,161 @@
/**
* 代码高亮:自学的练一练、Markdown.vue 里的代码块、以及 App.vue 交给 n-code 的都是这一份
* (配色在 highlight.css)。
* 只注册判题机认的几种语言,别的语言和不标语言的代码块按原文显示。
* 代码高亮:全站一套。编辑器(CodeMirror)和静态的代码(Markdown.vue 的代码块、自学的练一练、
* App.vue 交给 n-code 的提交代码)都用同一个解析器(lezer)、同一张类名表,同一段代码在哪儿颜色都一样。
* 颜色在 highlight.css。
*
* 原来静态代码走 highlight.js,它和编辑器的分词不一样(`int` 一边是类型一边是基本类型,
* `printf(` 一边是内置函数一边是函数调用),配色表写得再一致,染出来也对不上。
*
* 只认判题机认的几种语言,别的语言和不标语言的代码块按原文显示。
*/
import hljs from "highlight.js/lib/core"
import c from "highlight.js/lib/languages/c"
import cpp from "highlight.js/lib/languages/cpp"
import python from "highlight.js/lib/languages/python"
import sql from "highlight.js/lib/languages/sql"
import { cppLanguage } from "@codemirror/lang-cpp"
import { pythonLanguage } from "@codemirror/lang-python"
import { SQLite } from "@codemirror/lang-sql"
import { highlightCode, tagHighlighter, tags as t } from "@lezer/highlight"
import type { ConfigProviderProps } from "naive-ui"
hljs.registerLanguage("python", python)
hljs.registerLanguage("c", c)
hljs.registerLanguage("cpp", cpp)
hljs.registerLanguage("sql", sql)
/** 词的种类 → 类名。编辑器在 shared/themes/codeTheme.ts 里挂的也是它 */
export const codeHighlighter = tagHighlighter([
// `int` 这类基本类型和关键字一个颜色(Dracula 的 storage.type),自定义的类型才是青色
{
tag: [
t.keyword,
t.modifier,
t.operator,
t.processingInstruction,
t.escape,
t.standard(t.typeName),
],
class: "tok-keyword",
},
{ tag: [t.function(t.variableName), t.function(t.propertyName)], class: "tok-function" },
{ tag: t.string, class: "tok-string" },
{ tag: [t.number, t.bool, t.null, t.atom, t.self], class: "tok-number" },
{ tag: [t.typeName, t.className, t.namespace, t.standard(t.name)], class: "tok-type" },
{ tag: t.comment, class: "tok-comment" },
{ tag: t.invalid, class: "tok-invalid" },
{ tag: t.heading, class: "tok-heading" },
{ tag: t.strong, class: "tok-strong" },
{ tag: t.emphasis, class: "tok-emphasis" },
{ tag: [t.link, t.url], class: "tok-link" },
{ tag: t.monospace, class: "tok-monospace" },
{ tag: t.quote, class: "tok-quote" },
{ tag: t.strikethrough, class: "tok-strikethrough" },
])
export { hljs }
type Parser = typeof cppLanguage.parser
const LANGUAGE_ALIASES: Record<string, string> = {
python: "python",
py: "python",
python3: "python",
c: "c",
cpp: "cpp",
"c++": "cpp",
sql: "sql",
// C 和编辑器一样用 C++ 的解析器
const PARSERS: Record<string, Parser> = {
python: pythonLanguage.parser,
py: pythonLanguage.parser,
python3: pythonLanguage.parser,
c: cppLanguage.parser,
cpp: cppLanguage.parser,
"c++": cppLanguage.parser,
sql: SQLite.language.parser,
}
function parserFor(lang?: string): Parser | null {
return PARSERS[lang?.toLowerCase() ?? ""] ?? null
}
export function escapeHtml(text: string): string {
return text.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;")
}
function normalizeLang(lang?: string): string | null {
return LANGUAGE_ALIASES[lang?.toLowerCase() ?? ""] ?? null
function span(text: string, classes: string): string {
const html = escapeHtml(text)
return classes ? `<span class="${classes}">${html}</span>` : html
}
// 把整段代码高亮为 HTML;不支持的语言或异常时回退为转义文本
/** 按顺序吐出每一段文字和它的类名,换行单独吐一个 "\n"。跨行的词(块注释、三引号字符串)在换行处断开 */
function walk(
code: string,
lang: string | undefined,
put: (text: string, classes: string) => void,
) {
const parser = parserFor(lang)
if (!parser) {
put(code, "")
return
}
highlightCode(code, parser.parse(code), codeHighlighter, put, () => put("\n", ""))
}
/** 逐行高亮,返回每行的 HTML,每行的标签自己闭合 */
function toLines(code: string, lang?: string): string[] {
const lines = [""]
walk(code, lang, (text, classes) => {
text.split("\n").forEach((part, i) => {
if (i > 0) lines.push("")
lines[lines.length - 1] += span(part, classes)
})
})
return lines
}
/** 把整段代码高亮为 HTML;不支持的语言按转义后的原文 */
export function highlight(code: string, lang?: string): string {
const language = normalizeLang(lang)
if (language) {
try {
return hljs.highlight(code, { language }).value
} catch {
// fall through
}
}
return escapeHtml(code)
return toLines(code, lang).join("\n")
}
// 按行高亮:整体高亮后再按行切分,保证跨行 token 着色正确,返回逐行 HTML 数组
/** 按行高亮:整段一起解析再切行,跨行的词也能染对 */
export function highlightLines(lines: string[], lang?: string): string[] {
const language = normalizeLang(lang)
if (language) {
try {
const html = hljs.highlight(lines.join("\n"), { language }).value
return html.split("\n")
} catch {
// fall through
return toLines(lines.join("\n"), lang)
}
/**
* 填空题用:代码按「空里已经填上答案」的样子解析,但答案不输出,每个空的位置吐一个空的序号。
* 直接拿挖了空的代码去解析不行:语法被空搅乱,`(humidity ____ 40)` 会被认成类型声明,
* humidity 染成类型的颜色。holes 按位置从前往后排、互不重叠。
*/
export function highlightWithHoles(
code: string,
holes: { from: number; to: number }[],
lang?: string,
): (string | number)[] {
const out: (string | number)[] = []
let html = ""
let pos = 0
let hole = 0
const flush = () => {
if (html) out.push(html)
html = ""
}
return lines.map((line) => escapeHtml(line))
walk(code, lang, (text, classes) => {
const end = pos + text.length
let at = pos
while (at < end) {
const h = holes[hole]
if (h && at >= h.from) {
if (at === h.from) {
flush()
out.push(hole)
}
at = Math.min(end, h.to)
if (at === h.to) hole++
continue
}
const stop = h ? Math.min(end, h.from) : end
html += span(text.slice(at - pos, stop - pos), classes)
at = stop
}
pos = end
})
flush()
// 落在代码末尾、长度为 0 的空
for (; hole < holes.length; hole++) out.push(hole)
return out
}
/**
* 给 n-code 的:它只认 highlight.js 的接口。类型照 highlight.js 写(结果里还要 relevance 之类),
* 但 n-code 只调 getLanguage() 和读 highlight().value,所以只给这两样
*/
export const hljs = {
getLanguage: (lang: string) => (parserFor(lang) ? {} : undefined),
highlight: (code: string, { language }: { language: string }) => ({
value: highlight(code, language),
}),
} as unknown as NonNullable<ConfigProviderProps["hljs"]>
+107
View File
@@ -0,0 +1,107 @@
import { syntaxHighlighting } from "@codemirror/language"
import type { Extension } from "@codemirror/state"
import { EditorView } from "@codemirror/view"
import { codeHighlighter } from "../highlight"
/**
* 编辑器的外观:亮 / 暗两套只管底色、光标、选区、行号这些「框」,
* 代码的颜色两套都是 codeHighlighter 打的 tok-* 类名,跟着 html.dark 在 highlight.css 里切。
* 框沿用原来的 smoothy(亮)和 One Dark(暗),只把代码配色换成了 Alucard / Dracula。
*/
const highlighting = syntaxHighlighting(codeHighlighter)
const light = EditorView.theme({
"&": {
backgroundColor: "#ffffff",
color: "var(--code-fg)",
},
".cm-content": { caretColor: "#000000" },
".cm-cursor, .cm-dropCursor": { borderLeftColor: "#000000" },
"&.cm-focused .cm-selectionBackground, .cm-selectionBackground, .cm-content ::selection": {
backgroundColor: "#FFFD0054",
},
".cm-activeLine": { backgroundColor: "#00000008" },
".cm-gutters": {
backgroundColor: "#ffffff",
borderRight: "none",
color: "#00000070",
},
".cm-activeLineGutter": { backgroundColor: "#00000008" },
".cm-tooltip": {
border: "1px solid #e0e0e0",
backgroundColor: "#f8f8f8",
},
".cm-tooltip-autocomplete": {
"& > ul > li[aria-selected]": {
backgroundColor: "#e8e8e8",
color: "#000000",
},
},
})
const darkBackground = "#26262a",
highlightBackground = "#2c313a",
background = "#101014", // naive-ui
tooltipBackground = "#353a42",
stone = "#7d8799",
cursor = "#528bff"
const dark = EditorView.theme(
{
"&": {
color: "var(--code-fg)",
backgroundColor: background,
},
".cm-content": { caretColor: cursor },
".cm-cursor, .cm-dropCursor": { borderLeftColor: cursor },
"&.cm-focused .cm-selectionBackground, .cm-selectionBackground, .cm-content ::selection": {
backgroundColor: "#3E4451",
},
".cm-panels": { backgroundColor: darkBackground, color: "var(--code-fg)" },
".cm-panels.cm-panels-top": { borderBottom: "2px solid black" },
".cm-panels.cm-panels-bottom": { borderTop: "2px solid black" },
".cm-searchMatch": {
backgroundColor: "#72a1ff59",
outline: "1px solid #457dff",
},
".cm-searchMatch.cm-searchMatch-selected": { backgroundColor: "#6199ff2f" },
".cm-activeLine": { backgroundColor: "#6699ff0b" },
".cm-selectionMatch": { backgroundColor: "#aafe661a" },
"&.cm-focused .cm-matchingBracket, &.cm-focused .cm-nonmatchingBracket": {
backgroundColor: "#bad0f847",
},
".cm-gutters": {
backgroundColor: background,
color: stone,
border: "none",
},
".cm-activeLineGutter": { backgroundColor: highlightBackground },
".cm-foldPlaceholder": {
backgroundColor: "transparent",
border: "none",
color: "#ddd",
},
".cm-tooltip": {
border: "none",
backgroundColor: tooltipBackground,
},
".cm-tooltip .cm-tooltip-arrow:before": {
borderTopColor: "transparent",
borderBottomColor: "transparent",
},
".cm-tooltip .cm-tooltip-arrow:after": {
borderTopColor: tooltipBackground,
borderBottomColor: tooltipBackground,
},
".cm-tooltip-autocomplete": {
"& > ul > li[aria-selected]": {
backgroundColor: highlightBackground,
color: "var(--code-fg)",
},
},
},
{ dark: true },
)
export const lightCodeTheme: Extension = [light, highlighting]
export const darkCodeTheme: Extension = [dark, highlighting]
-101
View File
@@ -1,101 +0,0 @@
import { HighlightStyle, TagStyle, syntaxHighlighting } from "@codemirror/language"
import { Extension } from "@codemirror/state"
import { EditorView } from "@codemirror/view"
interface Options {
/**
* Theme variant. Determines which styles CodeMirror will apply by default.
*/
variant: Variant
/**
* Settings to customize the look of the editor, like background, gutter, selection and others.
*/
settings: Settings
/**
* Syntax highlighting styles.
*/
styles: TagStyle[]
}
type Variant = "light" | "dark"
interface Settings {
/**
* Editor background.
*/
background: string
/**
* Default text color.
*/
foreground: string
/**
* Caret color.
*/
caret: string
/**
* Selection background.
*/
selection: string
/**
* Background of highlighted lines.
*/
lineHighlight: string
/**
* Gutter background.
*/
gutterBackground: string
/**
* Text color inside gutter.
*/
gutterForeground: string
gutterBorderRight: string
}
export const createTheme = ({ variant, settings, styles }: Options): Extension => {
const theme = EditorView.theme(
{
// eslint-disable-next-line @typescript-eslint/naming-convention
"&": {
backgroundColor: settings.background,
color: settings.foreground,
},
".cm-content": {
caretColor: settings.caret,
},
".cm-cursor, .cm-dropCursor": {
borderLeftColor: settings.caret,
},
"&.cm-focused .cm-selectionBackground, .cm-selectionBackground, .cm-content ::selection": {
backgroundColor: settings.selection,
},
".cm-activeLine": {
backgroundColor: settings.lineHighlight,
},
".cm-gutters": {
backgroundColor: settings.gutterBackground,
borderRight: settings.gutterBorderRight,
color: settings.gutterForeground,
},
".cm-activeLineGutter": {
backgroundColor: settings.lineHighlight,
},
},
{
dark: variant === "dark",
},
)
const highlightStyle = HighlightStyle.define(styles)
const extension = [theme, syntaxHighlighting(highlightStyle)]
return extension
}
-139
View File
@@ -1,139 +0,0 @@
import { HighlightStyle, syntaxHighlighting } from "@codemirror/language"
import { Extension } from "@codemirror/state"
import { EditorView } from "@codemirror/view"
import { tags as t } from "@lezer/highlight"
// Using https://github.com/one-dark/vscode-one-dark-theme/ as reference for the colors
const chalky = "#e5c07b",
coral = "#e06c75",
cyan = "#56b6c2",
invalid = "#ffffff",
ivory = "#abb2bf",
stone = "#7d8799", // Brightened compared to original to increase contrast
malibu = "#61afef",
sage = "#98c379",
whiskey = "#d19a66",
violet = "#c678dd",
darkBackground = "#26262a",
highlightBackground = "#2c313a",
background = "#101014", // naive-ui
tooltipBackground = "#353a42",
selection = "#3E4451",
cursor = "#528bff"
/// The editor theme styles for One Dark.
const oneDarkTheme = EditorView.theme(
{
"&": {
color: ivory,
backgroundColor: background,
},
".cm-content": {
caretColor: cursor,
},
".cm-cursor, .cm-dropCursor": { borderLeftColor: cursor },
"&.cm-focused .cm-selectionBackground, .cm-selectionBackground, .cm-content ::selection": {
backgroundColor: selection,
},
".cm-panels": { backgroundColor: darkBackground, color: ivory },
".cm-panels.cm-panels-top": { borderBottom: "2px solid black" },
".cm-panels.cm-panels-bottom": { borderTop: "2px solid black" },
".cm-searchMatch": {
backgroundColor: "#72a1ff59",
outline: "1px solid #457dff",
},
".cm-searchMatch.cm-searchMatch-selected": {
backgroundColor: "#6199ff2f",
},
".cm-activeLine": { backgroundColor: "#6699ff0b" },
".cm-selectionMatch": { backgroundColor: "#aafe661a" },
"&.cm-focused .cm-matchingBracket, &.cm-focused .cm-nonmatchingBracket": {
backgroundColor: "#bad0f847",
},
".cm-gutters": {
backgroundColor: background,
color: stone,
border: "none",
},
".cm-activeLineGutter": {
backgroundColor: highlightBackground,
},
".cm-foldPlaceholder": {
backgroundColor: "transparent",
border: "none",
color: "#ddd",
},
".cm-tooltip": {
border: "none",
backgroundColor: tooltipBackground,
},
".cm-tooltip .cm-tooltip-arrow:before": {
borderTopColor: "transparent",
borderBottomColor: "transparent",
},
".cm-tooltip .cm-tooltip-arrow:after": {
borderTopColor: tooltipBackground,
borderBottomColor: tooltipBackground,
},
".cm-tooltip-autocomplete": {
"& > ul > li[aria-selected]": {
backgroundColor: highlightBackground,
color: ivory,
},
},
},
{ dark: true },
)
/// The highlighting style for code in the One Dark theme.
const oneDarkHighlightStyle = HighlightStyle.define([
{ tag: t.keyword, color: violet },
{
tag: [t.name, t.deleted, t.character, t.propertyName, t.macroName],
color: coral,
},
{ tag: [t.function(t.variableName), t.labelName], color: malibu },
{ tag: [t.color, t.constant(t.name), t.standard(t.name)], color: whiskey },
{ tag: [t.definition(t.name), t.separator], color: ivory },
{
tag: [
t.typeName,
t.className,
t.number,
t.changed,
t.annotation,
t.modifier,
t.self,
t.namespace,
],
color: chalky,
},
{
tag: [t.operator, t.operatorKeyword, t.url, t.escape, t.regexp, t.link, t.special(t.string)],
color: cyan,
},
{ tag: [t.meta, t.comment], color: stone },
{ tag: t.strong, fontWeight: "bold" },
{ tag: t.emphasis, fontStyle: "italic" },
{ tag: t.strikethrough, textDecoration: "line-through" },
{ tag: t.link, color: stone, textDecoration: "underline" },
{ tag: t.heading, fontWeight: "bold", color: coral },
{ tag: [t.atom, t.bool, t.special(t.variableName)], color: whiskey },
{ tag: [t.processingInstruction, t.string, t.inserted], color: sage },
{ tag: t.invalid, color: invalid },
])
/// Extension to enable the One Dark theme (both the editor theme and
/// the highlight style).
export const oneDark: Extension = [oneDarkTheme, syntaxHighlighting(oneDarkHighlightStyle)]
-100
View File
@@ -1,100 +0,0 @@
import { tags as t } from "@lezer/highlight"
import { EditorView } from "@codemirror/view"
import type { Extension } from "@codemirror/state"
import { createTheme } from "./createTheme"
const tooltipTheme = EditorView.theme({
".cm-tooltip": {
border: "1px solid #e0e0e0",
backgroundColor: "#f8f8f8",
},
".cm-tooltip-autocomplete": {
"& > ul > li[aria-selected]": {
backgroundColor: "#e8e8e8",
color: "#000000",
},
},
})
// Author: Kenneth Reitz
const smoothyBase = createTheme({
variant: "light",
settings: {
background: "#FFFFFF",
foreground: "#000000",
caret: "#000000",
selection: "#FFFD0054",
gutterBackground: "#FFFFFF",
gutterForeground: "#00000070",
gutterBorderRight: "none",
lineHighlight: "#00000008",
},
styles: [
{
tag: t.comment,
color: "#CFCFCF",
},
{
tag: [t.number, t.bool, t.null],
color: "#E66C29",
},
{
tag: [
t.className,
t.definition(t.propertyName),
t.function(t.variableName),
t.labelName,
t.definition(t.typeName),
],
color: "#2EB43B",
},
{
tag: t.keyword,
color: "#D8B229",
},
{
tag: t.operator,
color: "#4EA44E",
fontWeight: "bold",
},
{
tag: [t.definitionKeyword, t.modifier],
color: "#925A47",
},
{
tag: t.string,
color: "#704D3D",
},
{
tag: t.typeName,
color: "#2F8996",
},
{
tag: [t.variableName, t.propertyName],
color: "#77ACB0",
},
{
tag: t.self,
color: "#77ACB0",
fontWeight: "bold",
},
{
tag: t.regexp,
color: "#E3965E",
},
{
tag: [t.tagName, t.angleBracket],
color: "#BAA827",
},
{
tag: t.attributeName,
color: "#B06520",
},
{
tag: t.derefOperator,
color: "#000",
},
],
})
export const smoothy: Extension = [smoothyBase, tooltipTheme]
-1
View File
@@ -63,7 +63,6 @@
"copy-text-to-clipboard": "^3.2.2",
"date-fns": "^4.4.0",
"fflate": "^0.8.3",
"highlight.js": "^11.12.0",
"lib0": "0.2.119",
"markdown-it": "^14.3.0",
"mermaid": "^12.1.0",