272 lines
8.8 KiB
JavaScript
272 lines
8.8 KiB
JavaScript
import { computed, ref, unref } from 'vue'
|
|
import {
|
|
buildMediaPickerApplyState,
|
|
buildMediaPickerEmptyMessage,
|
|
buildMediaPickerPageItems,
|
|
buildMediaPickerRangeLabel,
|
|
buildMediaPickerUploadHint,
|
|
createDefaultMediaPickerPagination,
|
|
getGallerySelectionOrder as getGallerySelectionOrderFromSelection,
|
|
getMediaPickerAcceptForTarget,
|
|
getMediaPickerKindForTarget,
|
|
getMediaPickerTitleForTarget,
|
|
isGalleryMediaPickerTarget,
|
|
isMediaItemSelectableForTarget,
|
|
isUploadFileAllowedForMediaTarget,
|
|
normalizeMediaPickerPage,
|
|
normalizeMediaPickerResponse,
|
|
resolveSelectedMediaItems,
|
|
toggleMediaSelectionState
|
|
} from '../lib/markdown-editor-media.js'
|
|
import { useImeSearchInput } from './useImeSearchInput.js'
|
|
|
|
export const ADMIN_MARKDOWN_MEDIA_PICKER_PAGE_SIZE = 50
|
|
|
|
/**
|
|
* 관리자 Markdown 에디터의 미디어 선택 모달 상태를 관리한다.
|
|
* @param {Object} options - 상태 옵션
|
|
* @param {import('vue').Ref<Object>|import('vue').ComputedRef<Object>} options.uploadSizeLimits - 업로드 한도
|
|
* @param {number} [options.pageSize=50] - 페이지 크기
|
|
* @returns {Object} 미디어 선택 모달 상태와 액션
|
|
*/
|
|
export const useAdminMarkdownMediaPicker = ({
|
|
uploadSizeLimits,
|
|
pageSize = ADMIN_MARKDOWN_MEDIA_PICKER_PAGE_SIZE
|
|
}) => {
|
|
const mediaItems = ref([])
|
|
const isMediaPickerOpen = ref(false)
|
|
const isLoadingMedia = ref(false)
|
|
const isUploading = ref(false)
|
|
const mediaPickerTarget = ref('image')
|
|
const activeMediaPickerTab = ref('upload')
|
|
const mediaSearchQuery = ref('')
|
|
const mediaPickerPage = ref(1)
|
|
const mediaPickerPagination = ref(createDefaultMediaPickerPagination(pageSize))
|
|
const selectedMediaUrls = ref([])
|
|
const selectedMediaItemMap = ref({})
|
|
const {
|
|
displayValue: mediaSearchInput,
|
|
onInput: onMediaSearchInput,
|
|
onCompositionEnd: onMediaSearchCompositionEnd
|
|
} = useImeSearchInput(mediaSearchQuery, { debounce: 250 })
|
|
|
|
const mediaPickerAccept = computed(() => getMediaPickerAcceptForTarget(mediaPickerTarget.value))
|
|
const mediaPickerKind = computed(() => getMediaPickerKindForTarget(mediaPickerTarget.value))
|
|
const mediaPickerTitle = computed(() => getMediaPickerTitleForTarget(mediaPickerTarget.value))
|
|
const isGalleryMediaPicker = computed(() => isGalleryMediaPickerTarget(mediaPickerTarget.value))
|
|
const mediaPickerPageCount = computed(() => Math.max(1, Number(mediaPickerPagination.value.pageCount) || 1))
|
|
const filteredMediaItems = computed(() => mediaItems.value.filter(isMediaItemSelectableForCurrentTarget))
|
|
const mediaPickerRangeLabel = computed(() => buildMediaPickerRangeLabel({
|
|
pagination: mediaPickerPagination.value,
|
|
currentPage: mediaPickerPage.value,
|
|
pageCount: mediaPickerPageCount.value,
|
|
defaultPageSize: pageSize
|
|
}))
|
|
const mediaPickerPageItems = computed(() => buildMediaPickerPageItems({
|
|
currentPage: mediaPickerPage.value,
|
|
pageCount: mediaPickerPageCount.value
|
|
}))
|
|
const mediaPickerUploadHint = computed(() => buildMediaPickerUploadHint(
|
|
mediaPickerTarget.value,
|
|
unref(uploadSizeLimits)
|
|
))
|
|
const mediaPickerEmptyMessage = computed(() => buildMediaPickerEmptyMessage(mediaSearchQuery.value))
|
|
const mediaPickerApplyState = computed(() => buildMediaPickerApplyState({
|
|
selectedUrls: selectedMediaUrls.value,
|
|
isUploading: isUploading.value,
|
|
isGallery: isGalleryMediaPicker.value
|
|
}))
|
|
|
|
/**
|
|
* 미디어 항목이 현재 선택 대상과 맞는지 확인한다.
|
|
* @param {Object} item - 미디어 항목
|
|
* @returns {boolean} 선택 가능 여부
|
|
*/
|
|
function isMediaItemSelectableForCurrentTarget(item) {
|
|
return isMediaItemSelectableForTarget(item, mediaPickerTarget.value)
|
|
}
|
|
|
|
/**
|
|
* 업로드 파일이 현재 선택 대상에 맞는지 확인한다.
|
|
* @param {File} file - 브라우저 파일
|
|
* @returns {boolean} 허용 여부
|
|
*/
|
|
const isUploadFileAllowedForPicker = (file) => {
|
|
return isUploadFileAllowedForMediaTarget(file, mediaPickerTarget.value)
|
|
}
|
|
|
|
/**
|
|
* 미디어 선택 모달 상태를 연다.
|
|
* @param {string} target - 삽입 대상
|
|
* @returns {void}
|
|
*/
|
|
const openMediaPickerState = (target) => {
|
|
mediaPickerTarget.value = target
|
|
activeMediaPickerTab.value = 'upload'
|
|
mediaSearchQuery.value = ''
|
|
mediaPickerPage.value = 1
|
|
mediaPickerPagination.value = createDefaultMediaPickerPagination(pageSize)
|
|
selectedMediaUrls.value = []
|
|
selectedMediaItemMap.value = {}
|
|
isMediaPickerOpen.value = true
|
|
}
|
|
|
|
/**
|
|
* 미디어 선택 모달 상태를 닫는다.
|
|
* @returns {boolean} 닫힘 처리 여부
|
|
*/
|
|
const closeMediaPickerState = () => {
|
|
if (isUploading.value) {
|
|
return false
|
|
}
|
|
|
|
isMediaPickerOpen.value = false
|
|
selectedMediaUrls.value = []
|
|
selectedMediaItemMap.value = {}
|
|
activeMediaPickerTab.value = 'upload'
|
|
mediaSearchQuery.value = ''
|
|
mediaPickerPage.value = 1
|
|
mediaItems.value = []
|
|
mediaPickerPagination.value = createDefaultMediaPickerPagination(pageSize)
|
|
return true
|
|
}
|
|
|
|
/**
|
|
* 미디어 API 응답을 현재 상태에 반영한다.
|
|
* @param {Object} response - 미디어 API 응답
|
|
* @returns {void}
|
|
*/
|
|
const applyMediaPickerResponse = (response) => {
|
|
const normalizedResponse = normalizeMediaPickerResponse(response, pageSize)
|
|
mediaItems.value = normalizedResponse.items
|
|
mediaPickerPagination.value = normalizedResponse.pagination
|
|
mediaPickerPage.value = mediaPickerPagination.value.page || 1
|
|
}
|
|
|
|
/**
|
|
* 미디어 선택 페이지를 변경한다.
|
|
* @param {number} page - 이동할 페이지
|
|
* @returns {void}
|
|
*/
|
|
const setMediaPickerPage = (page) => {
|
|
mediaPickerPage.value = normalizeMediaPickerPage(page, mediaPickerPageCount.value)
|
|
}
|
|
|
|
/**
|
|
* 미디어 선택 상태를 토글한다.
|
|
* @param {Object} mediaItem - 미디어 항목
|
|
* @returns {void}
|
|
*/
|
|
const toggleMediaSelection = (mediaItem) => {
|
|
const nextSelection = toggleMediaSelectionState({
|
|
selectedUrls: selectedMediaUrls.value,
|
|
selectedItemMap: selectedMediaItemMap.value,
|
|
mediaItem,
|
|
isGallery: isGalleryMediaPicker.value
|
|
})
|
|
|
|
selectedMediaUrls.value = nextSelection.selectedUrls
|
|
selectedMediaItemMap.value = nextSelection.selectedItemMap
|
|
}
|
|
|
|
/**
|
|
* 선택된 미디어 항목을 현재 목록과 선택 맵에서 해석한다.
|
|
* @returns {Object[]} 선택된 미디어 항목
|
|
*/
|
|
const resolveCurrentSelectedMediaItems = () => resolveSelectedMediaItems(
|
|
selectedMediaUrls.value,
|
|
selectedMediaItemMap.value,
|
|
mediaItems.value
|
|
)
|
|
|
|
/**
|
|
* 업로드 직후 선택 상태를 갱신한다.
|
|
* @param {Object[]} uploadedFiles - 업로드된 파일 목록
|
|
* @returns {string[]} 선택된 URL 목록
|
|
*/
|
|
const selectUploadedMediaFiles = (uploadedFiles) => {
|
|
const uploadedUrls = (Array.isArray(uploadedFiles) ? uploadedFiles : [])
|
|
.filter(isMediaItemSelectableForCurrentTarget)
|
|
.map((file) => file.url)
|
|
|
|
selectedMediaItemMap.value = (Array.isArray(uploadedFiles) ? uploadedFiles : []).reduce((map, file) => {
|
|
if (uploadedUrls.includes(file.url)) {
|
|
map[file.url] = file
|
|
}
|
|
|
|
return map
|
|
}, {})
|
|
|
|
selectedMediaUrls.value = isGalleryMediaPicker.value
|
|
? uploadedUrls
|
|
: uploadedUrls.slice(0, 1)
|
|
|
|
return uploadedUrls
|
|
}
|
|
|
|
/**
|
|
* 업로드된 파일을 목록 앞에 임시로 합친다.
|
|
* @param {Object[]} uploadedFiles - 업로드된 파일 목록
|
|
* @returns {void}
|
|
*/
|
|
const prependUploadedMediaItems = (uploadedFiles) => {
|
|
mediaItems.value = [
|
|
...(Array.isArray(uploadedFiles) ? uploadedFiles : []),
|
|
...mediaItems.value
|
|
]
|
|
}
|
|
|
|
/**
|
|
* 갤러리 미디어 선택 순서를 반환한다.
|
|
* @param {string} url - 미디어 URL
|
|
* @returns {number} 1부터 시작하는 선택 순서
|
|
*/
|
|
const getGallerySelectionOrder = (url) => {
|
|
if (!isGalleryMediaPicker.value) {
|
|
return 0
|
|
}
|
|
|
|
return getGallerySelectionOrderFromSelection(selectedMediaUrls.value, url)
|
|
}
|
|
|
|
return {
|
|
mediaItems,
|
|
isMediaPickerOpen,
|
|
isLoadingMedia,
|
|
isUploading,
|
|
mediaPickerTarget,
|
|
activeMediaPickerTab,
|
|
mediaSearchQuery,
|
|
mediaSearchInput,
|
|
onMediaSearchInput,
|
|
onMediaSearchCompositionEnd,
|
|
mediaPickerPage,
|
|
mediaPickerPageSize: pageSize,
|
|
mediaPickerPagination,
|
|
selectedMediaUrls,
|
|
selectedMediaItemMap,
|
|
mediaPickerAccept,
|
|
mediaPickerKind,
|
|
mediaPickerTitle,
|
|
isGalleryMediaPicker,
|
|
mediaPickerPageCount,
|
|
filteredMediaItems,
|
|
mediaPickerRangeLabel,
|
|
mediaPickerPageItems,
|
|
mediaPickerUploadHint,
|
|
mediaPickerEmptyMessage,
|
|
mediaPickerApplyState,
|
|
isMediaItemSelectableForCurrentTarget,
|
|
isUploadFileAllowedForPicker,
|
|
openMediaPickerState,
|
|
closeMediaPickerState,
|
|
applyMediaPickerResponse,
|
|
setMediaPickerPage,
|
|
toggleMediaSelection,
|
|
resolveCurrentSelectedMediaItems,
|
|
selectUploadedMediaFiles,
|
|
prependUploadedMediaItems,
|
|
getGallerySelectionOrder
|
|
}
|
|
}
|