diff --git a/docs/contributing/index.md b/docs/contributing/index.md index a96d3ed0..8dce700a 100644 --- a/docs/contributing/index.md +++ b/docs/contributing/index.md @@ -49,6 +49,8 @@ URL에 쓰이는 폴더 이름은 소문자 kebab-case 영문으로 작성합니 메뉴는 **문서 유형 → 서비스 → 문서** 순서의 트리로 만들어집니다. 문서 이름은 front matter의 `title`을 사용하므로, 폴더 이름을 바꾸지 않고도 읽기 좋은 제목을 표시할 수 있습니다. 각 그룹은 접고 펼칠 수 있고, 현재 읽는 문서의 경로만 기본으로 열립니다. 메뉴 데이터는 빌드 결과에 포함되므로 브라우저가 Markdown을 다시 읽어 구성하지 않습니다. +서비스 제목은 해당 문서 유형의 서비스별 목록으로 연결되고, 개별 글은 모두 하위 메뉴에 표시됩니다. 이 목록 페이지도 빌드에서 자동 생성하므로 `docs///index.md`를 직접 추가하지 않습니다. + 개별 문서 추가를 위해 `mkdocs.yml`, `docs/.nav.yml`, README 또는 수동 목록을 수정하지 않습니다. 새 서비스·기술·태그 식별자가 필요할 때만 `docs-taxonomy.yml`을 같은 PR에서 한 번 갱신합니다. | 추가하거나 바꾸려는 것 | 작성자가 수정할 곳 | diff --git a/scripts/docs/generate_indexes.py b/scripts/docs/generate_indexes.py index b90741c9..caa3f5d7 100644 --- a/scripts/docs/generate_indexes.py +++ b/scripts/docs/generate_indexes.py @@ -282,8 +282,11 @@ def _build_service_page( slug: str, matching: list[Document], taxonomy: Mapping[str, Any], + *, + page_path: PurePosixPath | None = None, ) -> tuple[PurePosixPath, str]: - page_path = PurePosixPath(f"services/{slug}.md") + if page_path is None: + page_path = PurePosixPath(f"services/{slug}.md") services = taxonomy.get("services", {}) collections = taxonomy.get("collections", {}) label = _label_for(services, slug) @@ -384,6 +387,18 @@ def build_index_pages( matching = [doc for doc in docs if doc.metadata.get("document_type") == document_type] index_path, body = _build_collection_page(document_type, config, matching, taxonomy) pages[index_path] = body + by_primary_service: dict[str, list[Document]] = defaultdict(list) + for document in matching: + by_primary_service[document.relative_path.parts[1]].append(document) + for slug, service_docs in by_primary_service.items(): + # A real section index prevents Material from promoting the first bundle. + service_path, service_body = _build_service_page( + slug, + service_docs, + taxonomy, + page_path=PurePosixPath(config["path"]) / slug / "index.md", + ) + pages[service_path] = service_body by_service: dict[str, list[Document]] = defaultdict(list) for document in docs: diff --git a/tests/docs/test_indexes.py b/tests/docs/test_indexes.py index 8901aed9..b1ecfb97 100644 --- a/tests/docs/test_indexes.py +++ b/tests/docs/test_indexes.py @@ -130,6 +130,51 @@ def test_collection_page_groups_by_primary_service_without_duplicates(taxonomy: assert '## Azure Kubernetes Service { #service-azure-kubernetes-service }' in guide_index +def test_collection_service_landing_contains_only_its_own_documents(taxonomy: dict) -> None: + documents = [ + doc( + "cases/azure-kubernetes-service/first/index.md", + title="First case", + description="Case details", + document_type="case", + services=["azure-kubernetes-service", "azure-monitor"], + ), + doc( + "cases/azure-kubernetes-service/second/index.md", + title="Second case", + description="Case details", + document_type="case", + services=["azure-kubernetes-service"], + ), + doc( + "guides/azure-kubernetes-service/guide/index.md", + title="A guide", + description="Guide details", + document_type="guide", + services=["azure-kubernetes-service"], + ), + doc( + "cases/azure-monitor/other/index.md", + title="Other service case", + description="Case details", + document_type="case", + services=["azure-monitor"], + ), + ] + + pages = build_index_pages(documents, taxonomy) + service_landing = pages[PurePosixPath("cases/azure-kubernetes-service/index.md")] + + assert yaml.safe_load(service_landing.split("---", 2)[1])["title"] == "Azure Kubernetes Service" + assert service_landing.count('class="dg-doc-card ') == 2 + assert "[First case](first/index.md)" in service_landing + assert "[Second case](second/index.md)" in service_landing + assert "Other service case" not in service_landing + assert "A guide" not in service_landing + assert "First case" not in pages[PurePosixPath("cases/azure-monitor/index.md")] + assert "First case" in pages[PurePosixPath("services/azure-monitor.md")] + + def test_collection_page_reflects_real_counts_and_wrapper_classes(taxonomy: dict) -> None: documents = [ doc( diff --git a/tests/docs/test_site_pipeline.py b/tests/docs/test_site_pipeline.py index 07e63209..f651ecae 100644 --- a/tests/docs/test_site_pipeline.py +++ b/tests/docs/test_site_pipeline.py @@ -5,6 +5,7 @@ import json from pathlib import Path, PurePosixPath from types import SimpleNamespace +from urllib.parse import urljoin from markdown import Markdown from material.plugins.search.plugin import SearchIndex @@ -83,6 +84,7 @@ def test_indexes_are_generated_from_metadata_with_safe_yaml() -> None: assert set(pages) == { PurePosixPath("cases/index.md"), PurePosixPath("guides/index.md"), + PurePosixPath("guides/aks/index.md"), PurePosixPath("services/index.md"), PurePosixPath("services/aks.md"), PurePosixPath("services/azure-monitor.md"), @@ -347,6 +349,13 @@ def handle_data(self, text: str) -> None: unchanged_paths = (config_path, nav_path, root / "docs-taxonomy.yml") original_settings = [path.read_bytes() for path in unchanged_paths] build(load_config(str(config_path), strict=True)) + single_document_navigation = PrimaryNavigation() + single_document_navigation.feed( + (root / "site" / "index.html").read_text(encoding="utf-8") + ) + + assert single_document_navigation.links["guides/aks/network-diagnosis/"] == "AKS 네트워크 진단" + assert single_document_navigation.links["guides/aks/"] == "Azure Kubernetes Service" page = docs / "guides" / "aks" / "new-topic" / "index.md" page.parent.mkdir(parents=True) @@ -355,6 +364,12 @@ def handle_data(self, text: str) -> None: + "\n추가된 문서의 검색 본문입니다.\n", encoding="utf-8", ) + third_page = docs / "guides" / "aks" / "z-last-topic" / "index.md" + third_page.parent.mkdir(parents=True) + third_page.write_text( + SEARCH_DOCUMENT.replace("AKS 네트워크 진단", "세 번째 문서"), + encoding="utf-8", + ) build(load_config(str(config_path), strict=True)) navigation = PrimaryNavigation() navigation.feed((root / "site" / "index.html").read_text(encoding="utf-8")) @@ -363,13 +378,32 @@ def handle_data(self, text: str) -> None: ) assert [path.read_bytes() for path in unchanged_paths] == original_settings + assert navigation.links["guides/aks/network-diagnosis/"] == "AKS 네트워크 진단" assert navigation.links["guides/aks/new-topic/"] == "자동 게시 확인" + assert navigation.links["guides/aks/z-last-topic/"] == "세 번째 문서" + assert navigation.links["guides/aks/"] == "Azure Kubernetes Service" + assert navigation.links["guides/"] == "Guides" assert "자동 게시 확인" in (root / "site" / "guides" / "index.html").read_text(encoding="utf-8") assert any( entry["location"] == "guides/aks/new-topic/" and "추가된 문서" in entry["text"] for entry in search["docs"] ) + article_navigation = PrimaryNavigation() + article_navigation.feed( + (root / "site" / "guides" / "aks" / "network-diagnosis" / "index.html").read_text( + encoding="utf-8" + ) + ) + article_url = "https://example.test/guides/aks/network-diagnosis/" + resolved_links = { + urljoin(article_url, target): title + for target, title in article_navigation.links.items() + } + assert resolved_links[article_url] == "AKS 네트워크 진단" + assert resolved_links["https://example.test/guides/aks/"] == "Azure Kubernetes Service" + assert resolved_links["https://example.test/guides/aks/new-topic/"] == "자동 게시 확인" + assert resolved_links["https://example.test/guides/aks/z-last-topic/"] == "세 번째 문서" def write_search_index(root: Path, documents: list[dict[str, str]]) -> None: