-
Notifications
You must be signed in to change notification settings - Fork 465
/
Copy pathText.vue
118 lines (104 loc) · 3.92 KB
/
Text.vue
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
<script lang="ts" setup>
import { computed, ref } from 'vue'
import MarkdownIt from 'markdown-it'
import mdKatex from '@traptitech/markdown-it-katex'
import mila from 'markdown-it-link-attributes'
import hljs from 'highlight.js'
import { useBasicLayout } from '@/hooks/useBasicLayout'
import { t } from '@/locales'
interface Props {
inversion?: boolean
error?: boolean
text?: string
loading?: boolean
asRawText?: boolean
}
const props = defineProps<Props>()
const { isMobile } = useBasicLayout()
const textRef = ref<HTMLElement>()
const mdi = new MarkdownIt({
html: true,
linkify: true,
highlight(code, language) {
const validLang = !!(language && hljs.getLanguage(language))
if (validLang) {
const lang = language ?? ''
return highlightBlock(hljs.highlight(code, { language: lang }).value, lang)
}
return highlightBlock(hljs.highlightAuto(code).value, '')
},
})
mdi.use(mila, { attrs: { target: '_blank', rel: 'noopener' } })
mdi.use(mdKatex, { blockClass: 'katexmath-block rounded-md p-[10px]', errorColor: ' #cc0000' })
const wrapClass = computed(() => {
return [
'text-wrap',
'min-w-[20px]',
'rounded-md',
isMobile.value ? 'p-2' : 'px-3 py-2',
props.inversion ? 'bg-[#d2f9d1]' : 'bg-[#f4f6f8]',
props.inversion ? 'dark:bg-[#a1dc95]' : 'dark:bg-[#1e1e20]',
props.inversion ? 'message-request' : 'message-reply',
{ 'text-red-500': props.error },
]
})
function getVideoPreviewer({ type, vid }: { type: 'bilibili' | 'youtube'; vid: string }) {
let previewer = ''
if (type === 'bilibili') {
previewer = `<iframe src="//player.bilibili.com/player.html?bvid=${vid}&autoplay=false" scrolling="no" border="0" frameborder="no" framespacing="0" allowfullscreen="true"> </iframe>`
}
else if (type === 'youtube') {
previewer = `<iframe src="https://www.youtube.com/embed/${vid}" frameborder="0" allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>`
}
else {
// 更新其他视频网站的在线预览
}
return `<div style="margin-bottom:16px;maring-right:16px;">${previewer}</div>`
}
function generateVideoPreviewerText(text: string) {
if (!text)
return ''
// 这里截取视频网站和vid
const reg = /(www\.youtube\.com\/watch\?v\=[^\s\)]+)|(www\.bilibili\.com\/video\/[^\s\)]+)/g
const links = text.match(reg)
return links
? links?.map((link) => {
if (link.includes('youtube')) {
const pattern = /watch\?v\=([^\s\&\)]+)/
const matchResult = link.match(pattern)
return matchResult ? getVideoPreviewer({ type: 'youtube', vid: matchResult[1] }) : ''
}
else {
const pattern = /video\/(([^\s\/\?\)]+))/
const matchResult = link.match(pattern)
return matchResult ? getVideoPreviewer({ type: 'bilibili', vid: matchResult[1] }) : ''
}
}).join('\n')
: ''
}
const text = computed(() => {
const value = props.text ?? ''
if (!props.asRawText)
return mdi.render(value) + generateVideoPreviewerText(value)
return value
})
function highlightBlock(str: string, lang?: string) {
return `<pre class="code-block-wrapper"><div class="code-block-header"><span class="code-block-header__lang">${lang}</span><span class="code-block-header__copy">${t('chat.copyCode')}</span></div><code class="hljs code-block-body ${lang}">${str}</code></pre>`
}
defineExpose({ textRef })
</script>
<template>
<div class="text-black" :class="wrapClass">
<div ref="textRef" class="leading-relaxed break-words">
<div v-if="!inversion" class="flex items-end">
<div v-if="!asRawText" class="w-full markdown-body" v-html="text" />
<div v-else class="w-full whitespace-pre-wrap" v-text="text" />
<span v-if="loading" class="dark:text-white w-[4px] h-[20px] block animate-blink" />
</div>
<div v-else class="whitespace-pre-wrap" v-text="text" />
</div>
</div>
</template>
<style lang="less">
@import url(./style.less);
</style>