fix: HTML 渲染异常 (#152)

* fix: 修复 `API` 版本 HTML 会被渲染的问题[#146]

* chore: version 2.8.1
This commit is contained in:
Redon 2023-02-27 19:25:15 +08:00 committed by GitHub
parent 2c509c329f
commit 21fb4f817c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 73 additions and 17 deletions

View File

@ -1,3 +1,10 @@
## v2.8.1
`2023-02-27`
### BugFix
- 修复 `API` 版本不是 `Markdown` 时,普通 `HTML` 代码会被渲染的问题 [#146]
## v2.8.0 ## v2.8.0
`2023-02-27` `2023-02-27`

View File

@ -1,6 +1,6 @@
{ {
"name": "chatgpt-web", "name": "chatgpt-web",
"version": "2.8.0", "version": "2.8.1",
"private": false, "private": false,
"description": "ChatGPT Web", "description": "ChatGPT Web",
"author": "ChenZhaoYu <chenzhaoyu1994@gmail.com>", "author": "ChenZhaoYu <chenzhaoyu1994@gmail.com>",

View File

@ -0,0 +1,23 @@
import type { App, Directive } from 'vue'
import hljs from 'highlight.js'
import { includeCode } from '@/utils/format'
hljs.configure({ ignoreUnescapedHTML: true })
function highlightCode(el: HTMLElement) {
if (includeCode(el.textContent))
hljs.highlightBlock(el)
}
export default function setupHighlightDirective(app: App) {
const highLightDirective: Directive<HTMLElement> = {
mounted(el: HTMLElement) {
highlightCode(el)
},
updated(el: HTMLElement) {
highlightCode(el)
},
}
app.directive('highlight', highLightDirective)
}

View File

@ -1 +1,6 @@
export function setupDirectives() {} import type { App } from 'vue'
import setupHighlightDirective from './highlight'
export function setupDirectives(app: App) {
setupHighlightDirective(app)
}

View File

@ -1,5 +1,6 @@
import { createApp } from 'vue' import { createApp } from 'vue'
import App from './App.vue' import App from './App.vue'
import { setupDirectives } from './directives'
import { setupAssets } from '@/plugins' import { setupAssets } from '@/plugins'
import { setupStore } from '@/store' import { setupStore } from '@/store'
import { setupRouter } from '@/router' import { setupRouter } from '@/router'
@ -10,6 +11,8 @@ async function bootstrap() {
setupStore(app) setupStore(app)
setupDirectives(app)
await setupRouter(app) await setupRouter(app)
app.mount('#app') app.mount('#app')

15
src/utils/format/index.ts Normal file
View File

@ -0,0 +1,15 @@
// 转义 HTML 字符
export function encodeHTML(source: string) {
return source
.replace(/&/g, '&amp;')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;')
.replace(/"/g, '&quot;')
.replace(/'/g, '&#39;')
}
// 判断是否为代码块
export function includeCode(text: string | null | undefined) {
const regexp = /^(?:\s{4}|\t).+/gm
return !!(text?.includes(' = ') || text?.match(regexp))
}

View File

@ -1,6 +0,0 @@
function includeCode(text: string | null | undefined) {
const regexp = /^(?:\s{4}|\t).+/gm
return !!(text?.includes(' = ') || text?.match(regexp))
}
export default includeCode

View File

@ -3,6 +3,7 @@ import { computed } from 'vue'
import { marked } from 'marked' import { marked } from 'marked'
import hljs from 'highlight.js' import hljs from 'highlight.js'
import { useBasicLayout } from '@/hooks/useBasicLayout' import { useBasicLayout } from '@/hooks/useBasicLayout'
import { encodeHTML } from '@/utils/format'
interface Props { interface Props {
inversion?: boolean inversion?: boolean
@ -15,12 +16,19 @@ const props = defineProps<Props>()
const { isMobile } = useBasicLayout() const { isMobile } = useBasicLayout()
marked.setOptions({ const renderer = new marked.Renderer()
renderer: new marked.Renderer(),
highlight(code) { renderer.html = (html) => {
return hljs.highlightAuto(code).value return `<p>${encodeHTML(html)}</p>`
}, }
})
renderer.code = (code, language) => {
const validLang = !!(language && hljs.getLanguage(language))
const highlighted = validLang ? hljs.highlight(language, code).value : code
return `<pre><code class="hljs ${language}">${highlighted}</code></pre>`
}
marked.setOptions({ renderer })
const wrapClass = computed(() => { const wrapClass = computed(() => {
return [ return [
@ -35,9 +43,10 @@ const wrapClass = computed(() => {
}) })
const text = computed(() => { const text = computed(() => {
if (props.text && !props.inversion) const value = props.text ?? ''
return marked(props.text) if (!props.inversion)
return props.text return marked(value)
return value
}) })
</script> </script>