공개 본문 라이트박스 상태 분리
This commit is contained in:
191
composables/useContentMarkdownLightbox.js
Normal file
191
composables/useContentMarkdownLightbox.js
Normal file
@@ -0,0 +1,191 @@
|
||||
import { computed, getCurrentInstance, onBeforeUnmount, ref } from 'vue'
|
||||
|
||||
/**
|
||||
* 공개 Markdown 렌더러의 이미지 라이트박스 상태를 관리한다.
|
||||
* @param {Object} [options] - 설정
|
||||
* @param {() => (Window|null)} [options.getKeyboardTarget] - 키보드 이벤트 대상 반환 함수
|
||||
* @returns {{
|
||||
* activeLightboxImages: import('vue').Ref<Array<Object>>,
|
||||
* activeLightboxIndex: import('vue').Ref<number>,
|
||||
* activeLightboxImage: import('vue').ComputedRef<Object|undefined>,
|
||||
* openLightbox: (images: Array<Object>, index: number) => void,
|
||||
* openContentLightbox: (images: Array<Object>, 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<Object>} 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<Object>} 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
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user