Browse Source

feat(component): add markdown renderer with highlight

main
Mohan 1 month ago
parent
commit
e4beaab751
  1. 10
      package-lock.json
  2. 1
      package.json
  3. 22
      src/utils/markdown.ts
  4. 8
      src/views/ArticleDetailView.vue

10
package-lock.json

@ -12,6 +12,7 @@
"dompurify": "^3.2.4", "dompurify": "^3.2.4",
"highlight.js": "^11.11.1", "highlight.js": "^11.11.1",
"marked": "^18.0.9", "marked": "^18.0.9",
"marked-highlight": "^2.2.4",
"pinia": "^2.3.0", "pinia": "^2.3.0",
"vue": "^3.5.13", "vue": "^3.5.13",
"vue-router": "^4.5.0" "vue-router": "^4.5.0"
@ -2564,6 +2565,15 @@
"node": ">= 20" "node": ">= 20"
} }
}, },
"node_modules/marked-highlight": {
"version": "2.2.4",
"resolved": "https://registry.npmmirror.com/marked-highlight/-/marked-highlight-2.2.4.tgz",
"integrity": "sha512-PZxisNMJDduSjc0q6uvjsnqqHCXc9s0eyzxDO9sB1eNGJnd/H1/Fu+z6g/liC1dfJdFW4SftMwMlLvsBhUPrqQ==",
"license": "MIT",
"peerDependencies": {
"marked": ">=4 <19"
}
},
"node_modules/math-intrinsics": { "node_modules/math-intrinsics": {
"version": "1.1.0", "version": "1.1.0",
"resolved": "https://registry.npmmirror.com/math-intrinsics/-/math-intrinsics-1.1.0.tgz", "resolved": "https://registry.npmmirror.com/math-intrinsics/-/math-intrinsics-1.1.0.tgz",

1
package.json

@ -14,6 +14,7 @@
"dompurify": "^3.2.4", "dompurify": "^3.2.4",
"highlight.js": "^11.11.1", "highlight.js": "^11.11.1",
"marked": "^18.0.9", "marked": "^18.0.9",
"marked-highlight": "^2.2.4",
"pinia": "^2.3.0", "pinia": "^2.3.0",
"vue": "^3.5.13", "vue": "^3.5.13",
"vue-router": "^4.5.0" "vue-router": "^4.5.0"

22
src/utils/markdown.ts

@ -0,0 +1,22 @@
import { marked } from 'marked'
import { markedHighlight } from 'marked-highlight'
import hljs from 'highlight.js'
import DOMPurify from 'dompurify'
import 'highlight.js/styles/github-dark.css'
marked.use(
markedHighlight({
langPrefix: 'hljs language-',
highlight(code: string, lang: string) {
if (lang && hljs.getLanguage(lang)) {
return hljs.highlight(code, { language: lang }).value
}
return hljs.highlightAuto(code).value
}
})
)
export function renderMarkdown(content: string): string {
const rawHtml = marked.parse(content, { async: false }) as string
return DOMPurify.sanitize(rawHtml)
}

8
src/views/ArticleDetailView.vue

@ -1,8 +1,8 @@
<script setup lang="ts"> <script setup lang="ts">
import { ref, computed, watch } from 'vue' import { ref, computed, watch } from 'vue'
import { useRoute } from 'vue-router' import { useRoute } from 'vue-router'
import DOMPurify from 'dompurify'
import { articleApi } from '@/api/client' import { articleApi } from '@/api/client'
import { renderMarkdown } from '@/utils/markdown'
import type { ArticleDetail } from '@/generated/api' import type { ArticleDetail } from '@/generated/api'
const route = useRoute() const route = useRoute()
@ -12,9 +12,9 @@ const error = ref<string | null>(null)
const slug = computed(() => String(route.params.slug ?? '')) const slug = computed(() => String(route.params.slug ?? ''))
const sanitizedContent = computed(() => {
const renderedContent = computed(() => {
if (!article.value?.content) return '' if (!article.value?.content) return ''
return DOMPurify.sanitize(article.value.content)
return renderMarkdown(article.value.content)
}) })
function formatDate(iso?: string): string { function formatDate(iso?: string): string {
@ -53,7 +53,7 @@ watch(slug, load, { immediate: true })
<div class="detail-tags"> <div class="detail-tags">
<span v-for="tag in article.tagNames" :key="tag" class="tag">{{ tag }}</span> <span v-for="tag in article.tagNames" :key="tag" class="tag">{{ tag }}</span>
</div> </div>
<div class="article-content" v-html="sanitizedContent"></div>
<div class="article-content" v-html="renderedContent"></div>
</article> </article>
<p v-else-if="loading" class="hint-text">加载中...</p> <p v-else-if="loading" class="hint-text">加载中...</p>

Loading…
Cancel
Save