종류별 업로드 크기 한도와 413 안내를 추가하고, 임베드·미디어 라이브 프리뷰·제목 Enter 포커스·스크롤 동작을 보정한다. Co-authored-by: Cursor <cursoragent@cursor.com>
173 lines
4.2 KiB
JavaScript
173 lines
4.2 KiB
JavaScript
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: url }
|
|
: 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 ''
|
|
}
|