Corrige deux régressions de /local-workspace :
- le chemin du header restait bloqué sur « Home / <workspace> » quel que
soit le dossier affiché : la route rend désormais breadcrumb_items
(Home / <workspace> / <dossier> / <sous-dossier>, niveaux cliquables,
collapse « … » au-delà de 4) et la navigation sans rechargement recalcule
le chemin via l'event flowdeck:breadcrumb-changed ;
- le clic sur un dossier du sidebar affichait TOUS les composants à la
fois : Alpine.data('wsInitData') retournait le même objet singleton, le
2e montage (navigation partielle) levait « Cannot redefine property:
\ » et initTree abandonnait, laissant tout le contenu au state
brut. La factory retourne désormais une enveloppe fraîche par montage
qui délègue à l'état réactif partagé. #lw-config est aussi relu à chaque
exécution (le 2e montage gardait le folder_id du 1er chargement).
Inclus également le travail en cours de l'arbre : Library (colonnes Last
visited/Source, ordre d'en-tête, favoris à icônes Workspace), Meeting
Notes (bloc, CSS, routes, docs), coloration de code hljs, badges
favori/publié dans l'arbre local-workspace, docs (DATA_MODEL,
architectures) et tests associés.
98 lines
2.9 KiB
Python
98 lines
2.9 KiB
Python
"""FlowDeck — forge repository file importer (v5.6.0, Phase 5).
|
|
|
|
Imports a Gitea/GitHub repository's text files as pages, preserving the folder
|
|
hierarchy. Markdown files become pages; other text files become code blocks.
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
from pathlib import Path
|
|
|
|
from app.services.export import (
|
|
_TEXTUAL_EXTS,
|
|
_TEXTUAL_FILENAMES,
|
|
code_lang_for_file,
|
|
)
|
|
from app.services.importers.base import ImportPage, ImportResult
|
|
|
|
_MD_EXTS = {"md", "markdown"}
|
|
|
|
|
|
def _ext(path: str) -> str:
|
|
return Path(path).suffix.lower().lstrip(".")
|
|
|
|
|
|
def _basename(path: str) -> str:
|
|
return path.replace("\\", "/").rsplit("/", 1)[-1].lower()
|
|
|
|
|
|
def _is_textual(path: str) -> bool:
|
|
return _ext(path) in _TEXTUAL_EXTS or _basename(path) in _TEXTUAL_FILENAMES
|
|
|
|
|
|
def build_repo_result(
|
|
files: list[tuple[str, str]],
|
|
*,
|
|
owner: str,
|
|
repo: str,
|
|
provider: str = "",
|
|
) -> ImportResult:
|
|
"""Turn ``[(path, content)]`` into pages with folder hierarchy."""
|
|
result = ImportResult(source=f"forge-repo:{provider}" if provider else "forge-repo")
|
|
for path, content in files:
|
|
clean = path.replace("\\", "/").strip("/")
|
|
if not clean:
|
|
continue
|
|
ext = _ext(clean)
|
|
if ext in _MD_EXTS:
|
|
markdown = content
|
|
else:
|
|
lang = code_lang_for_file(clean)
|
|
markdown = f"```{lang}\n{content.rstrip()}\n```"
|
|
parts = clean.split("/")
|
|
result.pages.append(ImportPage(
|
|
title=parts[-1] or clean,
|
|
markdown=markdown,
|
|
source_path=clean,
|
|
parent_path="/".join(parts[:-1]),
|
|
external_id=f"{provider}:{owner}/{repo}:{clean}",
|
|
))
|
|
result.stats["rows"] = len(result.pages)
|
|
return result.finalize()
|
|
|
|
|
|
async def fetch_forge_repo(
|
|
adapter,
|
|
owner: str,
|
|
repo: str,
|
|
*,
|
|
provider: str = "",
|
|
path: str = "",
|
|
max_files: int = 200,
|
|
max_file_bytes: int = 512_000,
|
|
) -> ImportResult:
|
|
"""List a repo's files and fetch the textual ones."""
|
|
try:
|
|
metas = await adapter.list_repo_files(owner, repo, path)
|
|
except Exception as exc: # noqa: BLE001
|
|
result = ImportResult(source=f"forge-repo:{provider}" if provider else "forge-repo")
|
|
result.warn(f"Arborescence illisible : {exc}")
|
|
return result.finalize()
|
|
|
|
files: list[tuple[str, str]] = []
|
|
for meta in metas:
|
|
file_path = meta.get("path") or ""
|
|
if not _is_textual(file_path):
|
|
continue
|
|
if int(meta.get("size") or 0) > max_file_bytes:
|
|
continue
|
|
if len(files) >= max_files:
|
|
break
|
|
try:
|
|
content = await adapter.get_file_content(owner, repo, file_path)
|
|
except Exception: # noqa: BLE001 - skip unreadable files
|
|
continue
|
|
if not content or content == "[binary file]":
|
|
continue
|
|
files.append((file_path, content))
|
|
return build_repo_result(files, owner=owner, repo=repo, provider=provider)
|