import { computed, ref } from 'vue' import { addSlashSuppressedLine, clampSlashHighlightedIndex, filterSlashCommands, removeSlashSuppressedLine } from '../lib/markdown-slash-commands.js' /** * 관리자 Markdown 에디터의 슬래시 메뉴 상태를 관리한다. * @returns {{ * liveSlashSourceLine: import('vue').Ref, * liveSlashQuery: import('vue').Ref, * liveSlashHighlightedIndex: import('vue').Ref, * liveSlashMenuPosition: import('vue').Ref<{ top: number, left: number }>, * liveSlashMenuRef: import('vue').Ref, * liveSlashInsertLine: import('vue').Ref, * liveSlashSuppressedLines: import('vue').Ref>, * liveSlashSuppressedLineValues: import('vue').Ref>, * liveSlashKeyboardNav: import('vue').Ref, * liveSlashVisible: import('vue').ComputedRef, * liveSlashSuppressedLineList: import('vue').ComputedRef, * visibleLiveSlashCommands: import('vue').ComputedRef, * clearLiveSlashState: () => void, * openLiveSlashMenu: (sourceLine: number, query: string) => void, * isLiveSlashLineSuppressed: (line: number, lineValue?: string) => boolean, * suppressLiveSlashLine: (line: number, lineValue?: string) => void, * releaseLiveSlashLine: (line: number) => void, * resetLiveSlashHighlightedIndex: () => void, * clampLiveSlashHighlightedIndex: (commandCount: number) => void, * startLiveSlashKeyboardNavigation: (durationMs?: number) => void, * stopLiveSlashKeyboardNavigation: () => void * }} 슬래시 메뉴 상태와 액션 */ export const useAdminMarkdownSlashMenu = () => { const liveSlashSourceLine = ref(null) const liveSlashQuery = ref('') const liveSlashHighlightedIndex = ref(0) const liveSlashMenuPosition = ref({ top: 0, left: 0 }) const liveSlashMenuRef = ref(null) const liveSlashInsertLine = ref(null) const liveSlashSuppressedLines = ref(new Set()) const liveSlashSuppressedLineValues = ref({}) const liveSlashKeyboardNav = ref(false) let liveSlashKeyboardNavTimer = null const liveSlashVisible = computed(() => liveSlashSourceLine.value !== null) const liveSlashSuppressedLineList = computed(() => [...liveSlashSuppressedLines.value]) const visibleLiveSlashCommands = computed(() => filterSlashCommands(liveSlashQuery.value)) /** * 슬래시 메뉴 입력 상태를 초기화한다. * @returns {void} */ const clearLiveSlashState = () => { liveSlashSourceLine.value = null liveSlashQuery.value = '' liveSlashHighlightedIndex.value = 0 } /** * 슬래시 메뉴를 지정 줄과 검색어로 연다. * @param {number} sourceLine - 슬래시 명령이 입력된 줄 * @param {string} query - 검색어 * @returns {void} */ const openLiveSlashMenu = (sourceLine, query) => { liveSlashSourceLine.value = sourceLine liveSlashQuery.value = String(query ?? '') } /** * 슬래시 메뉴가 억제된 줄인지 확인한다. * @param {number} line - 줄 번호 * @returns {boolean} 억제 여부 */ const isLiveSlashLineSuppressed = (line, lineValue) => { if (!liveSlashSuppressedLines.value.has(line)) { return false } const suppressedValue = liveSlashSuppressedLineValues.value[String(line)] if (suppressedValue === undefined || lineValue === undefined) { return true } return suppressedValue === lineValue } /** * 지정 줄에서 슬래시 메뉴가 다시 열리지 않도록 억제한다. * @param {number} line - 줄 번호 * @param {string} [lineValue] - 억제 당시 줄 내용 * @returns {void} */ const suppressLiveSlashLine = (line, lineValue) => { liveSlashSuppressedLines.value = addSlashSuppressedLine(liveSlashSuppressedLines.value, line) if (Number.isFinite(line)) { liveSlashSuppressedLineValues.value = { ...liveSlashSuppressedLineValues.value, [String(line)]: String(lineValue ?? '') } } } /** * 지정 줄의 슬래시 메뉴 억제를 해제한다. * @param {number} line - 줄 번호 * @returns {void} */ const releaseLiveSlashLine = (line) => { liveSlashSuppressedLines.value = removeSlashSuppressedLine(liveSlashSuppressedLines.value, line) if (Number.isFinite(line)) { const nextValues = { ...liveSlashSuppressedLineValues.value } delete nextValues[String(line)] liveSlashSuppressedLineValues.value = nextValues } } /** * 슬래시 메뉴 하이라이트를 첫 항목으로 되돌린다. * @returns {void} */ const resetLiveSlashHighlightedIndex = () => { liveSlashHighlightedIndex.value = 0 } /** * 슬래시 메뉴 하이라이트를 명령 개수 안으로 보정한다. * @param {number} commandCount - 표시 가능한 명령 개수 * @returns {void} */ const clampLiveSlashHighlightedIndex = (commandCount) => { liveSlashHighlightedIndex.value = clampSlashHighlightedIndex(liveSlashHighlightedIndex.value, commandCount) } /** * 키보드 탐색 중 mouseenter 하이라이트를 잠시 무시한다. * @param {number} [durationMs=200] - 유지 시간(ms) * @returns {void} */ const startLiveSlashKeyboardNavigation = (durationMs = 200) => { liveSlashKeyboardNav.value = true globalThis.clearTimeout(liveSlashKeyboardNavTimer) liveSlashKeyboardNavTimer = globalThis.setTimeout(() => { liveSlashKeyboardNav.value = false }, durationMs) } /** * 슬래시 메뉴 키보드 탐색 타이머를 정리한다. * @returns {void} */ const stopLiveSlashKeyboardNavigation = () => { globalThis.clearTimeout(liveSlashKeyboardNavTimer) liveSlashKeyboardNavTimer = null liveSlashKeyboardNav.value = false } return { liveSlashSourceLine, liveSlashQuery, liveSlashHighlightedIndex, liveSlashMenuPosition, liveSlashMenuRef, liveSlashInsertLine, liveSlashSuppressedLines, liveSlashSuppressedLineValues, liveSlashKeyboardNav, liveSlashVisible, liveSlashSuppressedLineList, visibleLiveSlashCommands, clearLiveSlashState, openLiveSlashMenu, isLiveSlashLineSuppressed, suppressLiveSlashLine, releaseLiveSlashLine, resetLiveSlashHighlightedIndex, clampLiveSlashHighlightedIndex, startLiveSlashKeyboardNavigation, stopLiveSlashKeyboardNavigation } }