diff --git a/components/admin/AdminPostForm.vue b/components/admin/AdminPostForm.vue index 1d59548..fc14722 100644 --- a/components/admin/AdminPostForm.vue +++ b/components/admin/AdminPostForm.vue @@ -21,8 +21,10 @@ const slugTouched = ref(Boolean(props.initialPost.slug)) const blockEditor = ref(null) const mediaItems = ref([]) const isMediaPickerOpen = ref(false) +const mediaPickerTarget = ref('featuredImage') const isLoadingMedia = ref(false) const isUploadingFeaturedImage = ref(false) +const isUploadingOgImage = ref(false) const autosaveTimer = ref(null) const autosaveNotice = ref(null) const autosaveStatus = ref('') @@ -78,6 +80,7 @@ const form = reactive({ seoDescription: props.initialPost.seoDescription || '', canonicalUrl: props.initialPost.canonicalUrl || '', noindex: Boolean(props.initialPost.noindex), + ogImage: props.initialPost.ogImage || '', status: props.initialPost.status || 'draft', publishedAt: toDateTimeLocalValue(props.initialPost.publishedAt), tagsText: props.initialPost.tags?.join(', ') || '' @@ -152,6 +155,7 @@ const createPostPayload = () => { seoDescription: form.seoDescription.trim(), canonicalUrl: form.canonicalUrl.trim(), noindex: form.noindex, + ogImage: form.ogImage.trim() || null, status: form.status, publishedAt, tags: parseTags(form.tagsText) @@ -172,6 +176,7 @@ const createAutosavePayload = () => ({ seoDescription: form.seoDescription, canonicalUrl: form.canonicalUrl, noindex: form.noindex, + ogImage: form.ogImage, status: form.status, publishedAt: form.publishedAt, tagsText: form.tagsText @@ -188,6 +193,10 @@ const isEmptyAutosavePayload = (payload) => ![ payload.excerpt, payload.content, payload.featuredImage, + payload.seoTitle, + payload.seoDescription, + payload.canonicalUrl, + payload.ogImage, payload.tagsText ].some((value) => String(value || '').trim()) @@ -293,7 +302,8 @@ const fetchMediaItems = async () => { * 대표 이미지 선택 창 열기 * @returns {Promise} */ -const openMediaPicker = async () => { +const openMediaPicker = async (target = 'featuredImage') => { + mediaPickerTarget.value = target isMediaPickerOpen.value = true await fetchMediaItems() } @@ -311,8 +321,8 @@ const closeMediaPicker = () => { * @param {Object} item - 미디어 항목 * @returns {void} */ -const selectFeaturedImage = (item) => { - form.featuredImage = item.url +const selectPickedImage = (item) => { + form[mediaPickerTarget.value] = item.url closeMediaPicker() } @@ -324,6 +334,14 @@ const removeFeaturedImage = () => { form.featuredImage = '' } +/** + * OG 이미지 삭제 + * @returns {void} + */ +const removeOgImage = () => { + form.ogImage = '' +} + /** * 대표 이미지 파일 업로드 * @param {Event} event - 파일 입력 이벤트 @@ -352,6 +370,34 @@ const uploadFeaturedImage = async (event) => { } } +/** + * OG 이미지 파일 업로드 + * @param {Event} event - 파일 입력 이벤트 + * @returns {Promise} + */ +const uploadOgImage = async (event) => { + const files = event.target.files + + if (!files?.length) { + return + } + + const formData = new FormData() + formData.append('files', files[0]) + isUploadingOgImage.value = true + + try { + const result = await $fetch('/admin/api/uploads', { + method: 'POST', + body: formData + }) + form.ogImage = result.files?.[0]?.url || '' + } finally { + event.target.value = '' + isUploadingOgImage.value = false + } +} + /** * 제목 입력 후 본문 에디터로 이동 * @returns {void} @@ -557,7 +603,7 @@ defineExpose({ {{ form.featuredImage }}

-
+ +
+ OG 이미지 +
+ +
+

+ {{ form.ogImage }} +

+
+ + + +
+
+
+
+ + +
+
@@ -609,7 +688,7 @@ defineExpose({

- 대표 이미지 선택 + {{ mediaPickerTarget === 'ogImage' ? 'OG 이미지 선택' : '대표 이미지 선택' }}