forked from opentiny/tiny-engine
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathMarkdownRenderer.vue
More file actions
123 lines (108 loc) · 3.09 KB
/
Copy pathMarkdownRenderer.vue
File metadata and controls
123 lines (108 loc) · 3.09 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
<template>
<div v-html="renderContent" class="markdown-renderer" :class="themeClass"></div>
</template>
<script setup lang="ts">
import { computed, type PropType } from 'vue'
import DOMPurify from 'dompurify'
import MarkdownIt from 'markdown-it'
import type { Options } from 'markdown-it'
import hljs from 'highlight.js/lib/core'
import 'highlight.js/styles/github.css'
// 按需加载语言
import bash from 'highlight.js/lib/languages/bash'
import javascript from 'highlight.js/lib/languages/javascript'
import typescript from 'highlight.js/lib/languages/typescript'
import json from 'highlight.js/lib/languages/json'
import yaml from 'highlight.js/lib/languages/yaml'
import xml from 'highlight.js/lib/languages/xml'
import shell from 'highlight.js/lib/languages/shell'
// 注册语言
hljs.registerLanguage('bash', bash)
hljs.registerLanguage('javascript', javascript)
hljs.registerLanguage('typescript', typescript)
hljs.registerLanguage('json', json)
hljs.registerLanguage('yaml', yaml)
hljs.registerLanguage('xml', xml)
hljs.registerLanguage('shell', shell)
interface MarkdownMessage {
content: string | string[] | Record<string, unknown>[]
}
const props = defineProps({
message: {
type: Object as PropType<MarkdownMessage>,
default: () => ({ content: '' })
},
theme: {
type: String as PropType<'light' | 'dark'>,
default: 'light'
},
options: {
type: Object as PropType<Options>,
default: () => ({})
}
})
const themeClass = computed(() => `hljs-theme-${props.theme}`)
const escapeHtml = (s: string) =>
s.replace(/[&<>"']/g, (ch) => ({ '&': '&', '<': '<', '>': '>', '"': '"', "'": ''' }[ch]!))
const markdownIt = new MarkdownIt({
html: true,
breaks: true,
typographer: true,
highlight: (str: string, lang: string) => {
if (lang && hljs.getLanguage(lang)) {
try {
const { value } = hljs.highlight(str, { language: lang, ignoreIllegals: true })
return `<pre class="hljs"><code class="language-${lang}">${value}</code></pre>`
} catch (e) {
/* ignore */
}
}
return `<pre class="hljs"><code>${escapeHtml(str)}</code></pre>`
},
...props.options
})
const renderContent = computed(() => {
const content = Array.isArray(props.message.content)
? props.message.content
.map((item: any) => item?.text ?? item?.content ?? '')
.filter(Boolean)
.join('\n')
: typeof props.message.content === 'string'
? props.message.content
: ''
return DOMPurify.sanitize(markdownIt.render(content))
})
</script>
<style lang="less">
.markdown-renderer {
word-break: break-word;
pre {
border-radius: 6px;
padding: 1em;
overflow: auto;
line-height: 1.45;
> code {
font-size: 12px;
}
}
// TODO: 适配TInyEngine主题,实现跟随主题自动切换
/* 亮色主题 */
&.hljs-theme-light {
pre {
background-color: #f6f8fa;
}
}
/* 暗色主题 */
&.hljs-theme-dark {
pre {
background-color: #0d1117;
}
}
> *:first-child {
margin-top: 0 !important;
}
> *:last-child {
margin-bottom: 0 !important;
}
}
</style>