import { serializeImageMarkdown } from '../lib/markdown-image.js' import { createSelectedMediaInsertPlan } from '../lib/markdown-editor-media.js' import { insertMarkdownLinesAtSlashTarget } from '../lib/markdown-slash-commands.js' /** * 관리자 Markdown 에디터의 미디어 삽입 실행 함수를 만든다. * @param {Object} options - 실행 옵션 * @param {{ value: string }} options.value - 마크다운 본문 ref * @param {{ value: string }} options.mediaPickerTarget - 미디어 선택 대상 ref * @param {{ value: number|null }} options.liveSlashInsertLine - 슬래시 미디어 삽입 대기 줄 ref * @param {{ value: boolean }} options.isUploading - 업로드 중 여부 ref * @param {{ value: boolean }} options.isMediaPickerOpen - 미디어 선택 창 열림 여부 ref * @param {{ value: string }} options.activeMediaPickerTab - 미디어 선택 창 탭 ref * @param {() => Array} options.resolveCurrentSelectedMediaItems - 현재 선택 미디어 반환 함수 * @param {(files: FileList|Array) => Promise>} options.uploadMediaFilesWithState - 파일 업로드 함수 * @param {(files: FileList|Array) => Promise>} options.uploadFilesIntoMediaPicker - 미디어 선택 창 업로드 함수 * @param {() => Promise} options.fetchMediaItems - 미디어 목록 조회 함수 * @param {() => void} options.closeMediaPicker - 미디어 선택 창 닫기 함수 * @param {(snippet: string) => void} options.insertBlockSnippet - 블록 조각 삽입 함수 * @param {(images: Array) => void} options.appendImagesToActiveGallery - 활성 갤러리 이미지 추가 함수 * @param {(type: 'success'|'error'|'info', message: string) => void} options.showToast - 토스트 표시 함수 * @param {(error: unknown) => string} options.resolveUploadFetchErrorMessage - 업로드 오류 문구 변환 함수 * @returns {Object} 미디어 삽입 실행 함수 모음 */ export const useAdminMarkdownMediaActions = ({ value, mediaPickerTarget, liveSlashInsertLine, isUploading, isMediaPickerOpen, activeMediaPickerTab, resolveCurrentSelectedMediaItems, uploadMediaFilesWithState, uploadFilesIntoMediaPicker, fetchMediaItems, closeMediaPicker, insertBlockSnippet, appendImagesToActiveGallery, showToast, resolveUploadFetchErrorMessage }) => { /** * 이미지 마크다운 문자열을 생성한다. * @param {{ url: string, alt?: string, width?: string }} image - 이미지 정보 * @returns {string} 이미지 마크다운 */ const createImageMarkdown = (image) => serializeImageMarkdown(image) /** * 지정 줄에 마크다운 줄을 삽입한다. * @param {number} lineIndex - 삽입 위치 * @param {string[]} insertLines - 삽입 줄 * @returns {void} */ const insertMarkdownAtLine = (lineIndex, insertLines) => { value.value = insertMarkdownLinesAtSlashTarget(value.value, lineIndex, insertLines) } /** * 이미지 마크다운을 삽입한다. * @param {{ url: string, alt?: string, width?: string }} image - 이미지 정보 * @returns {void} */ const insertImage = (image) => { insertBlockSnippet(createImageMarkdown(image)) } /** * 갤러리 마크다운을 삽입한다. * @param {Array<{ url: string, alt?: string, width?: string }>} images - 이미지 목록 * @returns {void} */ const insertGallery = (images) => { if (!images.length) { return } insertBlockSnippet([':::gallery', ...images.map(createImageMarkdown), ':::'].join('\n')) } /** * 미디어 삽입 계획을 본문에 적용한다. * @param {{ kind: string, line?: number, lines?: string[], image?: Object, images?: Object[] }} plan - 삽입 계획 * @returns {void} */ const applyMediaInsertPlan = (plan) => { if (plan.kind === 'insert-at-line') { insertMarkdownAtLine(plan.line, plan.lines) } else if (plan.kind === 'image') { insertImage(plan.image) } else if (plan.kind === 'active-gallery') { appendImagesToActiveGallery(plan.images) } else if (plan.kind === 'gallery') { insertGallery(plan.images) } else if (plan.kind === 'block') { insertBlockSnippet(plan.lines.join('\n')) } } /** * 미디어 항목 목록을 마크다운에 삽입한다. * @param {Array} selectedItems - 선택 또는 업로드된 미디어 항목 * @param {string} [target] - 삽입 대상 * @returns {void} */ const insertSelectedMediaItems = (selectedItems, target = mediaPickerTarget.value) => { const pendingSlashLine = liveSlashInsertLine.value const plan = createSelectedMediaInsertPlan({ target, selectedItems, liveSlashLine: pendingSlashLine }) applyMediaInsertPlan(plan) if (pendingSlashLine !== null) { liveSlashInsertLine.value = null } } /** * 선택한 미디어를 마크다운에 삽입한다. * @returns {void} */ const applyMediaSelection = () => { insertSelectedMediaItems(resolveCurrentSelectedMediaItems()) closeMediaPicker() } /** * 업로드 파일을 현재 커서 위치에 삽입한다. * @param {FileList|Array} files - 파일 목록 * @param {'image'|'gallery'|'video'|'audio'|'file'} [target] - 삽입 대상 * @returns {Promise} */ const uploadAndInsert = async (files, target = 'image') => { if (!files?.length || isUploading.value) { return } try { const uploadedFiles = await uploadMediaFilesWithState(files) if (!uploadedFiles.length) { return } insertSelectedMediaItems(uploadedFiles, target) if (isMediaPickerOpen.value && activeMediaPickerTab.value === 'library') { await fetchMediaItems() } } catch (error) { showToast('error', resolveUploadFetchErrorMessage(error)) } } /** * 미디어 모달 업로드 탭에서 파일을 업로드하고 라이브러리 목록을 갱신한다. * @param {FileList|Array} files - 업로드 파일 목록 * @returns {Promise} */ const uploadFromMediaModal = async (files) => { if (!files?.length || isUploading.value) { return } try { const uploadedFiles = await uploadFilesIntoMediaPicker(files) if (!uploadedFiles.length) { return } showToast('success', '업로드가 완료되어 파일이 선택되었습니다.') } catch (error) { showToast('error', resolveUploadFetchErrorMessage(error)) } } return { insertMarkdownAtLine, insertImage, insertGallery, insertSelectedMediaItems, applyMediaSelection, uploadAndInsert, uploadFromMediaModal } }