import { nextTick } from 'vue' import { createCalloutOptionsReplacementPlan, createCodeOptionsReplacementPlan, createEmbedUrlReplacementPlan, createGalleryImageMovePlan, createGalleryImagesAppendPlan, createLiveInlineColorValue, createMediaImageRemovePlan, createMediaImagesReplacementPlan, createMediaImageUpdatePlan, createMediaUseAltPatch, createQuoteBackgroundValue, createSourceInlineColorValue, createToggleOptionsReplacementPlan } from '../lib/markdown-editor-block-options.js' /** * 관리자 Markdown 블록 패널의 옵션 적용 함수를 만든다. * @param {Object} options - 블록 액션 옵션 * @param {import('vue').Ref} options.value - Markdown 본문 ref * @param {import('vue').Ref} options.activeMediaBlock - 현재 미디어 블록 ref * @param {import('vue').Ref} options.activeBlockContext - 현재 활성 블록 ref * @param {import('vue').Ref} options.inlineColorSelectionContext - 인라인 색상 선택 컨텍스트 ref * @param {(plan: Object|null, focusEditor?: boolean) => boolean} options.applyLineReplacementPlan - 줄 교체 계획 적용 함수 * @param {() => void} options.ensureBlockPanelEngaged - 블록 패널 유지 함수 * @param {(images: Array) => void} options.insertGallery - 갤러리 삽입 함수 * @param {(start: number, end?: number, options?: Object) => void} options.setTextareaSelection - textarea 선택 범위 설정 함수 * @param {(focus: Object) => void} options.requestPreviewFocus - 미리보기 포커스 예약 함수 * @param {(line: number, column?: number, align?: string, offset?: number, block?: string) => void} options.focusPreviewEditableAtLine - 미리보기 줄 포커스 함수 * @returns {Object} 블록 패널 옵션 적용 함수 */ export const useAdminMarkdownBlockActions = (options) => { /** * 줄 교체 계획을 블록 패널 유지와 함께 적용한다. * @param {Object|null} plan - 줄 교체 계획 * @param {boolean} [engage=true] - 패널 유지 여부 * @returns {boolean} 적용 여부 */ const applyPanelLineReplacementPlan = (plan, engage = true) => { if (!plan) { return false } if (engage) { options.ensureBlockPanelEngaged() } return options.applyLineReplacementPlan(plan, false) } /** * 현재 미디어 블록을 이미지 목록 기준으로 다시 작성한다. * @param {Array<{ alt: string, url: string, width?: string }>} images - 이미지 목록 * @returns {void} */ const replaceActiveMediaImages = (images) => { options.applyLineReplacementPlan( createMediaImagesReplacementPlan(options.activeMediaBlock.value, images), false ) } /** * 현재 미디어 블록의 특정 이미지를 수정한다. * @param {number} imageIndex - 이미지 인덱스 * @param {Partial<{ alt: string, url: string, width: string, pixelWidth: number|null }>} patch - 변경 값 * @returns {void} */ const updateActiveMediaImage = (imageIndex, patch) => { applyPanelLineReplacementPlan( createMediaImageUpdatePlan(options.activeMediaBlock.value, imageIndex, patch) ) } /** * 현재 미디어 이미지의 파일명 캡션 사용 여부를 바꾼다. * @param {number} imageIndex - 이미지 인덱스 * @param {boolean} enabled - 파일명 캡션 사용 여부 * @returns {void} */ const setActiveMediaUseAlt = (imageIndex, enabled) => { const image = options.activeMediaBlock.value?.images[imageIndex] const patch = createMediaUseAltPatch(image, enabled) if (patch) { updateActiveMediaImage(imageIndex, patch) } } /** * 현재 갤러리 이미지 순서를 바꾼다. * @param {number} imageIndex - 이동할 이미지 인덱스 * @param {-1|1} direction - 이동 방향 * @returns {void} */ const moveActiveGalleryImage = (imageIndex, direction) => { applyPanelLineReplacementPlan( createGalleryImageMovePlan(options.activeMediaBlock.value, imageIndex, direction), false ) } /** * 현재 미디어 블록에서 이미지를 삭제한다. * @param {number} imageIndex - 삭제할 이미지 인덱스 * @returns {void} */ const removeActiveMediaImage = (imageIndex) => { applyPanelLineReplacementPlan( createMediaImageRemovePlan(options.activeMediaBlock.value, imageIndex), false ) } /** * 현재 갤러리에 이미지를 추가한다. * @param {Array<{ url: string, alt?: string, width?: string }>} images - 추가 이미지 목록 * @returns {void} */ const appendImagesToActiveGallery = (images) => { const plan = createGalleryImagesAppendPlan(options.activeMediaBlock.value, images) if (!plan) { options.insertGallery(images) return } options.applyLineReplacementPlan(plan, false) } /** * 현재 링크·iframe·HTML 블록 값을 수정한다. * @param {string} url - 블록 값 * @returns {void} */ const updateActiveEmbedUrl = (url) => { applyPanelLineReplacementPlan( createEmbedUrlReplacementPlan(options.activeBlockContext.value, url) ) } /** * 현재 인용 블록 배경색을 수정한다. * @param {string} background - 배경색 옵션 * @returns {void} */ const updateActiveQuoteBackground = (background) => { const nextValue = createQuoteBackgroundValue({ value: options.value.value, block: options.activeBlockContext.value, background }) if (nextValue === null) { return } options.ensureBlockPanelEngaged() options.value.value = nextValue } /** * 현재 콜아웃 블록 옵션을 수정한다. * @param {Partial<{ calloutEmojiEnabled: boolean, calloutEmoji: string, calloutBackground: string, title: string }>} patch - 변경 옵션 * @returns {void} */ const updateActiveCalloutOptions = (patch = {}) => { applyPanelLineReplacementPlan( createCalloutOptionsReplacementPlan(options.activeBlockContext.value, patch) ) } /** * 현재 코드 블록 옵션을 수정한다. * @param {Partial<{ language: string, showLineNumbers: boolean }>} patch - 변경 옵션 * @returns {void} */ const updateActiveCodeOptions = (patch = {}) => { applyPanelLineReplacementPlan( createCodeOptionsReplacementPlan(options.activeBlockContext.value, patch) ) } /** * 현재 토글 블록 옵션을 수정한다. * @param {Partial<{ defaultOpen: boolean }>} patch - 변경 옵션 * @returns {void} */ const updateActiveToggleOptions = (patch = {}) => { applyPanelLineReplacementPlan( createToggleOptionsReplacementPlan(options.activeBlockContext.value, patch) ) } /** * 현재 선택된 텍스트에 인라인 색상을 적용한다. * @param {string} color - 색상 이름 * @returns {void} */ const updateActiveInlineColor = (color) => { const context = options.inlineColorSelectionContext.value if (!context || context.kind !== 'inline-color') { return } options.ensureBlockPanelEngaged() const sourceResult = createSourceInlineColorValue({ value: options.value.value, context, color }) if (sourceResult) { options.value.value = sourceResult.value options.inlineColorSelectionContext.value = null options.setTextareaSelection(sourceResult.selectionStart, sourceResult.selectionEnd, { preserveScroll: true }) return } const liveResult = createLiveInlineColorValue({ value: options.value.value, context, color }) if (!liveResult) { return } options.value.value = liveResult.value options.inlineColorSelectionContext.value = null options.requestPreviewFocus({ line: liveResult.focusLine, offset: liveResult.focusOffset }) nextTick(() => { options.focusPreviewEditableAtLine( liveResult.focusLine, 0, 'auto', liveResult.focusOffset, 'nearest' ) }) } return { appendImagesToActiveGallery, moveActiveGalleryImage, removeActiveMediaImage, replaceActiveMediaImages, setActiveMediaUseAlt, updateActiveCalloutOptions, updateActiveCodeOptions, updateActiveEmbedUrl, updateActiveInlineColor, updateActiveMediaImage, updateActiveQuoteBackground, updateActiveToggleOptions } }