diff --git a/docs/deploy.md b/docs/deploy.md index c483396..6c2b613 100644 --- a/docs/deploy.md +++ b/docs/deploy.md @@ -1,6 +1,6 @@ # 배포 가이드 -> 로컬 기준 v1.10.71에서 `npm run lint`와 `npm run build` 검증을 통과했다. NAS 실제 컨테이너 기동과 도메인/프록시 접속 검증은 운영 배포 단계에서 진행한다. +> 로컬 기준 v1.10.72에서 `npm run lint`와 `npm run build` 검증을 통과했다. NAS 실제 컨테이너 기동과 도메인/프록시 접속 검증은 운영 배포 단계에서 진행한다. ## 빌드 유형 diff --git a/docs/map.md b/docs/map.md index 62f9e76..c70c30e 100644 --- a/docs/map.md +++ b/docs/map.md @@ -193,7 +193,7 @@ |------|------| | pages/admin/index.vue | 대시보드(상단 요약: 현재 접속자·오늘 접속자·게시물 수, 일일·주간·월간·년간 선택형 방문자수·평균 체류·50% 스크롤 차트, 차트 막대 클릭 시 하단 선택 기간 상세 갱신, 자동 갱신 중 선택 기간 유지, 선택 기간 기준 왼쪽 인기 게시물·인기 페이지와 오른쪽 유입원·디바이스, IP·열린 페이지 수·열린 페이지 목록 포함 접속자 목록) | | pages/admin/login.vue | 관리자 로그인, 일반 로그인과 같은 다크 인증 스타일·우측 배치 및 내부 오른쪽 정렬 폼, 이메일·비밀번호 모두 입력 시에만 제출 버튼 활성 | -| pages/admin/posts/index.vue | 글 목록, 본문 없는 `/admin/api/posts?paged=true` 페이지 응답 사용, 서버 검색·필터(상태·검색형 태그 combobox·추천·정렬)와 번호형 페이지네이션, `F` 검색창 포커스, 관리자 전용 상단 고정 배지·정렬·행 메뉴 토글, 헤더 우측 «새 글» 배치, 예약/초안/공개/멤버십/비공개 텍스트 상태, 추천 표시와 제목 사이 `featuredImageThumbnail` 우선 대표 이미지 썸네일, 제목 옆 댓글 수, 제목 아래 주소·공개 보기 hover 아이콘, 백링크 개수 열과 참조 게시물 목록 모달·이동 버튼, 별도 조회수 열의 누적 조회수·통계 모달 버튼, 적용 태그 전체 색상 배지, 화면 기준 행 more vert 메뉴(고정 전용 API·추천 전용 API·삭제 전 참조 경고·삭제) | +| pages/admin/posts/index.vue | 글 목록, 본문 없는 `/admin/api/posts?paged=true` 페이지 응답 사용, 서버 검색·필터(상태·검색형 태그 combobox·추천·정렬)와 번호형 페이지네이션, `F`/`ㄹ` 검색창 포커스, 관리자 전용 상단 고정 배지·정렬·행 메뉴 토글, 헤더 우측 «새 글» 배치, 예약/초안/공개/멤버십/비공개 텍스트 상태, 추천 표시와 제목 사이 `featuredImageThumbnail` 우선 대표 이미지 썸네일, 제목 옆 댓글 수, 제목 아래 주소·공개 보기 hover 아이콘, 백링크 개수 열과 고정 크기 참조 게시물 목록 모달·내부 검색·이동 아이콘, 별도 조회수 열의 누적 조회수·통계 모달 버튼, 적용 태그 전체 색상 배지, 화면 기준 행 more vert 메뉴(고정 전용 API·추천 전용 API·삭제 전 참조 경고·삭제) | | pages/admin/posts/new.vue | 글 작성, Ghost 스타일 작성 폼, 초안 `POST` 디바운스 자동 저장·첫 저장 준비 오버레이·첫 생성 중 추가 입력 PUT 반영·이탈 직전 플러시, 저장 토스트 | | pages/admin/posts/[id].vue | 글 수정, `AdminPostForm`, 저장·초안 디바운스 자동 저장(`autoSaving`)·이탈 직전 플러시·삭제·토스트 | | pages/admin/posts/[id]/analytics.vue | 게시물별 통계, 누적 조회수·기간별 조회/방문자 차트·유입/디바이스/키워드·읽음/체류/스크롤 지표 | diff --git a/docs/spec.md b/docs/spec.md index abf9d3a..3d4091b 100644 --- a/docs/spec.md +++ b/docs/spec.md @@ -201,10 +201,10 @@ layouts/ - 게시글 메뉴 라벨은 `게시글`로 표시하고, 우측 `+` 아이콘은 `/admin/posts/new`로 바로 이동한다. - 관리자 글 목록의 태그 컬럼은 게시물 태그 배열의 첫 번째 항목만 대표 태그로 표시하며, 배지는 태그 고유 색상을 반영한다. - 관리자 글 목록의 태그 필터는 태그 증가에 대비해 검색형 combobox로 표시한다. 후보는 전체 태그 API의 사용 중 태그 기준으로 보여 주며, 검색어가 없으면 `전체 태그`와 태그명을 정렬한 목록을, 입력 중에는 태그명·슬러그 부분 일치 결과를 표시한다. 방향키와 Enter로 선택할 수 있고, 선택된 태그는 토큰의 `×`로 전체 태그 상태로 되돌린다. -- 관리자 글 목록 API(`/admin/api/posts`)는 목록 최적화를 위해 게시물 본문 `content`를 반환하지 않는다. 기본 호출은 기존 호환용 배열을 반환하고, `paged=true` 쿼리를 보내면 `{ items, pagination, counts }` 구조를 반환한다. `page`, `pageSize`, `q`, `status`, `tag`, `featured`, `sort` 쿼리로 서버 검색·상태·태그·추천·정렬 필터를 적용하며 각 항목에 해당 글의 `/post/:slug` 공개 주소를 본문에 링크한 게시물 수 `backlinkCount`를 포함한다. 목록 검색은 제목·슬러그·요약·태그 기준으로 수행하고, 본문이 필요한 편집 화면은 `/admin/api/posts/:id` 상세 API를 사용한다. 관리자 전용 `adminPinned` 글은 공개 추천과 별개로 글 목록 정렬 최상단에 표시하며, 행 메뉴의 `상단 고정`·`고정 해제`에서 `/admin/api/posts/:id/pinned`로 토글한다. 백링크 열의 개수를 클릭하면 `/admin/api/posts/:id/references`로 링크한 게시물 목록을 조회해 모달로 표시한다. +- 관리자 글 목록 API(`/admin/api/posts`)는 목록 최적화를 위해 게시물 본문 `content`를 반환하지 않는다. 기본 호출은 기존 호환용 배열을 반환하고, `paged=true` 쿼리를 보내면 `{ items, pagination, counts }` 구조를 반환한다. `page`, `pageSize`, `q`, `status`, `tag`, `featured`, `sort` 쿼리로 서버 검색·상태·태그·추천·정렬 필터를 적용하며 각 항목에 해당 글의 `/post/:slug` 공개 주소를 본문에 링크한 게시물 수 `backlinkCount`를 포함한다. 목록 검색은 제목·슬러그·요약·태그 기준으로 수행하고, 본문이 필요한 편집 화면은 `/admin/api/posts/:id` 상세 API를 사용한다. 관리자 전용 `adminPinned` 글은 공개 추천과 별개로 글 목록 정렬 최상단에 표시하며, 행 메뉴의 `상단 고정`·`고정 해제`에서 `/admin/api/posts/:id/pinned`로 토글한다. 백링크 열의 개수를 클릭하면 `/admin/api/posts/:id/references`로 링크한 게시물 목록을 조회해 고정 크기 모달로 표시하고, 모달 내부에서 제목·슬러그 검색과 관리자 수정 화면 이동 아이콘을 제공한다. - 관리자 글 목록의 추천 토글은 전체 게시물 저장 API가 아니라 `/admin/api/posts/:id/featured` 전용 PUT API에 `{ isFeatured }`만 보내 수정한다. - 관리자 글 목록의 대표 이미지 미리보기는 작은 카드 썸네일이 있으면 `featuredImageThumbnail`을 우선 사용하고, 없을 때만 원본 `featuredImage`를 fallback으로 사용한다. -- 관리자 글 목록에서 입력창·모달 포커스가 아닐 때 `F` 키를 누르면 글 검색 입력창으로 포커스를 이동한다. +- 관리자 글 목록에서 입력창·모달 포커스가 아닐 때 `F` 또는 한글 키보드 `ㄹ` 키를 누르면 글 검색 입력창으로 포커스를 이동한다. - 관리자 글쓰기의 Tags 입력은 배지형 다중 입력을 유지하며, 오른쪽 트리거로 태그 추천 목록을 열 수 있다. 입력이 비어 있으면 공개 발행글 기준 최근 사용 태그 5개를 먼저 표시한 뒤 메인 태그를 이어서 보여 주고, 입력 중에는 기존 태그 이름·슬러그 부분 일치 결과를 추천한다. 추천 목록은 방향키와 Enter로 선택할 수 있다. 선택된 태그 배지는 태그 고유 색상을 반영한다. 게시물별 태그 최대 개수는 기본 5개이며, 사이트 설정 POST 설정의 `postTagLimit` 값으로 1~10개 범위에서 조절한다. - 관리자 글쓰기 상단 왼쪽 상태 영역은 `Published`, `Scheduled`, `Members`, `Private`, 초안 저장 상태 등 현재 상태 텍스트만 표시하며, 공개 게시물 이동은 오른쪽 설정 패널의 `View Post` 링크에서만 제공한다. 초안 자동 저장이 중복 슬러그 등으로 실패하면 같은 입력값은 반복 재시도하지 않고, 사용자가 슬러그나 본문을 바꿔 저장 payload가 달라졌을 때 다시 자동 저장을 시도한다. 중복 슬러그 오류는 헤더 오류나 토스트가 아니라 Post URL 입력창 아래 빨간 텍스트로 표시하고, 슬러그를 다시 입력하면 숨긴다. 중복 슬러그 오류가 표시된 동안 Publish/Update/최종 저장 버튼은 비활성화한다. 오른쪽 설정 패널 하단에는 본문 기준 단어 수, 공백 제외 문자 수와 공백 수, 예상 읽기 시간, 블록 수, 이미지 수를 작은 통계로 표시한다. - 관리자 미디어 검색창은 글·멤버 목록과 같은 돋보기 아이콘 포함 입력 스타일을 사용한다. 미디어 라이브러리 탭에서는 파일 추가 버튼으로 `/admin/api/uploads`에 직접 업로드할 수 있고, 현재 폴더를 보고 있으면 업로드 후 해당 폴더로 배치한다. 미디어 관리 화면은 `/admin/api/media?paged=1` 응답을 사용해 서버에서 탭·폴더·파일 종류·미사용·검색 조건을 적용한 뒤 50·100·200개 단위 페이지네이션을 제공하며 번호와 이전·다음 버튼으로 이동한다. 현재 페이지 선택은 화면에 표시된 삭제 가능 항목만 대상으로 하며, 선택 삭제는 사용 중이거나 회원 프로필에 연결된 잠금 항목을 제외하고 삭제한다. 미디어 그리드와 글쓰기 이미지 선택 모달은 원본 삽입·상세 확인은 유지하되, 목록 미리보기에는 가능한 작은 본문 720px 파생 이미지나 카드 썸네일을 우선 사용한다. 글쓰기 이미지 선택 모달의 라이브러리 탭도 `/admin/api/media?paged=1&tab=library` 서버 페이지 응답을 사용하며 파일명과 사용처 제목(게시물·페이지 제목) 검색, 번호형 페이지 이동을 제공한다. 갤러리 미디어 선택 항목에는 선택 순서를 오른쪽 상단 배지로 표시하며, 선택 해제 시 뒤 항목 순서를 자동으로 당긴다. 게시글 대표 이미지 선택 모달도 `/admin/api/media?paged=1&tab=library&kind=image` 서버 페이지 응답, 파일명/사용처 검색, 번호형 페이지 이동, 작은 파생 이미지 우선 미리보기를 사용한다. 두 글쓰기 미디어 모달은 검색 결과 개수와 무관하게 패널 높이를 고정하고 내부 목록만 스크롤한다. diff --git a/docs/update.md b/docs/update.md index 2b25b8b..0d73bd5 100644 --- a/docs/update.md +++ b/docs/update.md @@ -1,5 +1,14 @@ # 업데이트 이력 +## v1.10.72 + +- 관리자 글 목록 백링크 개수를 버튼 박스가 아닌 숫자형 텍스트 액션으로 표시하도록 수정. +- 백링크 모달에서 링크 횟수 문구를 제거하고 게시물 목록 중심으로 정리. +- 백링크 모달 크기를 고정하고 내부 게시물 검색 입력을 추가. +- 백링크 모달 닫기를 사이트 설정 화면과 같은 X 아이콘으로 통일. +- 백링크 모달 이동 액션을 게시물 목록의 공개 보기와 같은 화살표 아이콘으로 축약. +- 관리자 글 목록에서 포커스가 없을 때 `F`뿐 아니라 한글 키보드 `ㄹ` 입력도 검색창 포커스로 처리하도록 수정. + ## v1.10.71 - 관리자 글 목록에 백링크 열 추가. diff --git a/package.json b/package.json index e946f04..7521cbc 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "sori.studio", - "version": "1.10.71", + "version": "1.10.72", "private": true, "type": "module", "imports": { diff --git a/pages/admin/posts/index.vue b/pages/admin/posts/index.vue index e361165..4b2bd26 100644 --- a/pages/admin/posts/index.vue +++ b/pages/admin/posts/index.vue @@ -30,6 +30,7 @@ const referenceModalPost = ref(null) const referenceModalData = ref(createDefaultPostReferences()) const referenceModalLoading = ref(false) const referenceModalError = ref('') +const referenceModalSearchQuery = ref('') const postMenuTriggerRefs = new Map() const tagFilterRef = ref(null) const postSearchInputRef = ref(null) @@ -470,6 +471,25 @@ const isReferenceModalOpen = computed(() => Boolean(referenceModalPost.value)) /** 게시물 참조 목록 */ const referenceModalPosts = computed(() => referenceModalData.value?.posts || []) +/** 게시물 참조 모달 검색어 */ +const normalizedReferenceModalSearchQuery = computed(() => referenceModalSearchQuery.value.trim().toLowerCase()) + +/** 검색어가 적용된 게시물 참조 목록 */ +const filteredReferenceModalPosts = computed(() => { + const keyword = normalizedReferenceModalSearchQuery.value + + if (!keyword) { + return referenceModalPosts.value + } + + return referenceModalPosts.value.filter((post) => { + return [ + post.title, + post.slug + ].some((value) => String(value || '').toLowerCase().includes(keyword)) + }) +}) + /** 통계 모달 검색 유입 조회수 */ const analyticsModalSearchViews = computed(() => getAnalyticsSourceGroupViews(['search'])) @@ -611,6 +631,7 @@ const openReferenceModal = async (post) => { referenceModalPost.value = post referenceModalData.value = createDefaultPostReferences() referenceModalError.value = '' + referenceModalSearchQuery.value = '' referenceModalLoading.value = true try { @@ -630,6 +651,7 @@ const closeReferenceModal = () => { referenceModalPost.value = null referenceModalData.value = createDefaultPostReferences() referenceModalError.value = '' + referenceModalSearchQuery.value = '' referenceModalLoading.value = false } @@ -908,7 +930,7 @@ const onDocumentKeydown = (event) => { } if ( - event.key.toLowerCase() === 'f' + ['f', 'ㄹ'].includes(event.key.toLowerCase()) && !event.metaKey && !event.ctrlKey && !event.altKey @@ -1318,7 +1340,7 @@ watch(postPageCount, (pageCount) => { -
+

이 글을 링크한 게시물을 확인하는 중입니다.

@@ -1508,12 +1535,27 @@ watch(postPageCount, (pageCount) => { {{ referenceModalError }}