import { unref } from 'vue' import { buildCodeBlockLines } from '../lib/markdown-code-block.js' import { buildToggleBlockLines } from '../lib/markdown-toggle.js' /** * 관리자 라이브 Markdown fenced 블록 편집 이벤트 상태를 만든다. * @param {Object} options - fenced 블록 편집 옵션 * @param {import('vue').Ref|boolean} options.interactive - 라이브 편집 여부 * @param {import('vue').Ref|string} options.content - Markdown 본문 * @param {import('vue').Ref<'start'|'end'|'auto'>} options.pendingFocusPosition - 다음 포커스 커서 위치 * @param {import('vue').Ref} options.pendingFocusOffset - 다음 포커스 오프셋 * @param {(block: Object, replacementLines: string[]) => void} options.commitInlineBlockLines - 블록 줄 반영 콜백 * @param {(block: Object, options?: { lines?: string[] }) => void} options.insertBelowBlock - 블록 아래 삽입 콜백 * @returns {Object} fenced 블록 이벤트 핸들러 */ export const useContentMarkdownFencedBlocks = (options) => { /** * insert-below 이벤트 페이로드를 정규화한다. * @param {string|Object} payload - 페이로드 * @returns {{ value: string, raw: boolean, before: string, after: string, caretAtStart: boolean, caretAtEnd: boolean }} */ const normalizeInsertBelowPayload = (payload) => { if (typeof payload === 'string') { return { value: payload, raw: false, before: '', after: '', caretAtStart: false, caretAtEnd: true } } return { value: String(payload?.value ?? ''), raw: payload?.raw === true, before: String(payload?.before ?? ''), after: String(payload?.after ?? ''), caretAtStart: payload?.caretAtStart === true, caretAtEnd: payload?.caretAtEnd === true } } /** * 원본 마크다운 줄을 반환한다. * @param {number} lineIndex - 줄 번호 * @returns {string} 줄 텍스트 */ const getMarkdownLine = (lineIndex) => String(unref(options.content) || '').split('\n')[lineIndex] ?? '' /** * 인라인 편집 결과를 마크다운 줄로 반영한다. * @param {Object} block - 블록 * @param {string[]} replacementLines - 대체 줄 * @returns {void} */ const commitInlineBlockLines = (block, replacementLines) => { if (!unref(options.interactive)) { return } options.commitInlineBlockLines(block, replacementLines) } /** * 다음 외부 문단으로 포커스 위치를 준비한다. * @returns {void} */ const prepareNextParagraphFocus = () => { options.pendingFocusPosition.value = 'start' options.pendingFocusOffset.value = 0 } /** * 코드 블록 줄 번호 목록을 만든다. * @param {Object} block - 코드 블록 * @returns {number[]} 줄 번호(1부터) */ const getCodeLineNumbers = (block) => { const text = String(block.text ?? '') const lineCount = text.length ? text.split('\n').length : 1 return Array.from({ length: lineCount }, (_, index) => index + 1) } /** * 코드 블록 편집 반영 * @param {Object} block - 코드 블록 * @param {string[]} replacementLines - 마크다운 줄 * @returns {void} */ const onCodeBlockCommit = (block, replacementLines) => { commitInlineBlockLines(block, replacementLines) } /** * 코드 블록 마지막 줄에서 아래로 이탈한다. * @param {Object} block - 코드 블록 * @param {string|Object} payload - insert-below 페이로드 * @returns {void} */ const onCodeBlockInsertBelow = (block, payload) => { const { value } = normalizeInsertBelowPayload(payload) onCodeBlockCommit(block, buildCodeBlockLines({ language: block.codeLanguage, showLineNumbers: block.codeShowLineNumbers, body: value })) options.insertBelowBlock(block, { lines: [''] }) } /** * 콜아웃 편집 반영 * @param {Object} block - 콜아웃 블록 * @param {string[]} replacementLines - 마크다운 줄 * @returns {void} */ const onCalloutBlockCommit = (block, replacementLines) => { commitInlineBlockLines(block, replacementLines) } /** * 콜아웃 마지막 줄에서 아래로 이탈한다. * @param {Object} block - 콜아웃 블록 * @param {string|Object} payload - insert-below 페이로드 * @returns {void} */ const onCalloutBlockInsertBelow = (block, payload) => { const { value } = normalizeInsertBelowPayload(payload) const openingLine = getMarkdownLine(block.meta.startLine) onCalloutBlockCommit(block, [ openingLine, ...String(value ?? '').replace(/\r/g, '').split('\n'), ':::' ]) prepareNextParagraphFocus() options.insertBelowBlock(block, { lines: [''] }) } /** * 토글 편집 반영 * @param {Object} block - 토글 블록 * @param {string[]} replacementLines - 마크다운 줄 * @returns {void} */ const onToggleBlockCommit = (block, replacementLines) => { commitInlineBlockLines(block, replacementLines) } /** * 토글 본문 마지막 줄에서 아래로 이탈한다. * @param {Object} block - 토글 블록 * @param {string|Object} payload - insert-below 페이로드 * @returns {void} */ const onToggleBlockInsertBelow = (block, payload) => { const { value } = normalizeInsertBelowPayload(payload) onToggleBlockCommit(block, buildToggleBlockLines({ title: block.title, defaultOpen: block.defaultOpen, body: value })) prepareNextParagraphFocus() options.insertBelowBlock(block, { lines: [''] }) } return { getCodeLineNumbers, onCodeBlockCommit, onCodeBlockInsertBelow, onCalloutBlockCommit, onCalloutBlockInsertBelow, onToggleBlockCommit, onToggleBlockInsertBelow } }