FossilRepo

Rewrite relative img src to code/raw/ endpoint in rendered markdown/wiki

ragelink 2026-04-14 05:14 UTC trunk
Commit ebf469a023c4e056c04f3acc934807877bd7ff14b0345a7ee008d095f45811ef
1 file changed +26 -2
+26 -2
--- fossil/views.py
+++ fossil/views.py
@@ -62,11 +62,12 @@
6262
except Exception:
6363
pass
6464
return m.group(0)
6565
6666
html = re.sub(r'<code class="language-pikchr">(.*?)</code>', _render_pikchr_md, html, flags=re.DOTALL)
67
- return _rewrite_fossil_links(html, project_slug) if project_slug else html
67
+ html = _rewrite_fossil_links(html, project_slug) if project_slug else html
68
+ return _rewrite_img_srcs(html, project_slug, base_path) if project_slug else html
6869
6970
# Fossil wiki / HTML: convert Fossil-specific syntax to HTML
7071
# Fossil links: [path | text] or [path|text] — spaces around pipe are optional
7172
def _fossil_link_replace(match):
7273
path = match.group(1).strip()
@@ -139,11 +140,12 @@
139140
content = "\n".join(result)
140141
141142
# Wrap bare text blocks in <p> tags (lines not inside HTML tags)
142143
content = re.sub(r"\n\n(?!<)", "\n\n<p>", content)
143144
144
- return _rewrite_fossil_links(content, project_slug) if project_slug else content
145
+ content = _rewrite_fossil_links(content, project_slug) if project_slug else content
146
+ return _rewrite_img_srcs(content, project_slug, base_path) if project_slug else content
145147
146148
147149
def _is_markdown(content: str) -> bool:
148150
"""Detect if content is Markdown vs Fossil wiki/HTML.
149151
@@ -282,10 +284,32 @@
282284
# Do NOT rewrite fossil-scm.org/forum links — that's a separate Fossil
283285
# instance. If we have it locally as a different project, the user can
284286
# navigate there directly. Rewriting cross-repo links is fragile.
285287
return html
286288
289
+
290
+def _rewrite_img_srcs(html: str, project_slug: str, base_path: str) -> str:
291
+ """Rewrite relative img src attributes to the raw file endpoint.
292
+
293
+ Markdown files often reference images with relative paths (e.g. docs/tour.gif).
294
+ After rendering, those paths would resolve relative to the current page URL
295
+ (code/file/...) which returns HTML, not the image binary. Rewrite them to
296
+ code/raw/... which serves the raw file content.
297
+ """
298
+ if not project_slug:
299
+ return html
300
+ raw_base = f"/projects/{project_slug}/fossil/code/raw/{base_path}"
301
+
302
+ def replace_src(match):
303
+ src = match.group(1)
304
+ # Leave absolute URLs, root-relative paths, and data URIs alone
305
+ if src.startswith(("http://", "https://", "/", "data:")):
306
+ return match.group(0)
307
+ return f'src="{raw_base}{src}"'
308
+
309
+ return re.sub(r'src="([^"]*)"', replace_src, html)
310
+
287311
288312
def _get_repo_and_reader(slug, request=None, require="read"):
289313
"""Return (project, fossil_repo, reader) or raise 404/403.
290314
291315
require: "read", "write", or "admin"
292316
--- fossil/views.py
+++ fossil/views.py
@@ -62,11 +62,12 @@
62 except Exception:
63 pass
64 return m.group(0)
65
66 html = re.sub(r'<code class="language-pikchr">(.*?)</code>', _render_pikchr_md, html, flags=re.DOTALL)
67 return _rewrite_fossil_links(html, project_slug) if project_slug else html
 
68
69 # Fossil wiki / HTML: convert Fossil-specific syntax to HTML
70 # Fossil links: [path | text] or [path|text] — spaces around pipe are optional
71 def _fossil_link_replace(match):
72 path = match.group(1).strip()
@@ -139,11 +140,12 @@
139 content = "\n".join(result)
140
141 # Wrap bare text blocks in <p> tags (lines not inside HTML tags)
142 content = re.sub(r"\n\n(?!<)", "\n\n<p>", content)
143
144 return _rewrite_fossil_links(content, project_slug) if project_slug else content
 
145
146
147 def _is_markdown(content: str) -> bool:
148 """Detect if content is Markdown vs Fossil wiki/HTML.
149
@@ -282,10 +284,32 @@
282 # Do NOT rewrite fossil-scm.org/forum links — that's a separate Fossil
283 # instance. If we have it locally as a different project, the user can
284 # navigate there directly. Rewriting cross-repo links is fragile.
285 return html
286
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
287
288 def _get_repo_and_reader(slug, request=None, require="read"):
289 """Return (project, fossil_repo, reader) or raise 404/403.
290
291 require: "read", "write", or "admin"
292
--- fossil/views.py
+++ fossil/views.py
@@ -62,11 +62,12 @@
62 except Exception:
63 pass
64 return m.group(0)
65
66 html = re.sub(r'<code class="language-pikchr">(.*?)</code>', _render_pikchr_md, html, flags=re.DOTALL)
67 html = _rewrite_fossil_links(html, project_slug) if project_slug else html
68 return _rewrite_img_srcs(html, project_slug, base_path) if project_slug else html
69
70 # Fossil wiki / HTML: convert Fossil-specific syntax to HTML
71 # Fossil links: [path | text] or [path|text] — spaces around pipe are optional
72 def _fossil_link_replace(match):
73 path = match.group(1).strip()
@@ -139,11 +140,12 @@
140 content = "\n".join(result)
141
142 # Wrap bare text blocks in <p> tags (lines not inside HTML tags)
143 content = re.sub(r"\n\n(?!<)", "\n\n<p>", content)
144
145 content = _rewrite_fossil_links(content, project_slug) if project_slug else content
146 return _rewrite_img_srcs(content, project_slug, base_path) if project_slug else content
147
148
149 def _is_markdown(content: str) -> bool:
150 """Detect if content is Markdown vs Fossil wiki/HTML.
151
@@ -282,10 +284,32 @@
284 # Do NOT rewrite fossil-scm.org/forum links — that's a separate Fossil
285 # instance. If we have it locally as a different project, the user can
286 # navigate there directly. Rewriting cross-repo links is fragile.
287 return html
288
289
290 def _rewrite_img_srcs(html: str, project_slug: str, base_path: str) -> str:
291 """Rewrite relative img src attributes to the raw file endpoint.
292
293 Markdown files often reference images with relative paths (e.g. docs/tour.gif).
294 After rendering, those paths would resolve relative to the current page URL
295 (code/file/...) which returns HTML, not the image binary. Rewrite them to
296 code/raw/... which serves the raw file content.
297 """
298 if not project_slug:
299 return html
300 raw_base = f"/projects/{project_slug}/fossil/code/raw/{base_path}"
301
302 def replace_src(match):
303 src = match.group(1)
304 # Leave absolute URLs, root-relative paths, and data URIs alone
305 if src.startswith(("http://", "https://", "/", "data:")):
306 return match.group(0)
307 return f'src="{raw_base}{src}"'
308
309 return re.sub(r'src="([^"]*)"', replace_src, html)
310
311
312 def _get_repo_and_reader(slug, request=None, require="read"):
313 """Return (project, fossil_repo, reader) or raise 404/403.
314
315 require: "read", "write", or "admin"
316

Keyboard Shortcuts

Open search /
Next entry (timeline) j
Previous entry (timeline) k
Open focused entry Enter
Show this help ?
Toggle theme Top nav button