슬래시 명령 실행 분리
This commit is contained in:
172
composables/useAdminMarkdownSlashCommandActions.js
Normal file
172
composables/useAdminMarkdownSlashCommandActions.js
Normal file
@@ -0,0 +1,172 @@
|
||||
import { nextTick, unref } from 'vue'
|
||||
import {
|
||||
createSlashCommandApplyPlan,
|
||||
createSlashCommandEditorEffectPlan,
|
||||
getSlashCommandFocusTarget,
|
||||
getSlashCommandTextareaSelection,
|
||||
parseSlashInput,
|
||||
resolveSlashCommand
|
||||
} from '../lib/markdown-slash-commands.js'
|
||||
|
||||
/**
|
||||
* 관리자 Markdown 에디터의 슬래시 명령 적용 실행을 관리한다.
|
||||
* @param {Object} options - 슬래시 명령 실행 옵션
|
||||
* @param {import('vue').Ref<string>} options.activeMode - 현재 에디터 모드
|
||||
* @param {import('vue').Ref<string>} options.markdownValue - 마크다운 본문
|
||||
* @param {import('vue').Ref<number|null>} options.liveSlashSourceLine - 슬래시 입력 줄
|
||||
* @param {import('vue').Ref<string>} options.liveSlashQuery - 슬래시 검색어
|
||||
* @param {import('vue').Ref<number|null>} options.liveSlashInsertLine - 미디어 삽입 대기 줄
|
||||
* @param {() => void} options.clearLiveSlashState - 슬래시 상태 초기화 함수
|
||||
* @param {(line: number) => void} options.releaseLiveSlashLine - 슬래시 억제 해제 함수
|
||||
* @param {(line: number, lineValue?: string) => void} options.suppressLiveSlashLine - 슬래시 억제 함수
|
||||
* @param {(startLine: number, endLine: number, replacementLines: string[], focusEditor?: boolean) => void} options.replaceLineRange - 줄 교체 함수
|
||||
* @param {(mediaKind: 'image'|'gallery'|'video'|'audio'|'file') => void} options.openMediaPicker - 미디어 모달 열기 함수
|
||||
* @param {(start: number, end: number) => void} options.setTextareaSelection - textarea 선택 복원 함수
|
||||
* @param {(line: number, column?: number, position?: string, offset?: number) => void} options.focusPreviewEditableAtLine - 미리보기 줄 포커스 함수
|
||||
* @param {boolean} [options.isClient] - 클라이언트 실행 여부
|
||||
* @returns {{
|
||||
* applyLiveSlashCommand: (command: import('../lib/markdown-slash-commands.js').MarkdownSlashCommand) => void,
|
||||
* cancelLiveSlashCommand: () => void,
|
||||
* focusPreviewAfterSlashCommand: (line: number, replacementLines: string[]) => void,
|
||||
* focusTextareaAfterSlashCommand: (line: number, replacementLines: string[]) => void,
|
||||
* onLiveSlashApply: (payload?: { sourceLine?: number, value?: string }) => void,
|
||||
* onLiveSlashEnd: (payload?: { sourceLine?: number }) => void
|
||||
* }} 슬래시 명령 실행 액션
|
||||
*/
|
||||
export const useAdminMarkdownSlashCommandActions = (options) => {
|
||||
const isClient = options.isClient ?? import.meta.client
|
||||
|
||||
/**
|
||||
* 슬래시 명령 입력을 종료한다.
|
||||
* @param {{ sourceLine?: number }} [payload] - 종료 대상 줄
|
||||
* @returns {void}
|
||||
*/
|
||||
const onLiveSlashEnd = (payload) => {
|
||||
if (typeof payload?.sourceLine === 'number') {
|
||||
options.releaseLiveSlashLine(payload.sourceLine)
|
||||
}
|
||||
|
||||
options.clearLiveSlashState()
|
||||
}
|
||||
|
||||
/**
|
||||
* 슬래시 명령 입력을 취소한다(줄 내용은 유지, 메뉴만 닫음).
|
||||
* @returns {void}
|
||||
*/
|
||||
const cancelLiveSlashCommand = () => {
|
||||
const line = options.liveSlashSourceLine.value
|
||||
|
||||
if (line !== null) {
|
||||
const lines = String(unref(options.markdownValue) ?? '').split('\n')
|
||||
options.suppressLiveSlashLine(line, lines[line] ?? '')
|
||||
}
|
||||
|
||||
options.clearLiveSlashState()
|
||||
}
|
||||
|
||||
/**
|
||||
* 슬래시 명령 적용 후 미리보기 인라인 편집에 포커스를 복원한다.
|
||||
* @param {number} line - 줄 번호(0-based)
|
||||
* @param {string[]} replacementLines - 삽입된 줄
|
||||
* @returns {void}
|
||||
*/
|
||||
const focusPreviewAfterSlashCommand = (line, replacementLines) => {
|
||||
if (!isClient) {
|
||||
return
|
||||
}
|
||||
|
||||
const { line: focusLine, offset } = getSlashCommandFocusTarget(line, replacementLines)
|
||||
const requestFrame = globalThis.requestAnimationFrame ?? ((callback) => globalThis.setTimeout(callback, 0))
|
||||
|
||||
nextTick(() => {
|
||||
nextTick(() => {
|
||||
requestFrame(() => {
|
||||
requestFrame(() => {
|
||||
options.focusPreviewEditableAtLine(focusLine, 0, 'end', offset)
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 소스 모드 textarea에 슬래시 명령 적용 후 커서를 복원한다.
|
||||
* @param {number} line - 줄 번호(0-based)
|
||||
* @param {string[]} replacementLines - 삽입된 줄
|
||||
* @returns {void}
|
||||
*/
|
||||
const focusTextareaAfterSlashCommand = (line, replacementLines) => {
|
||||
const selection = getSlashCommandTextareaSelection({
|
||||
markdown: unref(options.markdownValue) ?? '',
|
||||
startLine: line,
|
||||
lines: replacementLines
|
||||
})
|
||||
options.setTextareaSelection(selection.start, selection.end)
|
||||
}
|
||||
|
||||
/**
|
||||
* 슬래시 명령을 적용한다.
|
||||
* @param {import('../lib/markdown-slash-commands.js').MarkdownSlashCommand} command - 명령
|
||||
* @returns {void}
|
||||
*/
|
||||
const applyLiveSlashCommand = (command) => {
|
||||
const plan = createSlashCommandApplyPlan({
|
||||
command,
|
||||
sourceLine: options.liveSlashSourceLine.value
|
||||
})
|
||||
const effectPlan = createSlashCommandEditorEffectPlan({
|
||||
plan,
|
||||
activeMode: options.activeMode.value
|
||||
})
|
||||
|
||||
if (effectPlan.kind === 'none') {
|
||||
return
|
||||
}
|
||||
|
||||
if (effectPlan.kind === 'media') {
|
||||
options.liveSlashInsertLine.value = effectPlan.line
|
||||
options.replaceLineRange(effectPlan.line, effectPlan.line, [], false)
|
||||
onLiveSlashEnd()
|
||||
options.openMediaPicker(effectPlan.mediaKind)
|
||||
return
|
||||
}
|
||||
|
||||
if (effectPlan.kind === 'lines') {
|
||||
options.releaseLiveSlashLine(effectPlan.line)
|
||||
options.replaceLineRange(effectPlan.line, effectPlan.line, effectPlan.lines, false)
|
||||
options.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 ?? options.liveSlashQuery.value)
|
||||
|
||||
if (!command) {
|
||||
onLiveSlashEnd({ sourceLine: payload?.sourceLine })
|
||||
return
|
||||
}
|
||||
|
||||
applyLiveSlashCommand(command)
|
||||
}
|
||||
|
||||
return {
|
||||
applyLiveSlashCommand,
|
||||
cancelLiveSlashCommand,
|
||||
focusPreviewAfterSlashCommand,
|
||||
focusTextareaAfterSlashCommand,
|
||||
onLiveSlashApply,
|
||||
onLiveSlashEnd
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user