diff --git a/soapbox/admin.py b/soapbox/admin.py index 10efd7c..5fa156e 100644 --- a/soapbox/admin.py +++ b/soapbox/admin.py @@ -339,7 +339,7 @@ def history(post_id: str): sha = request.args.get("sha") diff = None if sha: - diff = gitstore.path_diff(cfg.content_dir, sha, rel) + diff = gitstore.path_diff(cfg.content_dir, sha) if diff is None: abort(404) return render_template("admin/history.html", post=post, entries=entries, sha=sha, diff=diff) diff --git a/soapbox/build.py b/soapbox/build.py index d210388..36b833c 100644 --- a/soapbox/build.py +++ b/soapbox/build.py @@ -63,6 +63,7 @@ def build_site(cfg: Config) -> int: return env.get_template(template).render( site=site, source_url=SOURCE_URL, + site_url=cfg.site_url, # absolutt; kun for Open Graph/canonical page_path=page_path, rel=lambda target: relative_to(page_path, target), now=datetime.now(UTC), @@ -93,6 +94,7 @@ def build_site(cfg: Config) -> int: ) _write(tmp / "feed.xml", _feed(cfg, site, posts[:20])) + _write(tmp / "sitemap.xml", _sitemap(cfg, posts, sorted(guests))) # Statiske filer lagvis: innebygd først, eget tema oppå (overskriver). for d in reversed(cfg.theme_dirs): @@ -111,6 +113,27 @@ def build_site(cfg: Config) -> int: return len(posts) +def _sitemap(cfg: Config, posts: list[Post], guests: list[str]) -> str: + """sitemap.xml med forsiden, alle innlegg og gjesteoversiktene.""" + base = cfg.site_url + "/" + + def url(path: str, lastmod: datetime | None = None) -> str: + loc = f"{escape(base + quote(path))}" + mod = f"{lastmod.date().isoformat()}" if lastmod else "" + return f"{loc}{mod}" + + newest = posts[0].updated or posts[0].date if posts else None + entries = [url("", newest)] + entries += [url(p.url_path, p.updated or p.date) for p in posts] + entries += [url(f"{g}/") for g in guests] + return ( + '\n' + '' + + "".join(entries) + + "\n" + ) + + def _feed(cfg: Config, site: dict, posts: list[Post]) -> str: base = cfg.site_url + "/" items = [] diff --git a/soapbox/gitstore.py b/soapbox/gitstore.py index 0c3d2bb..3ab64b1 100644 --- a/soapbox/gitstore.py +++ b/soapbox/gitstore.py @@ -45,21 +45,11 @@ def log(repo: Path, limit: int = 20) -> list[str]: return out.stdout.splitlines() if out.returncode == 0 else [] -def path_log(repo: Path, path: str, limit: int = 50) -> list[dict]: - """Commits som har rørt ``path`` (relativ til repo), nyeste først. +_LOG_FORMAT = "--format=%H%x1f%aI%x1f%an%x1f%s" - Merk: en katalog som ble omdøpt (slug endret mens innlegget var utkast) - mister historikken fra før omdøpingen – git følger bare filer, ikke mapper. - """ - out = _git( - repo, - "log", - f"-{limit}", - "--format=%H%x1f%aI%x1f%an%x1f%s", - "--", - path, - check=False, - ) + +def _log(repo: Path, *args: str) -> list[dict]: + out = _git(repo, "log", _LOG_FORMAT, *args, check=False) entries = [] for line in out.stdout.splitlines(): sha, date, author, subject = line.split("\x1f", 3) @@ -67,9 +57,29 @@ def path_log(repo: Path, path: str, limit: int = 50) -> list[dict]: return entries -def path_diff(repo: Path, sha: str, path: str) -> str | None: - """Diff for ``path`` i én commit, eller None hvis commit-en ikke finnes.""" +def path_log(repo: Path, path: str, limit: int = 50) -> list[dict]: + """Commits som har rørt innleggsmappen ``path`` (relativ til repo), nyeste først. + + git følger filer, ikke mapper, så en katalog som ble omdøpt (slug endret + mens innlegget var utkast) ville mistet historikken fra før omdøpingen. + Derfor kombineres to logger: ``--follow`` på ``index.md`` (tar med tiden + før omdøpingen) og en vanlig logg på mappen (tar med bildeopplastinger). + """ + seen: dict[str, dict] = {} + for e in _log(repo, f"-{limit}", "--follow", "--", f"{path}/index.md") + _log( + repo, f"-{limit}", "--", path + ): + seen.setdefault(e["sha"], e) + return sorted(seen.values(), key=lambda e: e["date"], reverse=True)[:limit] + + +def path_diff(repo: Path, sha: str) -> str | None: + """Diff for én commit, eller None hvis commit-en ikke finnes. + + Diffen begrenses ikke til innleggets nåværende sti: før en omdøping lå + filene et annet sted, og hver commit er uansett én handling på ett innlegg. + """ if not all(c in "0123456789abcdef" for c in sha) or len(sha) < 7: return None - out = _git(repo, "show", "--format=", "--stat", "--patch", sha, "--", path, check=False) + out = _git(repo, "show", "--format=", "--stat", "--patch", sha, check=False) return out.stdout if out.returncode == 0 else None diff --git a/soapbox/themes/green/templates/base.html b/soapbox/themes/green/templates/base.html index 3393fe8..1b4c25a 100644 --- a/soapbox/themes/green/templates/base.html +++ b/soapbox/themes/green/templates/base.html @@ -8,6 +8,14 @@ +{# Open Graph krever absolutte URL-er; site_url er det eneste stedet det brukes utenom feed/sitemap. #} +{% block og %} + + + + +{% if site.description %}{% endif %} +{% endblock %} diff --git a/soapbox/themes/green/templates/post.html b/soapbox/themes/green/templates/post.html index e622ddf..443cff5 100644 --- a/soapbox/themes/green/templates/post.html +++ b/soapbox/themes/green/templates/post.html @@ -1,5 +1,20 @@ {% extends "base.html" %} {% block title %}{{ post.title }} – {{ site.title }}{% endblock %} +{% block og %} +{% set summary = post.body|excerpt(200) %} + + + + + +{% if summary %} +{% endif %} +{% if post.images %}{% endif %} + +{% if post.updated %}{% endif %} +{% if post.author %}{% endif %} + +{% endblock %} {% block content %}

{{ post.title }}

diff --git a/tests/test_app.py b/tests/test_app.py index d25b8b2..d13f5e9 100644 --- a/tests/test_app.py +++ b/tests/test_app.py @@ -180,6 +180,7 @@ def test_post_history(client, cfg): html = client.get(f"/admin/history/{pid}").text assert "Publiserte: Hist" in html and "Lagret: Hist" in html + assert "Opprettet utkast" in html # fra før slug-omdøpingen (--follow) sha = re.search(r"sha=([0-9a-f]{40})", html).group(1) diff = client.get(f"/admin/history/{pid}?sha={sha}").text assert '+andre' in diff and '-første' in diff @@ -264,3 +265,34 @@ def test_logo_links_to_blog_root(client): assert '' in html + assert '' in html + assert '' in html + assert '' in html + assert '' in html + assert '' in client.get("/").text + sm = client.get("/sitemap.xml") + assert sm.status_code == 200 + assert "https://blog.example.no/" in sm.text + assert "https://blog.example.no/og-test/" in sm.text