Navigare in paginile randate: breadcrumb sus, inainte/inapoi jos
Sus: "Sumarizari / Modulul 2 / #6 — ...", cu modulul legat la ancora lui din index. In 09_QA apare in plus "index tematic", pentru ca acolo se cauta dupa subiect, nu dupa data. Jos: lectia precedenta si urmatoarea din acelasi folder, cu titlul lor, plus un link la index intre ele. Titlurile sunt scurtate pentru bara: prefixul "Modul 2 / " ar repeta modulul deja prezent in breadcrumb, iar la Q&A titlul intreg ocupa trei randuri pe telefon, deci ramane doar data. render_file() merge in continuare apelat pe un singur fisier, cum il folosesc agentii de sumarizare: vecinii vin dintr-un glob pe folder, nu dintr-un parametru nou. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MtSTyTmt6AbCL9j5ajEDm1
This commit is contained in:
@@ -6,6 +6,7 @@ Run after adding/editing any summary.
|
||||
"""
|
||||
|
||||
import re
|
||||
from html import escape
|
||||
from pathlib import Path
|
||||
|
||||
import markdown
|
||||
@@ -28,6 +29,15 @@ PAGE_TEMPLATE = """<!DOCTYPE html>
|
||||
a {{ color: #1a5fb4; }}
|
||||
hr {{ border: none; border-top: 1px solid #ddd; margin: 2em 0; }}
|
||||
em {{ color: #555; }}
|
||||
nav {{ font-size: .8em; color: #888; line-height: 1.5; }}
|
||||
nav a {{ color: #777; text-decoration: none; }}
|
||||
nav a:hover {{ text-decoration: underline; }}
|
||||
.crumbs {{ padding-bottom: .5em; border-bottom: 1px solid #eee; }}
|
||||
.crumbs .sep {{ margin: 0 .35em; color: #bbb; }}
|
||||
.pager {{ margin-top: 3em; padding-top: 1em; border-top: 1px solid #ddd;
|
||||
display: flex; flex-wrap: wrap; gap: .6em 1em; justify-content: space-between; }}
|
||||
.pager a {{ overflow-wrap: anywhere; }}
|
||||
.pager .next {{ text-align: right; margin-left: auto; }}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
@@ -48,13 +58,68 @@ def check_svgs(md_path: Path, text: str) -> None:
|
||||
raise SystemExit(f"{md_path}:{line}: linie goală în interiorul <svg> — markdown va rupe blocul")
|
||||
|
||||
|
||||
MODULE_NAMES = {"09_QA": "Q&A", "16_ANALIZA_FUNDAMENTALA": "Analiză fundamentală",
|
||||
"02_CATALIZATORI": "Catalizatori"}
|
||||
|
||||
|
||||
def module_name(folder: str) -> str:
|
||||
"""03_MODUL_2 -> Modulul 2; nume speciale în MODULE_NAMES."""
|
||||
if folder in MODULE_NAMES:
|
||||
return MODULE_NAMES[folder]
|
||||
rest = folder.split("_", 1)[-1]
|
||||
if rest.startswith("MODUL_"):
|
||||
return "Modulul " + rest[6:]
|
||||
return rest.replace("_", " ").capitalize()
|
||||
|
||||
|
||||
def md_title(md_path: Path) -> str:
|
||||
text = md_path.read_text(encoding="utf-8")
|
||||
return text.splitlines()[0].lstrip("# ").strip() if text else md_path.stem
|
||||
|
||||
|
||||
def short_title(title: str, folder: str) -> str:
|
||||
"""Pentru bara de navigare. Titlul lecțiilor începe cu "Modul 2 / #6 — ...",
|
||||
iar modulul e deja în breadcrumb. La Q&A titlul întreg ocupă trei rânduri pe
|
||||
telefon, așa că rămâne doar "Q&A <data>" — restul se vede în H1, dedesubt."""
|
||||
short = re.sub(r"^Modul[^/]*/\s*", "", title)
|
||||
return short.split(" — ")[0] if folder == "09_QA" else short
|
||||
|
||||
|
||||
def nav_html(md_path: Path, title: str) -> tuple[str, str]:
|
||||
"""Breadcrumb sus + înainte/înapoi jos. Vecinii vin din glob pe folder,
|
||||
ca render_file să meargă apelat singur. Doar fișierele numerotate
|
||||
(lecțiile) intră în secvență — INDEX_TEMATIC nu."""
|
||||
folder = md_path.parent.name
|
||||
sep = '<span class="sep">/</span>'
|
||||
crumbs = ['<a href="../index.html">Sumarizări</a>',
|
||||
f'<a href="../index.html#{folder}">{escape(module_name(folder))}</a>']
|
||||
if folder == "09_QA" and md_path.stem != "INDEX_TEMATIC":
|
||||
crumbs.append('<a href="INDEX_TEMATIC.html">index tematic</a>')
|
||||
crumbs.append(escape(short_title(title, folder)))
|
||||
top = f'<nav class="crumbs">{sep.join(crumbs)}</nav>'
|
||||
|
||||
lessons = sorted(f for f in md_path.parent.glob("*.md") if f.name[0].isdigit())
|
||||
prev_a = next_a = ""
|
||||
if md_path in lessons:
|
||||
i = lessons.index(md_path)
|
||||
label = lambda f: f"{f.name.split('_')[0]}. {escape(short_title(md_title(f), folder))}"
|
||||
if i > 0:
|
||||
prev_a = f'<a class="prev" href="{lessons[i-1].stem}.html">← {label(lessons[i-1])}</a>'
|
||||
if i < len(lessons) - 1:
|
||||
next_a = f'<a class="next" href="{lessons[i+1].stem}.html">{label(lessons[i+1])} →</a>'
|
||||
bottom = (f'<nav class="pager">{prev_a}'
|
||||
f'<a class="up" href="../index.html#{folder}">↑ index</a>{next_a}</nav>')
|
||||
return top, bottom
|
||||
|
||||
|
||||
def render_file(md_path: Path) -> tuple[Path, str]:
|
||||
text = md_path.read_text(encoding="utf-8")
|
||||
check_svgs(md_path, text)
|
||||
body = markdown.markdown(text, extensions=["extra", "toc"])
|
||||
title = text.splitlines()[0].lstrip("# ").strip() if text else md_path.stem
|
||||
top, bottom = nav_html(md_path, title)
|
||||
html_path = md_path.with_suffix(".html")
|
||||
html_path.write_text(PAGE_TEMPLATE.format(title=title, body=body), encoding="utf-8")
|
||||
html_path.write_text(PAGE_TEMPLATE.format(title=title, body=top + body + bottom), encoding="utf-8")
|
||||
return html_path, title
|
||||
|
||||
|
||||
@@ -68,7 +133,7 @@ def write_index(pages: list[tuple[Path, str]]):
|
||||
if current is not None:
|
||||
sections.append("</ul>")
|
||||
current = path.parent.name
|
||||
sections.append(f"<h2>{current.replace('_', ' ')}</h2><ul>")
|
||||
sections.append(f'<h2 id="{current}">{escape(module_name(current))}</h2><ul>')
|
||||
sections.append(f'<li><a href="{path.relative_to(ROOT)}">{title}</a></li>')
|
||||
if current is not None:
|
||||
sections.append("</ul>")
|
||||
|
||||
Reference in New Issue
Block a user