From e3364e2c4424b373f80ac04d1ab2e3f3d82328e1 Mon Sep 17 00:00:00 2001 From: "Jason A. Dönerfield" Date: Sat, 18 Jul 2026 21:07:20 +0200 Subject: md2html: render linked .md via the about view, fix relative base Links to markdown files now point at //about/ (rendered through the about-filter) instead of //tree/ (raw blob), so clicking a README link to another .md shows rendered markdown. Also resolve relative links against the rendered file's own directory: about-formatting.sh now forwards the filename cgit passes ($1) to md2html as argv[1], and md2html uses dirname(argv[1]) as the base. This makes links *inside* a rendered subpage (docs/foo.md -> ./bar.md, ../README.md, images) resolve correctly. Non-markdown files and directories still go to tree/; images to plain/; about/ links carry no ?h= since the about view renders against the configured readme ref. --- filters/about-formatting.sh | 2 +- filters/html-converters/md2html | 69 ++++++++++++++++++++++++++--------------- 2 files changed, 45 insertions(+), 26 deletions(-) diff --git a/filters/about-formatting.sh b/filters/about-formatting.sh index 85daf9c..c6c4f9c 100755 --- a/filters/about-formatting.sh +++ b/filters/about-formatting.sh @@ -19,7 +19,7 @@ cd "$(dirname $0)/html-converters/" case "$(printf '%s' "$1" | tr '[:upper:]' '[:lower:]')" in - *.markdown|*.mdown|*.md|*.mkd) exec ./md2html; ;; + *.markdown|*.mdown|*.md|*.mkd) exec ./md2html "$1"; ;; *.rst) exec ./rst2html; ;; *.[1-9]) exec ./man2html; ;; *.htm|*.html) exec cat; ;; diff --git a/filters/html-converters/md2html b/filters/html-converters/md2html index 90021a7..79d4a17 100755 --- a/filters/html-converters/md2html +++ b/filters/html-converters/md2html @@ -358,17 +358,21 @@ html = markdown.markdown( "markdown.extensions.codehilite":{"css_class":"highlight"}}) # A README's relative paths point at repo content, but a browser resolves them -# against the page the readme is shown on (//about/ or the summary page -# //), not against the repo root — so they 404. cgit serves blobs only via -# its `plain`/`tree` commands, so rewrite relative references to absolute cgit -# URLs (absolute so it works both on the about page and the summary page): -# -> //plain/?h= (raw bytes for the browser) -# -> //tree/?h= (browsable file/dir view) -# Relative hrefs are resolved as paths against the repo root; a href that climbs -# out of the repo with ".." (e.g. ../sibling) is resolved against the repo's URL -# location instead (-> /sibling), matching how sibling repos are addressed. -# cgit exports the repo context to about-filters via these environment vars; if -# they're absent we leave the references untouched. +# against the page the readme is shown on (//about/... or the summary page +# //), not against the file's own location — so they 404 or hit the wrong +# file. cgit serves this content through three commands, so rewrite relative +# references to absolute cgit URLs (absolute so they work on the about page and +# the summary page alike): +# -> //plain/?h= (raw bytes) +# *.md -> //about/ (rendered markdown) +# other/dir -> //tree/?h= (browsable blob view) +# cgit hands the about-filter the path of the file being rendered as argv[1] +# (README.md, or docs/foo.md for a rendered subpage), so relative links resolve +# against *that* file's directory and links inside a rendered subpage work too. +# A href that climbs out of the repo with ".." (e.g. ../sibling) is resolved +# against the repo's URL location (-> /sibling). The about view renders against +# the configured readme ref, so about/ links carry no ?h=. If the repo context +# env vars are absent we leave the references untouched. import os, re from urllib.parse import urljoin repo = os.environ.get("CGIT_REPO_URL", "").strip("/") @@ -377,16 +381,16 @@ if repo: repo_root = "/" + repo + "/" plain = repo_root + "plain/" query = ("?h=" + branch) if branch else "" + # directory of the file being rendered; relative links resolve against it. + # argv[1] is the repo-relative readme/subpage path cgit passes the filter. + cur = sys.argv[1] if len(sys.argv) > 1 else "" + base_dir = "" if cur.startswith("/") else os.path.dirname(cur) + # markdown files are rendered via the about view; everything else is a blob + md_ext = re.compile(r"\.(?:md|markdown|mdown|mkd)$", re.I) # leave absolute, protocol-relative, root-relative, anchor, query-only and # scheme (mailto:, http:, data:, ...) URLs alone skip = re.compile(r"^(?:[a-z][a-z0-9+.\-]*:|//|/|[#?])", re.I) - def rewrite_img(m): - url = m.group(2) - if not url or skip.match(url): - return m.group(0) - return m.group(1) + plain + re.sub(r"^\./", "", url) + query + m.group(3) - def _split_frag(url): # split off a trailing #fragment (kept verbatim on the rewritten URL) if "#" in url: @@ -394,11 +398,13 @@ if repo: return path, "#" + frag return url, "" - def _normalize(path): - # resolve "." / ".." against the repo root; returns (climbs, cleaned) - # where `climbs` counts ".." segments that escaped above the repo root. + def _resolve(path): + # resolve `path` (relative to base_dir) into a repo-root-relative path, + # collapsing "." / ".."; returns (climbs, cleaned) where `climbs` counts + # ".." segments that escaped above the repo root. + combined = (base_dir + "/" + path) if base_dir else path out, climbs = [], 0 - for seg in path.split("/"): + for seg in combined.split("/"): if seg in ("", "."): continue if seg == "..": @@ -410,19 +416,32 @@ if repo: out.append(seg) return climbs, "/".join(out) + def rewrite_img(m): + url = m.group(2) + if not url or skip.match(url): + return m.group(0) + path, frag = _split_frag(url) + climbs, cleaned = _resolve(path) + if climbs or not cleaned: + return m.group(0) + return m.group(1) + plain + cleaned + query + frag + m.group(3) + def rewrite_href(m): url = m.group(2) if not url or skip.match(url): return m.group(0) path, frag = _split_frag(url) - climbs, cleaned = _normalize(path) + climbs, cleaned = _resolve(path) if climbs: # escapes the repo (sibling repo etc.): resolve against repo location dest = urljoin(repo_root, "../" * climbs + cleaned) - elif cleaned: - dest = repo_root + "tree/" + cleaned + query - else: + elif not cleaned: dest = repo_root + (query or "") + elif md_ext.search(cleaned): + # rendered through the about-filter (renders against the readme ref) + dest = repo_root + "about/" + cleaned + else: + dest = repo_root + "tree/" + cleaned + query return m.group(1) + dest + frag + m.group(3) html = re.sub(r'(]*?\bsrc=")([^"]*)(")', rewrite_img, html, flags=re.I) -- cgit v1.3.1-sl0p