mirror of
https://github.com/JimLiu/baoyu-skills.git
synced 2026-07-13 06:19:46 +08:00
5f994589b3
Converts markdown to styled HTML with inline CSS, code highlighting, math, PlantUML, footnotes, alerts, and WeChat-compatible themes.
239 lines
8.1 KiB
TypeScript
239 lines
8.1 KiB
TypeScript
import type { LanguageFn } from 'highlight.js'
|
|
import bash from 'highlight.js/lib/languages/bash'
|
|
import c from 'highlight.js/lib/languages/c'
|
|
import cpp from 'highlight.js/lib/languages/cpp'
|
|
import csharp from 'highlight.js/lib/languages/csharp'
|
|
import css from 'highlight.js/lib/languages/css'
|
|
import diff from 'highlight.js/lib/languages/diff'
|
|
import go from 'highlight.js/lib/languages/go'
|
|
import graphql from 'highlight.js/lib/languages/graphql'
|
|
import ini from 'highlight.js/lib/languages/ini'
|
|
import java from 'highlight.js/lib/languages/java'
|
|
import javascript from 'highlight.js/lib/languages/javascript'
|
|
import json from 'highlight.js/lib/languages/json'
|
|
import kotlin from 'highlight.js/lib/languages/kotlin'
|
|
import less from 'highlight.js/lib/languages/less'
|
|
import lua from 'highlight.js/lib/languages/lua'
|
|
import makefile from 'highlight.js/lib/languages/makefile'
|
|
import markdown from 'highlight.js/lib/languages/markdown'
|
|
import objectivec from 'highlight.js/lib/languages/objectivec'
|
|
import perl from 'highlight.js/lib/languages/perl'
|
|
import php from 'highlight.js/lib/languages/php'
|
|
import phpTemplate from 'highlight.js/lib/languages/php-template'
|
|
import plaintext from 'highlight.js/lib/languages/plaintext'
|
|
import python from 'highlight.js/lib/languages/python'
|
|
import pythonRepl from 'highlight.js/lib/languages/python-repl'
|
|
import r from 'highlight.js/lib/languages/r'
|
|
import ruby from 'highlight.js/lib/languages/ruby'
|
|
import rust from 'highlight.js/lib/languages/rust'
|
|
import scss from 'highlight.js/lib/languages/scss'
|
|
import shell from 'highlight.js/lib/languages/shell'
|
|
import sql from 'highlight.js/lib/languages/sql'
|
|
import swift from 'highlight.js/lib/languages/swift'
|
|
import typescript from 'highlight.js/lib/languages/typescript'
|
|
import vbnet from 'highlight.js/lib/languages/vbnet'
|
|
import wasm from 'highlight.js/lib/languages/wasm'
|
|
import xml from 'highlight.js/lib/languages/xml'
|
|
import yaml from 'highlight.js/lib/languages/yaml'
|
|
|
|
export const COMMON_LANGUAGES: Record<string, LanguageFn> = {
|
|
bash,
|
|
c,
|
|
cpp,
|
|
csharp,
|
|
css,
|
|
diff,
|
|
go,
|
|
graphql,
|
|
ini,
|
|
java,
|
|
javascript,
|
|
json,
|
|
kotlin,
|
|
less,
|
|
lua,
|
|
makefile,
|
|
markdown,
|
|
objectivec,
|
|
perl,
|
|
php,
|
|
'php-template': phpTemplate,
|
|
plaintext,
|
|
python,
|
|
'python-repl': pythonRepl,
|
|
r,
|
|
ruby,
|
|
rust,
|
|
scss,
|
|
shell,
|
|
sql,
|
|
swift,
|
|
typescript,
|
|
vbnet,
|
|
wasm,
|
|
xml,
|
|
yaml,
|
|
}
|
|
|
|
// highlight.js CDN 配置
|
|
const HLJS_VERSION = `11.11.1`
|
|
const HLJS_CDN_BASE = `https://cdn-doocs.oss-cn-shenzhen.aliyuncs.com/npm/highlightjs/${HLJS_VERSION}`
|
|
|
|
// 缓存正在加载的语言
|
|
const loadingLanguages = new Map<string, Promise<void>>()
|
|
|
|
/**
|
|
* 生成语言包的 CDN URL
|
|
*/
|
|
function grammarUrlFor(language: string): string {
|
|
return `${HLJS_CDN_BASE}/es/languages/${language}.min.js`
|
|
}
|
|
|
|
/**
|
|
* 动态加载并注册语言
|
|
* @param language 语言名称
|
|
* @param hljs highlight.js 实例
|
|
*/
|
|
export async function loadAndRegisterLanguage(language: string, hljs: any): Promise<void> {
|
|
// 如果已经注册,直接返回
|
|
if (hljs.getLanguage(language)) {
|
|
return
|
|
}
|
|
|
|
// 如果正在加载,等待加载完成
|
|
if (loadingLanguages.has(language)) {
|
|
await loadingLanguages.get(language)
|
|
return
|
|
}
|
|
|
|
// 开始加载
|
|
const loadPromise = (async () => {
|
|
try {
|
|
const module = await import(/* @vite-ignore */ grammarUrlFor(language))
|
|
hljs.registerLanguage(language, module.default)
|
|
}
|
|
catch (error) {
|
|
console.warn(`Failed to load language: ${language}`, error)
|
|
throw error
|
|
}
|
|
finally {
|
|
loadingLanguages.delete(language)
|
|
}
|
|
})()
|
|
|
|
loadingLanguages.set(language, loadPromise)
|
|
await loadPromise
|
|
}
|
|
|
|
/**
|
|
* 格式化高亮后的代码,处理空格和制表符
|
|
*/
|
|
function formatHighlightedCode(html: string, preserveNewlines = false): string {
|
|
let formatted = html
|
|
// 将 span 之间的空格移到 span 内部
|
|
formatted = formatted.replace(/(<span[^>]*>[^<]*<\/span>)(\s+)(<span[^>]*>[^<]*<\/span>)/g, (_: string, span1: string, spaces: string, span2: string) => span1 + span2.replace(/^(<span[^>]*>)/, `$1${spaces}`))
|
|
formatted = formatted.replace(/(\s+)(<span[^>]*>)/g, (_: string, spaces: string, span: string) => span.replace(/^(<span[^>]*>)/, `$1${spaces}`))
|
|
// 替换制表符为4个空格
|
|
formatted = formatted.replace(/\t/g, ` `)
|
|
|
|
if (preserveNewlines) {
|
|
// 替换换行符为 <br/>,并将空格转换为
|
|
formatted = formatted.replace(/\r\n/g, `<br/>`).replace(/\n/g, `<br/>`).replace(/(>[^<]+)|(^[^<]+)/g, (str: string) => str.replace(/\s/g, ` `))
|
|
}
|
|
else {
|
|
// 只将空格转换为
|
|
formatted = formatted.replace(/(>[^<]+)|(^[^<]+)/g, (str: string) => str.replace(/\s/g, ` `))
|
|
}
|
|
|
|
return formatted
|
|
}
|
|
|
|
/**
|
|
* 高亮代码并格式化(支持行号)
|
|
* @param text 原始代码文本
|
|
* @param language 语言名称
|
|
* @param hljs highlight.js 实例
|
|
* @param showLineNumber 是否显示行号
|
|
* @returns 格式化后的 HTML
|
|
*/
|
|
export function highlightAndFormatCode(text: string, language: string, hljs: any, showLineNumber: boolean): string {
|
|
let highlighted = ``
|
|
|
|
if (showLineNumber) {
|
|
const rawLines = text.replace(/\r\n/g, `\n`).split(`\n`)
|
|
|
|
const highlightedLines = rawLines.map((lineRaw) => {
|
|
const lineHtml = hljs.highlight(lineRaw, { language }).value
|
|
const formatted = formatHighlightedCode(lineHtml, false)
|
|
return formatted === `` ? ` ` : formatted
|
|
})
|
|
|
|
const lineNumbersHtml = highlightedLines.map((_, idx) => `<section style="padding:0 10px 0 0;line-height:1.75">${idx + 1}</section>`).join(``)
|
|
const codeInnerHtml = highlightedLines.join(`<br/>`)
|
|
const codeLinesHtml = `<div style="white-space:pre;min-width:max-content;line-height:1.75">${codeInnerHtml}</div>`
|
|
const lineNumberColumnStyles = `text-align:right;padding:8px 0;border-right:1px solid rgba(0,0,0,0.04);user-select:none;background:var(--code-bg,transparent);`
|
|
|
|
highlighted = `
|
|
<section style="display:flex;align-items:flex-start;overflow-x:hidden;overflow-y:auto;width:100%;max-width:100%;padding:0;box-sizing:border-box">
|
|
<section class="line-numbers" style="${lineNumberColumnStyles}">${lineNumbersHtml}</section>
|
|
<section class="code-scroll" style="flex:1 1 auto;overflow-x:auto;overflow-y:visible;padding:8px;min-width:0;box-sizing:border-box">${codeLinesHtml}</section>
|
|
</section>
|
|
`
|
|
}
|
|
else {
|
|
const rawHighlighted = hljs.highlight(text, { language }).value
|
|
highlighted = formatHighlightedCode(rawHighlighted, true)
|
|
}
|
|
|
|
return highlighted
|
|
}
|
|
|
|
export function highlightCodeBlock(codeBlock: Element, language: string, hljs: any): void {
|
|
const rawCode = codeBlock.getAttribute(`data-raw-code`)
|
|
const showLineNumber = codeBlock.getAttribute(`data-show-line-number`) === `true`
|
|
|
|
if (!rawCode)
|
|
return
|
|
|
|
const text = rawCode.replace(/"/g, `"`)
|
|
|
|
const highlighted = highlightAndFormatCode(text, language, hljs, showLineNumber)
|
|
|
|
codeBlock.innerHTML = highlighted
|
|
codeBlock.removeAttribute(`data-language-pending`)
|
|
codeBlock.removeAttribute(`data-raw-code`)
|
|
codeBlock.removeAttribute(`data-show-line-number`)
|
|
}
|
|
|
|
/**
|
|
* 高亮 DOM 中待处理的代码块
|
|
* 查找带有 data-language-pending 属性的代码块,动态加载语言后重新高亮
|
|
* @param hljs highlight.js 实例
|
|
* @param container 容器元素(可选,默认为 document)
|
|
*/
|
|
export function highlightPendingBlocks(hljs: any, container: Document | Element = document): void {
|
|
const pendingBlocks = container.querySelectorAll(`code[data-language-pending]`)
|
|
|
|
pendingBlocks.forEach((codeBlock) => {
|
|
const language = codeBlock.getAttribute(`data-language-pending`)
|
|
if (!language)
|
|
return
|
|
|
|
if (hljs.getLanguage(language)) {
|
|
// 语言已加载,直接高亮
|
|
highlightCodeBlock(codeBlock, language, hljs)
|
|
}
|
|
else {
|
|
// 动态加载语言后重新高亮
|
|
loadAndRegisterLanguage(language, hljs).then(() => {
|
|
highlightCodeBlock(codeBlock, language, hljs)
|
|
}).catch(() => {
|
|
// 加载失败,移除标记
|
|
codeBlock.removeAttribute(`data-language-pending`)
|
|
codeBlock.removeAttribute(`data-raw-code`)
|
|
codeBlock.removeAttribute(`data-show-line-number`)
|
|
})
|
|
}
|
|
})
|
|
}
|