diff --git a/components/admin/AdminMarkdownEditor.vue b/components/admin/AdminMarkdownEditor.vue index d266614..b980061 100644 --- a/components/admin/AdminMarkdownEditor.vue +++ b/components/admin/AdminMarkdownEditor.vue @@ -131,6 +131,10 @@ const markdownValue = computed({ /** textarea 포커스·블록 패널 상호작용 */ const isTextareaFocused = ref(false) const isBlockPanelEngaged = ref(false) +/** 한글 등 IME 조합 입력 중 여부 */ +const isTextComposing = ref(false) +/** 조합 입력 중 패널 닫힘을 방지하기 위한 마지막 블록 컨텍스트 */ +const lastStableBlockContext = ref(null) let blockPanelFocusTimer = null /** @@ -184,12 +188,24 @@ const activeBlockContext = computed(() => resolveActiveBlockContext( /** @deprecated 내부 호환 alias */ const activeMediaBlock = activeBlockContext +/** + * IME 조합 중에는 일시적으로 블록 판별이 비어도 직전 패널을 유지한다. + * @returns {Object|null} 패널 표시용 블록 컨텍스트 + */ +const visibleBlockContext = computed(() => { + if (activeBlockContext.value) { + return activeBlockContext.value + } + + return isTextComposing.value ? lastStableBlockContext.value : null +}) + /** * 블록 설정 패널 표시 여부 * @returns {boolean} */ const isBlockPanelVisible = computed(() => (activeMode.value === 'write' || isBlockPanelEngaged.value) - && Boolean(activeBlockContext.value) + && Boolean(visibleBlockContext.value) && (isTextareaFocused.value || isBlockPanelEngaged.value)) /** @@ -199,11 +215,17 @@ const isBlockPanelVisible = computed(() => (activeMode.value === 'write' || isBl const syncBlockPanelState = () => { emit('block-panel', { open: isBlockPanelVisible.value, - panel: activeBlockContext.value + panel: visibleBlockContext.value }) } -watch([isBlockPanelVisible, activeBlockContext], syncBlockPanelState, { deep: true }) +watch(activeBlockContext, (context) => { + if (context) { + lastStableBlockContext.value = context + } +}, { deep: true, immediate: true }) + +watch([isBlockPanelVisible, visibleBlockContext], syncBlockPanelState, { deep: true }) /** * 본문의 논리 줄(`\\n` 기준) 개수. 빈 본문은 1줄로 본다. @@ -400,6 +422,28 @@ const handleBlockPanelFocusOut = () => { }, 50) } +/** + * IME 조합 입력 시작 시 패널 컨텍스트를 유지한다. + * @returns {void} + */ +const handleTextareaCompositionStart = () => { + isTextComposing.value = true + + if (activeBlockContext.value) { + lastStableBlockContext.value = activeBlockContext.value + } +} + +/** + * IME 조합 입력 종료 후 실제 커서 줄 기준으로 패널을 다시 동기화한다. + * @returns {void} + */ +const handleTextareaCompositionEnd = () => { + isTextComposing.value = false + refreshCaretLogicalLine() + nextTick(syncBlockPanelState) +} + /** * 커서 위치 기준으로 활성 논리 줄 인덱스를 갱신하고 거터 스크롤을 맞춘다. * @returns {void} @@ -2756,6 +2800,8 @@ const handleKeydown = (event) => { @drop="handleDrop" @dragover.prevent @input="refreshCaretLogicalLine" + @compositionstart="handleTextareaCompositionStart" + @compositionend="handleTextareaCompositionEnd" @click="refreshCaretLogicalLine" @keyup="refreshCaretLogicalLine" @select="refreshCaretLogicalLine" diff --git a/components/site/SiteAnnouncementBar.vue b/components/site/SiteAnnouncementBar.vue index 82247f8..108bd34 100644 --- a/components/site/SiteAnnouncementBar.vue +++ b/components/site/SiteAnnouncementBar.vue @@ -17,6 +17,7 @@ const { data: siteSettings } = await useFetch('/api/site-settings', { announcementText: '', announcementUrl: '', announcementBackgroundColor: '#15171a', + announcementAlignment: 'center', updatedAt: null }) }) @@ -48,6 +49,8 @@ const announcementLink = computed(() => normalizeAnnouncementUrl(siteSettings.va const snoozeLabel = computed(() => `${ANNOUNCEMENT_SNOOZE_DAYS}일간 보지 않기`) +const announcementAlignment = computed(() => siteSettings.value?.announcementAlignment === 'left' ? 'left' : 'center') + const barStyle = computed(() => { const backgroundColor = siteSettings.value?.announcementBackgroundColor || '#15171a' return { @@ -158,11 +161,17 @@ onBeforeUnmount(() => { aria-label="사이트 공지" :aria-hidden="(!expanded).toString()" > -
+
로컬 기준 v1.5.35에서 `npm run lint`, `npm run build` 검증을 통과했다. NAS 실제 컨테이너 기동과 도메인/프록시 접속 검증은 운영 배포 단계에서 진행한다. +> 로컬 기준 v1.5.38에서 `npm run lint`, `npm run build` 검증을 통과했다. NAS 실제 컨테이너 기동과 도메인/프록시 접속 검증은 운영 배포 단계에서 진행한다. ## 빌드 유형 @@ -68,6 +68,11 @@ docker exec sori-studio-db pg_isready -U sori_studio -d sori_studio docker exec sori-studio-db psql -U sori_studio -d sori_studio -c 'SELECT count(*) AS posts_count FROM posts;' ``` +### v1.5.38 마이그레이션 + +- `047_site_settings_announcement_alignment.sql`: `site_settings`에 `announcement_alignment` 컬럼을 추가한다. +- 적용 후 관리자 사이트 설정의 어나운스 바 정렬(중앙/왼쪽)이 공개 화면에 반영되는지 확인한다. + ### v1.5.35 마이그레이션 - `045_analytics_traffic_sources.sql`: 방문자 유입원·디바이스·검색 키워드 일별 축약 집계 테이블과 중복 방문 제거용 컬럼을 추가한다. diff --git a/docs/history.md b/docs/history.md index cf1e62c..051999c 100644 --- a/docs/history.md +++ b/docs/history.md @@ -1,5 +1,9 @@ # 의사결정 이력 +## 2026-06-02 v1.5.38 — 어나운스 바는 운영자가 브랜드 톤에 맞춘다 + +어나운스 바는 모든 공개 화면 상단에 노출되는 안내 요소라 프리셋 몇 개만으로는 사이트별 브랜드 톤을 맞추기 어렵다. 브랜드 컬러처럼 hex 색상 입력을 허용하되, 텍스트 대비색은 배경 밝기에 따라 자동 계산해 가독성을 유지한다. 문구 정렬은 중앙과 왼쪽 두 가지로 제한해 닫기 버튼이 오른쪽 끝에 남는 기존 구조를 유지하면서도 공지 성격에 맞게 배치할 수 있게 했다. + ## 2026-06-02 v1.5.37 — 블록 옵션은 본문 옆 설정 패널로 모은다 콜아웃, 코드 블록, 토글은 본문 안에서 직접 편집할 수 있지만 옵션 조작 위치가 블록마다 다르면 작성 흐름이 끊긴다. 인용문 배경색처럼 커서가 놓인 블록을 오른쪽 패널에서 조정하도록 통일하면, 본문 텍스트와 구조 옵션을 분리해 다룰 수 있다. 옵션은 별도 메타 저장소를 만들지 않고 각 fenced 블록의 선언 줄에 반영해 Markdown 백업·Import 흐름과도 계속 호환되게 둔다. diff --git a/docs/map.md b/docs/map.md index 11f50b8..6ec04bc 100644 --- a/docs/map.md +++ b/docs/map.md @@ -65,7 +65,7 @@ |------|-----------| | components/auth/AuthPasswordVisibilityToggle.vue | 로그인·회원가입 비밀번호 표시/숨김 토글(SVG, scoped 스타일·`field-name`으로 접근성 레이블 구분) | | components/site/SiteTopChrome.vue | 공개 레이아웃 상단 고정 영역(어나운스 바+헤더), `--site-top-chrome-height` CSS 변수 | -| components/site/SiteAnnouncementBar.vue | 공개 사이트 상단 어나운스 배너(문구·선택 링크·배경색·닫기) | +| components/site/SiteAnnouncementBar.vue | 공개 사이트 상단 어나운스 배너(문구·선택 링크·hex 배경색·텍스트 정렬·닫기) | | components/site/SiteHeader.vue | 모든 공개 페이지 상단, 사이트 이름 텍스트 브랜드, `grid-cols-3`로 검색 패널 중앙 정렬(`md+`), 우측 사용자 아바타 드롭다운(아바타 없거나 비로그인 시 사람 아이콘), `/`·`SiteSearchModal` | | components/site/SiteSearchModal.vue | `Teleport`·전체 화면 딤·Tags/Posts 결과·일치 구간 강조, 열림 시 `html.site-search-open` 스크롤 잠금 | | components/site/LeftSidebar.vue | 왼쪽 사이드바, `lg+`는 `sticky`+고정 높이+내부 무스크롤바 스크롤, `lg` 미만은 고정 슬라이드 패널, 상단 메뉴는 `SidebarPrimaryNavList`+`provide`로 트리·펼침 상태(`sori-primary-nav-expanded`), Authors 영역은 비공개, 푸터 `footer` 링크는 `flex-wrap`·테마 버튼 `shrink-0`, 태그 카테고리·테마 점은 `site-sidebar-nav-row` 호버 | @@ -88,7 +88,7 @@ | components/admin/AdminMediaVideoThumbnail.vue | 관리자 미디어 목록 비디오 항목의 초반 프레임 캔버스 썸네일 | | components/admin/AdminPostForm.vue | 관리자 글 작성/수정 폼, Ghost형 툴바(영문 상태·Publish/Update/Unpublish/Unschedule, 서버 반영 상태 기준 분기), 초안만 서버 디바운스 자동 저장·신규 임시 슬러그·발행·예약·멤버십·비공개 상태 저장, 발행 모달(중앙 배치), 좌우 설정 패널, 미리보기 emit·미저장 이탈 가드, 추천 글 토글, 태그 색상 배지 다중 입력·메인 태그 드롭다운·부분 검색 추천 | | components/admin/AdminPageForm.vue | 관리자 페이지 작성/수정 폼, 게시글 작성과 같은 전체 화면 에디터·상단 저장 툴바·접이식 오른쪽 설정 패널, 페이지 공개 상태 선택, HTML 문서 기본 모드, 빈 본문/`!`+Tab HTML 골격 자동 완성, 항상 보이는 일반 텍스트/HTML 모드 선택, 한글 제목 영문 슬러그 자동 변환, HTML textarea 커서 위치 파일 URL 삽입 | -| components/admin/AdminMarkdownEditor.vue | 관리자 글 Markdown-first 에디터, 라이브·소스 모드 `/` 슬래시 명령·미디어 모달(이미지·갤러리·비디오·오디오·파일), 커서 블록 컨텍스트·`block-panel` emit, 라이브 이미지 설정 패널·이미지↔갤러리 드래그 변환(`merge-images-to-gallery`·`insert-image-to-gallery`·`extract-gallery-image`), 블록 패널 바깥 클릭 닫기·미디어 모달 중 유지, 인용·콜아웃·코드·토글 선언 줄 옵션 수정, 소스 모드 wrap 라인 번호 보정·라이브↔소스 위치 복원 | +| components/admin/AdminMarkdownEditor.vue | 관리자 글 Markdown-first 에디터, 라이브·소스 모드 `/` 슬래시 명령·미디어 모달(이미지·갤러리·비디오·오디오·파일), 커서 블록 컨텍스트·`block-panel` emit, 라이브 이미지 설정 패널·이미지↔갤러리 드래그 변환(`merge-images-to-gallery`·`insert-image-to-gallery`·`extract-gallery-image`), 블록 패널 바깥 클릭 닫기·미디어 모달 중 유지, 인용·콜아웃·코드·토글 선언 줄 옵션 수정, IME 조합 중 블록 패널 유지, 소스 모드 wrap 라인 번호 보정·라이브↔소스 위치 복원 | | components/admin/AdminEditorBlockPanel.vue | 게시물 설정 사이드바 오버레이 블록 설정(이미지·갤러리·임베드·인용 배경색·콜아웃·코드·토글), 갤러리 선택 이미지 강조 | | components/admin/AdminBlockEditor.vue | 관리자 글 블록형 에디터, 이미지/갤러리/콜아웃/토글/임베드 블록, 콜아웃 Emoji on/off·이모지 프리셋·배경 프리셋 선택(우측 고정 설정 패널), 갤러리 복수 미디어 선택·이미지 수별 열 배치·삽입 위치 표시 드래그 순서 변경, 한글 조합 입력 처리, Shift+Enter 줄바꿈, 코드 블록 단축 변환, AFFiNE 참고 세로 막대형 블록 핸들 선택/삭제/드래그 이동과 삽입선 표시, 하단 빈 입력 블록 유지, 본문 placeholder 표시 | | components/admin/AdminTagForm.vue | 관리자 태그 생성/수정 폼(이름/슬러그/설명/색상만 편집) | @@ -146,7 +146,7 @@ | pages/admin/tags/index.vue | 태그 관리(메인 태그 드래그 정렬 자동 저장·more vert 메뉴, 일반 태그 배지 more vert 메뉴·검색/정렬, 태그 추가 버튼), 액션 피드백 토스트 | | pages/admin/tags/new.vue | 태그 생성 | | pages/admin/tags/[id].vue | 태그 수정 | -| pages/admin/settings/index.vue | 사이트 설정 Ghost형 전체 화면, **POST 설정**(`showPostUpdatedAt` 토글·읽기 모드 비활성 톤), 블로그 제목·설명, **사이트 정보**(로고·URL·저작권), **메인 화면**(라이트·다크 커버 상하 개별 프리뷰·드롭존 업로드·오버레이 텍스트), **어나운스 바**(사용 토글·맞춤 설정·배경색·읽기 모드 비활성 톤), **스팸 필터**(가입 금지 닉네임), **게시물 내보내기** 독립 카드와 펼침형 전체·연도·월·직접 날짜 범위 작업 요청·목표 ZIP 용량·ZIP당 최대 게시물 수, 작업이 있을 때만 표시되는 **최근 내보내기 작업** 별도 카드(준비 완료 배지 숨김·진행 중 진행도·만료일·파일 체크 선택·전체 선택·선택 파일 다운로드·실패 작업 재시도·실패 상세 오류·작업 삭제), **게시물 가져오기** 독립 카드와 펼침형 ZIP 드롭존·적용 버튼·완료 요약·누락 자산 경고 표시, 진행 중 요청 버튼 잠금 | +| pages/admin/settings/index.vue | 사이트 설정 Ghost형 전체 화면, **POST 설정**(`showPostUpdatedAt` 토글·읽기 모드 비활성 톤), 블로그 제목·설명, **사이트 정보**(로고·URL·저작권), **메인 화면**(라이트·다크 커버 상하 개별 프리뷰·드롭존 업로드·오버레이 텍스트), **어나운스 바**(사용 토글·맞춤 설정·hex 배경색·텍스트 정렬·읽기 모드 비활성 톤), **스팸 필터**(가입 금지 닉네임), **게시물 내보내기** 독립 카드와 펼침형 전체·연도·월·직접 날짜 범위 작업 요청·목표 ZIP 용량·ZIP당 최대 게시물 수, 작업이 있을 때만 표시되는 **최근 내보내기 작업** 별도 카드(준비 완료 배지 숨김·진행 중 진행도·만료일·파일 체크 선택·전체 선택·선택 파일 다운로드·실패 작업 재시도·실패 상세 오류·작업 삭제), **게시물 가져오기** 독립 카드와 펼침형 ZIP 드롭존·적용 버튼·완료 요약·누락 자산 경고 표시, 진행 중 요청 버튼 잠금 | | lib/signup-blocked-usernames.js | 가입 금지 닉네임 정리·매칭·안내 문구 | | pages/admin/members/index.vue | 관리자 멤버 목록(Ghost형 테이블, 글 목록과 같은 테두리형 검색, 조건 필터, 멤버 추가 버튼, 닉네임+이메일, 등급+비활성 상태, 가입일+최근 활동, IP, 댓글 수) | | pages/admin/members/new.vue | 관리자 멤버 추가(썸네일 URL, 이름, 이메일, 레이블, 관리자 노트) | diff --git a/docs/spec.md b/docs/spec.md index b61d731..0e0d7be 100644 --- a/docs/spec.md +++ b/docs/spec.md @@ -44,6 +44,7 @@ - 라이트/다크 모드는 CSS 변수로 관리 - 기본 배경, 패널, 라인, 텍스트, 보조 텍스트, 입력, 강조 버튼 색상을 분리 - 브랜드 포인트 컬러는 사이트 설정의 `brandColor` 값을 공개 앱 루트의 `--site-accent` CSS 변수로 반영한다. 기본값은 `#ff4f2e`이며, 왼쪽 사이드 활성 네비게이션, 게시글 TOC 활성 항목, 댓글 등록 버튼 등 사용자 화면의 주요 강조 요소에 사용한다. +- 어나운스 바는 사이트 설정의 문구·링크·배경색·텍스트 정렬을 반영한다. 배경색은 3/6자리 hex 값을 저장하며 기본값은 `#15171a`, 텍스트 정렬은 `center` 또는 `left`를 사용한다. - 라이트 모드 기본 배경은 `#fcfcfc`로 통일하고 패널 구분은 보더로 처리 - 시스템 다크 모드는 `prefers-color-scheme: dark` 기준으로 우선 대응 - 사용자 수동 테마 전환은 `html[data-theme]`와 `localStorage.SITE_THEME`로 관리한다. 첫 페인트 전 `lib/site-theme-init.js` 인라인 스크립트가 테마를 적용해 시스템 다크·저장 라이트 불일치 시 깜빡임을 줄인다. 공개 페이지 로딩 중에는 `#site-splash`에 캐시된 로고 이미지 URL(`SITE_BRAND_LOGO_URL`, localStorage) 또는 사이트 제목(`NUXT_PUBLIC_SITE_TITLE`)을 잠깐 표시하고, 앱 마운트 후 `site-app-ready`로 숨긴다. `site_settings.logo_text`(기본 `井`)는 **이미지 로고가 없을 때** 헤더·사이드바에 쓰는 짧은 기호이며 localStorage·스플래시와는 별개다. @@ -76,7 +77,7 @@ - 댓글 아바타 이미지 로드 실패 시 이니셜 아바타로 즉시 대체한다. - 공개 게시물 본문은 콘텐츠 타입별 컴포넌트로 분리해 추후 스타일 변경이 쉽도록 구성 - 인용문(`>`)은 첫 줄 옵션 `> [!bg=blue]` 또는 `> {bg=blue}`로 배경색을 지정할 수 있으며, 지원 값은 콜아웃과 같은 `gray`, `blue`, `green`, `yellow`, `red`, `purple`, `pink`이다. -- 관리자 Markdown-first 글쓰기의 오른쪽 블록 설정 패널은 인용·콜아웃·코드 블록·토글 설정을 지원한다. 콜아웃은 아이콘 표시 여부·아이콘·배경색, 코드 블록은 언어·줄번호 표시 여부, 토글은 기본 펼침·닫힘 상태를 선언 줄에 저장한다. +- 관리자 Markdown-first 글쓰기의 오른쪽 블록 설정 패널은 인용·콜아웃·코드 블록·토글 설정을 지원한다. 콜아웃은 아이콘 표시 여부·아이콘·배경색, 코드 블록은 언어·줄번호 표시 여부, 토글은 기본 펼침·닫힘 상태를 선언 줄에 저장한다. 한글 등 IME 조합 입력 중에는 줄바꿈 직후 블록 판별이 일시적으로 비어도 마지막 블록 컨텍스트를 유지해 설정 패널이 닫히지 않게 한다. - 게시물 상세의 오른쪽 사이드바는 데스크톱에서 추천 사이트 대신 본문 H1~H3 제목 기반 TOC를 표시한다. TOC 링크는 본문 제목에 부여된 앵커 ID로 부드럽게 이동하며, 고정 상단 헤더 높이와 여백을 반영해 제목이 화면 밖에 걸리지 않게 한다. 본문 스크롤 중에는 현재 제목에 해당하는 TOC 항목을 강조하고, 목차 항목이 많으면 TOC 내부 스크롤이 활성 항목을 따라간다. 본문 제목이 없으면 목차 없음 문구를 표시한다. 오른쪽 사이드바가 본문 아래로 내려가는 모바일 폭에서는 TOC를 숨긴다. - 제목 우측 공유 버튼을 누르면 게시물 공유 모달을 연다. - 로그인 회원 ID가 게시물 `author_id`와 같으면 제목 우측 공유 버튼 옆에 수정 아이콘을 표시하며, 클릭 시 `/admin/posts/:id` 편집 화면을 새 탭으로 연다. diff --git a/docs/update.md b/docs/update.md index c849a78..317ad01 100644 --- a/docs/update.md +++ b/docs/update.md @@ -1,5 +1,13 @@ # 업데이트 이력 +## v1.5.38 + +- 관리자 사이트 설정: 어나운스 바 배경색을 프리셋뿐 아니라 직접 hex 색상으로 선택·입력할 수 있도록 수정. +- 관리자 사이트 설정: 어나운스 바 텍스트 정렬을 중앙 또는 왼쪽으로 선택할 수 있도록 추가. +- 공개 어나운스 바: 설정된 배경색과 텍스트 정렬을 반영하도록 수정. +- 게시물 글쓰기: 한글 IME 조합 입력 중 코드·콜아웃·토글 블록 설정 패널이 줄바꿈 뒤 닫히지 않도록 보강. +- DB: `site_settings.announcement_alignment` 컬럼 추가. + ## v1.5.37 - 게시물 글쓰기: 콜아웃 블록 오른쪽 설정 패널에서 아이콘 표시 여부·아이콘·배경색을 수정할 수 있도록 추가. diff --git a/lib/announcement-bar.js b/lib/announcement-bar.js index 132c084..cbed85d 100644 --- a/lib/announcement-bar.js +++ b/lib/announcement-bar.js @@ -11,14 +11,54 @@ export const ANNOUNCEMENT_BACKGROUND_PRESETS = [ /** @type {string} 기본 어나운스 바 배경색 */ export const DEFAULT_ANNOUNCEMENT_BACKGROUND_COLOR = '#15171a' +/** @type {string} 기본 어나운스 바 정렬 */ +export const DEFAULT_ANNOUNCEMENT_ALIGNMENT = 'center' + /** - * 어나운스 바 배경색이 허용 프리셋인지 확인한다. - * @param {string} value - hex 색상 - * @returns {boolean} 허용 여부 + * 어나운스 바 정렬 옵션 + * @type {ReadonlyArray<{ value: string, label: string }>} */ -export const isValidAnnouncementBackgroundColor = (value) => { - const normalized = (value || '').trim().toLowerCase() - return ANNOUNCEMENT_BACKGROUND_PRESETS.some((preset) => preset.value.toLowerCase() === normalized) +export const ANNOUNCEMENT_ALIGNMENT_OPTIONS = [ + { value: 'center', label: '중앙' }, + { value: 'left', label: '왼쪽' } +] + +/** + * 어나운스 바 배경색 형식이 올바른지 확인한다. + * @param {unknown} value - hex 색상 + * @returns {boolean} 유효 여부 + */ +export const isValidAnnouncementBackgroundColor = (value) => /^#(?:[0-9a-f]{3}|[0-9a-f]{6})$/i.test(String(value || '').trim()) + +/** + * 어나운스 바 배경색을 6자리 hex 값으로 정규화한다. + * @param {unknown} value - 입력 색상 + * @returns {string} 정규화된 색상 + */ +export const normalizeAnnouncementBackgroundColor = (value) => { + const color = String(value || '').trim().toLowerCase() + + if (!isValidAnnouncementBackgroundColor(color)) { + return DEFAULT_ANNOUNCEMENT_BACKGROUND_COLOR + } + + if (color.length === 4) { + return `#${color[1]}${color[1]}${color[2]}${color[2]}${color[3]}${color[3]}` + } + + return color +} + +/** + * 어나운스 바 정렬 값을 정리한다. + * @param {unknown} value - 입력 정렬 + * @returns {string} 정리된 정렬 + */ +export const normalizeAnnouncementAlignment = (value) => { + const alignment = String(value || '').trim().toLowerCase() + return ANNOUNCEMENT_ALIGNMENT_OPTIONS.some((option) => option.value === alignment) + ? alignment + : DEFAULT_ANNOUNCEMENT_ALIGNMENT } /** @@ -27,13 +67,19 @@ export const isValidAnnouncementBackgroundColor = (value) => { * @returns {string} 전경 hex 색상 */ export const getAnnouncementBarTextColor = (backgroundColor) => { - const normalized = (backgroundColor || '').trim().toLowerCase() + const normalized = normalizeAnnouncementBackgroundColor(backgroundColor) const preset = ANNOUNCEMENT_BACKGROUND_PRESETS.find((item) => item.value.toLowerCase() === normalized) if (preset) { return preset.textColor } - return '#ffffff' + const hex = normalized.slice(1) + const red = parseInt(hex.slice(0, 2), 16) + const green = parseInt(hex.slice(2, 4), 16) + const blue = parseInt(hex.slice(4, 6), 16) + const luminance = (red * 299 + green * 587 + blue * 114) / 1000 + + return luminance > 150 ? '#15171a' : '#ffffff' } /** diff --git a/package-lock.json b/package-lock.json index 42722c2..64548cf 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "sori.studio", - "version": "1.5.35", + "version": "1.5.38", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "sori.studio", - "version": "1.5.35", + "version": "1.5.38", "hasInstallScript": true, "dependencies": { "@nuxtjs/tailwindcss": "^6.14.0", diff --git a/package.json b/package.json index 4717216..9b25ef1 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "sori.studio", - "version": "1.5.37", + "version": "1.5.38", "private": true, "type": "module", "imports": { diff --git a/pages/admin/settings/index.vue b/pages/admin/settings/index.vue index 803f84f..9818c97 100644 --- a/pages/admin/settings/index.vue +++ b/pages/admin/settings/index.vue @@ -1,5 +1,12 @@