Browse Source

fix(markdown): hide front matter in editor preview and renderSafeMarkdown

Made-with: Cursor
main
npmrun 7 hours ago
parent
commit
2c07ec779c
  1. 8
      app/components/PostBodyMarkdownEditor.vue
  2. 4
      app/pages/@[publicSlug]/posts/[postSlug].vue
  3. 16
      app/utils/markdown-front-matter.ts
  4. 3
      app/utils/render-markdown.ts

8
app/components/PostBodyMarkdownEditor.vue

@ -1,6 +1,8 @@
<script setup lang="ts"> <script setup lang="ts">
import type MarkdownIt from 'markdown-it'
import { MdEditor } from 'md-editor-v3' import { MdEditor } from 'md-editor-v3'
import 'md-editor-v3/lib/style.css' import 'md-editor-v3/lib/style.css'
import { attachMarkdownItStripFrontMatter } from '../utils/markdown-front-matter'
const props = defineProps<{ const props = defineProps<{
modelValue: string modelValue: string
@ -13,6 +15,11 @@ const emit = defineEmits<{
const { fetchData } = useClientApi() const { fetchData } = useClientApi()
const toast = useToast() const toast = useToast()
const editorId = `post-body-md-${useId()}` const editorId = `post-body-md-${useId()}`
const previewFmRule = `strip_fm_${useId().replace(/[^a-zA-Z0-9_]/g, '_')}`
function markdownItConfig(md: MarkdownIt) {
attachMarkdownItStripFrontMatter(md, previewFmRule)
}
const local = computed({ const local = computed({
get: () => props.modelValue, get: () => props.modelValue,
@ -46,6 +53,7 @@ async function onUploadImg(files: File[], callback: (urls: string[]) => void) {
:preview="true" :preview="true"
preview-theme="github" preview-theme="github"
theme="light" theme="light"
:markdown-it-config="markdownItConfig"
:on-upload-img="onUploadImg" :on-upload-img="onUploadImg"
:style="{ height: 'min(72vh, 720px)' }" :style="{ height: 'min(72vh, 720px)' }"
class="w-full rounded-lg overflow-hidden ring ring-default" class="w-full rounded-lg overflow-hidden ring ring-default"

4
app/pages/@[publicSlug]/posts/[postSlug].vue

@ -1,6 +1,6 @@
<script setup lang="ts"> <script setup lang="ts">
import { unwrapApiBody, type ApiResponse } from '../../../utils/http/factory' import { unwrapApiBody, type ApiResponse } from '../../../utils/http/factory'
import { extractFrontMatterDesc, stripFrontMatter } from '../../../utils/markdown-front-matter' import { extractFrontMatterDesc } from '../../../utils/markdown-front-matter'
import { renderSafeMarkdown } from '../../../utils/render-markdown' import { renderSafeMarkdown } from '../../../utils/render-markdown'
import { formatOccurredOnDisplay, occurredOnToIsoAttr } from '../../../utils/timeline-datetime' import { formatOccurredOnDisplay, occurredOnToIsoAttr } from '../../../utils/timeline-datetime'
import { useAuthSession } from '../../../composables/useAuthSession' import { useAuthSession } from '../../../composables/useAuthSession'
@ -35,7 +35,7 @@ const { data, pending, error } = await useAsyncData(
) )
const renderedBody = computed(() => const renderedBody = computed(() =>
data.value ? renderSafeMarkdown(stripFrontMatter(data.value.bodyMarkdown)) : '', data.value ? renderSafeMarkdown(data.value.bodyMarkdown) : '',
) )
const leadSummary = computed(() => { const leadSummary = computed(() => {

16
app/utils/markdown-front-matter.ts

@ -1,3 +1,5 @@
import type MarkdownIt from "markdown-it"
/** 匹配以 --- 开头/结尾的 YAML front matter(全文开头) */ /** 匹配以 --- 开头/结尾的 YAML front matter(全文开头) */
const FRONT_MATTER_RE = /^---[\t ]*\r?\n([\s\S]*?)\r?\n---[\t ]*(?:\r?\n|$)([\s\S]*)$/ const FRONT_MATTER_RE = /^---[\t ]*\r?\n([\s\S]*?)\r?\n---[\t ]*(?:\r?\n|$)([\s\S]*)$/
@ -7,7 +9,9 @@ export function splitFrontMatter(markdown: string): { front: string | null; body
if (!m) { if (!m) {
return { front: null, body: markdown } return { front: null, body: markdown }
} }
return { front: m[1].trim(), body: m[2] } const frontBlock = m[1] ?? ""
const body = m[2] ?? ""
return { front: frontBlock.trim(), body }
} }
/** 正文渲染用:去掉 front matter,避免把元数据块排进 HTML */ /** 正文渲染用:去掉 front matter,避免把元数据块排进 HTML */
@ -55,3 +59,13 @@ export function listCardSummaryFromPostBody(bodyMarkdown: string, dbExcerpt: str
} }
return (dbExcerpt ?? "").trim() return (dbExcerpt ?? "").trim()
} }
/**
* md-editor-v3 `markdownItConfig` core 线 front matter
* `ruleName` useId
*/
export function attachMarkdownItStripFrontMatter(md: MarkdownIt, ruleName: string): void {
md.core.ruler.after("normalize", ruleName, (state) => {
state.src = stripFrontMatter(state.src)
})
}

3
app/utils/render-markdown.ts

@ -1,5 +1,6 @@
import MarkdownIt from 'markdown-it' import MarkdownIt from 'markdown-it'
import DOMPurify from 'isomorphic-dompurify' import DOMPurify from 'isomorphic-dompurify'
import { stripFrontMatter } from './markdown-front-matter'
const md = new MarkdownIt({ const md = new MarkdownIt({
html: false, html: false,
@ -13,5 +14,5 @@ export function renderSafeMarkdown(src: string): string {
if (!src.trim()) { if (!src.trim()) {
return '' return ''
} }
return DOMPurify.sanitize(md.render(src), { USE_PROFILES: { html: true } }) return DOMPurify.sanitize(md.render(stripFrontMatter(src)), { USE_PROFILES: { html: true } })
} }

Loading…
Cancel
Save