fix: 修复代码块语法高亮丢失 + 语言标签注入 + 安全性增强

- 新增 cdnjs.cloudflare.com CSP 白名单,允许加载 highlight.js
- 消毒策略放行 span 元素,保留 highlight.js 高亮 class
- 详情页和新帖子页引入 highlight.js 库和 GitHub 主题
- 代码块语言选择器默认 text,支持 prev/next 循环切换语言
- 代码块语言标签注入兼容 data-language 和 code class 双来源
- 修复 utils.js defer 导致 common.js 执行时 getCSRFToken 未定义
This commit is contained in:
2026-05-28 15:21:44 +08:00
parent 91f2cbebc1
commit fe81c879b0
6 changed files with 305 additions and 348 deletions

View File

@ -141,27 +141,34 @@ function setupToolbar() {
// ---- 代码块语言选择器 ----
const langSelect = document.getElementById('codeLangSelect')
if (langSelect) {
// 当光标在代码块内时显示语言选择器,移出时隐藏
// 阻止 select 获取焦点导致编辑器失焦
langSelect.addEventListener('mousedown', (e) => {
e.preventDefault()
// 手动切换选项
const opts = langSelect.options
let nextIdx = langSelect.selectedIndex + 1
if (nextIdx >= opts.length) nextIdx = 0
langSelect.selectedIndex = nextIdx
const lang = opts[nextIdx].value
// 在编辑器内更新代码块语言属性
editor.chain().focus().updateAttributes('codeBlock', { language: lang === 'text' ? null : lang }).run()
})
// 当光标在代码块内时显示语言选择器并同步当前语言,移出时隐藏
editor.on('selectionUpdate', () => {
if (editor.isActive('codeBlock')) {
langSelect.style.display = ''
const attrs = editor.getAttributes('codeBlock')
langSelect.value = attrs.language || ''
langSelect.value = attrs.language || 'text'
} else {
langSelect.style.display = 'none'
}
})
// 语言切换
langSelect.addEventListener('change', () => {
const lang = langSelect.value
editor.chain().focus().updateAttributes('codeBlock', { language: lang || null }).run()
editor.commands.focus()
})
}
// ---- 代码块语言标签注入 ----
// 为每个 <pre> 注入语言标签 DOM 元素(因为 CSS ::before 在 overflow:auto 容器中不可靠)
// Tiptap CodeBlockLowlight 在 pre 上输出 data-language 属性,同时 code 上有 language-xxx class
function injectCodeLangLabels() {
const editorEl = document.querySelector('#editor-container .tiptap')
if (!editorEl) return
@ -170,16 +177,22 @@ function setupToolbar() {
const old = pre.querySelector('.code-lang-label')
if (old) old.remove()
const lang = pre.getAttribute('data-language')
if (lang) {
const label = document.createElement('div')
label.className = 'code-lang-label'
label.textContent = lang
pre.insertBefore(label, pre.firstChild)
pre.style.paddingTop = '32px'
} else {
pre.style.paddingTop = ''
// 从 pre 的 data-language 或 code 的 class 中获取语言
let lang = pre.getAttribute('data-language')
if (!lang) {
const code = pre.querySelector('code')
if (code) {
const match = code.className.match(/language-(\w+)/)
if (match) lang = match[1]
}
}
lang = lang || 'text'
const label = document.createElement('div')
label.className = 'code-lang-label'
label.textContent = lang
pre.insertBefore(label, pre.firstChild)
pre.style.paddingTop = '32px'
})
}