라이브 미리보기 블록 계산 분리
This commit is contained in:
@@ -2,8 +2,6 @@
|
||||
import { normalizeMarkdownContent } from '../../lib/markdown-content-normalizer.js'
|
||||
import { resolveActiveBlockContext } from '../../lib/markdown-block-context.js'
|
||||
import {
|
||||
isImageUrl,
|
||||
parseImageMarkdownLine,
|
||||
serializeImageMarkdown
|
||||
} from '../../lib/markdown-image.js'
|
||||
import {
|
||||
@@ -54,6 +52,18 @@ import {
|
||||
createSourceInlineColorValue,
|
||||
createToggleOptionsReplacementPlan
|
||||
} from '../../lib/markdown-editor-block-options.js'
|
||||
import {
|
||||
createPreviewAppendParagraphValue,
|
||||
createPreviewDeleteLineValue,
|
||||
createPreviewExtractGalleryImageValue,
|
||||
createPreviewGalleryReorderValue,
|
||||
createPreviewImagesMergeValue,
|
||||
createPreviewInsertAfterLineResult,
|
||||
createPreviewInsertImageToGalleryValue,
|
||||
createPreviewMergeWithPreviousLineValue,
|
||||
createPreviewRemoveGalleryImageResult,
|
||||
parseEditorImageLine
|
||||
} from '../../lib/markdown-editor-preview-blocks.js'
|
||||
import {
|
||||
createSlashCommandApplyPlan,
|
||||
createSlashCommandEditorEffectPlan,
|
||||
@@ -1093,33 +1103,6 @@ const insertCodeBlock = () => {
|
||||
*/
|
||||
const createImageMarkdown = (image) => serializeImageMarkdown(image)
|
||||
|
||||
/**
|
||||
* 마크다운 이미지 줄과 단독 이미지 URL 줄을 같은 이미지 데이터로 해석한다.
|
||||
* @param {string} line - 원본 마크다운 줄
|
||||
* @returns {{ url: string, width: string, pixelWidth: number|null, caption: string, useAlt: boolean }|null} 이미지 데이터
|
||||
*/
|
||||
const parseEditorImageLine = (line) => {
|
||||
const image = parseImageMarkdownLine(line)
|
||||
|
||||
if (image) {
|
||||
return image
|
||||
}
|
||||
|
||||
const trimmed = String(line || '').trim()
|
||||
|
||||
if (!isImageUrl(trimmed)) {
|
||||
return null
|
||||
}
|
||||
|
||||
return {
|
||||
url: trimmed,
|
||||
width: 'regular',
|
||||
pixelWidth: null,
|
||||
caption: '',
|
||||
useAlt: false
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 미디어 선택 모달 파생 이미지 로드 실패 시 원본 URL로 되돌린다.
|
||||
* @param {Event} event - 이미지 오류 이벤트
|
||||
@@ -1250,15 +1233,16 @@ const setActiveMediaUseAlt = (imageIndex, enabled) => {
|
||||
* @returns {void}
|
||||
*/
|
||||
const onPreviewGalleryReorder = ({ startLine, endLine, images }) => {
|
||||
if (typeof startLine !== 'number' || typeof endLine !== 'number' || !Array.isArray(images)) {
|
||||
return
|
||||
}
|
||||
const nextValue = createPreviewGalleryReorderValue({
|
||||
value: markdownValue.value,
|
||||
startLine,
|
||||
endLine,
|
||||
images
|
||||
})
|
||||
|
||||
replaceLineRange(startLine, endLine, [
|
||||
':::gallery',
|
||||
...images.map(createImageMarkdown),
|
||||
':::'
|
||||
], false)
|
||||
if (nextValue !== null) {
|
||||
markdownValue.value = nextValue
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1267,33 +1251,15 @@ const onPreviewGalleryReorder = ({ startLine, endLine, images }) => {
|
||||
* @returns {void}
|
||||
*/
|
||||
const onPreviewMergeImagesToGallery = ({ sourceLine, targetLine }) => {
|
||||
if (typeof sourceLine !== 'number' || typeof targetLine !== 'number' || sourceLine === targetLine) {
|
||||
return
|
||||
const nextValue = createPreviewImagesMergeValue({
|
||||
value: markdownValue.value,
|
||||
sourceLine,
|
||||
targetLine
|
||||
})
|
||||
|
||||
if (nextValue !== null) {
|
||||
markdownValue.value = nextValue
|
||||
}
|
||||
|
||||
const lines = (markdownValue.value || '').split('\n')
|
||||
const firstLine = Math.min(sourceLine, targetLine)
|
||||
const secondLine = Math.max(sourceLine, targetLine)
|
||||
const firstImage = parseEditorImageLine(lines[firstLine] || '')
|
||||
const secondImage = parseEditorImageLine(lines[secondLine] || '')
|
||||
|
||||
if (!firstImage || !secondImage) {
|
||||
return
|
||||
}
|
||||
|
||||
const orderedImages = sourceLine < targetLine
|
||||
? [firstImage, secondImage]
|
||||
: [secondImage, firstImage]
|
||||
|
||||
const nextLines = [
|
||||
...lines.slice(0, firstLine),
|
||||
':::gallery',
|
||||
...orderedImages.map(createImageMarkdown),
|
||||
':::',
|
||||
...lines.slice(secondLine + 1)
|
||||
]
|
||||
|
||||
markdownValue.value = nextLines.join('\n')
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1308,43 +1274,18 @@ const onPreviewInsertImageToGallery = ({
|
||||
targetIndex,
|
||||
image
|
||||
}) => {
|
||||
if (typeof sourceLine !== 'number'
|
||||
|| typeof startLine !== 'number'
|
||||
|| typeof endLine !== 'number'
|
||||
|| typeof targetIndex !== 'number'
|
||||
|| sourceLine >= startLine && sourceLine <= endLine) {
|
||||
return
|
||||
const nextValue = createPreviewInsertImageToGalleryValue({
|
||||
value: markdownValue.value,
|
||||
sourceLine,
|
||||
startLine,
|
||||
endLine,
|
||||
targetIndex,
|
||||
image
|
||||
})
|
||||
|
||||
if (nextValue !== null) {
|
||||
markdownValue.value = nextValue
|
||||
}
|
||||
|
||||
const lines = (markdownValue.value || '').split('\n')
|
||||
const sourceImage = image && image.url ? image : parseEditorImageLine(lines[sourceLine] || '')
|
||||
const galleryImages = lines
|
||||
.slice(startLine + 1, endLine)
|
||||
.map((line) => parseEditorImageLine(line))
|
||||
.filter(Boolean)
|
||||
|
||||
if (!sourceImage || !galleryImages.length) {
|
||||
return
|
||||
}
|
||||
|
||||
const nextGalleryImages = [...galleryImages]
|
||||
const insertIndex = Math.max(0, Math.min(targetIndex, nextGalleryImages.length))
|
||||
nextGalleryImages.splice(insertIndex, 0, sourceImage)
|
||||
|
||||
const withoutSource = [
|
||||
...lines.slice(0, sourceLine),
|
||||
...lines.slice(sourceLine + 1)
|
||||
]
|
||||
const adjustedStartLine = startLine - (sourceLine < startLine ? 1 : 0)
|
||||
const adjustedEndLine = endLine - (sourceLine < endLine ? 1 : 0)
|
||||
|
||||
markdownValue.value = [
|
||||
...withoutSource.slice(0, adjustedStartLine),
|
||||
':::gallery',
|
||||
...nextGalleryImages.map(createImageMarkdown),
|
||||
':::',
|
||||
...withoutSource.slice(adjustedEndLine + 1)
|
||||
].join('\n')
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1353,33 +1294,19 @@ const onPreviewInsertImageToGallery = ({
|
||||
* @returns {void}
|
||||
*/
|
||||
const onPreviewRemoveGalleryImage = ({ startLine, endLine, imageIndex }) => {
|
||||
if (typeof startLine !== 'number' || typeof endLine !== 'number' || typeof imageIndex !== 'number') {
|
||||
const result = createPreviewRemoveGalleryImageResult({
|
||||
value: markdownValue.value,
|
||||
startLine,
|
||||
endLine,
|
||||
imageIndex
|
||||
})
|
||||
|
||||
if (!result) {
|
||||
return
|
||||
}
|
||||
|
||||
const lines = (markdownValue.value || '').split('\n')
|
||||
const galleryImages = lines
|
||||
.slice(startLine + 1, endLine)
|
||||
.map((line) => parseEditorImageLine(line))
|
||||
.filter(Boolean)
|
||||
|
||||
if (imageIndex < 0 || imageIndex >= galleryImages.length) {
|
||||
return
|
||||
}
|
||||
|
||||
const remaining = galleryImages.filter((_, index) => index !== imageIndex)
|
||||
const replacementLines = remaining.length === 0
|
||||
? []
|
||||
: remaining.length === 1
|
||||
? [createImageMarkdown(remaining[0])]
|
||||
: [
|
||||
':::gallery',
|
||||
...remaining.map(createImageMarkdown),
|
||||
':::'
|
||||
]
|
||||
|
||||
replaceLineRange(startLine, endLine, replacementLines, false)
|
||||
activeLogicalLineIndex.value = Math.max(0, Math.min(startLine + imageIndex, startLine + replacementLines.length - 1))
|
||||
markdownValue.value = result.value
|
||||
activeLogicalLineIndex.value = result.focusLine
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1394,61 +1321,18 @@ const onPreviewExtractGalleryImage = ({
|
||||
insertBeforeLine,
|
||||
image
|
||||
}) => {
|
||||
if (typeof startLine !== 'number'
|
||||
|| typeof endLine !== 'number'
|
||||
|| typeof imageIndex !== 'number'
|
||||
|| typeof insertBeforeLine !== 'number') {
|
||||
return
|
||||
const nextValue = createPreviewExtractGalleryImageValue({
|
||||
value: markdownValue.value,
|
||||
startLine,
|
||||
endLine,
|
||||
imageIndex,
|
||||
insertBeforeLine,
|
||||
image
|
||||
})
|
||||
|
||||
if (nextValue !== null) {
|
||||
markdownValue.value = nextValue
|
||||
}
|
||||
|
||||
const lines = (markdownValue.value || '').split('\n')
|
||||
const galleryImages = lines
|
||||
.slice(startLine + 1, endLine)
|
||||
.map((line) => parseEditorImageLine(line))
|
||||
.filter(Boolean)
|
||||
|
||||
if (imageIndex < 0 || imageIndex >= galleryImages.length) {
|
||||
return
|
||||
}
|
||||
|
||||
const extracted = image && image.url ? image : galleryImages[imageIndex]
|
||||
const remaining = galleryImages.filter((_, index) => index !== imageIndex)
|
||||
const galleryReplacement = remaining.length === 0
|
||||
? []
|
||||
: remaining.length === 1
|
||||
? [createImageMarkdown(remaining[0])]
|
||||
: [
|
||||
':::gallery',
|
||||
...remaining.map(createImageMarkdown),
|
||||
':::'
|
||||
]
|
||||
|
||||
const withoutGallery = [
|
||||
...lines.slice(0, startLine),
|
||||
...lines.slice(endLine + 1)
|
||||
]
|
||||
|
||||
const removedSpan = endLine - startLine + 1
|
||||
const addedSpan = galleryReplacement.length
|
||||
let insertAt = insertBeforeLine
|
||||
|
||||
if (insertBeforeLine > endLine) {
|
||||
insertAt -= removedSpan - addedSpan
|
||||
} else if (insertBeforeLine > startLine) {
|
||||
insertAt = startLine + addedSpan
|
||||
}
|
||||
|
||||
insertAt = Math.max(0, Math.min(insertAt, withoutGallery.length))
|
||||
withoutGallery.splice(insertAt, 0, createImageMarkdown(extracted))
|
||||
|
||||
let galleryAt = startLine
|
||||
|
||||
if (startLine > insertAt) {
|
||||
galleryAt += 1
|
||||
}
|
||||
|
||||
withoutGallery.splice(galleryAt, 0, ...galleryReplacement)
|
||||
markdownValue.value = withoutGallery.join('\n')
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1490,9 +1374,7 @@ const onPreviewContentReplace = ({ value }) => {
|
||||
* @returns {void}
|
||||
*/
|
||||
const onPreviewAppendParagraph = () => {
|
||||
const value = markdownValue.value ?? ''
|
||||
const trimmed = value.replace(/\n+$/, '')
|
||||
markdownValue.value = trimmed ? `${trimmed}\n\n` : ''
|
||||
markdownValue.value = createPreviewAppendParagraphValue(markdownValue.value)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1501,26 +1383,23 @@ const onPreviewAppendParagraph = () => {
|
||||
* @returns {void}
|
||||
*/
|
||||
const onPreviewInsertAfterLine = ({ afterLine, lines, focusLine }) => {
|
||||
if (typeof afterLine !== 'number' || !Array.isArray(lines)) {
|
||||
const result = createPreviewInsertAfterLineResult({
|
||||
value: markdownValue.value,
|
||||
afterLine,
|
||||
lines,
|
||||
focusLine
|
||||
})
|
||||
|
||||
if (!result) {
|
||||
return
|
||||
}
|
||||
|
||||
const sourceLines = (markdownValue.value ?? '').split('\n')
|
||||
const nextFocusLine = typeof focusLine === 'number'
|
||||
? focusLine
|
||||
: afterLine + 1
|
||||
|
||||
markdownValue.value = [
|
||||
...sourceLines.slice(0, afterLine + 1),
|
||||
...lines,
|
||||
...sourceLines.slice(afterLine + 1)
|
||||
].join('\n')
|
||||
|
||||
activeLogicalLineIndex.value = nextFocusLine
|
||||
markdownValue.value = result.value
|
||||
activeLogicalLineIndex.value = result.focusLine
|
||||
resetBlockPanelContext()
|
||||
|
||||
nextTick(() => {
|
||||
focusPreviewEditableAtLine(nextFocusLine, 0, 'start')
|
||||
focusPreviewEditableAtLine(result.focusLine, 0, 'start')
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1530,22 +1409,14 @@ const onPreviewInsertAfterLine = ({ afterLine, lines, focusLine }) => {
|
||||
* @returns {void}
|
||||
*/
|
||||
const onPreviewDeleteLine = (lineIndex) => {
|
||||
if (typeof lineIndex !== 'number' || lineIndex < 0) {
|
||||
return
|
||||
const nextValue = createPreviewDeleteLineValue({
|
||||
value: markdownValue.value,
|
||||
lineIndex
|
||||
})
|
||||
|
||||
if (nextValue !== null) {
|
||||
markdownValue.value = nextValue
|
||||
}
|
||||
|
||||
const sourceLines = (markdownValue.value ?? '').split('\n')
|
||||
|
||||
if (lineIndex >= sourceLines.length) {
|
||||
return
|
||||
}
|
||||
|
||||
const nextLines = [
|
||||
...sourceLines.slice(0, lineIndex),
|
||||
...sourceLines.slice(lineIndex + 1)
|
||||
]
|
||||
|
||||
markdownValue.value = nextLines.length ? nextLines.join('\n') : ''
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1594,11 +1465,6 @@ const handlePreviewKeydownCapture = (event) => {
|
||||
onPreviewDeleteLine(lineIndex)
|
||||
}
|
||||
|
||||
/**
|
||||
* 라이브 모드에서 현재 줄을 이전 줄 끝에 병합한다.
|
||||
* @param {{ lineIndex: number, mergedLine: string }} payload - 병합 위치·결과 줄
|
||||
* @returns {void}
|
||||
*/
|
||||
/**
|
||||
* 슬래시 메뉴 컴포넌트가 노출한 실제 DOM 엘리먼트를 반환한다.
|
||||
* @returns {HTMLElement|null} 메뉴 엘리먼트
|
||||
@@ -1980,22 +1846,21 @@ const insertMarkdownAtLine = (lineIndex, insertLines) => {
|
||||
markdownValue.value = insertMarkdownLinesAtSlashTarget(markdownValue.value, lineIndex, insertLines)
|
||||
}
|
||||
|
||||
/**
|
||||
* 라이브 모드에서 현재 줄을 이전 줄 끝에 병합한다.
|
||||
* @param {{ lineIndex: number, mergedLine: string }} payload - 병합 위치·결과 줄
|
||||
* @returns {void}
|
||||
*/
|
||||
const onPreviewMergeWithPreviousLine = ({ lineIndex, mergedLine }) => {
|
||||
if (typeof lineIndex !== 'number' || lineIndex <= 0) {
|
||||
return
|
||||
const nextValue = createPreviewMergeWithPreviousLineValue({
|
||||
value: markdownValue.value,
|
||||
lineIndex,
|
||||
mergedLine
|
||||
})
|
||||
|
||||
if (nextValue !== null) {
|
||||
markdownValue.value = nextValue
|
||||
}
|
||||
|
||||
const sourceLines = (markdownValue.value ?? '').split('\n')
|
||||
|
||||
if (lineIndex >= sourceLines.length) {
|
||||
return
|
||||
}
|
||||
|
||||
markdownValue.value = [
|
||||
...sourceLines.slice(0, lineIndex - 1),
|
||||
String(mergedLine ?? ''),
|
||||
...sourceLines.slice(lineIndex + 1)
|
||||
].join('\n')
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user