import { unref } from 'vue' import { formatQuoteLine, getQuoteContentStartLine, normalizeQuoteEditableValue } from '../lib/markdown-quote.js' /** * 관리자 라이브 Markdown 인용 편집 이벤트 상태를 만든다. * @param {Object} options - 인용 편집 옵션 * @param {import('vue').Ref|string} options.content - Markdown 본문 * @param {import('vue').Ref} options.pendingFocusLine - 다음 포커스 줄 * @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} 인용 이벤트 핸들러 */ export const useContentMarkdownQuotes = (options) => { /** * commit 이벤트 페이로드를 정규화한다. * @param {string|{ value: string, raw?: boolean }} payload - 페이로드 * @returns {{ value: string, raw: boolean }} */ const normalizeCommitPayload = (payload) => { if (typeof payload === 'string') { return { value: payload, raw: false } } return { value: String(payload?.value ?? ''), raw: payload?.raw === true } } /** * 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 {Object} block - 블록 * @returns {string[]} 줄 목록 */ const getBlockSourceLines = (block) => String(unref(options.content) || '').split('\n').slice( block.meta.startLine, (block.meta.endLine ?? block.meta.startLine) + 1 ) /** * 인용 블록 원본에서 옵션 선언 줄을 반환한다. * @param {Object} block - 인용 블록 * @returns {string[]} 옵션 선언 줄 */ const getQuoteOptionLines = (block) => { const contentStartLine = getQuoteContentStartLine(block) if (contentStartLine <= block.meta.startLine) { return [] } return getBlockSourceLines(block).slice(0, contentStartLine - block.meta.startLine) } /** * 인용 본문 문자열을 마크다운 줄로 변환한다. * @param {Object} block - 인용 블록 * @param {string|{ value?: string }} payload - 편집 페이로드 * @returns {string[]} 인용 마크다운 줄 */ const buildQuoteBlockLines = (block, payload) => { const value = typeof payload === 'string' ? payload : String(payload?.value ?? '') const bodyLines = String(value ?? '').replace(/\r/g, '').split('\n') const normalizedBodyLines = bodyLines.length ? bodyLines : [''] return [ ...getQuoteOptionLines(block), ...normalizedBodyLines.map((line) => formatQuoteLine(line, false)) ] } /** * 인용 블록 편집 반영 * @param {Object} block - 인용 블록 * @param {string|{ value?: string }} payload - 편집 페이로드 * @returns {void} */ const onQuoteBlockCommit = (block, payload) => { const normalizedPayload = typeof payload === 'object' && payload !== null ? { ...payload, value: normalizeQuoteEditableValue(payload.value) } : normalizeQuoteEditableValue(payload) options.commitInlineBlockLines(block, buildQuoteBlockLines(block, normalizedPayload)) } /** * 인용 블록 마지막 줄에서 아래로 이탈한다. * @param {Object} block - 인용 블록 * @param {string|Object} payload - insert-below 페이로드 * @returns {void} */ const onQuoteBlockInsertBelow = (block, payload) => { const { value } = normalizeInsertBelowPayload(payload) onQuoteBlockCommit(block, value) options.pendingFocusPosition.value = 'start' options.pendingFocusOffset.value = 0 options.insertBelowBlock(block, { lines: [''] }) } /** * 인용 블록을 일반 문단 줄로 되돌린다. * @param {Object} block - 인용 블록 * @param {string|Object} payload - 편집 페이로드 * @returns {void} */ const onQuoteBlockConvertToParagraph = (block, payload) => { const { value } = normalizeCommitPayload(payload) const lines = normalizeQuoteEditableValue(value).split('\n') const replacementLines = lines.length ? lines : [''] options.pendingFocusLine.value = block.meta.startLine options.pendingFocusPosition.value = 'start' options.commitInlineBlockLines(block, replacementLines) } return { buildQuoteBlockLines, onQuoteBlockCommit, onQuoteBlockInsertBelow, onQuoteBlockConvertToParagraph } }