import { computed, getCurrentInstance, onBeforeUnmount, ref } from 'vue' /** * 공개 Markdown 렌더러의 이미지 라이트박스 상태를 관리한다. * @param {Object} [options] - 설정 * @param {() => (Window|null)} [options.getKeyboardTarget] - 키보드 이벤트 대상 반환 함수 * @returns {{ * activeLightboxImages: import('vue').Ref>, * activeLightboxIndex: import('vue').Ref, * activeLightboxImage: import('vue').ComputedRef, * openLightbox: (images: Array, index: number) => void, * openContentLightbox: (images: Array, lightboxKey: string) => void, * closeLightbox: () => void, * showPreviousImage: () => void, * showNextImage: () => void, * handleLightboxKeydown: (event: KeyboardEvent) => void * }} 라이트박스 상태와 액션 */ export const useContentMarkdownLightbox = (options = {}) => { const getKeyboardTarget = options.getKeyboardTarget || (() => { if (!import.meta.client) { return null } return window }) const activeLightboxImages = ref([]) const activeLightboxIndex = ref(0) const activeLightboxImage = computed(() => activeLightboxImages.value[activeLightboxIndex.value]) let keyboardListenerAttached = false /** * 전역 키보드 이벤트를 연결한다. * @returns {void} */ const attachKeyboardListener = () => { if (keyboardListenerAttached) { return } const target = getKeyboardTarget() if (!target) { return } target.addEventListener('keydown', handleLightboxKeydown) keyboardListenerAttached = true } /** * 전역 키보드 이벤트를 해제한다. * @returns {void} */ const detachKeyboardListener = () => { if (!keyboardListenerAttached) { return } const target = getKeyboardTarget() if (target) { target.removeEventListener('keydown', handleLightboxKeydown) } keyboardListenerAttached = false } /** * 라이트박스를 연다. * @param {Array} images - 이미지 목록 * @param {number} index - 시작 인덱스 * @returns {void} */ const openLightbox = (images, index) => { const nextImages = Array.isArray(images) ? images : [] if (!nextImages.length) { activeLightboxImages.value = [] activeLightboxIndex.value = 0 detachKeyboardListener() return } activeLightboxImages.value = nextImages activeLightboxIndex.value = Math.max(0, Math.min(Number(index) || 0, nextImages.length - 1)) attachKeyboardListener() } /** * 본문 전체 이미지 목록에서 지정 이미지를 라이트박스로 연다. * @param {Array} images - 본문 라이트박스 이미지 목록 * @param {string} lightboxKey - 이미지 식별 키 * @returns {void} */ const openContentLightbox = (images, lightboxKey) => { const targetImages = Array.isArray(images) ? images : [] const index = targetImages.findIndex((image) => image.lightboxKey === lightboxKey) if (index < 0) { return } openLightbox(targetImages, index) } /** * 라이트박스를 닫는다. * @returns {void} */ const closeLightbox = () => { activeLightboxImages.value = [] activeLightboxIndex.value = 0 detachKeyboardListener() } /** * 라이트박스 이전 이미지로 이동한다. * @returns {void} */ const showPreviousImage = () => { if (activeLightboxImages.value.length <= 1) { return } activeLightboxIndex.value = activeLightboxIndex.value === 0 ? activeLightboxImages.value.length - 1 : activeLightboxIndex.value - 1 } /** * 라이트박스 다음 이미지로 이동한다. * @returns {void} */ const showNextImage = () => { if (activeLightboxImages.value.length <= 1) { return } activeLightboxIndex.value = (activeLightboxIndex.value + 1) % activeLightboxImages.value.length } /** * 라이트박스 키보드 조작을 처리한다. * @param {KeyboardEvent} event - 키보드 이벤트 * @returns {void} */ function handleLightboxKeydown (event) { if (!activeLightboxImage.value) { return } if (event.key === 'Escape') { event.preventDefault() closeLightbox() return } if (activeLightboxImages.value.length <= 1) { return } if (event.key === 'ArrowLeft') { event.preventDefault() showPreviousImage() return } if (event.key === 'ArrowRight') { event.preventDefault() showNextImage() } } if (getCurrentInstance()) { onBeforeUnmount(detachKeyboardListener) } return { activeLightboxImages, activeLightboxIndex, activeLightboxImage, openLightbox, openContentLightbox, closeLightbox, showPreviousImage, showNextImage, handleLightboxKeydown } }