공개 본문 문단 편집 분리

This commit is contained in:
2026-07-27 16:14:56 +09:00
parent ff2d78ff81
commit 16ea10229e
10 changed files with 290 additions and 147 deletions

View File

@@ -18,11 +18,7 @@ import {
parseOrderedListMarker,
stripListMarker
} from '../../lib/markdown-live-edit.js'
import { buildCodeFenceOpener } from '../../lib/markdown-code-block.js'
import {
buildCalloutOpenerLine,
CALLOUT_BACKGROUND_OPTIONS
} from '../../lib/markdown-callout.js'
import { CALLOUT_BACKGROUND_OPTIONS } from '../../lib/markdown-callout.js'
import {
formatQuoteLine,
getQuoteContentStartLine,
@@ -35,6 +31,7 @@ import { useContentMarkdownBlocks } from '../../composables/useContentMarkdownBl
import { useContentMarkdownImageDrag } from '../../composables/useContentMarkdownImageDrag.js'
import { useContentMarkdownLiveSelection } from '../../composables/useContentMarkdownLiveSelection.js'
import { useContentMarkdownFencedBlocks } from '../../composables/useContentMarkdownFencedBlocks.js'
import { useContentMarkdownParagraphs } from '../../composables/useContentMarkdownParagraphs.js'
import ContentMarkdownCodeBlockEditor from './ContentMarkdownCodeBlockEditor.vue'
import ProseCodeBlock from './ProseCodeBlock.vue'
import ProseHtml from './ProseHtml.vue'
@@ -91,9 +88,6 @@ const emit = defineEmits([
const CONTENT_LIST_MARKER_COLOR = '#2eb6ea'
/** @type {import('vue').Ref<Record<string, number>>} 갤러리 이미지 자연 비율 */
const galleryImageAspectRatios = ref({})
/** @type {number} 문단 분리 연속 호출 방지 */
let lastParagraphSplitAt = 0
const {
activeLightboxImages,
activeLightboxImage,
@@ -185,6 +179,17 @@ const {
insertBelowBlock: (block, options) => onInsertBelowBlock(block, options)
})
const {
onParagraphInlineCommit,
onSpacerInlineCommit,
onParagraphLiveInput,
onParagraphSplit
} = useContentMarkdownParagraphs({
pendingFocusLine,
pendingFocusPosition,
commitInlineBlockLines: (block, replacementLines) => commitInlineBlockLines(block, replacementLines)
})
/**
* 빈 줄 공백 블록 높이를 반환한다.
* @param {Object} block - 렌더링 블록
@@ -489,140 +494,6 @@ const onInsertAboveBlock = (block) => {
})
}
const onParagraphInlineCommit = (block, text) => {
const value = String(text ?? '').replace(/\r/g, '')
const lines = value.split('\n').map((line) => line.trimEnd())
if (lines.length <= 1) {
commitInlineBlockLines(block, [lines[0] ?? ''])
return
}
commitInlineBlockLines(block, lines.filter((line) => line.length > 0))
}
/**
* 빈 줄(스페이서) 편집 반영
* @param {Object} block - 블록
* @param {string} text - 편집된 텍스트
* @returns {void}
*/
const onSpacerInlineCommit = (block, text) => {
if (!String(text ?? '').trim()) {
return
}
commitInlineBlockLines(block, [text])
}
/**
* 문단 입력 중 블록 단축 변환을 처리한다.
* @param {Object} block - 문단 블록
* @param {string} text - 입력 텍스트
* @returns {void}
*/
const onParagraphLiveInput = (block, text) => {
if (String(text ?? '').trim() !== '>') {
return
}
pendingFocusLine.value = block.meta.startLine
pendingFocusPosition.value = 'start'
commitInlineBlockLines(block, ['> '])
}
/**
* 문단 Enter 분리 결과 줄 배열을 만든다.
* @param {string} head - 커서 앞 텍스트
* @param {string} tail - 커서 뒤 텍스트
* @returns {string[]} 분리된 줄
*/
const buildParagraphSplitLines = (head, tail) => {
const h = String(head ?? '').replace(/\n/g, ' ')
const t = String(tail ?? '').replace(/\n/g, ' ')
if (!t.length) {
return [h, '']
}
if (!h.length) {
return ['', t]
}
return [h, t]
}
/**
* 문단 Enter 입력을 블록 단축 입력으로 변환할지 확인한다.
* @param {Object} block - 문단 블록
* @param {string} before - 커서 앞 텍스트
* @param {string} after - 커서 뒤 텍스트
* @returns {boolean} 변환 처리 여부
*/
const applyParagraphShortcutSplit = (block, before, after) => {
const head = String(before ?? '').trim()
const tail = String(after ?? '')
if (tail.trim()) {
return false
}
if (/^```[A-Za-z0-9_-]*$/.test(head)) {
const opener = head === '```' ? buildCodeFenceOpener({ language: '', showLineNumbers: false }) : head
pendingFocusLine.value = block.meta.startLine + 1
pendingFocusPosition.value = 'start'
commitInlineBlockLines(block, [opener, '', '```'])
return true
}
if (head === '!!!' || head === ':::callout') {
pendingFocusLine.value = block.meta.startLine + 1
pendingFocusPosition.value = 'start'
commitInlineBlockLines(block, [
buildCalloutOpenerLine({
calloutEmojiEnabled: false,
calloutEmoji: '💡',
calloutBackground: 'blue',
title: ''
}),
'',
'',
':::'
])
return true
}
return false
}
/**
* 문단 Enter 분리 — 마크다운에 빈 줄을 넣어 다음 문단을 만든다.
* @param {Object} block - 블록
* @param {{ before: string, after: string }} payload - 커서 앞·뒤 텍스트
* @returns {void}
*/
const onParagraphSplit = (block, { before, after }) => {
const now = Date.now()
if (now - lastParagraphSplitAt < 120) {
return
}
lastParagraphSplitAt = now
if (applyParagraphShortcutSplit(block, before, after)) {
return
}
const replacementLines = buildParagraphSplitLines(before, after)
const focusLine = block.meta.startLine + Math.max(replacementLines.length - 1, 1)
pendingFocusLine.value = focusLine
pendingFocusPosition.value = 'start'
commitInlineBlockLines(block, replacementLines)
}
/**
* 블록 아래에 줄을 삽입한다.
* @param {Object} block - 블록