124 lines
3.0 KiB
Vue

<!-- eslint-disable unicorn/prefer-ternary -->
<!-- eslint-disable no-cond-assign -->
<!-- eslint-disable unicorn/prefer-string-slice -->
<script setup lang="ts">
import katex from 'katex'
import { initImgPreview } from '#/utils/tool'
import 'katex/dist/katex.min.css'
defineOptions({ name: 'LatexText' })
const props = defineProps<{
prefix?: any
text: any
}>()
const regex_dollar = /(\$[^$]+\$)/g
const regex_tag = /(<span data-.+?="[^"]*"><\/span>)/g
// 仅匹配合法 HTML 标签,避免将 $a<b$ 中的 <b 误判为标签并吞掉后续内容
const regex_html = /(<\/?[a-zA-Z][a-zA-Z0-9]*(?:\s+[^>]*)?\s*>)/g
function escapeHtml(text: string) {
return text
.replaceAll('&', '&amp;')
.replaceAll('<', '&lt;')
.replaceAll('>', '&gt;')
.replaceAll('"', '&quot;')
}
function addLatexText(list: any[], text: string) {
list.push({
isLatex: true,
content: katex.renderToString(text, { throwOnError: false, strict: false }),
})
}
function parseLatexDollar(list: any[], text: string) {
if (!text?.length) return
const htmlParts = text.split(regex_html)
for (const htmlPart of htmlParts) {
if (!htmlPart) continue
if (htmlPart.startsWith('<')) {
list.push({ content: htmlPart })
continue
}
const parts = htmlPart.split(regex_dollar)
for (const part of parts) {
if (part.startsWith('$') && part.endsWith('$') && part.length > 2) {
addLatexText(list, part.substring(1, part.length - 1))
} else if (part) {
list.push({
content: escapeHtml(part.replace(/\\([{}])/g, '$1')),
})
}
}
}
}
function parseLatexTag(list: any[], text: string) {
if (!text?.length) return
const parts = text.split(regex_tag)
if (!parts?.length) return
for (const part of parts) {
let match: any
if (
part.includes('type="formula"') &&
(match = part.match(/(?<=data-value=").*?(?=")/))
) {
const text = match[0]
.replace('\t', String.raw`\t`)
.replace('\n', String.raw`\n`)
addLatexText(list, text)
} else {
list.push({
content: part,
})
}
}
}
const renderText = computed(() => {
const tagList = [] as any[]
parseLatexTag(tagList, props.text)
const list = [] as any[]
for (const item of tagList) {
if (item.isLatex) {
list.push(item)
} else {
parseLatexDollar(list, item.content)
}
}
let html = ''
for (const item of list) {
html += item.content
}
html = html.trim()
const prefix = props.prefix
if (!prefix) return html
if (html.startsWith('<p') || html.startsWith('<div')) {
// 插入到第一个标签后面
html = html.replace(/^<\w+>/, (match) => {
return match + prefix
})
} else {
html = prefix + html
}
return html
})
const container = ref<HTMLDivElement>()
onMounted(() => {
initImgPreview(container.value)
})
</script>
<template>
<div class="latex-text" v-html="renderText" ref="container"></div>
</template>
<style lang="scss" scoped></style>