메뉴 관리 개편, 추천 사이트·1뎁스 네비, 우측 Recommended 연동(v1.1.13)
상단 네비는 평면 테이블·드래그로 편집하고 한 단계 하위만 허용한다. 추천 사이트 탭·location recommended·공개 API와 우측 사이드 카드·파비콘 URL 유틸을 추가한다. 문서·배포 마이그레이션 안내·관리자 레이아웃·설정 화면 등 누적 변경을 반영한다.
This commit is contained in:
@@ -1,5 +1,21 @@
|
||||
# 업데이트 요약
|
||||
|
||||
## v1.1.13
|
||||
|
||||
- 상단 메뉴 깊이를 한 단계로 제한하고, 추천 사이트를 DB·관리자 탭·우측 Recommended 카드(외부 파비콘 프록시)로 연결했다.
|
||||
|
||||
## v1.1.12
|
||||
|
||||
- 관리자 상단 메뉴에서 드래그 시 형제 끼움과 하위 편입을 색·문구로 구분하고, 왼쪽 번호를 계층형 개요(`2.1` 등)로 바꿨다.
|
||||
|
||||
## v1.1.11
|
||||
|
||||
- 공개 사이드바 1차 네비 비활성 표시·하위 간격을 정리하고, 관리자 상단 메뉴는 추가 후 드래그만으로 형제 순서·하위 편입을 바꾸도록 단순화했다.
|
||||
|
||||
## v1.1.10
|
||||
|
||||
- 관리자 사이트 설정 화면을 Ghost형 전체 화면(좌측 내비·스크롤 스파이·ESC 닫기)으로 바꾸고, 블로그 제목·설명은 읽기 전용 + 편집 시에만 입력하도록 정리. 상단 헤더 없이 우측 상단 고정 닫기, 사이드·본문 중앙 정렬 레이아웃을 적용한다.
|
||||
|
||||
## v1.1.9
|
||||
|
||||
- 관리자 글 목록에 상태·태그·정렬 필터와 댓글 수 표시를 추가.
|
||||
|
||||
@@ -137,8 +137,9 @@ cp .env.example .env.production
|
||||
# Docker 빌드 및 실행
|
||||
docker compose --env-file .env.production up -d --build
|
||||
|
||||
# 기존 운영 DB를 유지한 채 새 버전을 올릴 때 추천 글 컬럼 마이그레이션 적용
|
||||
# 기존 운영 DB를 유지한 채 새 버전을 올릴 때 추천 글·네비 location 마이그레이션 적용
|
||||
docker compose --env-file .env.production exec sori-studio-db psql -U sori_studio -d sori_studio -f /docker-entrypoint-initdb.d/023_add_post_featured.sql
|
||||
docker compose --env-file .env.production exec sori-studio-db psql -U sori_studio -d sori_studio -f /docker-entrypoint-initdb.d/024_navigation_recommended_location.sql
|
||||
```
|
||||
|
||||
### Docker 네트워크 충돌 대응
|
||||
|
||||
@@ -1,5 +1,29 @@
|
||||
# 의사결정 이력
|
||||
|
||||
## 2026-05-15 v1.1.13
|
||||
|
||||
### 상단 메뉴 1뎁스·추천 사이트·파비콘 프록시
|
||||
|
||||
사이드바 상단 네비는 운영상 루트와 그 직속 자식만이면 충분하고, 그보다 깊은 트리는 편집·표시 모두 부담이 된다. 저장·공개 트리 조립·관리자 드래그에서 한 단계로 막는다. 우측 Recommended는 하드코딩 대신 `location=recommended` 평면 행으로 두어 메뉴 관리 한 화면에서 다루게 하고, 외부 링크는 호스트만 추출해 Google Favicon CDN URL을 쓰면 별도 스크래핑 없이 아이콘을 얻을 수 있다(내부 경로는 생략).
|
||||
|
||||
## 2026-05-15 v1.1.12
|
||||
|
||||
### 상단 메뉴 편집: 드롭 구역 시각 구분과 계층형 개요 번호
|
||||
|
||||
행 위·중·아래만으로는 형제 순서 이동과 부모 변경(하위 편입)이 한눈에 구분되기 어렵고, 평면 행 번호(2,3,4…)는 부모·자식 관계와 맞지 않아 혼란스럽다. 드래그 중에는 파란 끝선·앰버 링과 짧은 한글 캡션으로 의미를 고정하고, 개요 열은 `1`, `2.1`, `2.2`처럼 트리 깊이에 맞춘 표기로 바꾸며 라벨 들여쓰기를 키워 구조를 읽기 쉽게 한다.
|
||||
|
||||
## 2026-05-15 v1.1.11
|
||||
|
||||
### 관리자 상단 네비를 평면 드래그 편집으로 통일
|
||||
|
||||
`하위` 전용 추가 버튼과 중첩 테이블은 트리 깊이가 늘수록 조작이 어색하고 공개 사이드바와 다른 시각 계층을 준다. 항목은 모두 `상단 메뉴 추가`로 만든 뒤, 한 테이블에서 들여쓰기만으로 깊이를 보여 주고, 행의 위·가운데·아래 드롭 구역으로 형제 사이 끼움과 하위 편입을 나누면 Ghost류 아웃라이너와 비슷한 자유도를 유지하면서 UI는 단순해진다.
|
||||
|
||||
## 2026-05-15 v1.1.10
|
||||
|
||||
### 관리자 사이트 설정을 전용 전체 화면으로 분리
|
||||
|
||||
공개 블로그 설정은 항목이 늘어날 예정이라 목록형 관리자 레이아웃 안에 두면 세로 공간과 시선 분산이 커진다. Ghost Admin처럼 설정만 별도 전체 화면으로 열고 좌측 앵커 내비와 우측 긴 스크롤을 두면 확장(타임존·공지·가져오기/보내기·스팸)을 같은 패턴으로 쌓을 수 있다. 따라서 `/admin/settings`에서는 기본 관리자 사이드바를 숨기고, 닫기·ESC와 문서 스크롤 잠금으로 집중도를 맞춘다.
|
||||
|
||||
## 2026-05-15 v1.1.9
|
||||
|
||||
### 추천 글을 저장 필드로 분리
|
||||
|
||||
13
docs/map.md
13
docs/map.md
@@ -8,7 +8,7 @@
|
||||
|------|------|
|
||||
| layouts/default.vue | 메인·목록·태그 — 3열 `gap`+중앙 `1fr`, `site-main` `max-w-[720px]`, 모바일 슬라이드 메뉴 |
|
||||
| layouts/post.vue | 개별 게시물 — `default`와 동일 |
|
||||
| layouts/admin.vue | 관리자 전체, 320px 밝은 Ghost형 사이드바, 우측 전체 높이 캔버스, 멤버 수 표시, 하단 사용자 드롭다운·설정, `내 프로필` 멤버 편집 이동, 게시글 `+` 새 글 진입, 글 작성/수정 화면의 전체 화면 편집 모드와 문서 스크롤 잠금 |
|
||||
| layouts/admin.vue | 관리자 전체, 320px 밝은 Ghost형 사이드바(대시보드 비활성 행·사이트 보기 새 창·콘텐츠 구간 여백), 우측 전체 높이 캔버스, 멤버 수 표시, 하단 사용자 드롭다운·설정, `내 프로필` 멤버 편집 이동, 게시글 `+` 새 글 진입, 글 작성/수정 화면의 전체 화면 편집 모드와 문서 스크롤 잠금, **`/admin/settings`에서는 사이드바 숨김·본문 전체 화면·설정용 문서 스크롤 잠금** |
|
||||
| layouts/page.vue | 고정 페이지 전체 화면 |
|
||||
| app.vue | 공개 사이트 설정 기반 파비콘 head 링크와 기본 title template |
|
||||
|
||||
@@ -23,7 +23,8 @@
|
||||
|
||||
| 파일 | 용도 |
|
||||
|------|------|
|
||||
| lib/navigation-editor-tree.js | 관리자 메뉴 UI·서버 `renumberSortOrderByTree`가 쓰는 `buildNavigationEditorTree` |
|
||||
| lib/external-favicon-url.js | 외부 URL 호스트 기준 Google `s2/favicons` 썸네일 URL 생성(내부 경로는 빈 문자열) |
|
||||
| lib/navigation-editor-tree.js | 관리자 메뉴 UI·서버 `renumberSortOrderByTree`가 쓰는 `buildNavigationEditorTree`, 관리자 상단 네비 평면 표용 `flattenNavigationEditorWrappers` |
|
||||
| lib/markdown-content-normalizer.js | 관리자 Markdown-first 전환 후 레거시 블록 배열·객체 본문 값을 저장용 마크다운 문자열로 변환 |
|
||||
|
||||
## Nuxt 모듈
|
||||
@@ -54,7 +55,7 @@
|
||||
| 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` 호버 |
|
||||
| components/site/SidebarPrimaryNavList.vue | 상단 네비: 부모·리프 동일 `before` 막대/호버 원형, 내부 현재 경로 `--site-accent`, 행 `w-full`+`site-sidebar-nav-row` 호버(`#F7F4EF` 라이트), `inject`·`localStorage` 펼침 |
|
||||
| components/site/RightSidebar.vue | 오른쪽 사이드바, 공개 사이트 이미지 로고 fallback, About 영역은 비공개, `lg+`는 고정 열 높이·스티키, 모바일은 본문 아래 전체 너비, 하단 푸터 `pr-3` |
|
||||
| components/site/RightSidebar.vue | 오른쪽 사이드바, 공개 `GET /api/navigation`의 `recommended` 카드 목록(외부 URL은 Google 파비콘 프록시 썸네일), Follow·구독 폼, About 영역은 비공개, `lg+` 스티키 |
|
||||
| components/site/MainColumn.vue | 메인 화면 중앙, `lg:max-w-[720px]`로 본문 상한 |
|
||||
| components/site/PostCard.vue | 목록의 게시물 카드, 대표 이미지 썸네일, 카드 hover 인터랙션, 태그는 있을 때만 메타에 표시 |
|
||||
| components/site/TagHeader.vue | 태그 페이지 헤더 |
|
||||
@@ -68,7 +69,6 @@
|
||||
| components/admin/AdminPageForm.vue | 관리자 페이지 작성/수정 폼, 대표 이미지 선택 |
|
||||
| components/admin/AdminMarkdownEditor.vue | 관리자 글 Markdown-first 에디터, textarea 기반 범위 선택·복사/붙여넣기, HTML 클립보드 마크다운 변환, Enter 새 문단·Shift+Enter 백슬래시 hard break 줄바꿈 입력, 작성 모드 왼쪽 바깥 absolute 논리 줄 번호 거터·거터 스크롤 동기화·스크롤바 숨김, 작성/미리보기 전환(`Cmd/Ctrl+E`)과 커서 복원, 작성 모드 툴바 마크다운 삽입, 미리보기 모드 툴바 숨김, 이미지·갤러리 업로드 및 미디어 라이브러리 삽입, 현재 이미지·갤러리 편집 패널 |
|
||||
| components/admin/AdminBlockEditor.vue | 관리자 글 블록형 에디터, 이미지/갤러리/콜아웃/토글/임베드 블록, 콜아웃 Emoji on/off·이모지 프리셋·배경 프리셋 선택(우측 고정 설정 패널), 갤러리 복수 미디어 선택·이미지 수별 열 배치·삽입 위치 표시 드래그 순서 변경, 한글 조합 입력 처리, Shift+Enter 줄바꿈, 코드 블록 단축 변환, AFFiNE 참고 세로 막대형 블록 핸들 선택/삭제/드래그 이동과 삽입선 표시, 하단 빈 입력 블록 유지, 본문 placeholder 표시 |
|
||||
| components/admin/AdminNavPrimaryBranch.vue | 관리자 상단 네비 트리(테이블·태그와 동일한 행 드래그 하이라이트, 하위·삭제) |
|
||||
| components/admin/AdminTagForm.vue | 관리자 태그 생성/수정 폼(이름/슬러그/설명/색상만 편집) |
|
||||
| components/admin/AdminMemberForm.vue | 관리자 멤버 추가/수정 폼(Ghost형 3분할, 요약 1fr·입력 2fr, 원형 썸네일 hover 등록·변경·삭제, 기본 정보·레이블·관리자 노트·활동 요약, 설정 메뉴의 비밀번호 변경·멤버 삭제 모달, 미저장 변경사항 이탈 확인) |
|
||||
| components/admin/AdminUnsavedChangesModal.vue | 관리자 편집 화면 공통 미저장 변경사항 이탈 확인 모달(상단 40px 위치) |
|
||||
@@ -115,11 +115,11 @@
|
||||
| pages/admin/pages/new.vue | 페이지 작성, 저장 토스트 |
|
||||
| pages/admin/pages/[id].vue | 페이지 수정, 저장/삭제 토스트 |
|
||||
| pages/admin/media/index.vue | 미디어 관리, **미디어 라이브러리/썸네일** 탭, 검색은 파일명·게시물/페이지 사용처 제목만, 라이브러리: 폴더 트리·드래그 이동 등, 현재 사이트 설정 로고·파비콘은 사용 중 파일로 잠금, 썸네일: 미참조 파일 삭제·이름 변경 가능, 상세 모달(연결 회원·폴더 편집·**다운로드**) |
|
||||
| pages/admin/navigation/index.vue | 메뉴 관리: 상단/하단 탭, 테이블+행 드래그(태그 메인과 동일 톤), `useAdminToast` |
|
||||
| pages/admin/navigation/index.vue | 메뉴 관리: 상단/하단/추천 사이트 탭, 상단은 1뎁스 제한·평면 테이블+계층형 개요·행 드래그(위/중/아래), 하단·추천은 평면 드래그, `useAdminToast` |
|
||||
| pages/admin/tags/index.vue | 태그 관리(메인 태그 드래그 정렬 자동 저장·일반 강등, 일반 태그 검색/메인 전환/삭제, 일반 태그 헤더의 태그 추가 버튼), 액션 피드백 토스트 |
|
||||
| pages/admin/tags/new.vue | 태그 생성 |
|
||||
| pages/admin/tags/[id].vue | 태그 수정 |
|
||||
| pages/admin/settings/index.vue | 사이트 설정(이름, 설명, URL, 1:1 로고 업로드·고유 URL 파비콘 생성, 저작권 문구) |
|
||||
| pages/admin/settings/index.vue | 사이트 설정 Ghost형 전체 화면: 좌측 검색·내비와 우측 본문을 **중앙 `max-w` 래퍼**에 묶고 본문은 **약 760px** 상한, 우측 상단 **고정 닫기**, 밝은 회색 배경·본문 열 흰색, 블로그 제목·설명은 읽기 전용·`편집` 시 입력·저장/취소, 기타(로고·URL·저작권) 저장, 타임존·어나운스·Import/Export·스팸은 플레이스홀더 |
|
||||
| pages/admin/members/index.vue | 관리자 멤버 목록(Ghost형 테이블, 검색, 조건 필터, 멤버 추가 버튼, 닉네임+이메일, 가입일+최근 활동, IP, 댓글 수, 활동 상태 텍스트) |
|
||||
| pages/admin/members/new.vue | 관리자 멤버 추가(썸네일 URL, 이름, 이메일, 레이블, 관리자 노트) |
|
||||
| pages/admin/members/[id].vue | 관리자 멤버 상세/수정(회원 요약, 가입 정보, 활동 요약, 기본 정보 저장, 삭제 후 목록 이동) |
|
||||
@@ -238,6 +238,7 @@
|
||||
| db/migrations/002_seed_development.sql | 개발용 샘플 데이터 |
|
||||
| db/migrations/003_add_tag_display_fields.sql | 태그 표시 순서와 색상 필드 추가 |
|
||||
| db/migrations/004_add_site_settings.sql | 사이트 설정 테이블 추가 |
|
||||
| db/migrations/024_navigation_recommended_location.sql | `navigation_items.location`에 `recommended` 값 허용 |
|
||||
| db/migrations/017_navigation_hierarchy.sql | `navigation_items`에 `parent_id`·`is_folder`, `(location,label,url)` 유니크 제거 |
|
||||
| db/migrations/019_dedupe_navigation_items.sql | 반복 마이그레이션으로 생긴 네비게이션 중복 행 정리 및 중복 방지 인덱스 |
|
||||
| db/migrations/005_add_navigation_items.sql | 네비게이션 항목 테이블 추가 |
|
||||
|
||||
13
docs/spec.md
13
docs/spec.md
@@ -318,7 +318,7 @@ components/content/
|
||||
| id | UUID | Primary Key |
|
||||
| label | String | 메뉴 표시 이름 |
|
||||
| url | String | 내부 경로 또는 외부 URL |
|
||||
| location | Enum | primary/footer |
|
||||
| location | Enum | primary / footer / recommended |
|
||||
| sort_order | Integer | 표시 순서 |
|
||||
| is_visible | Boolean | 공개 화면 표시 여부 |
|
||||
| created_at | DateTime | 생성일 |
|
||||
@@ -377,7 +377,7 @@ components/content/
|
||||
- `GET /api/tags` - 태그 목록
|
||||
- `GET /api/search?q=` - 통합 검색(태그 `name`·`slug`, 게시물 `title`·`excerpt`·`content` 부분 일치, 각 최대 12건, 발행 게시물만)
|
||||
- `GET /api/site-settings` - 공개 사이트 설정
|
||||
- `GET /api/navigation` - 공개 네비게이션(`primary`는 트리·`footer`는 평면, 상세는 위 메뉴/네비게이션 절)
|
||||
- `GET /api/navigation` - 공개 네비게이션(`primary`는 트리·`footer`·`recommended`는 평면, 상세는 위 메뉴/네비게이션 절)
|
||||
- `POST /api/auth/signup` - 회원 가입. 본문: `username`, `email`, `password`, 선택 `emailOtp`(6자리 숫자). **`GET /api/auth/bootstrap-status`의 `emailOtpConfigured`가 true이고 `needsAdminSetup`이 false일 때**(서버에 Resend·pepper 설정됨) `emailOtp`가 필수이며, 직전에 `POST /api/auth/email-otp/request`로 같은 이메일·`purpose: "signup"` 발송분과 일치해야 한다. 최초 관리자 부트스트랩(`needsAdminSetup: true`)에서는 `emailOtp` 없이 가입 가능. 이메일은 저장 시 소문자로 정규화한다.
|
||||
- `GET /api/auth/bootstrap-status` - 최초 관리자 등록 필요 여부(`hasUsers`, `needsAdminSetup`) 및 **이메일 OTP 사용 가능 여부** `emailOtpConfigured`(서버 `RESEND_API_KEY`·`RESEND_FROM_EMAIL` 및 pepper: 비어 있지 않은 `EMAIL_OTP_PEPPER` **또는** `MEMBER_SESSION_SECRET`)를 반환한다. pepper는 OTP 6자리를 DB에 해시해 둘 때만 쓰이는 서버 비밀(긴 난문자열 권장)이다.
|
||||
- `POST /api/auth/email-otp/request` - 본문: `email`, `purpose`(`"signup"` | `"password_reset"`). Resend로 6자리 OTP 메일을 보낸다. 미설정 시 503. `signup`은 이미 가입된 이메일이면 409, 최초 관리자 단계에서는 400. `password_reset`은 존재하지 않는 이메일도 동일한 성공 메시지를 반환하며(이메일 미발송), 요청 빈도 제한을 위해 DB에 더미 챌린지를 남길 수 있다. 55초 쿨다운·시간당 5회 한도. 실제 메일 발송이 실패하면 방금 생성한 챌린지는 즉시 삭제한다. 발송 성공 후 같은 이메일·용도의 이전 pending 챌린지는 무효화한다. DB 테이블 `email_otp_challenges`(마이그레이션 `018_email_otp_challenges.sql`) 필요.
|
||||
@@ -552,6 +552,7 @@ components/content/
|
||||
|
||||
### 사이트 설정
|
||||
|
||||
- 관리자 사이트 설정 UI는 `/admin/settings`에서 제공한다. Ghost Admin과 유사하게 **전체 화면**으로 표시하며, 좌측 내비와 우측 본문을 **한 덩어리로 중앙 정렬**(`max-w` 래퍼, 본문 카드 영역은 약 760px 상한)하고, 페이지 배경은 밝은 회색·본문 열은 흰색으로 구분한다. 우측 본문을 스크롤하면 현재 보이는 구역에 맞춰 좌측 메뉴 활성 배경을 갱신한다. **우측 상단 고정** 닫기 버튼과 `Escape` 키로 설정 화면을 닫으며, 브라우저 히스토리가 있으면 `뒤로 가기`, 없으면 `/admin`으로 이동한다. 타임존·어나운스 바·게시물 Import/Export·스팸 필터는 현재 **메뉴·안내 카드만** 제공하고 저장 API는 연결하지 않는다. **블로그 제목·설명** 카드는 기본적으로 사이트 이름·설명을 읽기 전용으로 보여 주고, `편집`을 눌렀을 때만 입력 필드·미리보기·저장/취소가 나타난다. 제목·설명 편집 중 `Escape`는 설정 닫기 대신 편집 취소로 동작한다.
|
||||
- 사이트 설정은 `site_settings` 테이블의 단일 레코드로 관리한다.
|
||||
- 관리자는 사이트 이름, 설명, 사이트 URL, 로고 이미지, 저작권 문구를 수정할 수 있다.
|
||||
- 로고 이미지는 1:1 비율로 저장하며 `/admin/api/settings/logo` 업로드 시 `/uploads/system/logo-YYYYMM-random.webp`와 `/uploads/system/favicon-YYYYMM-random.png`를 고유 파일명으로 함께 생성한다. 같은 URL 덮어쓰기로 인한 브라우저·운영 정적 캐시 문제를 피하기 위해 로고 교체마다 새 URL을 저장한다.
|
||||
@@ -562,12 +563,12 @@ components/content/
|
||||
### 메뉴/네비게이션
|
||||
|
||||
- 네비게이션은 `navigation_items` 테이블로 관리한다.
|
||||
- 컬럼: `parent_id`(nullable, self FK, `ON DELETE CASCADE`), `is_folder`(boolean, **자식이 있는 상단 항목이면 저장 시 서버가 true로 설정**), `location`(`primary`|`footer`), `sort_order`, `label`, `url`, `is_visible`(저장 시 항상 true로 정리). `(location, label, url)` 유니크 제약은 제거되었다.
|
||||
- 컬럼: `parent_id`(nullable, self FK, `ON DELETE CASCADE`), `is_folder`(boolean, **자식이 있는 상단 항목이면 저장 시 서버가 true로 설정**), `location`(`primary`|`footer`|`recommended`), `sort_order`, `label`, `url`, `is_visible`(저장 시 항상 true로 정리). `(location, label, url)` 유니크 제약은 제거되었다.
|
||||
- 동일 `location`·`parent_id`·`label`·`url` 조합은 중복 저장하지 않는다. 반복 마이그레이션으로 생긴 중복은 `019_dedupe_navigation_items.sql`이 정리하고, 표현식 유니크 인덱스로 재발을 막는다.
|
||||
- `GET /api/navigation` 응답: `primary`는 **트리**(각 노드에 `children` 배열이 있을 수 있음, 리프는 `children` 없음). 노드 필드: `id`, `label`, `url`, `isFolder`, `isVisible`. 트리 조립 시 **동일 `id`는 한 번만 반영**하고, DB 평면 행에 `parent_id`가 있어 자식으로 붙은 항목은 **루트에 두지 않는다**(레거시·중복 행으로 인한 사이드바 이중 표시 방지). `footer`는 **평면** 배열(`parent_id` 없음)이며 `id`, `label`, `url`, `isVisible`만 내려간다.
|
||||
- `PUT /admin/api/navigation` 요청 본문의 각 항목은 반드시 `id`(UUID)를 포함한다. 저장 시 `sort_order`는 서버가 위치별 트리 DFS 순으로 다시 부여한다. `parent_id`는 `primary`에서만 허용되며 `footer` 항목은 항상 루트다. `is_visible`·`is_folder`는 요청값과 무관하게 서버에서 **항상 표시·자식 유무 기준 폴더**로 덮어쓴다.
|
||||
- `GET /api/navigation` 응답: `primary`는 **트리**(각 노드에 `children` 배열이 있을 수 있음, 리프는 `children` 없음). 노드 필드: `id`, `label`, `url`, `isFolder`, `isVisible`. 트리 조립 시 **동일 `id`는 한 번만 반영**하고, DB 평면 행에 `parent_id`가 있어 자식으로 붙은 항목은 **루트에 두지 않는다**. **상단은 루트 직속 자식만** 트리에 붙이며(부모의 부모가 있는 행은 자식으로 무시), `footer`·`recommended`는 **평면** 배열(`parent_id` 없음)이며 `id`, `label`, `url`, `isVisible`만 내려간다.
|
||||
- `PUT /admin/api/navigation` 요청 본문의 각 항목은 반드시 `id`(UUID)를 포함한다. 저장 시 `sort_order`는 `primary`·`footer`·`recommended` 각각 위치별 트리(또는 평면 루트) DFS 순으로 다시 부여한다. `parent_id`는 `primary`에서만 허용되며 **상단은 한 단계(루트→자식)만** 허용한다. `footer`·`recommended` 항목은 항상 루트다. `is_visible`·`is_folder`는 요청값과 무관하게 서버에서 **항상 표시·자식 유무 기준 폴더**로 덮어쓴다.
|
||||
- URL은 `/`로 시작하는 내부 경로, `http(s)://` 외부 URL, 폴더 전용 자리 표시 `#`를 허용한다.
|
||||
- 관리자 메뉴 화면은 **상단 네비게이션**·**하단 네비게이션** 탭으로 구분한다. 편집 UI는 **태그 관리 메인 태그**와 같은 테이블·`cursor-move` 행 드래그 패턴을 쓰며, 드래그는 입력·버튼 위가 아닌 행 여백·번호 열에서 시작한다. 상단은 `AdminNavPrimaryBranch`로 트리·동일 부모 내 순서 변경·하위 추가를 제공한다. 하단은 한 단계 목록만 드래그 정렬한다.
|
||||
- 관리자 메뉴 화면은 **상단 네비게이션**·**하단 네비게이션**·**추천 사이트** 탭으로 구분한다. 편집 UI는 **태그 관리 메인 태그**와 같은 테이블·`cursor-move` 행 드래그 패턴을 쓰며, 드래그는 입력·버튼 위가 아닌 행 여백·개요 열에서 시작한다. 상단은 `buildNavigationEditorTree` 결과를 `flattenNavigationEditorWrappers`로 한 테이블에 평면 표시하고, **같은 행의 위 1/3·가운데·아래 1/3**에 드롭하면 각각 대상 **앞**(동일 부모)·**하위**(대상의 자식, **루트 행에만**)·**뒤**(동일 부모)로 이동한다. **상단은 루트→자식 한 단계만** 허용하며(하위 행에는 가운데 드롭이 형제 앞·뒤로 대체됨, 이미 하위가 있는 항목은 다른 항목의 하위로 넣을 수 없음), 드래그 중 하이라이트는 **형제 앞·뒤**를 파란 가로 끝선·연한 파란 배경, **하위 편입**을 앰버 링·연한 앰버 배경으로 구분하고, 개요 열에 `앞에 끼움` / `뒤에 끼움` / `하위로 넣기` 짧은 문구를 덧붙인다. 개요 열 표기는 `1`, `2.1`, `2.2`, `3`처럼 깊이별 계층 번호로 보이며, 라벨 열은 깊이만큼 `padding-left`로 들여쓴다. 자기 자신·자기 하위로의 편입은 거부한다. 하단·추천 사이트는 평면 목록만 드래그 정렬한다. 추천 사이트는 공개 홈 **오른쪽 사이드바** Recommended 영역에 카드로 노출되며, `https://` URL은 클라이언트에서 호스트를 뽑아 Google Favicon 프록시(`https://www.google.com/s2/favicons?domain=…`) 이미지로 표시할 수 있다(내부 경로만 있으면 아이콘 생략).
|
||||
- `parent_id` / `is_folder` 컬럼이 DB에 없을 때 저장은 실패한다. `npm run db:migrate:dev`로 `017_navigation_hierarchy.sql`을 적용해야 한다(저장 API는 해당 경우 한국어 안내 메시지를 반환할 수 있다).
|
||||
- 공개 왼쪽 사이드바 상단은 `SidebarPrimaryNavList`로 렌더링한다. **하위가 있는 노드**는 한 줄 `button`으로, **행 전체(이름·왼쪽 세로 데코·chevron) 클릭**으로 접기/펼친다. 부모·리프 모두 왼쪽 장식은 **기본 세로 막대(`--site-line`)**, **호버 시에만** 리프와 동일하게 **작은 원형**으로 전환한다. **내부 경로**(`/`로 시작, `//`·`http(s)` 제외)이고 현재 `route.path`와 정규화한 경로가 같으면 장식 색을 **`--site-accent`(브랜드 오렌지)**로 둔다. 외부 URL은 비교하지 않는다. 펼침 상태는 `localStorage` 키 `sori-primary-nav-expanded`에 저장된다. 상단·리프 링크·부모 버튼 행은 **`w-full`**로 `site-sidebar-nav-row` 호버 배경이 가로 전체를 쓴다(라이트 `#F7F4EF`, 다크는 `site-panel-hover`와 동일한 `color-mix` 패턴).
|
||||
- 관리자 메뉴 관리 화면에서 저장 API로 보내는 직렬화 결과가 서버에서 불러온 직후와 동일하면 `메뉴 저장` 버튼이 비활성화된다.
|
||||
|
||||
@@ -1,7 +1,32 @@
|
||||
# 업데이트 이력
|
||||
|
||||
## v1.1.13
|
||||
|
||||
- 상단 네비: 하위 1뎁스만 허용(서버 검증·공개 트리 조립·관리자 드래그·이미 하위가 있는 항목의 하위 편입 금지).
|
||||
- `navigation_items.location`에 `recommended` 추가(마이그레이션 `024_navigation_recommended_location.sql`), 관리자 메뉴에 추천 사이트 탭·공개 API `recommended`·우측 사이드 카드 목록.
|
||||
- 외부 링크 파비콘 표시용 `lib/external-favicon-url.js`(Google `s2/favicons` 프록시 URL).
|
||||
- 패키지 버전 `1.1.13`로 갱신.
|
||||
|
||||
## v1.1.12
|
||||
|
||||
- 관리자 상단 메뉴: 드롭 구역을 파란 끝선(형제 앞·뒤)·앰버 링(하위)과 개요 열 캡션으로 구분, 개요 번호를 `2.1`형 계층 표기로 변경·라벨 들여쓰기 확대.
|
||||
- 패키지 버전 `1.1.12`로 갱신.
|
||||
|
||||
## v1.1.11
|
||||
|
||||
- 공개 사이드바 1차 네비: 부모·하위 사이 `mt-2` 간격, 비활성 세로 표시를 `color-mix(in srgb, var(--site-line) 88%, var(--site-panel) 12%)` 톤으로 정리.
|
||||
- 관리자 상단 메뉴: `하위` 버튼 제거, `flattenNavigationEditorWrappers` 단일 테이블+행 위·중·아래 드롭으로 순서·부모 자유 변경, `AdminNavPrimaryBranch.vue` 제거.
|
||||
- 패키지 버전 `1.1.11`로 갱신.
|
||||
|
||||
## v1.1.10
|
||||
|
||||
- 관리자 `/admin/settings`를 Ghost형 전체 화면으로 재구성(좌측 검색·앵커 내비·우측 스크롤 스파이, X·ESC 닫기, 타임존·어나운스·Import/Export·스팸 섹션은 플레이스홀더).
|
||||
- 설정 경로에서 관리자 기본 사이드바를 숨기고 문서 스크롤 잠금(`admin-settings-document`)을 적용.
|
||||
- 관리자 `/admin/settings`에서 상단 헤더를 제거하고 우측 상단 고정 닫기만 두며, 사이드·본문 열을 `max-w-[1120px]` 래퍼로 중앙 정렬·본문 카드 폭은 `max-w-[760px]`로 Ghost에 가깝게 맞춤.
|
||||
|
||||
## v1.1.9
|
||||
|
||||
- 관리자 사이드바 상단에 대시보드(비활성 표시)·사이트 보기(`NUXT_PUBLIC_SITE_URL` 기준 새 창)·콘텐츠 메뉴 구분 여백 추가.
|
||||
- 관리자 글 목록에 상태·태그·최신순/오래된순 필터 추가.
|
||||
- 관리자 글 목록 상태 표시를 배지에서 단순 텍스트 색상 기준으로 정리하고 제목 옆 댓글 수 표시 추가.
|
||||
- 게시물 추천 여부(`is_featured`) 저장 필드와 글쓰기 사이드바 추천 토글 추가.
|
||||
|
||||
Reference in New Issue
Block a user