fix: 트리 경로 표시 개선 - 슬래시 제거 + 쿼리 파라미터 분리

- 자식 노드에서 부모와 겹치는 경로 + 앞의 / 제거
  /about → about, /about/press → press
- 쿼리 파라미터가 있는 URL을 path 하위에 쿼리만 표시
  /search?q=foo → search 하위에 q=foo
- findPathParent에서 쿼리 URL의 부모를 같은 path로 매핑

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
jungwoo choi
2026-02-14 07:42:28 +09:00
parent de0863c8ad
commit c88967cb6f
2 changed files with 54 additions and 12 deletions

View File

@ -20,20 +20,38 @@ interface PageTreeProps {
/**
* URL 경로 기반으로 부모 페이지를 찾는다.
* 경로 세그먼트를 하나씩 올라가며 실제 존재하는 페이지를 찾고,
* 없으면 null (최상위)을 반환한다.
* 1. 쿼리 파라미터가 있으면 같은 path의 쿼리 없는 URL을 부모로
* 2. 경로 세그먼트를 하나씩 올라가며 실제 존재하는 페이지를 찾음
* 3. 없으면 null (최상위)
*
* 예: /about/press → /about (존재) → 부모
* 예: /search?q=foo → /search (존재) → 부모
* /about/press → /about (존재) → 부모
* /howyoutubeworks → / (존재) → 부모
* /creators/podcasts → / (존재, /creators 없음) → 부모
*/
function findPathParent(url: string, pageUrls: Set<string>): string | null {
try {
const parsed = new URL(url);
const path = parsed.pathname;
const hasQuery = !!parsed.search;
// 루트 경로는 부모 없음
if (path === "/" || path === "") return null;
// 루트 경로 (쿼리 없음)는 부모 없음
if ((path === "/" || path === "") && !hasQuery) return null;
// 쿼리가 있으면 같은 path의 쿼리 없는 URL을 부모로 찾기
if (hasQuery) {
const pathOnlyUrl = `${parsed.origin}${path}`;
if (pageUrls.has(pathOnlyUrl) && pathOnlyUrl !== url) {
return pathOnlyUrl;
}
// 쿼리 없는 같은 path가 없으면 path 기반으로 계속 탐색
}
// 루트 경로 + 쿼리인 경우 (예: /?q=foo) → 루트가 부모
if (path === "/" || path === "") {
const rootWithSlash = `${parsed.origin}/`;
if (pageUrls.has(rootWithSlash) && rootWithSlash !== url) return rootWithSlash;
return null;
}
const cleanPath = path.endsWith("/") ? path.slice(0, -1) : path;
const segments = cleanPath.split("/").filter(Boolean);

View File

@ -32,20 +32,44 @@ interface PageTreeNodeProps {
/**
* URL에서 부모 경로와 겹치는 앞부분을 제거하여 표시명 반환.
* 예: parentPath="/about", url="/about/press" → "/press"
* parentPath="/", url="/about" → "/about"
* 루트만 "/", 나머지는 슬래시 없이 표시.
* 쿼리 파라미터만 다른 URL은 쿼리 부분만 표시.
*
* 예: parentPath="/about", url="/about/press" → "press"
* parentPath="/", url="/about" → "about"
* parentPath="/search", url="/search?q=foo" → "q=foo"
* parentPath=undefined, url="/" → "/"
*/
function getDisplayName(url: string, parentPath?: string): string {
try {
const parsed = new URL(url);
const path = parsed.pathname || "/";
if (path === "/" || path === "") return "/";
const query = parsed.search ? parsed.search.slice(1) : ""; // "?" 제거
if (parentPath && parentPath !== "/" && path.startsWith(parentPath)) {
return path.slice(parentPath.length) || path;
if (path === "/" && !query) return "/";
// 같은 경로에 쿼리만 다른 경우 → 쿼리만 표시
if (parentPath && path === parentPath && query) {
return query;
}
return path;
// 부모 경로 접두사 제거
let display = path;
if (parentPath && parentPath !== "/" && path.startsWith(parentPath)) {
display = path.slice(parentPath.length);
}
// 앞의 슬래시 제거 (루트 제외)
if (display.startsWith("/")) {
display = display.slice(1);
}
// 쿼리가 있으면 붙이기
if (query) {
display = display ? `${display}?${query}` : query;
}
return display || "/";
} catch {
return url;
}