From 60f9fd52f0bdbf4f4add360826523d55cb4a86d7 Mon Sep 17 00:00:00 2001 From: zenn Date: Sun, 3 May 2026 09:58:27 +0900 Subject: [PATCH] =?UTF-8?q?=EC=98=88=EC=95=BD=20=EB=B0=9C=ED=96=89=20?= =?UTF-8?q?=EA=B8=B0=EB=8A=A5=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- components/admin/AdminPostForm.vue | 68 ++++++++++++++++++++++- docs/history.md | 8 +++ docs/map.md | 4 +- docs/spec.md | 2 + docs/todo.md | 1 - docs/update.md | 9 +++ package-lock.json | 4 +- package.json | 2 +- pages/admin/posts/[id].vue | 10 +++- pages/admin/posts/index.vue | 44 ++++++++++++++- server/repositories/content-repository.js | 8 +++ 11 files changed, 151 insertions(+), 9 deletions(-) diff --git a/components/admin/AdminPostForm.vue b/components/admin/AdminPostForm.vue index f4446b5..b7ba17d 100644 --- a/components/admin/AdminPostForm.vue +++ b/components/admin/AdminPostForm.vue @@ -28,6 +28,46 @@ const autosaveNotice = ref(null) const autosaveStatus = ref('') const isRestoringAutosave = ref(false) +/** + * ISO 날짜를 datetime-local 입력값으로 변환 + * @param {string} value - ISO 날짜 문자열 + * @returns {string} datetime-local 입력값 + */ +function toDateTimeLocalValue(value) { + if (!value) { + return '' + } + + const date = new Date(value) + + if (Number.isNaN(date.getTime())) { + return '' + } + + const offsetDate = new Date(date.getTime() - date.getTimezoneOffset() * 60000) + + return offsetDate.toISOString().slice(0, 16) +} + +/** + * datetime-local 입력값을 ISO 문자열로 변환 + * @param {string} value - datetime-local 입력값 + * @returns {string | null} ISO 날짜 문자열 + */ +function toIsoDateTime(value) { + if (!value) { + return null + } + + const date = new Date(value) + + if (Number.isNaN(date.getTime())) { + return null + } + + return date.toISOString() +} + const form = reactive({ title: props.initialPost.title || '', slug: props.initialPost.slug || '', @@ -35,6 +75,7 @@ const form = reactive({ content: props.initialPost.content || '', featuredImage: props.initialPost.featuredImage || '', status: props.initialPost.status || 'draft', + publishedAt: toDateTimeLocalValue(props.initialPost.publishedAt), tagsText: props.initialPost.tags?.join(', ') || '' }) @@ -78,13 +119,23 @@ const parseTags = (value) => [...new Set(value .map((tag) => toSlug(tag)) .filter(Boolean))] +/** + * 예약 발행 여부 확인 + * @returns {boolean} 예약 발행 여부 + */ +const isScheduledPost = () => { + const publishedAt = toIsoDateTime(form.publishedAt) + + return form.status === 'published' && Boolean(publishedAt) && new Date(publishedAt) > new Date() +} + /** * 게시물 입력값 생성 * @returns {Object} 게시물 입력값 */ const createPostPayload = () => { const publishedAt = form.status === 'published' - ? props.initialPost.publishedAt || new Date().toISOString() + ? toIsoDateTime(form.publishedAt) || props.initialPost.publishedAt || new Date().toISOString() : null return { @@ -110,6 +161,7 @@ const createAutosavePayload = () => ({ content: form.content, featuredImage: form.featuredImage, status: form.status, + publishedAt: form.publishedAt, tagsText: form.tagsText }) @@ -381,6 +433,20 @@ defineExpose({ + +