From 59a50a0c970570be9f96a34524d357956c14a646 Mon Sep 17 00:00:00 2001 From: zenn Date: Fri, 15 May 2026 11:21:57 +0900 Subject: [PATCH] =?UTF-8?q?=ED=83=9C=EA=B7=B8=20=EA=B4=80=EB=A6=AC=20?= =?UTF-8?q?=EC=9E=90=EB=8F=99=20=EC=A0=80=EC=9E=A5=20=EC=A0=95=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/changelog.md | 5 ++ docs/history.md | 6 +++ docs/map.md | 4 +- docs/spec.md | 10 ++-- docs/update.md | 8 +++ package-lock.json | 4 +- package.json | 2 +- pages/admin/tags/index.vue | 53 +++++++++++-------- server/routes/admin/api/settings/logo.post.js | 10 ++-- 9 files changed, 67 insertions(+), 35 deletions(-) diff --git a/docs/changelog.md b/docs/changelog.md index 36396fb..a6c3bc3 100644 --- a/docs/changelog.md +++ b/docs/changelog.md @@ -1,5 +1,10 @@ # 업데이트 요약 +## v1.1.8 + +- 로고·파비콘 파일명 접미사를 년월+랜덤 문자열로 줄임. +- 태그 추가 버튼을 일반 태그 영역으로 옮기고, 메인 태그 순서는 드래그 후 자동 저장되도록 개선. + ## v1.1.7 - 사이트 로고와 파비콘을 교체할 때 새 고유 URL로 저장해 운영 환경에서 이전 이미지가 캐시에 남는 문제를 줄임. diff --git a/docs/history.md b/docs/history.md index c6d946e..75bcd9e 100644 --- a/docs/history.md +++ b/docs/history.md @@ -1,5 +1,11 @@ # 의사결정 이력 +## 2026-05-15 v1.1.8 + +### 태그 순서 저장을 드롭 즉시 자동화 + +메인 태그 정렬은 드래그 자체가 명확한 저장 의도를 가진 조작이므로 별도의 `정렬 저장` 버튼을 두면 화면의 책임이 나뉘어 보인다. 태그 추가 버튼도 화면 전체 제목 옆에 있으면 메인 태그 추가처럼 보일 수 있어, 새 태그가 기본적으로 일반 태그로 생성되는 현재 구조에 맞춰 일반 태그 섹션 헤더 오른쪽으로 옮겼다. 순서 저장 중에는 추가 드래그를 잠시 막아 서버 순서와 화면 순서가 어긋나지 않게 한다. + ## 2026-05-15 v1.1.7 ### 사이트 로고 파일명을 교체마다 고유하게 저장 diff --git a/docs/map.md b/docs/map.md index 7d9d1b2..57bf7e9 100644 --- a/docs/map.md +++ b/docs/map.md @@ -116,7 +116,7 @@ | pages/admin/pages/[id].vue | 페이지 수정, 저장/삭제 토스트 | | pages/admin/media/index.vue | 미디어 관리, **미디어 라이브러리/썸네일** 탭, 검색은 파일명·게시물/페이지 사용처 제목만, 라이브러리: 폴더 트리·드래그 이동 등, 현재 사이트 설정 로고·파비콘은 사용 중 파일로 잠금, 썸네일: 미참조 파일 삭제·이름 변경 가능, 상세 모달(연결 회원·폴더 편집·**다운로드**) | | pages/admin/navigation/index.vue | 메뉴 관리: 상단/하단 탭, 테이블+행 드래그(태그 메인과 동일 톤), `useAdminToast` | -| pages/admin/tags/index.vue | 태그 관리(메인 태그 드래그 정렬·일반 강등, 일반 태그 검색/메인 전환/삭제), 액션 피드백 토스트, 순서 변경 시에만 정렬 저장 활성 | +| pages/admin/tags/index.vue | 태그 관리(메인 태그 드래그 정렬 자동 저장·일반 강등, 일반 태그 검색/메인 전환/삭제, 일반 태그 헤더의 태그 추가 버튼), 액션 피드백 토스트 | | pages/admin/tags/new.vue | 태그 생성 | | pages/admin/tags/[id].vue | 태그 수정 | | pages/admin/settings/index.vue | 사이트 설정(이름, 설명, URL, 1:1 로고 업로드·고유 URL 파비콘 생성, 저작권 문구) | @@ -201,7 +201,7 @@ | server/routes/admin/api/tags/reorder.put.js | 관리자 메인 태그 순서 일괄 저장 API | | server/routes/admin/api/settings.get.js | 관리자 사이트 설정 조회 API | | server/routes/admin/api/settings.put.js | 관리자 사이트 설정 수정 API | -| server/routes/admin/api/settings/logo.post.js | 관리자 사이트 로고 업로드 API(`/uploads/system/logo-*.webp`, `/uploads/system/favicon-*.png` 생성, `시스템` 미디어 메타 저장) | +| server/routes/admin/api/settings/logo.post.js | 관리자 사이트 로고 업로드 API(`/uploads/system/logo-YYYYMM-*.webp`, `/uploads/system/favicon-YYYYMM-*.png` 생성, `시스템` 미디어 메타 저장) | | server/routes/admin/api/navigation.get.js | 관리자 네비게이션 목록 API | | server/routes/admin/api/navigation.put.js | 관리자 네비게이션 일괄 저장 API | | server/routes/admin/api/members.get.js | 관리자 멤버 목록 API | diff --git a/docs/spec.md b/docs/spec.md index 2d8f2e4..b7a3837 100644 --- a/docs/spec.md +++ b/docs/spec.md @@ -445,8 +445,8 @@ components/content/ > 공개 `GET /api/tags`는 `managed`(메인 태그)만 반환한다. > 관리자 태그 목록 응답은 각 태그의 `postCount`, `lastUsedAt`, `updatedAt`을 포함한다. > 관리자 태그 목록은 `managed` 우선, `sort_order ASC, 최근 사용/수정 DESC, name ASC` 기준으로 정렬한다. -> 메인 태그 순서 저장은 드래그 순서를 받아 `sort_order`를 순차 값으로 다시 저장한다. -> 메인 태그 순서가 서버에서 불러온 순서와 같을 때는 `정렬 저장` 버튼이 비활성화된다. +> 메인 태그 순서 저장은 드래그 드롭 직후 자동으로 실행되며, 드래그 순서를 받아 `sort_order`를 순차 값으로 다시 저장한다. +> 메인 태그 순서 저장 중에는 추가 드래그를 잠시 막고 저장 상태를 표시한다. > 관리자 태그 추가 화면에서 직접 생성한 태그는 기본적으로 `general`(일반 태그)로 생성하고, 태그 관리 화면의 일반 태그 목록에 바로 표시한다. > 게시물 작성에서 새로 생기는 태그는 기본적으로 `general`(일반 태그)로 생성한다. > 메인 태그는 목록에서 `일반 태그로 변경` 액션으로 강등하며, 일반 태그는 배지형 전체 목록에서 확인·필터·최근 사용순·많이 사용순·이름순 정렬·메인 전환·삭제를 수행한다. @@ -550,7 +550,7 @@ components/content/ - 사이트 설정은 `site_settings` 테이블의 단일 레코드로 관리한다. - 관리자는 사이트 이름, 설명, 사이트 URL, 로고 이미지, 저작권 문구를 수정할 수 있다. -- 로고 이미지는 1:1 비율로 저장하며 `/admin/api/settings/logo` 업로드 시 `/uploads/system/logo-*.webp`와 `/uploads/system/favicon-*.png`를 고유 파일명으로 함께 생성한다. 같은 URL 덮어쓰기로 인한 브라우저·운영 정적 캐시 문제를 피하기 위해 로고 교체마다 새 URL을 저장한다. +- 로고 이미지는 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 링크로 연결한다. - DB 연결이 없는 환경에서는 환경 변수와 기본값 기반 설정을 사용한다. @@ -612,8 +612,8 @@ components/content/ /uploads/posts/YYYY/MM/filename.webp /uploads/pages/YYYY/MM/filename.webp /uploads/members/avatars/YYYY/MM/filename.webp -/uploads/system/logo-YYYYMMDDTHHMMSS-random.webp -/uploads/system/favicon-YYYYMMDDTHHMMSS-random.png +/uploads/system/logo-YYYYMM-random.webp +/uploads/system/favicon-YYYYMM-random.png ``` - 관리자 에디터 이미지 업로드 API는 디스크상 `public/uploads/posts/YYYY/MM/`에 저장하지만, DB가 있을 때 `media_metadata`에는 논리 폴더 **`미분류`**로 기록한다. 메타가 없을 때도 서버 목록에서는 `posts/...` 경로를 **`미분류`**로 표시한다(디스크 1단계 `posts`와 이중 표기하지 않음). 저장 파일명은 업로드 원본 파일명(안전 문자만 유지)을 우선 쓰고, 같은 월 디렉터리에 동일 이름이 있으면 `이름-2`, `이름-3` 식으로 넘버링한다. diff --git a/docs/update.md b/docs/update.md index 78a1bf5..d665979 100644 --- a/docs/update.md +++ b/docs/update.md @@ -1,5 +1,13 @@ # 업데이트 이력 +## v1.1.8 + +- 사이트 로고·파비콘 고유 파일명 접미사를 년월+랜덤 문자열 형식으로 간소화. +- 관리자 태그 관리 화면의 `태그 추가` 버튼을 일반 태그 섹션 헤더 오른쪽으로 이동. +- 메인 태그 `정렬 저장` 버튼을 제거하고 드래그 드롭 직후 자동 저장되도록 수정. +- 메인 태그 순서 자동 저장 중 추가 드래그를 막고 저장 상태를 표시하도록 정리. +- 패키지 버전 `1.1.8`로 갱신. + ## v1.1.7 - 사이트 로고 업로드가 고정 `/uploads/system/logo.webp` 덮어쓰기 대신 고유 파일명 URL을 저장하도록 수정. diff --git a/package-lock.json b/package-lock.json index 3f006fa..ce693a4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "sori.studio", - "version": "1.1.7", + "version": "1.1.8", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "sori.studio", - "version": "1.1.7", + "version": "1.1.8", "hasInstallScript": true, "dependencies": { "@nuxtjs/tailwindcss": "^6.14.0", diff --git a/package.json b/package.json index da321d4..d2bd9c7 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "sori.studio", - "version": "1.1.7", + "version": "1.1.8", "private": true, "type": "module", "imports": { diff --git a/pages/admin/tags/index.vue b/pages/admin/tags/index.vue index 4e25ca4..a92066d 100644 --- a/pages/admin/tags/index.vue +++ b/pages/admin/tags/index.vue @@ -54,7 +54,7 @@ const filteredGeneralTags = computed(() => { ) }) -/** 서버 기준 메인 태그 id 순서(정렬 저장 버튼 활성 비교용) */ +/** 서버 기준 메인 태그 id 순서(자동 저장 필요 여부 비교용) */ const baselineManagedTagIds = ref([]) /** @@ -77,7 +77,7 @@ const refreshTagsFromServer = async () => { resetManagedOrderBaseline() /** - * 메인 태그 드래그 순서가 기준선과 다른지 여부 + * 메인 태그 드래그 순서가 서버 기준선과 다른지 여부 * @returns {boolean} 변경 여부 */ const isManagedOrderDirty = computed(() => { @@ -116,6 +116,11 @@ const showToast = (type, message) => { * @returns {void} */ const handleDragStart = (event, tagId) => { + if (savingOrder.value) { + event.preventDefault() + return + } + if (!event.dataTransfer) { return } @@ -130,6 +135,10 @@ const handleDragStart = (event, tagId) => { * @returns {void} */ const handleDragOver = (event, tagId) => { + if (savingOrder.value) { + return + } + event.preventDefault() dragOverTagId.value = tagId } @@ -172,15 +181,17 @@ const moveManagedTag = (sourceId, targetId) => { * 관리용 태그 드롭 처리 * @param {DragEvent} event - 드래그 이벤트 * @param {string} targetId - 대상 태그 ID - * @returns {void} + * @returns {Promise} */ -const handleDrop = (event, targetId) => { +const handleDrop = async (event, targetId) => { event.preventDefault() - if (!draggingTagId.value) { + if (!draggingTagId.value || savingOrder.value) { return } moveManagedTag(draggingTagId.value, targetId) handleDragEnd() + await nextTick() + await saveManagedOrder() } /** @@ -204,9 +215,10 @@ const saveManagedOrder = async () => { tags.value = [...reordered] await refreshTagsFromServer() - showToast('success', '메인 태그 순서가 저장되었습니다.') + showToast('success', '메인 태그 순서가 자동 저장되었습니다.') } catch (error) { showToast('error', error?.data?.message || '정렬 순서를 저장하지 못했습니다.') + await refreshTagsFromServer() } finally { savingOrder.value = false } @@ -328,7 +340,7 @@ onBeforeUnmount(() => {