perf(Markdown): 题面、教程、AI 提示每次打开都去 unpkg.com 拉四个库,机房网络下又慢又不稳

md-editor-v3 的 MdPreview 不管正文里有没有代码、公式,都会从 unpkg.com 加载
highlight.js、katex、mermaid、echarts(mermaid 还是另一份 11.x)。而我们只用到最基础的
Markdown:题面 95% 是富文本存的 HTML,教程是标题、表格、图片,AI 输出是标题、列表、
表格、少量代码块,库里一处公式、mermaid 都没有。

- 显示:新组件 shared/components/Markdown.vue,直接用 markdown-it,代码高亮走本地的
  highlight.js(和练一练共用 shared/highlight.ts,补了 C++ / SQL)。9 处 MdPreview 全换掉。
  正文默认 16px,教程也统一成 16px。
- 编辑:后台教程编辑器换成项目已有的 CodeMirror,加一排格式按钮;图片可以按按钮选、
  粘贴、拖进来,都是先上传再插入。md-editor-v3 从依赖里删掉。
- AI 生成的内容(AI 提示、AI 分析、班级报告)不再放行原生 HTML:学生能在代码里诱导
  AI 吐出 <img onerror>,老师在后台一打开就执行。库里 1196 条 AI 输出没有一个 HTML
  标签,关掉不影响显示。题面、教程照旧放行。
- 长英文改成在单词之间折行,原来 break-all 会把 "I live" 断成 "I l / ive"。

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
xuyueandClaude Opus 5.5 committed 2026-10-12 14:02:54 +08:00
1 parent c497ccadc8
commit 6f47ad8358
24 files changed
+504 -308

No files matched your search

+59
View File
@@ -0,0 +1,59 @@
/**
* 代码高亮:自学的练一练、以及 Markdown.vue 里的代码块共用这一份(配色在 highlight.css)。
* 只注册判题机认的几种语言,别的语言和不标语言的代码块按原文显示。
*/
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"
hljs.registerLanguage("python", python)
hljs.registerLanguage("c", c)
hljs.registerLanguage("cpp", cpp)
hljs.registerLanguage("sql", sql)
const LANGUAGE_ALIASES: Record<string, string> = {
python: "python",
py: "python",
python3: "python",
c: "c",
cpp: "cpp",
"c++": "cpp",
sql: "sql",
}
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
}
// 把整段代码高亮为 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)
}
// 按行高亮:整体高亮后再按行切分,保证跨行 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 lines.map((line) => escapeHtml(line))
}