refactor: 💡 使用 markdown-it 替换 marked,获得更好的体验

 Closes: #290 #283
This commit is contained in:
ChandlerVer5 2023-03-05 12:47:53 +08:00
parent de34d97529
commit 8619ac4667
4 changed files with 69 additions and 82 deletions

View File

@ -23,10 +23,11 @@
"common:prepare": "husky install" "common:prepare": "husky install"
}, },
"dependencies": { "dependencies": {
"@traptitech/markdown-it-katex": "^3.6.0",
"@vueuse/core": "^9.13.0", "@vueuse/core": "^9.13.0",
"highlight.js": "^11.7.0", "highlight.js": "^11.7.0",
"katex": "^0.16.4", "katex": "^0.16.4",
"marked": "^4.2.12", "markdown-it": "^13.0.1",
"naive-ui": "^2.34.3", "naive-ui": "^2.34.3",
"pinia": "^2.0.32", "pinia": "^2.0.32",
"vue": "^3.2.47", "vue": "^3.2.47",
@ -40,7 +41,7 @@
"@iconify/vue": "^4.1.0", "@iconify/vue": "^4.1.0",
"@types/crypto-js": "^4.1.1", "@types/crypto-js": "^4.1.1",
"@types/katex": "^0.16.0", "@types/katex": "^0.16.0",
"@types/marked": "^4.0.8", "@types/markdown-it": "^12.2.3",
"@types/node": "^18.14.6", "@types/node": "^18.14.6",
"@vitejs/plugin-vue": "^4.0.0", "@vitejs/plugin-vue": "^4.0.0",
"autoprefixer": "^10.4.13", "autoprefixer": "^10.4.13",

View File

@ -5,9 +5,10 @@ specifiers:
'@commitlint/cli': ^17.4.4 '@commitlint/cli': ^17.4.4
'@commitlint/config-conventional': ^17.4.4 '@commitlint/config-conventional': ^17.4.4
'@iconify/vue': ^4.1.0 '@iconify/vue': ^4.1.0
'@traptitech/markdown-it-katex': ^3.6.0
'@types/crypto-js': ^4.1.1 '@types/crypto-js': ^4.1.1
'@types/katex': ^0.16.0 '@types/katex': ^0.16.0
'@types/marked': ^4.0.8 '@types/markdown-it': ^12.2.3
'@types/node': ^18.14.6 '@types/node': ^18.14.6
'@vitejs/plugin-vue': ^4.0.0 '@vitejs/plugin-vue': ^4.0.0
'@vueuse/core': ^9.13.0 '@vueuse/core': ^9.13.0
@ -20,7 +21,7 @@ specifiers:
katex: ^0.16.4 katex: ^0.16.4
less: ^4.1.3 less: ^4.1.3
lint-staged: ^13.1.2 lint-staged: ^13.1.2
marked: ^4.2.12 markdown-it: ^13.0.1
naive-ui: ^2.34.3 naive-ui: ^2.34.3
npm-run-all: ^4.1.5 npm-run-all: ^4.1.5
pinia: ^2.0.32 pinia: ^2.0.32
@ -35,10 +36,11 @@ specifiers:
vue-tsc: ^1.2.0 vue-tsc: ^1.2.0
dependencies: dependencies:
'@traptitech/markdown-it-katex': 3.6.0
'@vueuse/core': 9.13.0_vue@3.2.47 '@vueuse/core': 9.13.0_vue@3.2.47
highlight.js: 11.7.0 highlight.js: 11.7.0
katex: 0.16.4 katex: 0.16.4
marked: 4.2.12 markdown-it: 13.0.1
naive-ui: 2.34.3_vue@3.2.47 naive-ui: 2.34.3_vue@3.2.47
pinia: 2.0.32_hmuptsblhheur2tugfgucj7gc4 pinia: 2.0.32_hmuptsblhheur2tugfgucj7gc4
vue: 3.2.47 vue: 3.2.47
@ -52,7 +54,7 @@ devDependencies:
'@iconify/vue': 4.1.0_vue@3.2.47 '@iconify/vue': 4.1.0_vue@3.2.47
'@types/crypto-js': 4.1.1 '@types/crypto-js': 4.1.1
'@types/katex': 0.16.0 '@types/katex': 0.16.0
'@types/marked': 4.0.8 '@types/markdown-it': 12.2.3
'@types/node': 18.14.6 '@types/node': 18.14.6
'@vitejs/plugin-vue': 4.0.0_vite@4.1.4+vue@3.2.47 '@vitejs/plugin-vue': 4.0.0_vite@4.1.4+vue@3.2.47
autoprefixer: 10.4.13_postcss@8.4.21 autoprefixer: 10.4.13_postcss@8.4.21
@ -744,6 +746,12 @@ packages:
fastq: 1.15.0 fastq: 1.15.0
dev: true dev: true
/@traptitech/markdown-it-katex/3.6.0:
resolution: {integrity: sha512-CnJzTWxsgLGXFdSrWRaGz7GZ1kUUi8g3E9HzJmeveX1YwVJavrKYqysktfHZQsujdnRqV5O7g8FPKEA/aeTkOQ==}
dependencies:
katex: 0.16.4
dev: false
/@tsconfig/node10/1.0.9: /@tsconfig/node10/1.0.9:
resolution: {integrity: sha512-jNsYVVxU8v5g43Erja32laIDHXeoNvFEpX33OK4d6hljo3jDhCBDhx5dhCCTMWUojscpAagGiRkBKxpdl9fxqA==} resolution: {integrity: sha512-jNsYVVxU8v5g43Erja32laIDHXeoNvFEpX33OK4d6hljo3jDhCBDhx5dhCCTMWUojscpAagGiRkBKxpdl9fxqA==}
dev: true dev: true
@ -780,6 +788,10 @@ packages:
resolution: {integrity: sha512-hz+S3nV6Mym5xPbT9fnO8dDhBFQguMYpY0Ipxv06JMi1ORgnEM4M1ymWDUhUNer3ElLmT583opRo4RzxKmh9jw==} resolution: {integrity: sha512-hz+S3nV6Mym5xPbT9fnO8dDhBFQguMYpY0Ipxv06JMi1ORgnEM4M1ymWDUhUNer3ElLmT583opRo4RzxKmh9jw==}
dev: true dev: true
/@types/linkify-it/3.0.2:
resolution: {integrity: sha512-HZQYqbiFVWufzCwexrvh694SOim8z2d+xJl5UNamcvQFejLY/2YUtzXHYi3cHdI7PMlS8ejH2slRAOJQ32aNbA==}
dev: true
/@types/lodash-es/4.17.6: /@types/lodash-es/4.17.6:
resolution: {integrity: sha512-R+zTeVUKDdfoRxpAryaQNRKk3105Rrgx2CFRClIgRGaqDTdjsm8h6IYA8ir584W3ePzkZfst5xIgDwYrlh9HLg==} resolution: {integrity: sha512-R+zTeVUKDdfoRxpAryaQNRKk3105Rrgx2CFRClIgRGaqDTdjsm8h6IYA8ir584W3ePzkZfst5xIgDwYrlh9HLg==}
dependencies: dependencies:
@ -790,8 +802,11 @@ packages:
resolution: {integrity: sha512-BdZ5BCCvho3EIXw6wUCXHe7rS53AIDPLE+JzwgT+OsJk53oBfbSmZZ7CX4VaRoN78N+TJpFi9QPlfIVNmJYWxQ==} resolution: {integrity: sha512-BdZ5BCCvho3EIXw6wUCXHe7rS53AIDPLE+JzwgT+OsJk53oBfbSmZZ7CX4VaRoN78N+TJpFi9QPlfIVNmJYWxQ==}
dev: false dev: false
/@types/marked/4.0.8: /@types/markdown-it/12.2.3:
resolution: {integrity: sha512-HVNzMT5QlWCOdeuBsgXP8EZzKUf0+AXzN+sLmjvaB3ZlLqO+e4u0uXrdw9ub69wBKFs+c6/pA4r9sy6cCDvImw==} resolution: {integrity: sha512-GKMHFfv3458yYy+v/N8gjufHO6MSZKCOXpZc5GXIWWy8uldwfmPn98vp81gZ5f9SVw8YYBctgfJ22a2d7AOMeQ==}
dependencies:
'@types/linkify-it': 3.0.2
'@types/mdurl': 1.0.2
dev: true dev: true
/@types/mdast/3.0.10: /@types/mdast/3.0.10:
@ -800,6 +815,10 @@ packages:
'@types/unist': 2.0.6 '@types/unist': 2.0.6
dev: true dev: true
/@types/mdurl/1.0.2:
resolution: {integrity: sha512-eC4U9MlIcu2q0KQmXszyn5Akca/0jrQmwDRgpAMJai7qBWq4amIQhZyNau4VYGtCeALvW1/NtjzJJ567aZxfKA==}
dev: true
/@types/minimist/1.2.2: /@types/minimist/1.2.2:
resolution: {integrity: sha512-jhuKLIRrhvCPLqwPcx6INqmKeiA5EWrsCOPhrlFSrbrmU4ZMPjj5Ul/oLCMDO98XRUIwVm78xICz4EPCektzeQ==} resolution: {integrity: sha512-jhuKLIRrhvCPLqwPcx6INqmKeiA5EWrsCOPhrlFSrbrmU4ZMPjj5Ul/oLCMDO98XRUIwVm78xICz4EPCektzeQ==}
dev: true dev: true
@ -1232,7 +1251,6 @@ packages:
/argparse/2.0.1: /argparse/2.0.1:
resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==}
dev: true
/array-ify/1.0.0: /array-ify/1.0.0:
resolution: {integrity: sha512-c5AMf34bKdvPhQ7tBGhqkgKNUzMr4WUs+WDtC2ZUGOUncbxKMTvqxYctiseW3+L4bA8ec+GcZ6/A/FW4m8ukng==} resolution: {integrity: sha512-c5AMf34bKdvPhQ7tBGhqkgKNUzMr4WUs+WDtC2ZUGOUncbxKMTvqxYctiseW3+L4bA8ec+GcZ6/A/FW4m8ukng==}
@ -1841,6 +1859,11 @@ packages:
resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==} resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==}
dev: true dev: true
/entities/3.0.1:
resolution: {integrity: sha512-WiyBqoomrwMdFG1e0kqvASYfnlb0lp8M5o5Fw2OFq1hNZxxcNk8Ik0Xm7LxzBhuidnZB/UtBqVCgUz3kBOP51Q==}
engines: {node: '>=0.12'}
dev: false
/entities/4.4.0: /entities/4.4.0:
resolution: {integrity: sha512-oYp7156SP8LkeGD0GF85ad1X9Ai79WtRsZ2gxJqtBuzH+98YUV6jkHEKlZkMbcrjJjIVJNIDP/3WL9wQkoPbWA==} resolution: {integrity: sha512-oYp7156SP8LkeGD0GF85ad1X9Ai79WtRsZ2gxJqtBuzH+98YUV6jkHEKlZkMbcrjJjIVJNIDP/3WL9wQkoPbWA==}
engines: {node: '>=0.12'} engines: {node: '>=0.12'}
@ -3148,6 +3171,12 @@ packages:
resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==} resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==}
dev: true dev: true
/linkify-it/4.0.1:
resolution: {integrity: sha512-C7bfi1UZmoj8+PQx22XyeXCuBlokoyWQL5pWSP+EI6nzRylyThouddufc2c1NDIcP9k5agmN9fLpA7VNJfIiqw==}
dependencies:
uc.micro: 1.0.6
dev: false
/lint-staged/13.1.2: /lint-staged/13.1.2:
resolution: {integrity: sha512-K9b4FPbWkpnupvK3WXZLbgu9pchUJ6N7TtVZjbaPsoizkqFUDkUReUL25xdrCljJs7uLUF3tZ7nVPeo/6lp+6w==} resolution: {integrity: sha512-K9b4FPbWkpnupvK3WXZLbgu9pchUJ6N7TtVZjbaPsoizkqFUDkUReUL25xdrCljJs7uLUF3tZ7nVPeo/6lp+6w==}
engines: {node: ^14.13.1 || >=16.0.0} engines: {node: ^14.13.1 || >=16.0.0}
@ -3317,10 +3346,15 @@ packages:
engines: {node: '>=8'} engines: {node: '>=8'}
dev: true dev: true
/marked/4.2.12: /markdown-it/13.0.1:
resolution: {integrity: sha512-yr8hSKa3Fv4D3jdZmtMMPghgVt6TWbk86WQaWhDloQjRSQhMMYCAro7jP7VDJrjjdV8pxVxMssXS8B8Y5DZ5aw==} resolution: {integrity: sha512-lTlxriVoy2criHP0JKRhO2VDG9c2ypWCsT237eDiLqi09rmbKoUetyGHq2uOIRoRS//kfoJckS0eUzzkDR+k2Q==}
engines: {node: '>= 12'}
hasBin: true hasBin: true
dependencies:
argparse: 2.0.1
entities: 3.0.1
linkify-it: 4.0.1
mdurl: 1.0.1
uc.micro: 1.0.6
dev: false dev: false
/mdast-util-from-markdown/0.8.5: /mdast-util-from-markdown/0.8.5:
@ -3339,6 +3373,10 @@ packages:
resolution: {integrity: sha512-AW4DRS3QbBayY/jJmD8437V1Gombjf8RSOUCMFBuo5iHi58AGEgVCKQ+ezHkZZDpAQS75hcBMpLqjpJTjtUL7w==} resolution: {integrity: sha512-AW4DRS3QbBayY/jJmD8437V1Gombjf8RSOUCMFBuo5iHi58AGEgVCKQ+ezHkZZDpAQS75hcBMpLqjpJTjtUL7w==}
dev: true dev: true
/mdurl/1.0.1:
resolution: {integrity: sha512-/sKlQJCBYVY9Ers9hqzKou4H6V5UWc/M59TH2dvkt+84itfnq7uFOMLpOiOS4ujvHP4etln18fmIxA5R5fll0g==}
dev: false
/memorystream/0.3.1: /memorystream/0.3.1:
resolution: {integrity: sha512-S3UwM3yj5mtUSEfP41UZmt/0SCoVYUcU1rkXv+BQ5Ig8ndL4sPoJNBUJERafdPb5jjHJGuMgytgKvKIf58XNBw==} resolution: {integrity: sha512-S3UwM3yj5mtUSEfP41UZmt/0SCoVYUcU1rkXv+BQ5Ig8ndL4sPoJNBUJERafdPb5jjHJGuMgytgKvKIf58XNBw==}
engines: {node: '>= 0.10.0'} engines: {node: '>= 0.10.0'}
@ -4582,6 +4620,10 @@ packages:
engines: {node: '>=4.2.0'} engines: {node: '>=4.2.0'}
hasBin: true hasBin: true
/uc.micro/1.0.6:
resolution: {integrity: sha512-8Y75pvTYkLJW2hWQHXxoqRgV7qb9B+9vFEtidML+7koHUFapnVJAZ6cKs+Qjz5Aw3aZWHMC6u0wJE3At+nSGwA==}
dev: false
/unbox-primitive/1.0.2: /unbox-primitive/1.0.2:
resolution: {integrity: sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw==} resolution: {integrity: sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw==}
dependencies: dependencies:

View File

@ -1,10 +1,9 @@
<script lang="ts" setup> <script lang="ts" setup>
import { computed, ref } from 'vue' import { computed, ref } from 'vue'
import katex from 'katex' import MarkdownIt from 'markdown-it'
import { marked } from 'marked' import mdKatex from '@traptitech/markdown-it-katex'
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'
import { t } from '@/locales' import { t } from '@/locales'
interface Props { interface Props {
@ -18,77 +17,21 @@ const props = defineProps<Props>()
const { isMobile } = useBasicLayout() const { isMobile } = useBasicLayout()
const renderer = new marked.Renderer()
const textRef = ref<HTMLElement>() const textRef = ref<HTMLElement>()
renderer.html = (html) => { const mdi = new MarkdownIt({
return `<p>${encodeHTML(html)}</p>` linkify: true,
} highlight: (code, language) => {
const validLang = !!(language && hljs.getLanguage(language))
renderer.code = (code, language) => { if (validLang) {
const validLang = !!(language && hljs.getLanguage(language)) const lang = language ?? ''
if (validLang) { 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 ${language}">${hljs.highlight(code, { language: lang }).value}</code></pre>`
const lang = language ?? '' }
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 ${language}">${hljs.highlight(code, { language: lang }).value}</code></pre>` return `<pre style="background: none">${hljs.highlightAuto(code).value}</pre>`
}
return `<pre style="background: none">${hljs.highlightAuto(code).value}</pre>`
}
marked.setOptions({
renderer,
highlight(code) {
return hljs.highlightAuto(code).value
}, },
}) })
const katexOptions = { mdi.use(mdKatex, { blockClass: 'katexmath-block rounded-md p-[10px]', errorColor: ' #cc0000' })
throwOnError: false,
}
const katexInline = {
name: 'katexInline',
level: 'inline',
start(src: string) {
return src.indexOf('$')
},
tokenizer(src: string) {
const match = src.match(/^\$+([^$\n]+?)\$+/)
if (match) {
return {
type: 'katexInline',
raw: match[0],
text: match[1].trim(),
}
}
},
renderer(token: marked.Tokens.Generic) {
return katex.renderToString(token.text, katexOptions)
},
}
const katexBlock = {
name: 'katexBlock',
level: 'block',
start(src: string) {
return src.indexOf('\n$$')
},
tokenizer(src: string) {
const match = src.match(/^\$\$+\n([^$]+?)\n\$\$+\n/)
if (match) {
return {
type: 'katexBlock',
raw: match[0],
text: match[1].trim(),
}
}
},
renderer(token: marked.Tokens.Generic) {
return `<p>${katex.renderToString(token.text, katexOptions)}</p>`
},
}
marked.use({ extensions: [katexInline, katexBlock] })
const wrapClass = computed(() => { const wrapClass = computed(() => {
return [ return [
@ -105,7 +48,7 @@ const wrapClass = computed(() => {
const text = computed(() => { const text = computed(() => {
const value = props.text ?? '' const value = props.text ?? ''
if (!props.inversion) if (!props.inversion)
return marked(value) return mdi.render(value)
return value return value
}) })

View File

@ -19,6 +19,7 @@
line-height: 1.65; line-height: 1.65;
} }
.katexmath-block,
.highlight pre, .highlight pre,
pre { pre {
background-color: #fff; background-color: #fff;