diff --git a/app/routers/docs_reader.py b/app/routers/docs_reader.py index 8a3a747..6ea6d3a 100644 --- a/app/routers/docs_reader.py +++ b/app/routers/docs_reader.py @@ -38,37 +38,44 @@ async def list_docs(req: Request): # 1. Pinned Docs (from whitelist) pinned = [] - + pinned_paths = set() + + for filename, title in ALLOWED_DOCS.items(): + # Check root level only for pinned items (simpler, or check recursively and pin first?) + # Legacy behavior was pinned items are usually in root. + # Let's verify existence. + full_path = _find_file_recursive(base_path, filename) + if full_path: + # We want the relative path as the ID + rel_path = os.path.relpath(full_path, base_path) + pinned.append({"name": title, "file": rel_path}) + pinned_paths.add(rel_path) + # 2. All other docs (dynamic scan) library = [] - # helper to check if already in pinned - pinned_filenames = set() - - for filename, title in ALLOWED_DOCS.items(): - full_path = _find_file(base_path, filename) # Helper now needs to look deeper? - if full_path: - pinned.append({"name": title, "file": filename}) - pinned_filenames.add(filename) - - # Scan for all .md files recursively if os.path.exists(base_path): for root, dirs, files in os.walk(base_path): for file in files: if file.endswith(".md"): - # Calculate relative path or just filename. - # If we use just filename, distinct files with same name in diff folders conflict. - # Current view_doc takes {filename}. Let's assume filenames are unique enough or we handle paths. - # For now, let's just show them if they aren't pinned. + full_path = os.path.join(root, file) + rel_path = os.path.relpath(full_path, base_path) - if file not in pinned_filenames: - # Try to get a nice name (Title from first line?) or filename - name = file.replace(".md", "").replace("-", " ").title() - library.append({"name": name, "file": file}) + if rel_path not in pinned_paths: + # Improved Naming: File name + Parent Dir if nested + # e.g. crumbcodex/README.md -> "Crumbcodex / Readme" + parent = os.path.basename(root) + safe_name = file.replace(".md", "").replace("-", " ").title() + + if parent and parent != os.path.basename(base_path) and parent != "docs": + # If deeper than root + display_name = f"{parent.title()} / {safe_name}" + else: + display_name = safe_name + + library.append({"name": display_name, "file": rel_path}) - # Combine or separate? Let's just return all in 'docs' for now to fit the template expectation. - # The template iterates 'docs'. - + # Combine all_docs = pinned + sorted(library, key=lambda x: x["name"]) return req.app.state.render( @@ -78,27 +85,29 @@ async def list_docs(req: Request): page_title="Dokumentation" ) -@router.get("/docs/{filename}", response_class=HTMLResponse) -async def view_doc(req: Request, filename: str): +@router.get("/docs/{file_path:path}", response_class=HTMLResponse) +async def view_doc(req: Request, file_path: str): """ - Render a specific markdown file. Now supports any MD file found. + Render a specific markdown file. specific via relative path. """ - # Security: basic check to prevent directory traversal outside allowable scope - if ".." in filename or "/" in filename: # filename param is usually just the last part if caught by path param? - # Actually FastAPI path param "{filename}" stops at slashes unless defined as "{path:path}". - # So 'filename' here is just the leaf name. - pass - + # Security: Prevent traversal up + if ".." in file_path: + raise HTTPException(400, "Invalid path.") + base_path = _get_docs_base_path() + full_path = os.path.join(base_path, file_path) - # Use recursive find - file_path = _find_file_recursive(base_path, filename) - - if not file_path: - raise HTTPException(404, "File not on server.") + if not os.path.exists(full_path) or not os.path.isfile(full_path): + # Fallback: maybe it was a legacy link with just filename? + # Try finding it recursively if direct path fails + found = _find_file_recursive(base_path, os.path.basename(file_path)) + if found: + full_path = found + else: + raise HTTPException(404, "File not on server.") try: - with open(file_path, "r", encoding="utf-8") as f: + with open(full_path, "r", encoding="utf-8") as f: content = f.read() html_content = markdown.markdown( @@ -107,14 +116,20 @@ async def view_doc(req: Request, filename: str): ) # Determine Title - title = ALLOWED_DOCS.get(filename, filename.replace(".md", "").replace("-", " ").title()) + filename = os.path.basename(full_path) + # Check if pinned to get nice title + title = filename.replace(".md", "").replace("-", " ").title() + for k, v in ALLOWED_DOCS.items(): + if k == filename: + title = v + break return req.app.state.render( req, "pages/doc_viewer.html", doc_title=title, doc_content=html_content, - filename=filename + filename=file_path # Keep full path for context? Or filename? Template uses it for back link maybe? ) except Exception as e: