POST 설정 정규화 로직 분리

This commit is contained in:
2026-07-24 11:30:19 +09:00
parent d885b844ad
commit 47c1b02d2b
7 changed files with 119 additions and 116 deletions

View File

@@ -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://` 자동 보정, `/` 내부 경로) |

View File

@@ -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`: 미디어 목록, 폴더 트리, 상세 모달, 재생성 액션을 작은 단위로 분리.

View File

@@ -1,5 +1,11 @@
# 업데이트 이력
## v1.11.35
- POST 설정 정규화·스냅샷 복사·변경 비교 로직을 `lib/admin-post-settings-section.js`로 분리.
- 설정 화면의 POST 설정 시작·취소·저장 로직을 공통 POST 설정 helper 기반으로 정리.
- 설정 저장 payload의 POST 설정 필드 정규화를 한 경로로 통합.
## v1.11.34
- 관리자 설정의 블로그 제목·설명 카드에서 POST 설정에 속한 업로드 제한 입력을 제거.

View File

@@ -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])
}

4
package-lock.json generated
View File

@@ -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",

View File

@@ -1,6 +1,6 @@
{
"name": "sori.studio",
"version": "1.11.34",
"version": "1.11.35",
"private": true,
"type": "module",
"imports": {

View File

@@ -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
}
}