브랜드 설정 정규화 로직 분리

This commit is contained in:
2026-07-24 11:59:02 +09:00
parent 526c9b9de9
commit f7bf73a719
7 changed files with 78 additions and 17 deletions

View File

@@ -31,6 +31,7 @@
| lib/admin-social-settings-section.js | 관리자 설정 SNS 설정 필드 목록·기본 스냅샷·정규화·스냅샷 복사·변경 비교 helper |
| lib/admin-post-settings-section.js | 관리자 설정 POST 설정 필드 목록·기본 스냅샷·정규화·스냅샷 복사·변경 비교 helper |
| lib/admin-spam-settings-section.js | 관리자 설정 스팸 필터 필드 목록·기본 스냅샷·정규화·textarea 변환·스냅샷 복사·변경 비교 helper |
| lib/admin-brand-settings-section.js | 관리자 설정 브랜드 설정 필드 목록·기본 스냅샷·정규화·스냅샷 복사·변경 비교 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`: 정규화가 필요한 설정 섹션(브랜드·어나운스 바)의 편집 상태·스냅샷·저장 요청 로직을 composable 단위로 추가 분리.
- `pages/admin/settings/index.vue`: 정규화가 필요한 설정 섹션(어나운스 바)의 편집 상태·스냅샷·저장 요청 로직을 composable 단위로 추가 분리.
- `components/content/ContentMarkdownRenderer.vue`: 파싱·블록 렌더링·라이브 편집 중개·라이트박스·광고 삽입 책임을 분리.
- `components/admin/AdminPostForm.vue`: 태그 입력, 대표 이미지 선택, 발행/초안 저장 상태, 우측 설정 패널 책임을 분리.
- `pages/admin/media/index.vue`: 미디어 목록, 폴더 트리, 상세 모달, 재생성 액션을 작은 단위로 분리.

View File

@@ -1,5 +1,11 @@
# 업데이트 이력
## v1.11.38
- 브랜드 설정 정규화·스냅샷 복사·변경 비교 로직을 `lib/admin-brand-settings-section.js`로 분리.
- 설정 화면의 브랜드 컬러 입력 정규화와 편집 시작·취소·저장 로직을 공통 helper 기반으로 정리.
- 설정 저장 payload의 브랜드 설정 필드 정규화를 한 경로로 통합.
## v1.11.37
- 스팸 필터 설정 정규화·스냅샷 복사·변경 비교 로직을 `lib/admin-spam-settings-section.js`로 분리.

View File

@@ -0,0 +1,48 @@
import {
DEFAULT_BRAND_COLOR,
normalizeBrandColor
} from './brand-color.js'
export const BRAND_SETTINGS_FIELDS = ['brandColor']
/**
* 브랜드 설정 기본 스냅샷을 만든다.
* @returns {Object} 기본 브랜드 설정 값
*/
export const createDefaultBrandSettingsSnapshot = () => ({
brandColor: DEFAULT_BRAND_COLOR
})
/**
* 브랜드 설정 값을 저장·비교 가능한 형태로 정규화한다.
* @param {Object} source - 원본 설정 값
* @returns {Object} 정규화된 브랜드 설정 값
*/
export const normalizeBrandSettingsSection = (source = {}) => ({
brandColor: normalizeBrandColor(source.brandColor || DEFAULT_BRAND_COLOR)
})
/**
* 브랜드 설정 필드만 대상 객체에 복사한다.
* @param {Object} target - 값을 받을 객체
* @param {Object} source - 값을 제공할 객체
* @returns {void}
*/
export const assignBrandSettingsSection = (target, source = {}) => {
const normalized = normalizeBrandSettingsSection(source)
BRAND_SETTINGS_FIELDS.forEach((field) => {
target[field] = normalized[field]
})
}
/**
* 두 브랜드 설정 값이 다른지 확인한다.
* @param {Object} current - 현재 설정 값
* @param {Object} snapshot - 비교할 스냅샷
* @returns {boolean} 변경 여부
*/
export const hasBrandSettingsSectionChanges = (current, snapshot) => {
const normalizedCurrent = normalizeBrandSettingsSection(current)
const normalizedSnapshot = normalizeBrandSettingsSection(snapshot)
return BRAND_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.37",
"version": "1.11.38",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "sori.studio",
"version": "1.11.37",
"version": "1.11.38",
"hasInstallScript": true,
"dependencies": {
"@nuxtjs/tailwindcss": "^6.14.0",

View File

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

View File

@@ -5,7 +5,6 @@ import {
normalizeAnnouncementAlignment,
normalizeAnnouncementBackgroundColor
} from '~/lib/announcement-bar.js'
import { DEFAULT_BRAND_COLOR, normalizeBrandColor } from '~/lib/brand-color.js'
import {
DISPLAY_COUNT_OPTIONS,
POST_LIST_COUNT_OPTIONS
@@ -38,6 +37,13 @@ import {
normalizeSpamSettingsSection,
parseSpamBlockedUsernamesText
} from '~/lib/admin-spam-settings-section.js'
import {
BRAND_SETTINGS_FIELDS,
assignBrandSettingsSection,
createDefaultBrandSettingsSnapshot,
hasBrandSettingsSectionChanges,
normalizeBrandSettingsSection
} from '~/lib/admin-brand-settings-section.js'
definePageMeta({
layout: 'admin'
@@ -174,9 +180,7 @@ const homeCoverSnapshot = reactive({
homeCoverText: ''
})
/** 편집 시작 시점의 브랜드 설정(취소 시 복원용) */
const brandSnapshot = reactive({
brandColor: DEFAULT_BRAND_COLOR
})
const brandSnapshot = reactive(createDefaultBrandSettingsSnapshot())
/** 맞춤 설정 시작 시점의 어나운스 바(취소 시 복원용) */
const announcementSnapshot = reactive({
announcementEnabled: false,
@@ -229,6 +233,7 @@ const {
const initialPostSettings = normalizePostSettingsSection(settings.value)
const initialSocialSettings = normalizeSocialSettingsSection(settings.value)
const initialSpamSettings = normalizeSpamSettingsSection(settings.value)
const initialBrandSettings = normalizeBrandSettingsSection(settings.value)
const form = reactive({
title: settings.value?.title || 'sori.studio',
@@ -244,7 +249,7 @@ const form = reactive({
homeCoverDarkImageUrl: settings.value?.homeCoverDarkImageUrl || '',
homeCoverTitle: settings.value?.homeCoverTitle || '',
homeCoverText: settings.value?.homeCoverText || '',
brandColor: normalizeBrandColor(settings.value?.brandColor || DEFAULT_BRAND_COLOR),
...initialBrandSettings,
announcementEnabled: Boolean(settings.value?.announcementEnabled),
announcementText: settings.value?.announcementText || '',
announcementUrl: settings.value?.announcementUrl || '',
@@ -314,7 +319,7 @@ const hasHomeCoverChanges = computed(() => editHomeCover.value && (
* @returns {boolean} 변경 여부
*/
const hasBrandChanges = computed(() => editBrand.value
&& normalizeBrandColor(form.brandColor) !== normalizeBrandColor(brandSnapshot.brandColor))
&& hasBrandSettingsSectionChanges(form, brandSnapshot))
/**
* 어나운스 바 변경 여부
@@ -1078,7 +1083,7 @@ const buildSiteSettingsPayload = (fields = null) => {
homeCoverDarkImageUrl: form.homeCoverDarkImageUrl || '',
homeCoverTitle: form.homeCoverTitle || '',
homeCoverText: form.homeCoverText || '',
brandColor: normalizeBrandColor(form.brandColor || DEFAULT_BRAND_COLOR),
...normalizeBrandSettingsSection(form),
announcementEnabled: Boolean(form.announcementEnabled),
announcementText: form.announcementText || '',
announcementUrl: form.announcementUrl || '',
@@ -1458,8 +1463,8 @@ const saveHomeCoverSection = async () => {
* @returns {void}
*/
const beginEditBrand = () => {
brandSnapshot.brandColor = normalizeBrandColor(form.brandColor || DEFAULT_BRAND_COLOR)
form.brandColor = brandSnapshot.brandColor
assignBrandSettingsSection(brandSnapshot, form)
assignBrandSettingsSection(form, brandSnapshot)
editBrand.value = true
}
@@ -1468,7 +1473,7 @@ const beginEditBrand = () => {
* @returns {void}
*/
const cancelEditBrand = () => {
form.brandColor = brandSnapshot.brandColor
assignBrandSettingsSection(form, brandSnapshot)
editBrand.value = false
}
@@ -1477,7 +1482,7 @@ const cancelEditBrand = () => {
* @returns {void}
*/
const normalizeBrandColorInput = () => {
form.brandColor = normalizeBrandColor(form.brandColor || DEFAULT_BRAND_COLOR)
assignBrandSettingsSection(form, form)
}
/**
@@ -1494,11 +1499,12 @@ const saveBrandSection = async () => {
const ok = await persistSiteSettings({
successToast: '브랜드 컬러가 저장되었습니다.',
savingFlag: savingBrand,
fields: ['brandColor']
fields: BRAND_SETTINGS_FIELDS
})
if (ok) {
brandSnapshot.brandColor = normalizeBrandColor(form.brandColor || DEFAULT_BRAND_COLOR)
assignBrandSettingsSection(brandSnapshot, form)
assignBrandSettingsSection(form, brandSnapshot)
editBrand.value = false
}
}