From 47c1b02d2b0d080667820fdd57f029fb16c3f276 Mon Sep 17 00:00:00 2001 From: zenn Date: Fri, 24 Jul 2026 11:30:19 +0900 Subject: [PATCH] =?UTF-8?q?POST=20=EC=84=A4=EC=A0=95=20=EC=A0=95=EA=B7=9C?= =?UTF-8?q?=ED=99=94=20=EB=A1=9C=EC=A7=81=20=EB=B6=84=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/map.md | 1 + docs/todo.md | 2 +- docs/update.md | 6 ++ lib/admin-post-settings-section.js | 88 +++++++++++++++++++ package-lock.json | 4 +- package.json | 2 +- pages/admin/settings/index.vue | 132 +++++------------------------ 7 files changed, 119 insertions(+), 116 deletions(-) create mode 100644 lib/admin-post-settings-section.js diff --git a/docs/map.md b/docs/map.md index 417ccfe..a8d07ec 100644 --- a/docs/map.md +++ b/docs/map.md @@ -28,6 +28,7 @@ | 파일 | 용도 | |------|------| | lib/upload-size-limit.js | 관리자 미디어 업로드 종류별 최대 바이트 판별·1회 파일 수/총량 제한 정규화·용량 문구(서버·클라이언트 공용) | +| lib/admin-post-settings-section.js | 관리자 설정 POST 설정 필드 목록·기본 스냅샷·정규화·스냅샷 복사·변경 비교 helper | | lib/post-card-thumbnail-url.js | 게시물 카드 썸네일 `thumbs/*-card.webp`, Pages 커버 썸네일 `thumbs/*-page-cover.webp`, 본문 표시용 `body-1600`/`body-720`·라이트박스용 `body-2560` 파생 이미지 URL 판별과 레거시 `body`/`lightbox` 판별, 자동 생성 이미지 캐시 버전 URL 생성(대표 이미지·본문 이미지·갤러리·Pages 커버 선택 및 서버 검증 공용) | | lib/external-favicon-url.js | 외부 URL origin 기준 Google `gstatic.com/faviconV2` 파비콘 URL 생성(내부 경로는 빈 문자열) | | lib/navigation-url.js | 네비게이션 URL 접두사 분리·합성·정규화(`https://` 자동 보정, `/` 내부 경로) | diff --git a/docs/todo.md b/docs/todo.md index 4a6fe4a..66e09ff 100644 --- a/docs/todo.md +++ b/docs/todo.md @@ -4,7 +4,7 @@ - 거대 컴포넌트 책임 분리 진행. - `components/admin/AdminMarkdownEditor.vue`: 미디어 모달, 슬래시 명령, 히스토리·선택 복원, 라이브/소스 전환 로직을 단계적으로 composable 또는 하위 컴포넌트로 분리. - - `pages/admin/settings/index.vue`: 정규화가 필요한 설정 섹션(SNS·POST 설정·브랜드·어나운스 바·스팸 필터)의 편집 상태·스냅샷·저장 요청 로직을 composable 단위로 추가 분리. + - `pages/admin/settings/index.vue`: 정규화가 필요한 설정 섹션(SNS·브랜드·어나운스 바·스팸 필터)의 편집 상태·스냅샷·저장 요청 로직을 composable 단위로 추가 분리. - `components/content/ContentMarkdownRenderer.vue`: 파싱·블록 렌더링·라이브 편집 중개·라이트박스·광고 삽입 책임을 분리. - `components/admin/AdminPostForm.vue`: 태그 입력, 대표 이미지 선택, 발행/초안 저장 상태, 우측 설정 패널 책임을 분리. - `pages/admin/media/index.vue`: 미디어 목록, 폴더 트리, 상세 모달, 재생성 액션을 작은 단위로 분리. diff --git a/docs/update.md b/docs/update.md index e332757..55a3fdd 100644 --- a/docs/update.md +++ b/docs/update.md @@ -1,5 +1,11 @@ # 업데이트 이력 +## v1.11.35 + +- POST 설정 정규화·스냅샷 복사·변경 비교 로직을 `lib/admin-post-settings-section.js`로 분리. +- 설정 화면의 POST 설정 시작·취소·저장 로직을 공통 POST 설정 helper 기반으로 정리. +- 설정 저장 payload의 POST 설정 필드 정규화를 한 경로로 통합. + ## v1.11.34 - 관리자 설정의 블로그 제목·설명 카드에서 POST 설정에 속한 업로드 제한 입력을 제거. diff --git a/lib/admin-post-settings-section.js b/lib/admin-post-settings-section.js new file mode 100644 index 0000000..a3cd70d --- /dev/null +++ b/lib/admin-post-settings-section.js @@ -0,0 +1,88 @@ +import { + DEFAULT_FEATURED_POST_LIMIT, + DEFAULT_HOME_LATEST_POST_LIMIT, + DEFAULT_PAGE_SECTION_ITEM_LIMIT, + DEFAULT_POST_LIST_PAGE_SIZE, + DEFAULT_TAG_POST_LIST_PAGE_SIZE, + normalizeDisplayCount +} from './display-count-settings.js' +import { + DEFAULT_POST_TAG_LIMIT, + normalizePostTagLimit +} from './post-tag-limit.js' +import { + DEFAULT_UPLOAD_MAX_FILE_COUNT, + DEFAULT_UPLOAD_MAX_TOTAL_SIZE, + normalizeUploadMaxFileCount, + normalizeUploadMaxTotalSize +} from './upload-size-limit.js' + +export const POST_SETTINGS_FIELDS = [ + 'showPostUpdatedAt', + 'postTagLimit', + 'homeLatestPostLimit', + 'postListPageSize', + 'tagPostListPageSize', + 'featuredPostLimit', + 'pageSectionItemLimit', + 'uploadMaxFileCount', + 'uploadMaxTotalSize' +] + +/** + * POST 설정 기본 스냅샷을 만든다. + * @returns {Object} 기본 POST 설정 값 + */ +export const createDefaultPostSettingsSnapshot = () => ({ + showPostUpdatedAt: false, + postTagLimit: DEFAULT_POST_TAG_LIMIT, + homeLatestPostLimit: DEFAULT_HOME_LATEST_POST_LIMIT, + postListPageSize: DEFAULT_POST_LIST_PAGE_SIZE, + tagPostListPageSize: DEFAULT_TAG_POST_LIST_PAGE_SIZE, + featuredPostLimit: DEFAULT_FEATURED_POST_LIMIT, + pageSectionItemLimit: DEFAULT_PAGE_SECTION_ITEM_LIMIT, + uploadMaxFileCount: DEFAULT_UPLOAD_MAX_FILE_COUNT, + uploadMaxTotalSize: DEFAULT_UPLOAD_MAX_TOTAL_SIZE +}) + +/** + * POST 설정 값을 저장·비교 가능한 형태로 정규화한다. + * @param {Object} source - 원본 설정 값 + * @returns {Object} 정규화된 POST 설정 값 + */ +export const normalizePostSettingsSection = (source = {}) => ({ + showPostUpdatedAt: Boolean(source.showPostUpdatedAt), + postTagLimit: normalizePostTagLimit(source.postTagLimit), + homeLatestPostLimit: normalizeDisplayCount(source.homeLatestPostLimit, DEFAULT_HOME_LATEST_POST_LIMIT), + postListPageSize: normalizeDisplayCount(source.postListPageSize, DEFAULT_POST_LIST_PAGE_SIZE), + tagPostListPageSize: normalizeDisplayCount(source.tagPostListPageSize, DEFAULT_TAG_POST_LIST_PAGE_SIZE), + featuredPostLimit: normalizeDisplayCount(source.featuredPostLimit, DEFAULT_FEATURED_POST_LIMIT), + pageSectionItemLimit: normalizeDisplayCount(source.pageSectionItemLimit, DEFAULT_PAGE_SECTION_ITEM_LIMIT), + uploadMaxFileCount: normalizeUploadMaxFileCount(source.uploadMaxFileCount), + uploadMaxTotalSize: normalizeUploadMaxTotalSize(source.uploadMaxTotalSize) +}) + +/** + * POST 설정 필드만 대상 객체에 복사한다. + * @param {Object} target - 값을 받을 객체 + * @param {Object} source - 값을 제공할 객체 + * @returns {void} + */ +export const assignPostSettingsSection = (target, source = {}) => { + const normalized = normalizePostSettingsSection(source) + POST_SETTINGS_FIELDS.forEach((field) => { + target[field] = normalized[field] + }) +} + +/** + * 두 POST 설정 값이 다른지 확인한다. + * @param {Object} current - 현재 설정 값 + * @param {Object} snapshot - 비교할 스냅샷 + * @returns {boolean} 변경 여부 + */ +export const hasPostSettingsSectionChanges = (current, snapshot) => { + const normalizedCurrent = normalizePostSettingsSection(current) + const normalizedSnapshot = normalizePostSettingsSection(snapshot) + return POST_SETTINGS_FIELDS.some((field) => normalizedCurrent[field] !== normalizedSnapshot[field]) +} diff --git a/package-lock.json b/package-lock.json index b4bbb2f..3e49cf1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "sori.studio", - "version": "1.11.34", + "version": "1.11.35", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "sori.studio", - "version": "1.11.34", + "version": "1.11.35", "hasInstallScript": true, "dependencies": { "@nuxtjs/tailwindcss": "^6.14.0", diff --git a/package.json b/package.json index 759b4c0..ac7e7db 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "sori.studio", - "version": "1.11.34", + "version": "1.11.35", "private": true, "type": "module", "imports": { diff --git a/pages/admin/settings/index.vue b/pages/admin/settings/index.vue index deb4dc3..cdc5d08 100644 --- a/pages/admin/settings/index.vue +++ b/pages/admin/settings/index.vue @@ -14,30 +14,21 @@ import { normalizeSocialLinks } from '~/lib/social-links.js' import { - DEFAULT_POST_TAG_LIMIT, - MAX_POST_TAG_LIMIT, - MIN_POST_TAG_LIMIT, - normalizePostTagLimit -} from '~/lib/post-tag-limit.js' -import { - DEFAULT_FEATURED_POST_LIMIT, - DEFAULT_HOME_LATEST_POST_LIMIT, - DEFAULT_PAGE_SECTION_ITEM_LIMIT, - DEFAULT_POST_LIST_PAGE_SIZE, - DEFAULT_TAG_POST_LIST_PAGE_SIZE, DISPLAY_COUNT_OPTIONS, - POST_LIST_COUNT_OPTIONS, - normalizeDisplayCount + POST_LIST_COUNT_OPTIONS } from '~/lib/display-count-settings.js' import { - DEFAULT_UPLOAD_MAX_FILE_COUNT, - DEFAULT_UPLOAD_MAX_TOTAL_SIZE, UPLOAD_FILE_COUNT_OPTIONS, UPLOAD_TOTAL_SIZE_OPTIONS, - formatUploadSizeLimit, - normalizeUploadMaxFileCount, - normalizeUploadMaxTotalSize + formatUploadSizeLimit } from '~/lib/upload-size-limit.js' +import { + POST_SETTINGS_FIELDS, + assignPostSettingsSection, + createDefaultPostSettingsSnapshot, + hasPostSettingsSectionChanges, + normalizePostSettingsSection +} from '~/lib/admin-post-settings-section.js' definePageMeta({ layout: 'admin' @@ -167,17 +158,7 @@ const socialSnapshot = reactive({ socialLinks: [] }) /** 편집 시작 시점의 POST 설정(취소 시 복원용) */ -const postSnapshot = reactive({ - showPostUpdatedAt: false, - postTagLimit: DEFAULT_POST_TAG_LIMIT, - homeLatestPostLimit: DEFAULT_HOME_LATEST_POST_LIMIT, - postListPageSize: DEFAULT_POST_LIST_PAGE_SIZE, - tagPostListPageSize: DEFAULT_TAG_POST_LIST_PAGE_SIZE, - featuredPostLimit: DEFAULT_FEATURED_POST_LIMIT, - pageSectionItemLimit: DEFAULT_PAGE_SECTION_ITEM_LIMIT, - uploadMaxFileCount: DEFAULT_UPLOAD_MAX_FILE_COUNT, - uploadMaxTotalSize: DEFAULT_UPLOAD_MAX_TOTAL_SIZE -}) +const postSnapshot = reactive(createDefaultPostSettingsSnapshot()) /** 편집 시작 시점의 메인 화면 커버(취소 시 복원용) */ const homeCoverSnapshot = reactive({ homeCoverImageUrl: '', @@ -240,6 +221,7 @@ const { } = await useFetch('/admin/api/settings/backup-summary', { default: () => null }) +const initialPostSettings = normalizePostSettingsSection(settings.value) const form = reactive({ title: settings.value?.title || 'sori.studio', @@ -250,15 +232,7 @@ const form = reactive({ faviconUrl: settings.value?.faviconUrl || '', copyrightText: settings.value?.copyrightText || '©2026 sori.studio', socialLinks: normalizeSocialLinks(settings.value?.socialLinks || []), - showPostUpdatedAt: Boolean(settings.value?.showPostUpdatedAt), - postTagLimit: normalizePostTagLimit(settings.value?.postTagLimit), - homeLatestPostLimit: normalizeDisplayCount(settings.value?.homeLatestPostLimit, DEFAULT_HOME_LATEST_POST_LIMIT), - postListPageSize: normalizeDisplayCount(settings.value?.postListPageSize, DEFAULT_POST_LIST_PAGE_SIZE), - tagPostListPageSize: normalizeDisplayCount(settings.value?.tagPostListPageSize, DEFAULT_TAG_POST_LIST_PAGE_SIZE), - featuredPostLimit: normalizeDisplayCount(settings.value?.featuredPostLimit, DEFAULT_FEATURED_POST_LIMIT), - pageSectionItemLimit: normalizeDisplayCount(settings.value?.pageSectionItemLimit, DEFAULT_PAGE_SECTION_ITEM_LIMIT), - uploadMaxFileCount: normalizeUploadMaxFileCount(settings.value?.uploadMaxFileCount), - uploadMaxTotalSize: normalizeUploadMaxTotalSize(settings.value?.uploadMaxTotalSize), + ...initialPostSettings, homeCoverImageUrl: settings.value?.homeCoverImageUrl || '', homeCoverDarkImageUrl: settings.value?.homeCoverDarkImageUrl || '', homeCoverTitle: settings.value?.homeCoverTitle || '', @@ -315,17 +289,7 @@ const hasSocialChanges = computed(() => editSocial.value * @returns {boolean} 변경 여부 */ const hasPostChanges = computed(() => editPost.value - && ( - form.showPostUpdatedAt !== postSnapshot.showPostUpdatedAt - || normalizePostTagLimit(form.postTagLimit) !== normalizePostTagLimit(postSnapshot.postTagLimit) - || normalizeDisplayCount(form.homeLatestPostLimit, DEFAULT_HOME_LATEST_POST_LIMIT) !== normalizeDisplayCount(postSnapshot.homeLatestPostLimit, DEFAULT_HOME_LATEST_POST_LIMIT) - || normalizeDisplayCount(form.postListPageSize, DEFAULT_POST_LIST_PAGE_SIZE) !== normalizeDisplayCount(postSnapshot.postListPageSize, DEFAULT_POST_LIST_PAGE_SIZE) - || normalizeDisplayCount(form.tagPostListPageSize, DEFAULT_TAG_POST_LIST_PAGE_SIZE) !== normalizeDisplayCount(postSnapshot.tagPostListPageSize, DEFAULT_TAG_POST_LIST_PAGE_SIZE) - || normalizeDisplayCount(form.featuredPostLimit, DEFAULT_FEATURED_POST_LIMIT) !== normalizeDisplayCount(postSnapshot.featuredPostLimit, DEFAULT_FEATURED_POST_LIMIT) - || normalizeDisplayCount(form.pageSectionItemLimit, DEFAULT_PAGE_SECTION_ITEM_LIMIT) !== normalizeDisplayCount(postSnapshot.pageSectionItemLimit, DEFAULT_PAGE_SECTION_ITEM_LIMIT) - || normalizeUploadMaxFileCount(form.uploadMaxFileCount) !== normalizeUploadMaxFileCount(postSnapshot.uploadMaxFileCount) - || normalizeUploadMaxTotalSize(form.uploadMaxTotalSize) !== normalizeUploadMaxTotalSize(postSnapshot.uploadMaxTotalSize) - )) + && hasPostSettingsSectionChanges(form, postSnapshot)) /** * 메인 화면 커버 변경 여부 @@ -1102,15 +1066,7 @@ const buildSiteSettingsPayload = (fields = null) => { faviconUrl: form.faviconUrl, copyrightText: form.copyrightText, socialLinks: normalizeSocialLinks(form.socialLinks), - showPostUpdatedAt: Boolean(form.showPostUpdatedAt), - postTagLimit: normalizePostTagLimit(form.postTagLimit), - homeLatestPostLimit: normalizeDisplayCount(form.homeLatestPostLimit, DEFAULT_HOME_LATEST_POST_LIMIT), - postListPageSize: normalizeDisplayCount(form.postListPageSize, DEFAULT_POST_LIST_PAGE_SIZE), - tagPostListPageSize: normalizeDisplayCount(form.tagPostListPageSize, DEFAULT_TAG_POST_LIST_PAGE_SIZE), - featuredPostLimit: normalizeDisplayCount(form.featuredPostLimit, DEFAULT_FEATURED_POST_LIMIT), - pageSectionItemLimit: normalizeDisplayCount(form.pageSectionItemLimit, DEFAULT_PAGE_SECTION_ITEM_LIMIT), - uploadMaxFileCount: normalizeUploadMaxFileCount(form.uploadMaxFileCount), - uploadMaxTotalSize: normalizeUploadMaxTotalSize(form.uploadMaxTotalSize), + ...normalizePostSettingsSection(form), homeCoverImageUrl: form.homeCoverImageUrl || '', homeCoverDarkImageUrl: form.homeCoverDarkImageUrl || '', homeCoverTitle: form.homeCoverTitle || '', @@ -1329,23 +1285,8 @@ const saveSocialSection = async () => { * @returns {void} */ const beginEditPost = () => { - postSnapshot.showPostUpdatedAt = form.showPostUpdatedAt - postSnapshot.postTagLimit = normalizePostTagLimit(form.postTagLimit) - postSnapshot.homeLatestPostLimit = normalizeDisplayCount(form.homeLatestPostLimit, DEFAULT_HOME_LATEST_POST_LIMIT) - postSnapshot.postListPageSize = normalizeDisplayCount(form.postListPageSize, DEFAULT_POST_LIST_PAGE_SIZE) - postSnapshot.tagPostListPageSize = normalizeDisplayCount(form.tagPostListPageSize, DEFAULT_TAG_POST_LIST_PAGE_SIZE) - postSnapshot.featuredPostLimit = normalizeDisplayCount(form.featuredPostLimit, DEFAULT_FEATURED_POST_LIMIT) - postSnapshot.pageSectionItemLimit = normalizeDisplayCount(form.pageSectionItemLimit, DEFAULT_PAGE_SECTION_ITEM_LIMIT) - postSnapshot.uploadMaxFileCount = normalizeUploadMaxFileCount(form.uploadMaxFileCount) - postSnapshot.uploadMaxTotalSize = normalizeUploadMaxTotalSize(form.uploadMaxTotalSize) - form.postTagLimit = normalizePostTagLimit(form.postTagLimit) - form.homeLatestPostLimit = postSnapshot.homeLatestPostLimit - form.postListPageSize = postSnapshot.postListPageSize - form.tagPostListPageSize = postSnapshot.tagPostListPageSize - form.featuredPostLimit = postSnapshot.featuredPostLimit - form.pageSectionItemLimit = postSnapshot.pageSectionItemLimit - form.uploadMaxFileCount = postSnapshot.uploadMaxFileCount - form.uploadMaxTotalSize = postSnapshot.uploadMaxTotalSize + assignPostSettingsSection(postSnapshot, form) + assignPostSettingsSection(form, postSnapshot) editPost.value = true } @@ -1354,15 +1295,7 @@ const beginEditPost = () => { * @returns {void} */ const cancelEditPost = () => { - form.showPostUpdatedAt = postSnapshot.showPostUpdatedAt - form.postTagLimit = normalizePostTagLimit(postSnapshot.postTagLimit) - form.homeLatestPostLimit = normalizeDisplayCount(postSnapshot.homeLatestPostLimit, DEFAULT_HOME_LATEST_POST_LIMIT) - form.postListPageSize = normalizeDisplayCount(postSnapshot.postListPageSize, DEFAULT_POST_LIST_PAGE_SIZE) - form.tagPostListPageSize = normalizeDisplayCount(postSnapshot.tagPostListPageSize, DEFAULT_TAG_POST_LIST_PAGE_SIZE) - form.featuredPostLimit = normalizeDisplayCount(postSnapshot.featuredPostLimit, DEFAULT_FEATURED_POST_LIMIT) - form.pageSectionItemLimit = normalizeDisplayCount(postSnapshot.pageSectionItemLimit, DEFAULT_PAGE_SECTION_ITEM_LIMIT) - form.uploadMaxFileCount = normalizeUploadMaxFileCount(postSnapshot.uploadMaxFileCount) - form.uploadMaxTotalSize = normalizeUploadMaxTotalSize(postSnapshot.uploadMaxTotalSize) + assignPostSettingsSection(form, postSnapshot) editPost.value = false } @@ -1378,37 +1311,12 @@ const savePostSection = async () => { const ok = await persistSiteSettings({ successToast: 'POST 설정이 저장되었습니다.', savingFlag: savingPost, - fields: [ - 'showPostUpdatedAt', - 'postTagLimit', - 'homeLatestPostLimit', - 'postListPageSize', - 'tagPostListPageSize', - 'featuredPostLimit', - 'pageSectionItemLimit', - 'uploadMaxFileCount', - 'uploadMaxTotalSize' - ] + fields: POST_SETTINGS_FIELDS }) if (ok) { - postSnapshot.showPostUpdatedAt = form.showPostUpdatedAt - postSnapshot.postTagLimit = normalizePostTagLimit(form.postTagLimit) - postSnapshot.homeLatestPostLimit = normalizeDisplayCount(form.homeLatestPostLimit, DEFAULT_HOME_LATEST_POST_LIMIT) - postSnapshot.postListPageSize = normalizeDisplayCount(form.postListPageSize, DEFAULT_POST_LIST_PAGE_SIZE) - postSnapshot.tagPostListPageSize = normalizeDisplayCount(form.tagPostListPageSize, DEFAULT_TAG_POST_LIST_PAGE_SIZE) - postSnapshot.featuredPostLimit = normalizeDisplayCount(form.featuredPostLimit, DEFAULT_FEATURED_POST_LIMIT) - postSnapshot.pageSectionItemLimit = normalizeDisplayCount(form.pageSectionItemLimit, DEFAULT_PAGE_SECTION_ITEM_LIMIT) - postSnapshot.uploadMaxFileCount = normalizeUploadMaxFileCount(form.uploadMaxFileCount) - postSnapshot.uploadMaxTotalSize = normalizeUploadMaxTotalSize(form.uploadMaxTotalSize) - form.postTagLimit = postSnapshot.postTagLimit - form.homeLatestPostLimit = postSnapshot.homeLatestPostLimit - form.postListPageSize = postSnapshot.postListPageSize - form.tagPostListPageSize = postSnapshot.tagPostListPageSize - form.featuredPostLimit = postSnapshot.featuredPostLimit - form.pageSectionItemLimit = postSnapshot.pageSectionItemLimit - form.uploadMaxFileCount = postSnapshot.uploadMaxFileCount - form.uploadMaxTotalSize = postSnapshot.uploadMaxTotalSize + assignPostSettingsSection(postSnapshot, form) + assignPostSettingsSection(form, postSnapshot) editPost.value = false } }