124 lines
3.0 KiB
Vue
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('&', '&')
|
|
.replaceAll('<', '<')
|
|
.replaceAll('>', '>')
|
|
.replaceAll('"', '"')
|
|
}
|
|
|
|
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>
|