공개 본문 목록 편집 분리

This commit is contained in:
2026-07-27 16:29:23 +09:00
parent 16ea10229e
commit 9efa6d6af5
11 changed files with 407 additions and 151 deletions

View File

@@ -12,11 +12,7 @@ import {
import {
appendTextToMarkdownLine,
getAppendTextForMerge,
getMergeJunctionDisplayOffset,
hasListMarker,
isEmptyListMarkerLine,
parseOrderedListMarker,
stripListMarker
getMergeJunctionDisplayOffset
} from '../../lib/markdown-live-edit.js'
import { CALLOUT_BACKGROUND_OPTIONS } from '../../lib/markdown-callout.js'
import {
@@ -32,6 +28,7 @@ import { useContentMarkdownImageDrag } from '../../composables/useContentMarkdow
import { useContentMarkdownLiveSelection } from '../../composables/useContentMarkdownLiveSelection.js'
import { useContentMarkdownFencedBlocks } from '../../composables/useContentMarkdownFencedBlocks.js'
import { useContentMarkdownParagraphs } from '../../composables/useContentMarkdownParagraphs.js'
import { useContentMarkdownLists } from '../../composables/useContentMarkdownLists.js'
import ContentMarkdownCodeBlockEditor from './ContentMarkdownCodeBlockEditor.vue'
import ProseCodeBlock from './ProseCodeBlock.vue'
import ProseHtml from './ProseHtml.vue'
@@ -190,6 +187,18 @@ const {
commitInlineBlockLines: (block, replacementLines) => commitInlineBlockLines(block, replacementLines)
})
const {
getListMarkerNumber,
onListItemInlineCommit,
onListItemInsertBelow
} = useContentMarkdownLists({
content: computed(() => props.content),
pendingFocusLine,
pendingFocusPosition,
commitInlineBlockLines: (block, replacementLines) => commitInlineBlockLines(block, replacementLines),
insertBelowBlock: (block, options) => onInsertBelowBlock(block, options)
})
/**
* 빈 줄 공백 블록 높이를 반환한다.
* @param {Object} block - 렌더링 블록
@@ -401,64 +410,6 @@ const getBlockSourceLines = (block) => String(props.content || '').split('\n').s
(block.meta.endLine ?? block.meta.startLine) + 1
)
/**
* 목록 항목 마크다운 줄을 만든다.
* @param {string} value - 편집 값
* @param {boolean} raw - 원문 모드 여부
* @param {Object} block - 목록 블록
* @param {number} itemIndex - 항목 인덱스
* @returns {string} 마크다운 줄
*/
/**
* 순서 목록 항목에 표시할 번호를 반환한다.
* @param {Object} block - 목록 블록
* @param {number} itemIndex - 항목 인덱스
* @returns {number} 목록 번호
*/
const getListMarkerNumber = (block, itemIndex) => {
if (block.listNumbers?.[itemIndex] != null) {
return block.listNumbers[itemIndex]
}
const line = getMarkdownLine(block.meta.startLine + itemIndex)
const parsed = parseOrderedListMarker(line)
return parsed?.number ?? itemIndex + 1
}
const formatListLine = (value, raw, block, itemIndex) => {
if (raw) {
if (!hasListMarker(value, block.ordered)) {
return String(value ?? '').trim()
}
return value
}
const clean = stripListMarker(value, block.ordered)
if (block.ordered) {
const number = getListMarkerNumber(block, itemIndex)
return clean ? `${number}. ${clean}` : `${number}. `
}
return clean ? `- ${clean}` : '- '
}
/**
* 빈 목록 마커 줄을 만든다.
* @param {Object} block - 목록 블록
* @param {number} itemIndex - 항목 인덱스
* @returns {string} 마크다운 줄
*/
const createEmptyListMarkerLine = (block, itemIndex) => {
if (block.ordered) {
return `${getListMarkerNumber(block, itemIndex)}. `
}
return '- '
}
/**
* 인라인 편집 결과를 마크다운 줄로 반영한다.
* @param {Object} block - 블록
@@ -676,58 +627,6 @@ const onPreviewCardKeydown = (event, block) => {
}
}
/**
* 목록 항목 Enter — 빈 마커 줄이면 문단으로 탈출, 내용이 있으면 아래에 빈 줄만 삽입한다.
* @param {Object} block - 목록 블록
* @param {number} itemIndex - 항목 인덱스
* @param {string|{ value: string, raw?: boolean }} payload - 편집 내용
* @returns {void}
*/
const onListItemInsertBelow = (block, itemIndex, payload) => {
const { value, raw, before, after, caretAtStart, caretAtEnd } = normalizeInsertBelowPayload(payload)
const nextLines = getBlockSourceLines(block)
if (caretAtStart && after.length) {
nextLines[itemIndex] = formatListLine(after, raw, block, itemIndex)
nextLines.splice(itemIndex, 0, createEmptyListMarkerLine(block, itemIndex))
pendingFocusLine.value = block.meta.startLine + itemIndex
pendingFocusPosition.value = 'start'
commitInlineBlockLines(block, nextLines)
return
}
if (before.length && after.length) {
nextLines[itemIndex] = formatListLine(before, raw, block, itemIndex)
nextLines.splice(itemIndex + 1, 0, formatListLine(after, raw, block, itemIndex + 1))
pendingFocusLine.value = block.meta.startLine + itemIndex + 1
pendingFocusPosition.value = 'start'
commitInlineBlockLines(block, nextLines)
return
}
if (itemIndex < nextLines.length) {
nextLines[itemIndex] = formatListLine(value, raw, block, itemIndex)
}
const committedLine = nextLines[itemIndex] ?? ''
if (isEmptyListMarkerLine(committedLine, block.ordered)) {
nextLines[itemIndex] = ''
pendingFocusLine.value = block.meta.startLine + itemIndex
pendingFocusPosition.value = 'start'
commitInlineBlockLines(block, nextLines)
return
}
commitInlineBlockLines(block, nextLines)
if (caretAtEnd || !before.length) {
pendingFocusLine.value = (block.meta.endLine ?? block.meta.startLine) + 1
pendingFocusPosition.value = 'start'
onInsertBelowBlock(block, { lines: [''] })
}
}
/**
* 라이브 모드 하단 클릭 — 새 문단 추가
* @returns {void}
@@ -917,31 +816,6 @@ const onQuoteBlockConvertToParagraph = (block, payload) => {
commitInlineBlockLines(block, replacementLines)
}
/**
* 목록 항목 인라인 편집 반영
* @param {Object} block - 블록
* @param {number} itemIndex - 항목 인덱스
* @param {string|{ value: string, raw?: boolean }} payload - 편집 내용
* @returns {void}
*/
const onListItemInlineCommit = (block, itemIndex, payload) => {
const { value, raw } = normalizeCommitPayload(payload)
const sourceLines = String(props.content || '').split('\n').slice(
block.meta.startLine,
(block.meta.endLine ?? block.meta.startLine) + 1
)
const nextLines = sourceLines.map((line, index) => {
if (index !== itemIndex) {
return line
}
return formatListLine(value, raw, block, index)
})
commitInlineBlockLines(block, nextLines)
}
/**
* 라이브 모드에서 현재 줄을 삭제한다.
* @param {number} lineIndex - 줄 번호