99 lines
3.1 KiB
Python
99 lines
3.1 KiB
Python
"""Page /doc — documentation interne (markdown rendu en HTML côté Python)."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import os
|
|
import re
|
|
from pathlib import Path
|
|
|
|
import markdown as md
|
|
import reflex as rx
|
|
|
|
from ..state import AuthState
|
|
from ..sidebar import layout
|
|
from ..components import scan_docs
|
|
|
|
_RE_TITLE = re.compile(r"^#\s+(.+?)\s*$", re.MULTILINE)
|
|
|
|
# Une seule instance de Markdown réutilisée (extensions activées).
|
|
_MD = md.Markdown(extensions=["tables", "fenced_code", "attr_list", "sane_lists"])
|
|
|
|
|
|
def _render_md(text: str) -> str:
|
|
_MD.reset()
|
|
return _MD.convert(text)
|
|
|
|
|
|
class DocState(AuthState):
|
|
sections: list[dict] = []
|
|
selected_slug: str = ""
|
|
selected_title: str = ""
|
|
selected_html: str = ""
|
|
|
|
def load_data(self):
|
|
if not self.authenticated:
|
|
return rx.redirect("/login")
|
|
self.sections = scan_docs()
|
|
if not self.sections:
|
|
self.selected_slug = ""
|
|
self.selected_title = ""
|
|
self.selected_html = "<p><em>Aucune documentation disponible. Ajoutez des fichiers <code>.md</code> dans <code>data/docs/</code>.</em></p>"
|
|
return
|
|
slugs = [s["slug"] for s in self.sections]
|
|
if self.selected_slug not in slugs:
|
|
self.selected_slug = slugs[0]
|
|
self._load_selected()
|
|
|
|
def select_section(self, slug: str):
|
|
self.selected_slug = slug
|
|
self._load_selected()
|
|
|
|
def _load_selected(self):
|
|
section = next((s for s in self.sections if s["slug"] == self.selected_slug), None)
|
|
if not section:
|
|
self.selected_title = ""
|
|
self.selected_html = ""
|
|
return
|
|
try:
|
|
text = Path(section["path"]).read_text(encoding="utf-8")
|
|
except Exception as e:
|
|
self.selected_title = section["title"]
|
|
self.selected_html = f"<p><em>Erreur de lecture : {e}</em></p>"
|
|
return
|
|
# Retirer le H1 (déjà affiché en titre)
|
|
m = _RE_TITLE.search(text)
|
|
if m:
|
|
text = text[m.end():].lstrip("\n")
|
|
self.selected_title = section["title"]
|
|
self.selected_html = _render_md(text)
|
|
|
|
|
|
# ── UI ────────────────────────────────────────────────────────────────────────
|
|
|
|
|
|
def _content() -> rx.Component:
|
|
return rx.box(
|
|
rx.heading(DocState.selected_title, size="6", margin_bottom="1rem"),
|
|
rx.html(DocState.selected_html, class_name="doc-content"),
|
|
padding="1.5rem 2rem",
|
|
background_color="var(--surface)",
|
|
border="1px solid var(--gray-5)",
|
|
border_radius="8px",
|
|
width="100%",
|
|
class_name="anim-fade",
|
|
)
|
|
|
|
|
|
def doc_page() -> rx.Component:
|
|
return layout(
|
|
rx.vstack(
|
|
rx.heading("Documentation", size="7"),
|
|
rx.text(
|
|
"Guide d'utilisation et fonctionnement interne de l'application.",
|
|
size="2", color="#666",
|
|
),
|
|
_content(),
|
|
spacing="3",
|
|
width="100%",
|
|
)
|
|
)
|