관리자 소스 선택 편집 계산 분리

This commit is contained in:
2026-07-27 17:09:14 +09:00
parent a8119d22ca
commit 20ea9169c6
10 changed files with 446 additions and 66 deletions

View File

@@ -16,6 +16,17 @@ import {
getMarkdownLineBodyPrefixLength,
getSelectionOffsetInElement
} from '../../lib/markdown-live-selection.js'
import {
deleteMarkdownSelectedLines,
getMarkdownLineIndexAtOffset,
getMarkdownLineStartOffset,
getMarkdownSelectionState,
insertMarkdownBlockSnippet,
insertMarkdownInlineLink,
replaceMarkdownSelection,
transformMarkdownSelectedLines,
wrapMarkdownInlineSelection
} from '../../lib/markdown-source-selection.js'
import {
getEditorHistoryKeyboardAction,
isEditorModeToggleShortcut,
@@ -583,10 +594,7 @@ const parseCssPixelValue = (value, fallback) => {
* @returns {number} 문자 오프셋
*/
const getLineStartOffset = (lineIndex) => {
const lines = (markdownValue.value ?? '').split('\n')
const safeLineIndex = Math.min(Math.max(0, lineIndex), Math.max(0, lines.length - 1))
return lines.slice(0, safeLineIndex).join('\n').length + (safeLineIndex > 0 ? 1 : 0)
return getMarkdownLineStartOffset(markdownValue.value, lineIndex)
}
/**
@@ -595,10 +603,7 @@ const getLineStartOffset = (lineIndex) => {
* @returns {number} 줄 번호(0-based)
*/
const getLineIndexAtOffset = (offset) => {
const value = markdownValue.value ?? ''
const safeOffset = Math.min(Math.max(0, offset), value.length)
return Math.max(0, value.slice(0, safeOffset).split('\n').length - 1)
return getMarkdownLineIndexAtOffset(markdownValue.value, offset)
}
/**
@@ -1509,22 +1514,10 @@ const focusFirstBlock = () => {
* @returns {{ start: number, end: number, value: string }} 선택 정보
*/
const getSelectionState = () => {
const textarea = getWriteTextareaElement()
const value = markdownValue.value
if (!textarea) {
return {
start: value.length,
end: value.length,
value
}
}
return {
start: textarea.selectionStart,
end: textarea.selectionEnd,
value
}
return getMarkdownSelectionState({
value: markdownValue.value,
textarea: getWriteTextareaElement()
})
}
/**
@@ -1597,9 +1590,17 @@ const setTextareaSelection = (start, end = start, options = {}) => {
*/
const replaceSelection = (replacement, cursorOffset = replacement.length, selectionLength = null) => {
const { start, end, value } = getSelectionState()
markdownValue.value = `${value.slice(0, start)}${replacement}${value.slice(end)}`
const nextStart = start + cursorOffset
setTextareaSelection(nextStart, nextStart + (selectionLength ?? 0))
const result = replaceMarkdownSelection({
value,
start,
end,
replacement,
cursorOffset,
selectionLength
})
markdownValue.value = result.value
setTextareaSelection(result.selectionStart, result.selectionEnd)
}
/**
@@ -1609,10 +1610,15 @@ const replaceSelection = (replacement, cursorOffset = replacement.length, select
*/
const insertBlockSnippet = (snippet) => {
const { start, end, value } = getSelectionState()
const needsBeforeLine = start > 0 && value[start - 1] !== '\n'
const needsAfterLine = end < value.length && value[end] !== '\n'
const replacement = `${needsBeforeLine ? '\n\n' : ''}${snippet}${needsAfterLine ? '\n\n' : ''}`
replaceSelection(replacement)
const result = insertMarkdownBlockSnippet({
value,
start,
end,
snippet
})
markdownValue.value = result.value
setTextareaSelection(result.selectionStart, result.selectionEnd)
}
/**
@@ -1624,12 +1630,17 @@ const insertBlockSnippet = (snippet) => {
*/
const wrapInline = (prefix, suffix, placeholder) => {
const { start, end, value } = getSelectionState()
const selected = value.slice(start, end)
const inner = selected || placeholder
const replacement = `${prefix}${inner}${suffix}`
markdownValue.value = `${value.slice(0, start)}${replacement}${value.slice(end)}`
const selectionStart = start + prefix.length
setTextareaSelection(selectionStart, selectionStart + inner.length)
const result = wrapMarkdownInlineSelection({
value,
start,
end,
prefix,
suffix,
placeholder
})
markdownValue.value = result.value
setTextareaSelection(result.selectionStart, result.selectionEnd)
}
/**
@@ -1638,12 +1649,14 @@ const wrapInline = (prefix, suffix, placeholder) => {
*/
const insertInlineLink = () => {
const { start, end, value } = getSelectionState()
const selected = value.slice(start, end) || '링크 텍스트'
const replacement = `[${selected}](https://)`
const result = insertMarkdownInlineLink({
value,
start,
end
})
markdownValue.value = `${value.slice(0, start)}${replacement}${value.slice(end)}`
const urlStart = start + selected.length + 3
setTextareaSelection(urlStart, urlStart + 'https://'.length)
markdownValue.value = result.value
setTextareaSelection(result.selectionStart, result.selectionEnd)
}
/**
@@ -1653,14 +1666,15 @@ const insertInlineLink = () => {
*/
const transformSelectedLines = (transformLine) => {
const { start, end, value } = getSelectionState()
const lineStart = value.lastIndexOf('\n', Math.max(0, start - 1)) + 1
const nextLineBreak = value.indexOf('\n', end)
const lineEnd = nextLineBreak === -1 ? value.length : nextLineBreak
const selectedBlock = value.slice(lineStart, lineEnd)
const replacement = selectedBlock.split('\n').map(transformLine).join('\n')
const result = transformMarkdownSelectedLines({
value,
start,
end,
transformLine
})
markdownValue.value = `${value.slice(0, lineStart)}${replacement}${value.slice(lineEnd)}`
setTextareaSelection(lineStart, lineStart + replacement.length)
markdownValue.value = result.value
setTextareaSelection(result.selectionStart, result.selectionEnd)
}
/**
@@ -1669,19 +1683,14 @@ const transformSelectedLines = (transformLine) => {
*/
const deleteSelectedSourceLines = () => {
const { start, end, value } = getSelectionState()
const safeStart = Math.min(start, end)
const safeEnd = Math.max(start, end)
const lineStart = value.lastIndexOf('\n', Math.max(0, safeStart - 1)) + 1
const nextLineBreak = value.indexOf('\n', safeEnd)
const lineEnd = nextLineBreak === -1 ? value.length : nextLineBreak + 1
const previousLineBreak = value.lastIndexOf('\n', Math.max(0, lineStart - 2))
const nextValue = `${value.slice(0, lineStart)}${value.slice(lineEnd)}`
const nextSelection = lineStart > 0
? previousLineBreak + 1
: 0
const result = deleteMarkdownSelectedLines({
value,
start,
end
})
markdownValue.value = nextValue
setTextareaSelection(Math.min(nextSelection, nextValue.length))
markdownValue.value = result.value
setTextareaSelection(result.selectionStart, result.selectionEnd)
}
/**