레거시 본문 저장 형식 정규화
This commit is contained in:
172
lib/markdown-content-normalizer.js
Normal file
172
lib/markdown-content-normalizer.js
Normal file
@@ -0,0 +1,172 @@
|
||||
const BLANK_PARAGRAPH_MARKER = '<!--sori:blank-paragraph-->'
|
||||
|
||||
const blockSpacingTypes = new Set(['list'])
|
||||
|
||||
/**
|
||||
* 이미지 블록을 마크다운 문자열로 변환한다.
|
||||
* @param {Object} image - 이미지 데이터
|
||||
* @returns {string} 이미지 마크다운
|
||||
*/
|
||||
const serializeImageBlock = (image = {}) => {
|
||||
const url = String(image.url || '').trim()
|
||||
|
||||
if (!url) {
|
||||
return ''
|
||||
}
|
||||
|
||||
const width = image.width && image.width !== 'regular'
|
||||
? `{width=${image.width}}`
|
||||
: ''
|
||||
|
||||
return `${width}`
|
||||
}
|
||||
|
||||
/**
|
||||
* 레거시 블록 하나를 마크다운 조각으로 변환한다.
|
||||
* @param {Object} block - 레거시 에디터 블록
|
||||
* @param {number} index - 블록 인덱스
|
||||
* @param {number} total - 전체 블록 수
|
||||
* @returns {{ type: string, value: string }|null} 마크다운 조각
|
||||
*/
|
||||
const serializeLegacyBlock = (block = {}, index = 0, total = 1) => {
|
||||
if (typeof block.value === 'string') {
|
||||
return block.value.trim()
|
||||
? { type: block.type || 'paragraph', value: block.value }
|
||||
: null
|
||||
}
|
||||
|
||||
const type = block.type || 'paragraph'
|
||||
const rawText = String(block.text || '')
|
||||
const text = rawText.trim()
|
||||
|
||||
if (type === 'divider') {
|
||||
return { type, value: '---' }
|
||||
}
|
||||
|
||||
if (type === 'image') {
|
||||
const image = serializeImageBlock(block)
|
||||
return image ? { type, value: image } : null
|
||||
}
|
||||
|
||||
if (type === 'gallery') {
|
||||
const images = Array.isArray(block.images)
|
||||
? block.images.map(serializeImageBlock).filter(Boolean)
|
||||
: []
|
||||
|
||||
return images.length
|
||||
? { type, value: [':::gallery', ...images, ':::'].join('\n') }
|
||||
: null
|
||||
}
|
||||
|
||||
if (type === 'callout') {
|
||||
const emoji = block.calloutEmojiEnabled === false
|
||||
? 'none'
|
||||
: (block.calloutEmoji || '💡')
|
||||
const background = block.calloutBackground || 'blue'
|
||||
|
||||
return text
|
||||
? { type, value: `:::callout emoji=${emoji} bg=${background}\n${text}\n:::` }
|
||||
: null
|
||||
}
|
||||
|
||||
if (type === 'toggle') {
|
||||
const title = String(block.title || '').trim()
|
||||
|
||||
return title || text
|
||||
? { type, value: `:::toggle ${title || '더 보기'}\n${text}\n:::` }
|
||||
: null
|
||||
}
|
||||
|
||||
if (type === 'embed') {
|
||||
const url = String(block.url || '').trim()
|
||||
|
||||
return url
|
||||
? { type, value: `:::embed\n${url}\n:::` }
|
||||
: null
|
||||
}
|
||||
|
||||
if (type === 'paragraph' && !text) {
|
||||
return index === total - 1
|
||||
? null
|
||||
: { type, value: BLANK_PARAGRAPH_MARKER }
|
||||
}
|
||||
|
||||
if (!text) {
|
||||
return null
|
||||
}
|
||||
|
||||
if (type === 'heading') {
|
||||
return { type, value: `${'#'.repeat(block.level || 2)} ${text}` }
|
||||
}
|
||||
|
||||
if (type === 'quote') {
|
||||
return { type, value: `> ${text}` }
|
||||
}
|
||||
|
||||
if (type === 'list') {
|
||||
return { type, value: `- ${text}` }
|
||||
}
|
||||
|
||||
if (type === 'code') {
|
||||
return { type, value: `\`\`\`\n${rawText}\n\`\`\`` }
|
||||
}
|
||||
|
||||
return { type, value: text }
|
||||
}
|
||||
|
||||
/**
|
||||
* 레거시 블록 배열을 저장용 마크다운 문자열로 변환한다.
|
||||
* @param {Array<Object>} blocks - 레거시 블록 목록
|
||||
* @returns {string} 마크다운 문자열
|
||||
*/
|
||||
const serializeLegacyBlocks = (blocks) => blocks
|
||||
.map((block, index) => serializeLegacyBlock(block, index, blocks.length))
|
||||
.filter(Boolean)
|
||||
.reduce((markdown, block, index, blocksList) => {
|
||||
if (index === 0) {
|
||||
return block.value
|
||||
}
|
||||
|
||||
const previousBlock = blocksList[index - 1]
|
||||
const joiner = blockSpacingTypes.has(previousBlock.type) && blockSpacingTypes.has(block.type)
|
||||
? '\n'
|
||||
: '\n\n'
|
||||
|
||||
return `${markdown}${joiner}${block.value}`
|
||||
}, '')
|
||||
|
||||
/**
|
||||
* 게시물/페이지 본문 값을 저장 가능한 마크다운 문자열로 정규화한다.
|
||||
* @param {unknown} value - 본문 값
|
||||
* @returns {string} 마크다운 문자열
|
||||
*/
|
||||
export const normalizeMarkdownContent = (value) => {
|
||||
if (typeof value === 'string') {
|
||||
return value
|
||||
}
|
||||
|
||||
if (Array.isArray(value)) {
|
||||
return serializeLegacyBlocks(value)
|
||||
}
|
||||
|
||||
if (value && typeof value === 'object') {
|
||||
if (typeof value.content === 'string') {
|
||||
return value.content
|
||||
}
|
||||
|
||||
if (Array.isArray(value.blocks)) {
|
||||
return serializeLegacyBlocks(value.blocks)
|
||||
}
|
||||
|
||||
if (typeof value.markdown === 'string') {
|
||||
return value.markdown
|
||||
}
|
||||
|
||||
if (typeof value.type === 'string') {
|
||||
const block = serializeLegacyBlock(value)
|
||||
return block?.value || ''
|
||||
}
|
||||
}
|
||||
|
||||
return ''
|
||||
}
|
||||
Reference in New Issue
Block a user