라이브 작성 블록 렌더링과 발행 시각 보정
This commit is contained in:
@@ -373,6 +373,64 @@ export const markdownMultilineInlineToHtml = (value) => String(value || '')
|
||||
.map((line) => markdownInlineToHtml(line))
|
||||
.join('<br>')
|
||||
|
||||
/**
|
||||
* 여러 줄 마크다운을 라이브 편집용 블록 HTML로 변환한다.
|
||||
* @param {string} value - 줄바꿈 포함 마크다운
|
||||
* @returns {string} HTML
|
||||
*/
|
||||
export const markdownMultilineBlockToHtml = (value) => {
|
||||
const lines = String(value || '').replace(/<br\s*\/?>/gi, '\n').split('\n')
|
||||
const htmlParts = []
|
||||
let index = 0
|
||||
|
||||
while (index < lines.length) {
|
||||
const line = lines[index]
|
||||
const bulletMatch = line.match(/^\s*-\s+(.*)$/)
|
||||
const orderedMatch = line.match(/^\s*\d+\.\s+(.*)$/)
|
||||
|
||||
if (bulletMatch) {
|
||||
const items = []
|
||||
|
||||
while (index < lines.length) {
|
||||
const itemMatch = lines[index].match(/^\s*-\s+(.*)$/)
|
||||
|
||||
if (!itemMatch) {
|
||||
break
|
||||
}
|
||||
|
||||
items.push(`<li>${markdownInlineToHtml(itemMatch[1])}</li>`)
|
||||
index += 1
|
||||
}
|
||||
|
||||
htmlParts.push(`<ul>${items.join('')}</ul>`)
|
||||
continue
|
||||
}
|
||||
|
||||
if (orderedMatch) {
|
||||
const items = []
|
||||
|
||||
while (index < lines.length) {
|
||||
const itemMatch = lines[index].match(/^\s*\d+\.\s+(.*)$/)
|
||||
|
||||
if (!itemMatch) {
|
||||
break
|
||||
}
|
||||
|
||||
items.push(`<li>${markdownInlineToHtml(itemMatch[1])}</li>`)
|
||||
index += 1
|
||||
}
|
||||
|
||||
htmlParts.push(`<ol>${items.join('')}</ol>`)
|
||||
continue
|
||||
}
|
||||
|
||||
htmlParts.push(line ? markdownInlineToHtml(line) : '<br>')
|
||||
index += 1
|
||||
}
|
||||
|
||||
return htmlParts.join('<br>')
|
||||
}
|
||||
|
||||
/**
|
||||
* 인라인 HTML 노드를 마크다운 문자열로 변환한다.
|
||||
* @param {Node} node - HTML 노드
|
||||
@@ -421,6 +479,24 @@ export const convertHtmlInlineNodeToMarkdown = (node) => {
|
||||
return isInlineColorName(inlineColor) ? formatInlineColorMarkdown(inlineColor, childText) : childText
|
||||
}
|
||||
|
||||
if (tagName === 'ul') {
|
||||
return Array.from(element.children)
|
||||
.filter((child) => child.tagName.toLowerCase() === 'li')
|
||||
.map((child) => `- ${Array.from(child.childNodes).map(convertHtmlInlineNodeToMarkdown).join('')}`)
|
||||
.join('\n')
|
||||
}
|
||||
|
||||
if (tagName === 'ol') {
|
||||
return Array.from(element.children)
|
||||
.filter((child) => child.tagName.toLowerCase() === 'li')
|
||||
.map((child, index) => `${index + 1}. ${Array.from(child.childNodes).map(convertHtmlInlineNodeToMarkdown).join('')}`)
|
||||
.join('\n')
|
||||
}
|
||||
|
||||
if (tagName === 'li') {
|
||||
return childText
|
||||
}
|
||||
|
||||
if (tagName === 'img') {
|
||||
const src = element.getAttribute('src')
|
||||
const alt = element.getAttribute('alt') || ''
|
||||
@@ -503,6 +579,33 @@ function* iterateEditableTextUnits(root) {
|
||||
return
|
||||
}
|
||||
|
||||
if (tag === 'ul' || tag === 'ol') {
|
||||
if (parentIsRoot && indexInParent > 0) {
|
||||
yield { kind: 'block-break', node: null, length: 1 }
|
||||
}
|
||||
|
||||
const items = [...element.children].filter((child) => child.tagName.toLowerCase() === 'li')
|
||||
|
||||
for (let itemIndex = 0; itemIndex < items.length; itemIndex += 1) {
|
||||
if (itemIndex > 0) {
|
||||
yield { kind: 'block-break', node: null, length: 1 }
|
||||
}
|
||||
|
||||
const marker = tag === 'ol' ? `${itemIndex + 1}. ` : '- '
|
||||
const item = items[itemIndex]
|
||||
|
||||
yield { kind: 'marker', node: item, length: marker.length }
|
||||
|
||||
const children = [...item.childNodes]
|
||||
|
||||
for (let childIndex = 0; childIndex < children.length; childIndex += 1) {
|
||||
yield* visit(children[childIndex], false, childIndex)
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
if (isEditableBlockBreak(element, root)) {
|
||||
if (parentIsRoot && indexInParent > 0) {
|
||||
yield { kind: 'block-break', node: null, length: 1 }
|
||||
@@ -671,6 +774,27 @@ export const setEditableCaretOffset = (root, targetOffset) => {
|
||||
continue
|
||||
}
|
||||
|
||||
if (unit.kind === 'marker' && unit.node?.nodeType === Node.ELEMENT_NODE) {
|
||||
if (walked + unit.length >= safeOffset) {
|
||||
const textWalker = document.createTreeWalker(unit.node, NodeFilter.SHOW_TEXT)
|
||||
const firstText = textWalker.nextNode()
|
||||
|
||||
if (firstText) {
|
||||
const range = document.createRange()
|
||||
range.setStart(firstText, 0)
|
||||
range.collapse(true)
|
||||
const selection = window.getSelection()
|
||||
selection?.removeAllRanges()
|
||||
selection?.addRange(range)
|
||||
placed = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
walked += unit.length
|
||||
continue
|
||||
}
|
||||
|
||||
if (walked + unit.length >= safeOffset) {
|
||||
const range = document.createRange()
|
||||
range.selectNodeContents(root)
|
||||
|
||||
Reference in New Issue
Block a user