본문 문단과 줄바꿈 처리 정리

This commit is contained in:
2026-05-14 16:33:30 +09:00
parent f5bfb560e2
commit 113c974ee5
9 changed files with 200 additions and 31 deletions

View File

@@ -99,6 +99,31 @@ const parseImageLine = (line) => {
}
}
/**
* 독립 블록으로 해석할 수 있는 마크다운 행인지 확인한다.
* @param {string} line - 마크다운 행
* @returns {boolean} 블록 시작 여부
*/
const isMarkdownBlockStart = (line) => {
const trimmedLine = line.trim()
return trimmedLine === BLANK_PARAGRAPH_MARKER ||
trimmedLine === '>>>' ||
trimmedLine === ':::bookmark' ||
trimmedLine === ':::signup' ||
trimmedLine === ':::gallery' ||
trimmedLine === ':::embed' ||
trimmedLine.startsWith(':::callout') ||
trimmedLine.startsWith(':::toggle') ||
trimmedLine.startsWith('```') ||
trimmedLine === '---' ||
/^(#{1,6})\s+(.+)$/.test(trimmedLine) ||
trimmedLine.startsWith('> ') ||
/^- /.test(trimmedLine) ||
/^\d+\.\s+/.test(trimmedLine) ||
Boolean(parseImageLine(trimmedLine))
}
/**
* 닫힘 표식까지의 행 목록을 반환
* @param {Array<string>} lines - 전체 마크다운 행
@@ -224,14 +249,7 @@ const parseMarkdownBlocks = (markdown) => {
const line = lines[index]
const trimmedLine = line.trim()
if (trimmedLine === BLANK_PARAGRAPH_MARKER) {
blocks.push(createBlock('spacer', '', null, `block-${blocks.length}`))
index += 1
continue
}
if (!trimmedLine) {
blocks.push(createBlock('spacer', '', null, `block-${blocks.length}`))
if (trimmedLine === BLANK_PARAGRAPH_MARKER || !trimmedLine) {
index += 1
continue
}
@@ -380,8 +398,22 @@ const parseMarkdownBlocks = (markdown) => {
continue
}
blocks.push(createBlock('paragraph', trimmedLine, null, `block-${blocks.length}`))
const paragraphLines = [trimmedLine]
index += 1
while (index < lines.length) {
const nextLine = lines[index]
const nextTrimmedLine = nextLine.trim()
if (!nextTrimmedLine || isMarkdownBlockStart(nextTrimmedLine)) {
break
}
paragraphLines.push(nextTrimmedLine)
index += 1
}
blocks.push(createBlock('paragraph', paragraphLines.join('\n'), null, `block-${blocks.length}`))
}
return blocks
@@ -447,6 +479,15 @@ const parseInlineSegments = (value) => {
return segments.length ? segments : [{ type: 'text', text: source }]
}
/**
* 줄바꿈이 포함된 인라인 마크다운을 줄 단위 세그먼트로 변환한다.
* @param {string} value - 원본 문자열
* @returns {Array<Array<{ type: string, text: string, href?: string }>>} 줄별 인라인 세그먼트
*/
const parseInlineSegmentLines = (value) => {
return String(value || '').split('\n').map(parseInlineSegments)
}
/**
* 라이트박스를 연다
* @param {Array<Object>} images - 이미지 목록
@@ -489,8 +530,7 @@ const showNextImage = () => {
<template>
<div class="content-markdown-renderer">
<template v-for="block in blocks" :key="block.id">
<div v-if="block.type === 'spacer'" class="content-markdown-renderer__spacer h-6" aria-hidden="true" />
<ProseHeading v-else-if="block.type === 'heading'" :level="block.level">
<ProseHeading v-if="block.type === 'heading'" :level="block.level">
<template v-for="(segment, segmentIndex) in parseInlineSegments(block.text)" :key="`${block.id}-heading-${segmentIndex}`">
<strong v-if="segment.type === 'strong'">{{ segment.text }}</strong>
<em v-else-if="segment.type === 'em'">{{ segment.text }}</em>
@@ -576,13 +616,16 @@ const showNextImage = () => {
class="content-markdown-renderer__code my-6 overflow-x-auto rounded bg-[#15171a] px-4 py-3 text-sm leading-6 text-white"
><code>{{ block.text }}</code></pre>
<hr v-else-if="block.type === 'divider'" class="content-markdown-renderer__divider my-10 border-line">
<p v-else class="content-markdown-renderer__paragraph my-5 text-[15px] leading-8 text-[var(--site-text)]">
<template v-for="(segment, segmentIndex) in parseInlineSegments(block.text)" :key="`${block.id}-paragraph-${segmentIndex}`">
<strong v-if="segment.type === 'strong'">{{ segment.text }}</strong>
<em v-else-if="segment.type === 'em'">{{ segment.text }}</em>
<code v-else-if="segment.type === 'code'" class="rounded bg-[var(--site-panel)] px-1 py-0.5 text-[0.9em]">{{ segment.text }}</code>
<a v-else-if="segment.type === 'link'" class="text-[var(--site-accent)] underline underline-offset-4" :href="segment.href" target="_blank" rel="noreferrer">{{ segment.text }}</a>
<template v-else>{{ segment.text }}</template>
<p v-else class="content-markdown-renderer__paragraph mb-6 text-[15px] leading-8 text-[var(--site-text)] last:mb-0">
<template v-for="(lineSegments, lineIndex) in parseInlineSegmentLines(block.text)" :key="`${block.id}-paragraph-line-${lineIndex}`">
<br v-if="lineIndex > 0">
<template v-for="(segment, segmentIndex) in lineSegments" :key="`${block.id}-paragraph-${lineIndex}-${segmentIndex}`">
<strong v-if="segment.type === 'strong'">{{ segment.text }}</strong>
<em v-else-if="segment.type === 'em'">{{ segment.text }}</em>
<code v-else-if="segment.type === 'code'" class="rounded bg-[var(--site-panel)] px-1 py-0.5 text-[0.9em]">{{ segment.text }}</code>
<a v-else-if="segment.type === 'link'" class="text-[var(--site-accent)] underline underline-offset-4" :href="segment.href" target="_blank" rel="noreferrer">{{ segment.text }}</a>
<template v-else>{{ segment.text }}</template>
</template>
</template>
</p>
</template>