미디어 썸네일 재생성 로직 분리
This commit is contained in:
150
composables/useAdminMediaThumbnailRegeneration.js
Normal file
150
composables/useAdminMediaThumbnailRegeneration.js
Normal file
@@ -0,0 +1,150 @@
|
|||||||
|
import { ref } from 'vue'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 관리자 미디어 화면의 썸네일 재생성 상태와 액션을 만든다.
|
||||||
|
* @param {Object} options - 재생성 옵션
|
||||||
|
* @param {Function} options.refresh - 미디어 목록 새로고침 함수
|
||||||
|
* @param {Object} options.selectedMediaUrl - 선택된 미디어 URL ref
|
||||||
|
* @param {Function} options.showToast - 관리자 토스트 표시 함수
|
||||||
|
* @returns {Object} 썸네일 생성 가능 판별과 재생성 액션
|
||||||
|
*/
|
||||||
|
export const useAdminMediaThumbnailRegeneration = ({
|
||||||
|
refresh,
|
||||||
|
selectedMediaUrl,
|
||||||
|
showToast
|
||||||
|
}) => {
|
||||||
|
const regeneratingThumbnailUrl = ref('')
|
||||||
|
const regeneratingBodyThumbnailUrl = ref('')
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 카드 썸네일을 생성할 수 있는 원본 이미지인지 확인한다.
|
||||||
|
* @param {Object} item - 미디어 항목
|
||||||
|
* @returns {boolean} 생성 가능 여부
|
||||||
|
*/
|
||||||
|
const canRegeneratePostThumbnail = (item) => (
|
||||||
|
item?.thumbnailStatus?.role === 'original'
|
||||||
|
&& item?.thumbnailStatus?.variant !== 'pageCover'
|
||||||
|
&& (item.thumbnailStatus.isFallbackActive || item.thumbnailStatus.hasThumbnail)
|
||||||
|
)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 페이지 커버 썸네일을 생성할 수 있는 원본 이미지인지 확인한다.
|
||||||
|
* @param {Object} item - 미디어 항목
|
||||||
|
* @returns {boolean} 생성 가능 여부
|
||||||
|
*/
|
||||||
|
const canRegeneratePageCoverThumbnail = (item) => (
|
||||||
|
item?.thumbnailStatus?.role === 'original'
|
||||||
|
&& item?.thumbnailStatus?.variant === 'pageCover'
|
||||||
|
&& (item.thumbnailStatus.isFallbackActive || item.thumbnailStatus.hasThumbnail)
|
||||||
|
)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 본문 표시용 이미지를 생성할 수 있는 원본 이미지인지 확인한다.
|
||||||
|
* @param {Object} item - 미디어 항목
|
||||||
|
* @returns {boolean} 생성 가능 여부
|
||||||
|
*/
|
||||||
|
const canRegeneratePostBodyThumbnail = (item) => (
|
||||||
|
item?.bodyThumbnailStatus?.role === 'original'
|
||||||
|
&& (item.bodyThumbnailStatus.isFallbackActive || item.bodyThumbnailStatus.hasThumbnail)
|
||||||
|
)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 선택한 원본 이미지의 대표 이미지 파일을 다시 생성한다.
|
||||||
|
* @param {Object} item - 미디어 항목
|
||||||
|
* @returns {Promise<void>}
|
||||||
|
*/
|
||||||
|
const regeneratePostThumbnail = async (item) => {
|
||||||
|
if (!canRegeneratePostThumbnail(item)) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
regeneratingThumbnailUrl.value = item.url
|
||||||
|
|
||||||
|
try {
|
||||||
|
await $fetch('/admin/api/media/thumbnail', {
|
||||||
|
method: 'POST',
|
||||||
|
body: {
|
||||||
|
url: item.url
|
||||||
|
}
|
||||||
|
})
|
||||||
|
await refresh()
|
||||||
|
selectedMediaUrl.value = item.url
|
||||||
|
showToast('success', '대표 이미지 파일을 생성했습니다.')
|
||||||
|
} catch (error) {
|
||||||
|
showToast('error', error?.data?.message || '대표 이미지 파일을 생성하지 못했습니다.')
|
||||||
|
} finally {
|
||||||
|
regeneratingThumbnailUrl.value = ''
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 선택한 원본 이미지의 페이지 커버 썸네일을 다시 생성한다.
|
||||||
|
* @param {Object} item - 미디어 항목
|
||||||
|
* @returns {Promise<void>}
|
||||||
|
*/
|
||||||
|
const regeneratePageCoverThumbnail = async (item) => {
|
||||||
|
if (!canRegeneratePageCoverThumbnail(item)) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
regeneratingThumbnailUrl.value = item.url
|
||||||
|
|
||||||
|
try {
|
||||||
|
await $fetch('/admin/api/media/thumbnail', {
|
||||||
|
method: 'POST',
|
||||||
|
body: {
|
||||||
|
url: item.url,
|
||||||
|
kind: 'pageCover'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
await refresh()
|
||||||
|
selectedMediaUrl.value = item.url
|
||||||
|
showToast('success', '페이지 커버 썸네일을 생성했습니다.')
|
||||||
|
} catch (error) {
|
||||||
|
showToast('error', error?.data?.message || '페이지 커버 썸네일을 생성하지 못했습니다.')
|
||||||
|
} finally {
|
||||||
|
regeneratingThumbnailUrl.value = ''
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 선택한 원본 이미지의 본문 표시용 이미지를 다시 생성한다.
|
||||||
|
* @param {Object} item - 미디어 항목
|
||||||
|
* @returns {Promise<void>}
|
||||||
|
*/
|
||||||
|
const regeneratePostBodyThumbnail = async (item) => {
|
||||||
|
if (!canRegeneratePostBodyThumbnail(item)) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
regeneratingBodyThumbnailUrl.value = item.url
|
||||||
|
|
||||||
|
try {
|
||||||
|
await $fetch('/admin/api/media/thumbnail', {
|
||||||
|
method: 'POST',
|
||||||
|
body: {
|
||||||
|
url: item.url,
|
||||||
|
kind: 'body'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
await refresh()
|
||||||
|
selectedMediaUrl.value = item.url
|
||||||
|
showToast('success', '본문 이미지를 생성했습니다.')
|
||||||
|
} catch (error) {
|
||||||
|
showToast('error', error?.data?.message || '본문 이미지를 생성하지 못했습니다.')
|
||||||
|
} finally {
|
||||||
|
regeneratingBodyThumbnailUrl.value = ''
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
canRegeneratePageCoverThumbnail,
|
||||||
|
canRegeneratePostBodyThumbnail,
|
||||||
|
canRegeneratePostThumbnail,
|
||||||
|
regeneratePageCoverThumbnail,
|
||||||
|
regeneratePostBodyThumbnail,
|
||||||
|
regeneratePostThumbnail,
|
||||||
|
regeneratingBodyThumbnailUrl,
|
||||||
|
regeneratingThumbnailUrl
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -25,6 +25,7 @@
|
|||||||
| composables/useAdminPostTags.js | 관리자 게시물 폼 태그 파싱·추천·선택·삭제·키보드 처리 상태 |
|
| composables/useAdminPostTags.js | 관리자 게시물 폼 태그 파싱·추천·선택·삭제·키보드 처리 상태 |
|
||||||
| composables/useAdminFeaturedImagePicker.js | 관리자 게시물 폼 대표 이미지 선택 모달의 검색·페이지네이션·업로드·선택 상태 |
|
| composables/useAdminFeaturedImagePicker.js | 관리자 게시물 폼 대표 이미지 선택 모달의 검색·페이지네이션·업로드·선택 상태 |
|
||||||
| composables/useAdminPostPublishState.js | 관리자 게시물 폼 발행·예약·발행취소 상태와 날짜 입력 상태 |
|
| composables/useAdminPostPublishState.js | 관리자 게시물 폼 발행·예약·발행취소 상태와 날짜 입력 상태 |
|
||||||
|
| composables/useAdminMediaThumbnailRegeneration.js | 관리자 미디어 화면 대표 이미지·페이지 커버·본문 이미지 재생성 가능 여부와 API 호출 상태 |
|
||||||
|
|
||||||
## 공유 라이브러리(서버·클라이언트 공통)
|
## 공유 라이브러리(서버·클라이언트 공통)
|
||||||
|
|
||||||
|
|||||||
@@ -5,7 +5,6 @@
|
|||||||
- 거대 컴포넌트 책임 분리 진행.
|
- 거대 컴포넌트 책임 분리 진행.
|
||||||
- `components/admin/AdminMarkdownEditor.vue`: 미디어 모달, 슬래시 명령, 히스토리·선택 복원, 라이브/소스 전환 로직을 단계적으로 composable 또는 하위 컴포넌트로 분리.
|
- `components/admin/AdminMarkdownEditor.vue`: 미디어 모달, 슬래시 명령, 히스토리·선택 복원, 라이브/소스 전환 로직을 단계적으로 composable 또는 하위 컴포넌트로 분리.
|
||||||
- `components/content/ContentMarkdownRenderer.vue`: 파싱·블록 렌더링·라이브 편집 중개·라이트박스·광고 삽입 책임을 분리.
|
- `components/content/ContentMarkdownRenderer.vue`: 파싱·블록 렌더링·라이브 편집 중개·라이트박스·광고 삽입 책임을 분리.
|
||||||
- `pages/admin/media/index.vue`: 재생성 액션을 작은 단위로 분리.
|
|
||||||
- 미디어 카탈로그 전환 코드 수명 정리.
|
- 미디어 카탈로그 전환 코드 수명 정리.
|
||||||
- 운영 `MEDIA_CATALOG_MODE=database` 안정화 후 `shadow`/`filesystem` 런타임 분기 축소 가능 여부 검토.
|
- 운영 `MEDIA_CATALOG_MODE=database` 안정화 후 `shadow`/`filesystem` 런타임 분기 축소 가능 여부 검토.
|
||||||
- fallback은 운영 안정성에 필요하므로 제거 전 reconcile·로그·테스트 기준을 먼저 정리.
|
- fallback은 운영 안정성에 필요하므로 제거 전 reconcile·로그·테스트 기준을 먼저 정리.
|
||||||
|
|||||||
@@ -1,5 +1,11 @@
|
|||||||
# 업데이트 이력
|
# 업데이트 이력
|
||||||
|
|
||||||
|
## v1.11.47
|
||||||
|
|
||||||
|
- 관리자 미디어 화면의 대표 이미지·페이지 커버·본문 이미지 재생성 상태와 API 호출을 `useAdminMediaThumbnailRegeneration` composable로 분리.
|
||||||
|
- 썸네일 생성 가능 여부 판별과 생성 중 URL 상태를 한 경로에서 관리하도록 정리.
|
||||||
|
- 미디어 화면은 재생성 버튼 이벤트 연결만 담당하도록 축소.
|
||||||
|
|
||||||
## v1.11.46
|
## v1.11.46
|
||||||
|
|
||||||
- 관리자 미디어 화면의 상세 모달 렌더링을 `AdminMediaDetailModal` 컴포넌트로 분리.
|
- 관리자 미디어 화면의 상세 모달 렌더링을 `AdminMediaDetailModal` 컴포넌트로 분리.
|
||||||
|
|||||||
4
package-lock.json
generated
4
package-lock.json
generated
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "sori.studio",
|
"name": "sori.studio",
|
||||||
"version": "1.11.46",
|
"version": "1.11.47",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "sori.studio",
|
"name": "sori.studio",
|
||||||
"version": "1.11.46",
|
"version": "1.11.47",
|
||||||
"hasInstallScript": true,
|
"hasInstallScript": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@nuxtjs/tailwindcss": "^6.14.0",
|
"@nuxtjs/tailwindcss": "^6.14.0",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "sori.studio",
|
"name": "sori.studio",
|
||||||
"version": "1.11.46",
|
"version": "1.11.47",
|
||||||
"private": true,
|
"private": true,
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"imports": {
|
"imports": {
|
||||||
|
|||||||
@@ -53,8 +53,6 @@ const editingUrl = ref('')
|
|||||||
const editingName = ref('')
|
const editingName = ref('')
|
||||||
const editingCategory = ref('')
|
const editingCategory = ref('')
|
||||||
const deletingUrl = ref('')
|
const deletingUrl = ref('')
|
||||||
const regeneratingThumbnailUrl = ref('')
|
|
||||||
const regeneratingBodyThumbnailUrl = ref('')
|
|
||||||
const selectedMediaUrl = ref('')
|
const selectedMediaUrl = ref('')
|
||||||
const selectedMediaUrls = ref([])
|
const selectedMediaUrls = ref([])
|
||||||
const lastSelectedIndex = ref(-1)
|
const lastSelectedIndex = ref(-1)
|
||||||
@@ -123,6 +121,21 @@ const { data: mediaResponse, refresh } = await useFetch('/admin/api/media', {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const {
|
||||||
|
canRegeneratePageCoverThumbnail,
|
||||||
|
canRegeneratePostBodyThumbnail,
|
||||||
|
canRegeneratePostThumbnail,
|
||||||
|
regeneratePageCoverThumbnail,
|
||||||
|
regeneratePostBodyThumbnail,
|
||||||
|
regeneratePostThumbnail,
|
||||||
|
regeneratingBodyThumbnailUrl,
|
||||||
|
regeneratingThumbnailUrl
|
||||||
|
} = useAdminMediaThumbnailRegeneration({
|
||||||
|
refresh,
|
||||||
|
selectedMediaUrl,
|
||||||
|
showToast
|
||||||
|
})
|
||||||
|
|
||||||
const mediaItems = computed(() => Array.isArray(mediaResponse.value)
|
const mediaItems = computed(() => Array.isArray(mediaResponse.value)
|
||||||
? mediaResponse.value
|
? mediaResponse.value
|
||||||
: (mediaResponse.value?.items || []))
|
: (mediaResponse.value?.items || []))
|
||||||
@@ -397,38 +410,6 @@ const getMediaUsageBadgeCount = (item) => {
|
|||||||
return usageCount + avatarOwnerCount
|
return usageCount + avatarOwnerCount
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 카드 썸네일을 생성할 수 있는 원본 이미지인지 확인한다.
|
|
||||||
* @param {Object} item - 미디어 항목
|
|
||||||
* @returns {boolean} 생성 가능 여부
|
|
||||||
*/
|
|
||||||
const canRegeneratePostThumbnail = (item) => (
|
|
||||||
item?.thumbnailStatus?.role === 'original'
|
|
||||||
&& item?.thumbnailStatus?.variant !== 'pageCover'
|
|
||||||
&& (item.thumbnailStatus.isFallbackActive || item.thumbnailStatus.hasThumbnail)
|
|
||||||
)
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 페이지 커버 썸네일을 생성할 수 있는 원본 이미지인지 확인한다.
|
|
||||||
* @param {Object} item - 미디어 항목
|
|
||||||
* @returns {boolean} 생성 가능 여부
|
|
||||||
*/
|
|
||||||
const canRegeneratePageCoverThumbnail = (item) => (
|
|
||||||
item?.thumbnailStatus?.role === 'original'
|
|
||||||
&& item?.thumbnailStatus?.variant === 'pageCover'
|
|
||||||
&& (item.thumbnailStatus.isFallbackActive || item.thumbnailStatus.hasThumbnail)
|
|
||||||
)
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 본문 표시용 이미지를 생성할 수 있는 원본 이미지인지 확인한다.
|
|
||||||
* @param {Object} item - 미디어 항목
|
|
||||||
* @returns {boolean} 생성 가능 여부
|
|
||||||
*/
|
|
||||||
const canRegeneratePostBodyThumbnail = (item) => (
|
|
||||||
item?.bodyThumbnailStatus?.role === 'original'
|
|
||||||
&& (item.bodyThumbnailStatus.isFallbackActive || item.bodyThumbnailStatus.hasThumbnail)
|
|
||||||
)
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 미디어 항목 종류를 반환한다.
|
* 미디어 항목 종류를 반환한다.
|
||||||
* @param {Object} item - 미디어 항목
|
* @param {Object} item - 미디어 항목
|
||||||
@@ -1103,95 +1084,6 @@ const deleteMedia = async (item) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 선택한 원본 이미지의 대표 이미지 파일을 다시 생성한다.
|
|
||||||
* @param {Object} item - 미디어 항목
|
|
||||||
* @returns {Promise<void>}
|
|
||||||
*/
|
|
||||||
const regeneratePostThumbnail = async (item) => {
|
|
||||||
if (!canRegeneratePostThumbnail(item)) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
regeneratingThumbnailUrl.value = item.url
|
|
||||||
|
|
||||||
try {
|
|
||||||
await $fetch('/admin/api/media/thumbnail', {
|
|
||||||
method: 'POST',
|
|
||||||
body: {
|
|
||||||
url: item.url
|
|
||||||
}
|
|
||||||
})
|
|
||||||
await refresh()
|
|
||||||
selectedMediaUrl.value = item.url
|
|
||||||
showToast('success', '대표 이미지 파일을 생성했습니다.')
|
|
||||||
} catch (error) {
|
|
||||||
showToast('error', error?.data?.message || '대표 이미지 파일을 생성하지 못했습니다.')
|
|
||||||
} finally {
|
|
||||||
regeneratingThumbnailUrl.value = ''
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 선택한 원본 이미지의 페이지 커버 썸네일을 다시 생성한다.
|
|
||||||
* @param {Object} item - 미디어 항목
|
|
||||||
* @returns {Promise<void>}
|
|
||||||
*/
|
|
||||||
const regeneratePageCoverThumbnail = async (item) => {
|
|
||||||
if (!canRegeneratePageCoverThumbnail(item)) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
regeneratingThumbnailUrl.value = item.url
|
|
||||||
|
|
||||||
try {
|
|
||||||
await $fetch('/admin/api/media/thumbnail', {
|
|
||||||
method: 'POST',
|
|
||||||
body: {
|
|
||||||
url: item.url,
|
|
||||||
kind: 'pageCover'
|
|
||||||
}
|
|
||||||
})
|
|
||||||
await refresh()
|
|
||||||
selectedMediaUrl.value = item.url
|
|
||||||
showToast('success', '페이지 커버 썸네일을 생성했습니다.')
|
|
||||||
} catch (error) {
|
|
||||||
showToast('error', error?.data?.message || '페이지 커버 썸네일을 생성하지 못했습니다.')
|
|
||||||
} finally {
|
|
||||||
regeneratingThumbnailUrl.value = ''
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 선택한 원본 이미지의 본문 표시용 이미지를 다시 생성한다.
|
|
||||||
* @param {Object} item - 미디어 항목
|
|
||||||
* @returns {Promise<void>}
|
|
||||||
*/
|
|
||||||
const regeneratePostBodyThumbnail = async (item) => {
|
|
||||||
if (!canRegeneratePostBodyThumbnail(item)) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
regeneratingBodyThumbnailUrl.value = item.url
|
|
||||||
|
|
||||||
try {
|
|
||||||
await $fetch('/admin/api/media/thumbnail', {
|
|
||||||
method: 'POST',
|
|
||||||
body: {
|
|
||||||
url: item.url,
|
|
||||||
kind: 'body'
|
|
||||||
}
|
|
||||||
})
|
|
||||||
await refresh()
|
|
||||||
selectedMediaUrl.value = item.url
|
|
||||||
showToast('success', '본문 이미지를 생성했습니다.')
|
|
||||||
} catch (error) {
|
|
||||||
showToast('error', error?.data?.message || '본문 이미지를 생성하지 못했습니다.')
|
|
||||||
} finally {
|
|
||||||
regeneratingBodyThumbnailUrl.value = ''
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 선택한 미디어 중 삭제 가능한 항목을 한 번에 삭제한다.
|
* 선택한 미디어 중 삭제 가능한 항목을 한 번에 삭제한다.
|
||||||
* @returns {Promise<void>}
|
* @returns {Promise<void>}
|
||||||
|
|||||||
Reference in New Issue
Block a user