슬래시 명령 실행 분리

This commit is contained in:
2026-07-29 11:43:39 +09:00
parent 393d173af5
commit 77271399cc
10 changed files with 375 additions and 133 deletions

View File

@@ -39,19 +39,15 @@ import {
} from '../../lib/markdown-editor-media.js'
import { parseEditorImageLine } from '../../lib/markdown-editor-preview-blocks.js'
import {
createSlashCommandApplyPlan,
createSlashCommandEditorEffectPlan,
getNextSlashHighlightedIndex,
getSlashCommandFocusTarget,
getSlashCommandTextareaSelection,
getSlashMenuKeyboardAction,
insertMarkdownLinesAtSlashTarget,
parseSlashInput,
resolveSlashCommand,
shouldCaptureSlashMenuKeyboardAction
} from '../../lib/markdown-slash-commands.js'
import { useAdminMarkdownSlashMenu } from '../../composables/useAdminMarkdownSlashMenu.js'
import { useAdminMarkdownSlashMenuPosition } from '../../composables/useAdminMarkdownSlashMenuPosition.js'
import { useAdminMarkdownSlashCommandActions } from '../../composables/useAdminMarkdownSlashCommandActions.js'
import { useAdminMarkdownHistory } from '../../composables/useAdminMarkdownHistory.js'
import { useAdminMarkdownMediaPicker } from '../../composables/useAdminMarkdownMediaPicker.js'
import { useAdminMarkdownFocusBridge } from '../../composables/useAdminMarkdownFocusBridge.js'
@@ -1277,129 +1273,6 @@ const onLiveSlashUpdate = (payload) => {
nextTick(updateLiveSlashMenuPosition)
}
/**
* 슬래시 명령 입력을 종료한다.
* @returns {void}
*/
const onLiveSlashEnd = (payload) => {
if (typeof payload?.sourceLine === 'number') {
releaseLiveSlashLine(payload.sourceLine)
}
clearLiveSlashState()
}
/**
* 슬래시 명령 입력을 취소한다(줄 내용은 유지, 메뉴만 닫음).
* @returns {void}
*/
const cancelLiveSlashCommand = () => {
const line = liveSlashSourceLine.value
if (line !== null) {
const lines = (markdownValue.value ?? '').split('\n')
suppressLiveSlashLine(line, lines[line] ?? '')
}
clearLiveSlashState()
}
/**
* 슬래시 명령 적용 후 미리보기 인라인 편집에 포커스를 복원한다.
* @param {number} line - 줄 번호(0-based)
* @param {string[]} replacementLines - 삽입된 줄
* @returns {void}
*/
const focusPreviewAfterSlashCommand = (line, replacementLines) => {
if (!import.meta.client) {
return
}
const { line: focusLine, offset } = getSlashCommandFocusTarget(line, replacementLines)
nextTick(() => {
nextTick(() => {
requestAnimationFrame(() => {
requestAnimationFrame(() => {
focusPreviewEditableAtLine(focusLine, 0, 'end', offset)
})
})
})
})
}
/**
* 소스 모드 textarea에 슬래시 명령 적용 후 커서를 복원한다.
* @param {number} line - 줄 번호(0-based)
* @param {string[]} replacementLines - 삽입된 줄
* @returns {void}
*/
const focusTextareaAfterSlashCommand = (line, replacementLines) => {
const selection = getSlashCommandTextareaSelection({
markdown: markdownValue.value ?? '',
startLine: line,
lines: replacementLines
})
setTextareaSelection(selection.start, selection.end)
}
/**
* 슬래시 명령을 적용한다.
* @param {import('../../lib/markdown-slash-commands.js').MarkdownSlashCommand} command - 명령
* @returns {void}
*/
const applyLiveSlashCommand = (command) => {
const plan = createSlashCommandApplyPlan({
command,
sourceLine: liveSlashSourceLine.value
})
const effectPlan = createSlashCommandEditorEffectPlan({
plan,
activeMode: activeMode.value
})
if (effectPlan.kind === 'none') {
return
}
if (effectPlan.kind === 'media') {
liveSlashInsertLine.value = effectPlan.line
replaceLineRange(effectPlan.line, effectPlan.line, [], false)
onLiveSlashEnd()
openMediaPicker(effectPlan.mediaKind)
return
}
if (effectPlan.kind === 'lines') {
releaseLiveSlashLine(effectPlan.line)
replaceLineRange(effectPlan.line, effectPlan.line, effectPlan.lines, false)
clearLiveSlashState()
if (effectPlan.focusTarget === 'textarea') {
focusTextareaAfterSlashCommand(effectPlan.line, effectPlan.lines)
} else {
focusPreviewAfterSlashCommand(effectPlan.line, effectPlan.lines)
}
}
}
/**
* 슬래시 명령 Enter 적용
* @param {{ sourceLine: number, value: string }} payload - 줄·입력 값
* @returns {void}
*/
const onLiveSlashApply = (payload) => {
const parsed = parseSlashInput(payload?.value)
const command = resolveSlashCommand(parsed?.query ?? liveSlashQuery.value)
if (!command) {
onLiveSlashEnd({ sourceLine: payload?.sourceLine })
return
}
applyLiveSlashCommand(command)
}
/**
* 지정 줄에 마크다운 줄을 삽입한다.
* @param {number} lineIndex - 삽입 위치(0-based)
@@ -1502,6 +1375,26 @@ const closeMediaPicker = () => {
liveSlashInsertLine.value = null
}
const {
applyLiveSlashCommand,
cancelLiveSlashCommand,
onLiveSlashApply,
onLiveSlashEnd
} = useAdminMarkdownSlashCommandActions({
activeMode,
markdownValue,
liveSlashSourceLine,
liveSlashQuery,
liveSlashInsertLine,
clearLiveSlashState,
releaseLiveSlashLine,
suppressLiveSlashLine,
replaceLineRange,
openMediaPicker,
setTextareaSelection,
focusPreviewEditableAtLine
})
/**
* 미디어 선택 모달 페이지를 변경한다.
* @param {number} page - 이동할 페이지