From 526c9b9de971b6f14ea893e258f0a22241ac4f89 Mon Sep 17 00:00:00 2001 From: zenn Date: Fri, 24 Jul 2026 11:36:45 +0900 Subject: [PATCH] =?UTF-8?q?=EC=8A=A4=ED=8C=B8=20=ED=95=84=ED=84=B0=20?= =?UTF-8?q?=EC=84=A4=EC=A0=95=20=EC=A0=95=EA=B7=9C=ED=99=94=20=EB=A1=9C?= =?UTF-8?q?=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-spam-settings-section.js | 66 ++++++++++++++++++++++++++++++ package-lock.json | 4 +- package.json | 2 +- pages/admin/settings/index.vue | 38 +++++++++-------- 7 files changed, 99 insertions(+), 20 deletions(-) create mode 100644 lib/admin-spam-settings-section.js diff --git a/docs/map.md b/docs/map.md index e0be93d..60804c5 100644 --- a/docs/map.md +++ b/docs/map.md @@ -30,6 +30,7 @@ | lib/upload-size-limit.js | 관리자 미디어 업로드 종류별 최대 바이트 판별·1회 파일 수/총량 제한 정규화·용량 문구(서버·클라이언트 공용) | | 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/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 264ace9..b006677 100644 --- a/docs/todo.md +++ b/docs/todo.md @@ -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`: 미디어 목록, 폴더 트리, 상세 모달, 재생성 액션을 작은 단위로 분리. diff --git a/docs/update.md b/docs/update.md index bb40383..3c85671 100644 --- a/docs/update.md +++ b/docs/update.md @@ -1,5 +1,11 @@ # 업데이트 이력 +## v1.11.37 + +- 스팸 필터 설정 정규화·스냅샷 복사·변경 비교 로직을 `lib/admin-spam-settings-section.js`로 분리. +- 설정 화면의 스팸 필터 textarea 변환과 편집 시작·취소·저장 로직을 공통 helper 기반으로 정리. +- 설정 저장 payload의 스팸 필터 필드 정규화를 한 경로로 통합. + ## v1.11.36 - SNS 설정 정규화·스냅샷 복사·변경 비교 로직을 `lib/admin-social-settings-section.js`로 분리. diff --git a/lib/admin-spam-settings-section.js b/lib/admin-spam-settings-section.js new file mode 100644 index 0000000..8fad9a1 --- /dev/null +++ b/lib/admin-spam-settings-section.js @@ -0,0 +1,66 @@ +import { + normalizeSignupBlockedUsernames, + parseSignupBlockedUsernamesFromText +} from './signup-blocked-usernames.js' + +export const SPAM_SETTINGS_FIELDS = ['signupBlockedUsernames'] + +/** + * 스팸 필터 설정 기본 스냅샷을 만든다. + * @returns {Object} 기본 스팸 필터 설정 값 + */ +export const createDefaultSpamSettingsSnapshot = () => ({ + signupBlockedUsernames: normalizeSignupBlockedUsernames() +}) + +/** + * 스팸 필터 설정 값을 저장·비교 가능한 형태로 정규화한다. + * @param {Object} source - 원본 설정 값 + * @returns {Object} 정규화된 스팸 필터 설정 값 + */ +export const normalizeSpamSettingsSection = (source = {}) => ({ + signupBlockedUsernames: normalizeSignupBlockedUsernames(source.signupBlockedUsernames) +}) + +/** + * 스팸 필터 설정 필드만 대상 객체에 복사한다. + * @param {Object} target - 값을 받을 객체 + * @param {Object} source - 값을 제공할 객체 + * @returns {void} + */ +export const assignSpamSettingsSection = (target, source = {}) => { + const normalized = normalizeSpamSettingsSection(source) + SPAM_SETTINGS_FIELDS.forEach((field) => { + target[field] = normalized[field] + }) +} + +/** + * 두 스팸 필터 설정 값이 다른지 확인한다. + * @param {Object} current - 현재 설정 값 + * @param {Object} snapshot - 비교할 스냅샷 + * @returns {boolean} 변경 여부 + */ +export const hasSpamSettingsSectionChanges = (current, snapshot) => { + const normalizedCurrent = normalizeSpamSettingsSection(current) + const normalizedSnapshot = normalizeSpamSettingsSection(snapshot) + return JSON.stringify(normalizedCurrent.signupBlockedUsernames) !== JSON.stringify(normalizedSnapshot.signupBlockedUsernames) +} + +/** + * 스팸 필터 금지 닉네임 배열을 textarea 값으로 변환한다. + * @param {Object} source - 스팸 필터 설정 값 + * @returns {string} 줄바꿈 구분 textarea 값 + */ +export const formatSpamBlockedUsernamesText = (source = {}) => { + return normalizeSpamSettingsSection(source).signupBlockedUsernames.join('\n') +} + +/** + * textarea 값을 스팸 필터 설정 값으로 변환한다. + * @param {string} text - 줄바꿈 구분 입력 + * @returns {Object} 정규화된 스팸 필터 설정 값 + */ +export const parseSpamBlockedUsernamesText = (text) => ({ + signupBlockedUsernames: parseSignupBlockedUsernamesFromText(text) +}) diff --git a/package-lock.json b/package-lock.json index 9eab2e0..3042e4f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "sori.studio", - "version": "1.11.36", + "version": "1.11.37", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "sori.studio", - "version": "1.11.36", + "version": "1.11.37", "hasInstallScript": true, "dependencies": { "@nuxtjs/tailwindcss": "^6.14.0", diff --git a/package.json b/package.json index f6ddd5f..112ae36 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "sori.studio", - "version": "1.11.36", + "version": "1.11.37", "private": true, "type": "module", "imports": { diff --git a/pages/admin/settings/index.vue b/pages/admin/settings/index.vue index b2fda07..655582d 100644 --- a/pages/admin/settings/index.vue +++ b/pages/admin/settings/index.vue @@ -6,10 +6,6 @@ import { normalizeAnnouncementBackgroundColor } from '~/lib/announcement-bar.js' import { DEFAULT_BRAND_COLOR, normalizeBrandColor } from '~/lib/brand-color.js' -import { - normalizeSignupBlockedUsernames, - parseSignupBlockedUsernamesFromText -} from '~/lib/signup-blocked-usernames.js' import { DISPLAY_COUNT_OPTIONS, POST_LIST_COUNT_OPTIONS @@ -33,6 +29,15 @@ import { hasSocialSettingsSectionChanges, normalizeSocialSettingsSection } from '~/lib/admin-social-settings-section.js' +import { + SPAM_SETTINGS_FIELDS, + assignSpamSettingsSection, + createDefaultSpamSettingsSnapshot, + formatSpamBlockedUsernamesText, + hasSpamSettingsSectionChanges, + normalizeSpamSettingsSection, + parseSpamBlockedUsernamesText +} from '~/lib/admin-spam-settings-section.js' definePageMeta({ layout: 'admin' @@ -181,9 +186,7 @@ const announcementSnapshot = reactive({ announcementAlignment: DEFAULT_ANNOUNCEMENT_ALIGNMENT }) /** 편집 시작 시점의 스팸 필터(취소 시 복원용) */ -const spamSnapshot = reactive({ - signupBlockedUsernames: [] -}) +const spamSnapshot = reactive(createDefaultSpamSettingsSnapshot()) /** 편집 시작 시점의 사이트 코드(취소 시 복원용) */ const siteCodeSnapshot = reactive({ adsTxt: '', @@ -225,6 +228,7 @@ const { }) const initialPostSettings = normalizePostSettingsSection(settings.value) const initialSocialSettings = normalizeSocialSettingsSection(settings.value) +const initialSpamSettings = normalizeSpamSettingsSection(settings.value) const form = reactive({ title: settings.value?.title || 'sori.studio', @@ -246,7 +250,7 @@ const form = reactive({ announcementUrl: settings.value?.announcementUrl || '', announcementBackgroundColor: normalizeAnnouncementBackgroundColor(settings.value?.announcementBackgroundColor || DEFAULT_ANNOUNCEMENT_BACKGROUND_COLOR), announcementAlignment: normalizeAnnouncementAlignment(settings.value?.announcementAlignment || DEFAULT_ANNOUNCEMENT_ALIGNMENT), - signupBlockedUsernames: normalizeSignupBlockedUsernames(settings.value?.signupBlockedUsernames), + ...initialSpamSettings, adsTxt: settings.value?.adsTxt || '', customHeadCode: settings.value?.customHeadCode || '', customFooterCode: settings.value?.customFooterCode || '', @@ -329,7 +333,7 @@ const hasAnnouncementChanges = computed(() => customizeAnnouncement.value && ( * @returns {boolean} 변경 여부 */ const hasSpamChanges = computed(() => editSpam.value - && JSON.stringify(form.signupBlockedUsernames) !== JSON.stringify(spamSnapshot.signupBlockedUsernames)) + && hasSpamSettingsSectionChanges(form, spamSnapshot)) /** * 사이트 코드 변경 여부 @@ -599,9 +603,9 @@ const getPostExportProgressDescription = (job) => { * 가입 금지 닉네임 textarea 바인딩 */ const signupBlockedUsernamesText = computed({ - get: () => form.signupBlockedUsernames.join('\n'), + get: () => formatSpamBlockedUsernamesText(form), set: (value) => { - form.signupBlockedUsernames = parseSignupBlockedUsernamesFromText(value) + assignSpamSettingsSection(form, parseSpamBlockedUsernamesText(value)) } }) @@ -1080,7 +1084,7 @@ const buildSiteSettingsPayload = (fields = null) => { announcementUrl: form.announcementUrl || '', announcementBackgroundColor: normalizeAnnouncementBackgroundColor(form.announcementBackgroundColor || DEFAULT_ANNOUNCEMENT_BACKGROUND_COLOR), announcementAlignment: normalizeAnnouncementAlignment(form.announcementAlignment || DEFAULT_ANNOUNCEMENT_ALIGNMENT), - signupBlockedUsernames: normalizeSignupBlockedUsernames(form.signupBlockedUsernames), + ...normalizeSpamSettingsSection(form), adsTxt: form.adsTxt || '', customHeadCode: form.customHeadCode || '', customFooterCode: form.customFooterCode || '', @@ -1561,7 +1565,8 @@ const saveAnnouncementSection = async () => { * @returns {void} */ const beginEditSpam = () => { - spamSnapshot.signupBlockedUsernames = [...form.signupBlockedUsernames] + assignSpamSettingsSection(spamSnapshot, form) + assignSpamSettingsSection(form, spamSnapshot) editSpam.value = true } @@ -1570,7 +1575,7 @@ const beginEditSpam = () => { * @returns {void} */ const cancelEditSpam = () => { - form.signupBlockedUsernames = [...spamSnapshot.signupBlockedUsernames] + assignSpamSettingsSection(form, spamSnapshot) editSpam.value = false } @@ -1586,11 +1591,12 @@ const saveSpamSection = async () => { const ok = await persistSiteSettings({ successToast: '스팸 필터 설정이 저장되었습니다.', savingFlag: savingSpam, - fields: ['signupBlockedUsernames'] + fields: SPAM_SETTINGS_FIELDS }) if (ok) { - spamSnapshot.signupBlockedUsernames = [...form.signupBlockedUsernames] + assignSpamSettingsSection(spamSnapshot, form) + assignSpamSettingsSection(form, spamSnapshot) editSpam.value = false } }