v1.3.2: 어나운스 바 슬라이드·설정 내비 아이콘

어나운스 바는 숨김 확인 후 슬라이드 인/아웃하고 7일간 보지 않기를 지원한다. 설정 좌측 내비에 타임존·메인 화면·어나운스·Import/Export·스팸 필터 아이콘을 추가한다.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-05-19 16:23:20 +09:00
parent b77f37a94e
commit b6a3228b09
7 changed files with 325 additions and 90 deletions

View File

@@ -3,7 +3,7 @@
* 관리자 사이트 설정 좌측 내비 아이콘
* @property {string} [iconId] - 아이콘 식별자. 미지정·미구현 시 자리 표시(placeholder)만 렌더
*/
const props = defineProps({
defineProps({
iconId: {
type: String,
default: ''
@@ -17,6 +17,7 @@ const props = defineProps({
:class="iconId ? `admin-settings-nav-icon--${iconId}` : 'admin-settings-nav-icon--placeholder'"
aria-hidden="true"
>
<!-- 블로그 제목·설명 -->
<svg
v-if="iconId === 'title-desc'"
class="admin-settings-nav-icon__svg pointer-events-none size-4"
@@ -24,35 +25,83 @@ const props = defineProps({
viewBox="-0.75 -0.75 24 24"
fill="none"
>
<path
d="M2.109375 6.32625h18.28125s1.40625 0 1.40625 1.40625v7.03125s0 1.40625 -1.40625 1.40625H2.109375s-1.40625 0 -1.40625 -1.40625v-7.03125s0 -1.40625 1.40625 -1.40625"
stroke="currentColor"
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="1.5"
/>
<path
d="m16.171875 17.57625 0 -12.65625"
stroke="currentColor"
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="1.5"
/>
<path
d="M11.953125 21.795a4.21875 4.21875 0 0 0 4.21875 -4.21875 4.21875 4.21875 0 0 0 4.21875 4.21875"
stroke="currentColor"
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="1.5"
/>
<path
d="M11.953125 0.70125a4.21875 4.21875 0 0 1 4.21875 4.21875 4.21875 4.21875 0 0 1 4.21875 -4.21875"
stroke="currentColor"
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="1.5"
/>
<path d="M2.109375 6.32625h18.28125s1.40625 0 1.40625 1.40625v7.03125s0 1.40625 -1.40625 1.40625H2.109375s-1.40625 0 -1.40625 -1.40625v-7.03125s0 -1.40625 1.40625 -1.40625" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" />
<path d="m16.171875 17.57625 0 -12.65625" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" />
<path d="M11.953125 21.795a4.21875 4.21875 0 0 0 4.21875 -4.21875 4.21875 4.21875 0 0 0 4.21875 4.21875" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" />
<path d="M11.953125 0.70125a4.21875 4.21875 0 0 1 4.21875 4.21875 4.21875 4.21875 0 0 1 4.21875 -4.21875" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" />
</svg>
<!-- 타임존 -->
<svg
v-else-if="iconId === 'timezone'"
class="admin-settings-nav-icon__svg pointer-events-none size-4"
xmlns="http://www.w3.org/2000/svg"
viewBox="-0.75 -0.75 24 24"
fill="none"
>
<path d="M10.546875 16.171875a5.625 5.625 0 1 0 11.25 0 5.625 5.625 0 1 0 -11.25 0Z" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" />
<path d="m18.658125000000002 16.171875 -2.48625 0 0 -2.4853125" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" />
<path d="M9.838125 21.703125a10.5478125 10.5478125 0 1 1 11.866875 -11.85375" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" />
<path d="M8.7084375 21.4884375C7.2825 19.3959375 6.328125 15.593437499999999 6.328125 11.25S7.2825 3.105 8.7084375 1.0115625" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" />
<path d="m0.7265625 10.546875 8.9278125 0" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" />
<path d="M2.8115625 4.921875 19.6875 4.921875" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" />
<path d="m1.92 16.171875 5.814375 0" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" />
<path d="M13.7915625 1.0115625a15.9215625 15.9215625 0 0 1 2.15625 6.69" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" />
</svg>
<!-- 메인 화면 -->
<svg
v-else-if="iconId === 'home-cover'"
class="admin-settings-nav-icon__svg pointer-events-none size-4"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
fill="none"
>
<path d="M3 2.25h18s1.5 0 1.5 1.5v16.5s0 1.5 -1.5 1.5H3s-1.5 0 -1.5 -1.5V3.75s0 -1.5 1.5 -1.5" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" />
<path d="m1.5 6.75 21 0" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" />
<path d="m9 6.75 0 15" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" />
<path d="m9 14.25 13.5 0" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" />
</svg>
<!-- 어나운스 -->
<svg
v-else-if="iconId === 'announcement'"
class="admin-settings-nav-icon__svg pointer-events-none size-4"
xmlns="http://www.w3.org/2000/svg"
viewBox="-0.75 -0.75 24 24"
fill="none"
>
<path d="M6.328125 14.296875H4.21875a3.515625 3.515625 0 0 1 0 -7.03125h2.109375Z" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" />
<path d="M6.328125 14.296875a20.90625 20.90625 0 0 1 11.593125 3.5100000000000002l1.0631249999999999 0.70875V3.046875l-1.0631249999999999 0.70875A20.90625 20.90625 0 0 1 6.328125 7.265625Z" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" />
<path d="m21.796875 9.375 0 2.8125" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" />
<path d="M6.328125 14.296875A6.7865625 6.7865625 0 0 0 8.4375 19.21875" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" />
</svg>
<!-- Import/Export -->
<svg
v-else-if="iconId === 'import-export'"
class="admin-settings-nav-icon__svg pointer-events-none size-4"
xmlns="http://www.w3.org/2000/svg"
viewBox="-0.75 -0.75 24 24"
fill="none"
>
<path d="m11.2509375 3.515625 0 11.25" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" />
<path d="m7.0321875 10.546875 4.21875 4.21875 4.21875 -4.21875" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" />
<path d="M21.797812500000003 14.765625v1.40625a2.8125 2.8125 0 0 1 -2.8125 2.8125h-15.46875a2.8125 2.8125 0 0 1 -2.8125 -2.8125v-1.40625" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" />
</svg>
<!-- 스팸 필터 -->
<svg
v-else-if="iconId === 'spam'"
class="admin-settings-nav-icon__svg pointer-events-none size-4"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
fill="none"
>
<path d="M19.0902 4.90918L4.9082 19.0912" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" />
<path d="M12 22C17.5228 22 22 17.5228 22 12C22 6.47715 17.5228 2 12 2C6.47715 2 2 6.47715 2 12C2 17.5228 6.47715 22 12 22Z" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" />
</svg>
<span
v-else
class="admin-settings-nav-icon__placeholder size-4 rounded-sm border border-dashed border-[#c8ced3]"

View File

@@ -1,10 +1,15 @@
<script setup>
import {
ANNOUNCEMENT_SNOOZE_DAYS,
dismissAnnouncementForDays,
dismissAnnouncementForSession,
getAnnouncementBarTextColor,
isAnnouncementDismissed,
normalizeAnnouncementUrl
} from '~/lib/announcement-bar.js'
const DISMISS_STORAGE_KEY = 'SITE_ANNOUNCEMENT_DISMISSED_AT'
/** @type {number} 슬라이드 애니메이션 시간(ms) */
const SLIDE_MS = 320
const { data: siteSettings } = await useFetch('/api/site-settings', {
default: () => ({
@@ -16,29 +21,33 @@ const { data: siteSettings } = await useFetch('/api/site-settings', {
})
})
/** DOM에 바를 둘지(애니메이션 종료 전까지 유지) */
const inDom = ref(false)
/** 펼침(아래로 슬라이드) 애니메이션 상태 */
const expanded = ref(false)
/** 숨김 처리 완료 여부 */
const dismissed = ref(false)
let closeTimer = null
/**
* 어나운스 바 노출 여부
* @returns {boolean} 노출 여부
* 설정상 어나운스 바를 켤 수 있는지
* @returns {boolean} 가능 여부
*/
const isVisible = computed(() => {
const isEligible = computed(() => {
if (!siteSettings.value?.announcementEnabled) {
return false
}
const text = (siteSettings.value?.announcementText || '').trim()
if (!text) {
return false
}
return !dismissed.value
return Boolean((siteSettings.value?.announcementText || '').trim())
})
const announcementText = computed(() => (siteSettings.value?.announcementText || '').trim())
const announcementLink = computed(() => normalizeAnnouncementUrl(siteSettings.value?.announcementUrl || ''))
const snoozeLabel = computed(() => `${ANNOUNCEMENT_SNOOZE_DAYS}일간 보지 않기`)
const barStyle = computed(() => {
const backgroundColor = siteSettings.value?.announcementBackgroundColor || '#15171a'
return {
@@ -48,69 +57,140 @@ const barStyle = computed(() => {
})
/**
* 로컬에 저장된 닫기 상태를 반영한다.
* @returns {void}
* 어나운스 바를 펼친다.
* @returns {Promise<void>}
*/
const syncDismissedFromStorage = () => {
if (!import.meta.client) {
return
}
const stored = localStorage.getItem(DISMISS_STORAGE_KEY)
const updatedAt = siteSettings.value?.updatedAt || ''
dismissed.value = Boolean(stored && stored === updatedAt)
const openBar = async () => {
inDom.value = true
expanded.value = false
await nextTick()
requestAnimationFrame(() => {
requestAnimationFrame(() => {
expanded.value = true
})
})
}
/**
* 어나운스 바를 닫는다.
* 어나운스 바를 접고 DOM에서 제거한다.
* @param {() => void} persistDismiss - localStorage·sessionStorage 저장
* @returns {void}
*/
const dismissAnnouncement = () => {
dismissed.value = true
const closeBar = (persistDismiss) => {
if (!inDom.value) {
return
}
persistDismiss()
expanded.value = false
if (closeTimer) {
clearTimeout(closeTimer)
}
closeTimer = setTimeout(() => {
inDom.value = false
dismissed.value = true
closeTimer = null
}, SLIDE_MS)
}
/**
* 이번 방문(세션) 동안만 닫는다.
* @returns {void}
*/
const dismissForSession = () => {
closeBar(() => dismissAnnouncementForSession(siteSettings.value))
}
/**
* N일간 보지 않기로 닫는다.
* @returns {void}
*/
const dismissForSnooze = () => {
closeBar(() => dismissAnnouncementForDays(siteSettings.value))
}
watch(() => siteSettings.value?.updatedAt, async () => {
if (!import.meta.client) {
return
}
localStorage.setItem(DISMISS_STORAGE_KEY, siteSettings.value?.updatedAt || '')
}
const hidden = isAnnouncementDismissed(siteSettings.value)
dismissed.value = hidden
watch(() => siteSettings.value?.updatedAt, syncDismissedFromStorage)
if (!isEligible.value || hidden) {
expanded.value = false
inDom.value = false
return
}
await openBar()
})
onMounted(() => {
syncDismissedFromStorage()
dismissed.value = isAnnouncementDismissed(siteSettings.value)
if (isEligible.value && !dismissed.value) {
openBar()
}
})
onBeforeUnmount(() => {
if (closeTimer) {
clearTimeout(closeTimer)
}
})
</script>
<template>
<div
v-if="isVisible"
class="site-announcement-bar relative z-30 min-h-9 w-full text-center text-sm font-medium"
:style="barStyle"
role="region"
aria-label="사이트 공지"
v-if="inDom"
class="site-announcement-bar-shell grid transition-[grid-template-rows] duration-300 ease-out motion-reduce:transition-none"
:class="expanded ? 'grid-rows-[1fr]' : 'grid-rows-[0fr]'"
:style="{ transitionDuration: `${SLIDE_MS}ms` }"
>
<div class="site-announcement-bar__inner relative mx-auto flex max-w-[1294px] items-center justify-center px-10 py-2.5 lg:px-12">
<component
:is="announcementLink ? 'a' : 'span'"
class="site-announcement-bar__text line-clamp-2"
:class="announcementLink ? 'hover:underline' : ''"
:href="announcementLink || undefined"
:target="announcementLink ? '_blank' : undefined"
:rel="announcementLink ? 'noreferrer' : undefined"
<div class="min-h-0 overflow-hidden">
<div
class="site-announcement-bar relative z-30 w-full text-center text-sm font-medium"
:style="barStyle"
role="region"
aria-label="사이트 공지"
:aria-hidden="(!expanded).toString()"
>
{{ announcementText }}
</component>
<button
class="site-announcement-bar__close absolute top-1/2 right-3 inline-flex size-7 -translate-y-1/2 items-center justify-center rounded-full opacity-80 transition hover:opacity-100 lg:right-4"
type="button"
aria-label="공지 닫기"
@click="dismissAnnouncement"
>
<svg xmlns="http://www.w3.org/2000/svg" class="size-4" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
<path d="M18 6 6 18" />
<path d="m6 6 12 12" />
</svg>
</button>
<div class="site-announcement-bar__inner relative mx-auto flex min-h-9 max-w-[1294px] items-center justify-center gap-3 px-4 py-2.5 sm:px-6 lg:px-8">
<component
:is="announcementLink ? 'a' : 'span'"
class="site-announcement-bar__text min-w-0 flex-1 line-clamp-2 px-6 sm:px-8"
:class="announcementLink ? 'hover:underline' : ''"
:href="announcementLink || undefined"
:target="announcementLink ? '_blank' : undefined"
:rel="announcementLink ? 'noreferrer' : undefined"
>
{{ announcementText }}
</component>
<div class="site-announcement-bar__actions absolute top-1/2 right-3 flex shrink-0 -translate-y-1/2 items-center gap-2 sm:right-4 lg:right-5">
<button
class="site-announcement-bar__snooze whitespace-nowrap text-xs font-medium underline-offset-2 opacity-90 transition hover:underline hover:opacity-100 sm:text-[13px]"
type="button"
@click="dismissForSnooze"
>
{{ snoozeLabel }}
</button>
<button
class="site-announcement-bar__close inline-flex size-7 items-center justify-center rounded-full opacity-80 transition hover:opacity-100"
type="button"
aria-label="이번 방문 동안 닫기"
@click="dismissForSession"
>
<svg xmlns="http://www.w3.org/2000/svg" class="size-4" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
<path d="M18 6 6 18" />
<path d="m6 6 12 12" />
</svg>
</button>
</div>
</div>
</div>
</div>
</div>
</template>

View File

@@ -567,7 +567,7 @@ components/content/
- **메인 화면**(`home_cover_image_url`, `home_cover_title`, `home_cover_text`): 홈(`/`) 상단 720px 커버 배너. 이미지가 있을 때만 `HomeHero`를 표시하며, 제목·짧은 본문은 이미지 왼쪽 하단 그라데이션 오버레이로 겹친다. 관리자 UI에서는 커버 파일 업로드·제목·본문을 편집한 뒤 **저장** 한 번에 `PUT /admin/api/settings`로 반영한다. 파일 업로드 API는 디스크에만 올리고 URL을 돌려준다(가로 720px WebP, `/uploads/system/home-cover-YYYYMM-random.webp`).
- **POST 설정**(`show_post_updated_at`): 발행 후 본문·메타 수정이 있을 때 관리자 글 목록·공개 글 상세에 수정 시각 보조 줄을 표시할지 여부.
- **가입 금지 닉네임**(`signup_blocked_usernames`, JSON 문자열): 회원가입·회원 프로필 닉네임 변경 시 닉네임에 목록 단어가 포함되면 거부한다(대소문자 무시, 부분 일치). 안내 문구는 `{단어}은 사용할 수 없는 단어입니다.` 형식이다. 기본값: `admin`, `master`, `zenn`, `sori`, `sori.studio`.
- **어나운스 바**(`announcement_enabled`, `announcement_text`, `announcement_url`, `announcement_background_color`): `announcement_enabled`가 true이고 문구가 비어 있지 않으면 공개 레이아웃(`default`·`post`) 헤더 위에 전체 너비 배너를 표시한다. `announcement_url`이 있으면 문구를 링크로 감싼다(내부 `/…` 또는 `http(s)://`). 배경색은 `#15171a`·`#ffffff`·`#ff4f2e` 중 하나. 방문자가 닫으면 `localStorage``SITE_ANNOUNCEMENT_DISMISSED_AT`에 당시 `updatedAt`을 저장하고, 설정이 다시 저장되`updatedAt`바뀌면 배너가 다시 나타난다.
- **어나운스 바**(`announcement_enabled`, `announcement_text`, `announcement_url`, `announcement_background_color`): `announcement_enabled`가 true이고 문구가 비어 있지 않으면 공개 레이아웃(`default`·`post`) 헤더 위에 전체 너비 배너를 표시한다. `announcement_url`이 있으면 문구를 링크로 감싼다(내부 `/…` 또는 `http(s)://`). 배경색은 `#15171a`·`#ffffff`·`#ff4f2e` 중 하나. 방문자**이번 방문 동안 닫기**(X, `sessionStorage`) 또는 **7일간 보지 않기**(`localStorage`, 만료 시각 저장)를 선택할 수 있다. 공지 내용이 바뀌`updatedAt`달라지면 다시 노출된다.
- 로고 이미지는 1:1 비율로 저장하며 `/admin/api/settings/logo` 업로드 시 `/uploads/system/logo-YYYYMM-random.webp``/uploads/system/favicon-YYYYMM-random.png`를 고유 파일명으로 함께 생성한다. 같은 URL 덮어쓰기로 인한 브라우저·운영 정적 캐시 문제를 피하기 위해 로고 교체마다 새 URL을 저장한다.
- 공개 헤더와 오른쪽 사이드바는 공개 사이트 설정 API 값을 사용한다.
- 공개 헤더와 오른쪽 사이드바는 `logo_url`이 있으면 이미지 로고를 표시하고, 없으면 `logo_text` fallback을 쓴다. `favicon_url`은 head의 icon 링크로 연결한다.

View File

@@ -1,5 +1,11 @@
# 업데이트 이력
## v1.3.2
- 관리자 설정 내비: 타임존·메인 화면·어나운스 바·Import/Export·스팸 필터 아이콘 추가.
- 어나운스 바: 클라이언트에서 숨김 여부 확인 후 아래로 슬라이드 인. 닫기·7일간 보지 않기 시 위로 슬라이드 아웃 후 제거(깜빡임 방지).
- 어나운스 바: X는 이번 방문(세션)만 숨김, `7일간 보지 않기` 텍스트 버튼으로 localStorage 7일 스누즈.
## v1.3.1
- 스팸 필터: 가입 금지 닉네임(`signupBlockedUsernames`) 설정·저장. 기본 admin, master, zenn, sori, sori.studio. 회원가입·프로필 닉네임 변경 시 검사, `zenn은 사용할 수 없는 단어입니다.` 형식 안내. 마이그레이션 `029_site_settings_signup_blocked_usernames.sql`.

View File

@@ -62,3 +62,103 @@ export const normalizeAnnouncementUrl = (url) => {
return ''
}
/** @type {string} 어나운스 바 7일 숨김 localStorage 키 */
export const ANNOUNCEMENT_DISMISS_STORAGE_KEY = 'SITE_ANNOUNCEMENT_DISMISS'
/** @type {string} 어나운스 바 이번 방문(세션) 숨김 sessionStorage 키 */
export const ANNOUNCEMENT_SESSION_DISMISS_KEY = 'SITE_ANNOUNCEMENT_SESSION_DISMISS'
/** @type {number} 기본 숨김 일수 */
export const ANNOUNCEMENT_SNOOZE_DAYS = 7
/**
* 어나운스 바 닫기 식별 키(설정 저장 시각)
* @param {{ updatedAt?: string | null }} settings - 사이트 설정
* @returns {string} 식별 키
*/
export const getAnnouncementDismissKey = (settings) => settings?.updatedAt || ''
/**
* 어나운스 바가 숨김 상태인지 확인한다.
* @param {{ updatedAt?: string | null }} settings - 사이트 설정
* @returns {boolean} 숨김 여부
*/
export const isAnnouncementDismissed = (settings) => {
if (!import.meta.client) {
return false
}
const key = getAnnouncementDismissKey(settings)
if (!key) {
return false
}
const sessionDismissed = sessionStorage.getItem(ANNOUNCEMENT_SESSION_DISMISS_KEY)
if (sessionDismissed === key) {
return true
}
const raw = localStorage.getItem(ANNOUNCEMENT_DISMISS_STORAGE_KEY)
if (!raw) {
return false
}
try {
const parsed = JSON.parse(raw)
if (parsed?.key !== key) {
return false
}
if (typeof parsed.until === 'number' && Date.now() < parsed.until) {
return true
}
if (parsed.until == null && parsed.key === key) {
return true
}
} catch {
return false
}
return false
}
/**
* 이번 브라우저 방문(세션) 동안만 어나운스 바를 숨긴다.
* @param {{ updatedAt?: string | null }} settings - 사이트 설정
* @returns {void}
*/
export const dismissAnnouncementForSession = (settings) => {
if (!import.meta.client) {
return
}
const key = getAnnouncementDismissKey(settings)
if (!key) {
return
}
sessionStorage.setItem(ANNOUNCEMENT_SESSION_DISMISS_KEY, key)
}
/**
* 지정 일수 동안 어나운스 바를 숨긴다.
* @param {{ updatedAt?: string | null }} settings - 사이트 설정
* @param {number} [days=ANNOUNCEMENT_SNOOZE_DAYS] - 숨김 일수
* @returns {void}
*/
export const dismissAnnouncementForDays = (settings, days = ANNOUNCEMENT_SNOOZE_DAYS) => {
if (!import.meta.client) {
return
}
const key = getAnnouncementDismissKey(settings)
if (!key) {
return
}
const until = Date.now() + days * 24 * 60 * 60 * 1000
localStorage.setItem(ANNOUNCEMENT_DISMISS_STORAGE_KEY, JSON.stringify({ key, until }))
sessionStorage.setItem(ANNOUNCEMENT_SESSION_DISMISS_KEY, key)
}

View File

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

View File

@@ -172,7 +172,7 @@ const settingsNavGroups = [
heading: '일반',
items: [
{ id: 'admin-settings-section-title', label: '블로그 제목·설명', keywords: 'title description site name', iconId: 'title-desc' },
{ id: 'admin-settings-section-timezone', label: '타임존', keywords: 'timezone seoul gmt' },
{ id: 'admin-settings-section-timezone', label: '타임존', keywords: 'timezone seoul gmt', iconId: 'timezone' },
{ id: 'admin-settings-section-misc', label: '기타 설정', keywords: 'logo url copyright favicon' }
]
},
@@ -185,15 +185,15 @@ const settingsNavGroups = [
{
heading: '사이트',
items: [
{ id: 'admin-settings-section-home-cover', label: '메인 화면', keywords: 'home cover hero banner image' },
{ id: 'admin-settings-section-announcement', label: '어나운스 바', keywords: 'announcement banner notice' }
{ id: 'admin-settings-section-home-cover', label: '메인 화면', keywords: 'home cover hero banner image', iconId: 'home-cover' },
{ id: 'admin-settings-section-announcement', label: '어나운스 바', keywords: 'announcement banner notice', iconId: 'announcement' }
]
},
{
heading: '콘텐츠·안전',
items: [
{ id: 'admin-settings-section-import-export', label: '게시물 Import/Export', keywords: 'import export backup' },
{ id: 'admin-settings-section-spam', label: '스팸 필터', keywords: 'spam moderation comments' }
{ id: 'admin-settings-section-import-export', label: '게시물 Import/Export', keywords: 'import export backup', iconId: 'import-export' },
{ id: 'admin-settings-section-spam', label: '스팸 필터', keywords: 'spam moderation comments', iconId: 'spam' }
]
}
]
@@ -1434,7 +1434,7 @@ onBeforeUnmount(() => {
v-if="!customizeAnnouncement"
class="mr-5 mt-1 text-pretty text-sm leading-relaxed text-[#657080]"
>
홈페이지 상단에 중요한 공지나 링크를 표시합니다. 방문자는 닫기 버튼으로 숨길 으며, 설정을 바꾸 다시 노출됩니다.
홈페이지 상단에 중요한 공지나 링크를 표시합니다. 방문자는 X로 이번 방문만 닫거나, 7일간 보지 않기를 선택할 습니다. 공지를 수정·저장하 다시 노출됩니다.
</p>
</div>
<div class="-mr-1 mt-[-5px] flex shrink-0 items-center justify-start gap-2">