FossilRepo
Rewrite relative img src to code/raw/ endpoint in rendered markdown/wiki
Commit
ebf469a023c4e056c04f3acc934807877bd7ff14b0345a7ee008d095f45811ef
Parent
1f367243b5a57fb…
1 file changed
+26
-2
+26
-2
| --- fossil/views.py | ||
| +++ fossil/views.py | ||
| @@ -62,11 +62,12 @@ | ||
| 62 | 62 | except Exception: |
| 63 | 63 | pass |
| 64 | 64 | return m.group(0) |
| 65 | 65 | |
| 66 | 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 | |
| 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 | |
| 68 | 69 | |
| 69 | 70 | # Fossil wiki / HTML: convert Fossil-specific syntax to HTML |
| 70 | 71 | # Fossil links: [path | text] or [path|text] — spaces around pipe are optional |
| 71 | 72 | def _fossil_link_replace(match): |
| 72 | 73 | path = match.group(1).strip() |
| @@ -139,11 +140,12 @@ | ||
| 139 | 140 | content = "\n".join(result) |
| 140 | 141 | |
| 141 | 142 | # Wrap bare text blocks in <p> tags (lines not inside HTML tags) |
| 142 | 143 | content = re.sub(r"\n\n(?!<)", "\n\n<p>", content) |
| 143 | 144 | |
| 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 | |
| 145 | 147 | |
| 146 | 148 | |
| 147 | 149 | def _is_markdown(content: str) -> bool: |
| 148 | 150 | """Detect if content is Markdown vs Fossil wiki/HTML. |
| 149 | 151 | |
| @@ -282,10 +284,32 @@ | ||
| 282 | 284 | # Do NOT rewrite fossil-scm.org/forum links — that's a separate Fossil |
| 283 | 285 | # instance. If we have it locally as a different project, the user can |
| 284 | 286 | # navigate there directly. Rewriting cross-repo links is fragile. |
| 285 | 287 | return html |
| 286 | 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 | + | |
| 287 | 311 | |
| 288 | 312 | def _get_repo_and_reader(slug, request=None, require="read"): |
| 289 | 313 | """Return (project, fossil_repo, reader) or raise 404/403. |
| 290 | 314 | |
| 291 | 315 | require: "read", "write", or "admin" |
| 292 | 316 |
| --- 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 |