FossilRepo
Add branches view, Source/Rendered toggle, Pikchr diagrams Branches: - New /fossil/branches/ view listing all branches - Shows branch name, last checkin hash, user, count, time ago - "Branches" tab in project navigation Code file viewer: - Source/Rendered toggle for .wiki, .md, .html files - "Rendered" mode passes content through _render_fossil_content - Toggle pills in the file header bar - Fixes viewing docs like release-checklist.wiki as rendered markup Pikchr diagrams: - <verbatim type="pikchr"> blocks rendered to SVG via fossil pikchr CLI - ```pikchr fenced code blocks in markdown also rendered - Falls back to <pre><code> display if rendering fails
Commit
b452cbefb7c7ed6b9f8ec6856e1c51e3b09efd10f1291d8313b7fd4b56e02e54
Parent
fefca89c2be51c7…
6 files changed
+31
+1
+36
-1
+4
+14
+17
-3
+31
| --- fossil/reader.py | ||
| +++ fossil/reader.py | ||
| @@ -371,10 +371,41 @@ | ||
| 371 | 371 | for r in rows: |
| 372 | 372 | contributors.append({"user": r["user"], "count": r["cnt"]}) |
| 373 | 373 | except sqlite3.OperationalError: |
| 374 | 374 | pass |
| 375 | 375 | return contributors |
| 376 | + | |
| 377 | + def get_branches(self) -> list[dict]: | |
| 378 | + """Get all branches with their latest checkin info.""" | |
| 379 | + branches = [] | |
| 380 | + try: | |
| 381 | + rows = self.conn.execute( | |
| 382 | + """ | |
| 383 | + SELECT tag.tagname, max(event.mtime) as last_mtime, event.user, | |
| 384 | + count(tagxref.rid) as checkin_count, blob.uuid | |
| 385 | + FROM tag | |
| 386 | + JOIN tagxref ON tag.tagid = tagxref.tagid | |
| 387 | + JOIN event ON tagxref.rid = event.objid | |
| 388 | + JOIN blob ON event.objid = blob.rid | |
| 389 | + WHERE tag.tagname LIKE 'sym-%' AND event.type = 'ci' | |
| 390 | + GROUP BY tag.tagname | |
| 391 | + ORDER BY last_mtime DESC | |
| 392 | + """, | |
| 393 | + ).fetchall() | |
| 394 | + for r in rows: | |
| 395 | + branches.append( | |
| 396 | + { | |
| 397 | + "name": r["tagname"].replace("sym-", "", 1), | |
| 398 | + "last_checkin": _julian_to_datetime(r["last_mtime"]), | |
| 399 | + "last_user": r["user"] or "", | |
| 400 | + "checkin_count": r["checkin_count"], | |
| 401 | + "last_uuid": r["uuid"], | |
| 402 | + } | |
| 403 | + ) | |
| 404 | + except sqlite3.OperationalError: | |
| 405 | + pass | |
| 406 | + return branches | |
| 376 | 407 | |
| 377 | 408 | # --- Timeline --- |
| 378 | 409 | |
| 379 | 410 | def get_timeline(self, limit: int = 50, offset: int = 0, event_type: str | None = None) -> list[TimelineEntry]: |
| 380 | 411 | sql = """ |
| 381 | 412 |
| --- fossil/reader.py | |
| +++ fossil/reader.py | |
| @@ -371,10 +371,41 @@ | |
| 371 | for r in rows: |
| 372 | contributors.append({"user": r["user"], "count": r["cnt"]}) |
| 373 | except sqlite3.OperationalError: |
| 374 | pass |
| 375 | return contributors |
| 376 | |
| 377 | # --- Timeline --- |
| 378 | |
| 379 | def get_timeline(self, limit: int = 50, offset: int = 0, event_type: str | None = None) -> list[TimelineEntry]: |
| 380 | sql = """ |
| 381 |
| --- fossil/reader.py | |
| +++ fossil/reader.py | |
| @@ -371,10 +371,41 @@ | |
| 371 | for r in rows: |
| 372 | contributors.append({"user": r["user"], "count": r["cnt"]}) |
| 373 | except sqlite3.OperationalError: |
| 374 | pass |
| 375 | return contributors |
| 376 | |
| 377 | def get_branches(self) -> list[dict]: |
| 378 | """Get all branches with their latest checkin info.""" |
| 379 | branches = [] |
| 380 | try: |
| 381 | rows = self.conn.execute( |
| 382 | """ |
| 383 | SELECT tag.tagname, max(event.mtime) as last_mtime, event.user, |
| 384 | count(tagxref.rid) as checkin_count, blob.uuid |
| 385 | FROM tag |
| 386 | JOIN tagxref ON tag.tagid = tagxref.tagid |
| 387 | JOIN event ON tagxref.rid = event.objid |
| 388 | JOIN blob ON event.objid = blob.rid |
| 389 | WHERE tag.tagname LIKE 'sym-%' AND event.type = 'ci' |
| 390 | GROUP BY tag.tagname |
| 391 | ORDER BY last_mtime DESC |
| 392 | """, |
| 393 | ).fetchall() |
| 394 | for r in rows: |
| 395 | branches.append( |
| 396 | { |
| 397 | "name": r["tagname"].replace("sym-", "", 1), |
| 398 | "last_checkin": _julian_to_datetime(r["last_mtime"]), |
| 399 | "last_user": r["user"] or "", |
| 400 | "checkin_count": r["checkin_count"], |
| 401 | "last_uuid": r["uuid"], |
| 402 | } |
| 403 | ) |
| 404 | except sqlite3.OperationalError: |
| 405 | pass |
| 406 | return branches |
| 407 | |
| 408 | # --- Timeline --- |
| 409 | |
| 410 | def get_timeline(self, limit: int = 50, offset: int = 0, event_type: str | None = None) -> list[TimelineEntry]: |
| 411 | sql = """ |
| 412 |
+1
| --- fossil/urls.py | ||
| +++ fossil/urls.py | ||
| @@ -18,8 +18,9 @@ | ||
| 18 | 18 | path("wiki/edit/<path:page_name>", views.wiki_edit, name="wiki_edit"), |
| 19 | 19 | path("tickets/create/", views.ticket_create, name="ticket_create"), |
| 20 | 20 | path("forum/", views.forum_list, name="forum"), |
| 21 | 21 | path("forum/<str:thread_uuid>/", views.forum_thread, name="forum_thread"), |
| 22 | 22 | path("user/<str:username>/", views.user_activity, name="user_activity"), |
| 23 | + path("branches/", views.branch_list, name="branches"), | |
| 23 | 24 | path("docs/", views.fossil_docs, name="docs"), |
| 24 | 25 | path("docs/<path:doc_path>", views.fossil_doc_page, name="doc_page"), |
| 25 | 26 | ] |
| 26 | 27 |
| --- fossil/urls.py | |
| +++ fossil/urls.py | |
| @@ -18,8 +18,9 @@ | |
| 18 | path("wiki/edit/<path:page_name>", views.wiki_edit, name="wiki_edit"), |
| 19 | path("tickets/create/", views.ticket_create, name="ticket_create"), |
| 20 | path("forum/", views.forum_list, name="forum"), |
| 21 | path("forum/<str:thread_uuid>/", views.forum_thread, name="forum_thread"), |
| 22 | path("user/<str:username>/", views.user_activity, name="user_activity"), |
| 23 | path("docs/", views.fossil_docs, name="docs"), |
| 24 | path("docs/<path:doc_path>", views.fossil_doc_page, name="doc_page"), |
| 25 | ] |
| 26 |
| --- fossil/urls.py | |
| +++ fossil/urls.py | |
| @@ -18,8 +18,9 @@ | |
| 18 | path("wiki/edit/<path:page_name>", views.wiki_edit, name="wiki_edit"), |
| 19 | path("tickets/create/", views.ticket_create, name="ticket_create"), |
| 20 | path("forum/", views.forum_list, name="forum"), |
| 21 | path("forum/<str:thread_uuid>/", views.forum_thread, name="forum_thread"), |
| 22 | path("user/<str:username>/", views.user_activity, name="user_activity"), |
| 23 | path("branches/", views.branch_list, name="branches"), |
| 24 | path("docs/", views.fossil_docs, name="docs"), |
| 25 | path("docs/<path:doc_path>", views.fossil_doc_page, name="doc_page"), |
| 26 | ] |
| 27 |
+36
-1
| --- fossil/views.py | ||
| +++ fossil/views.py | ||
| @@ -339,13 +339,22 @@ | ||
| 339 | 339 | for i, part in enumerate(parts): |
| 340 | 340 | file_breadcrumbs.append({"name": part, "path": "/".join(parts[: i + 1])}) |
| 341 | 341 | |
| 342 | 342 | # Split into lines for line-number display |
| 343 | 343 | lines = content.split("\n") if not is_binary else [] |
| 344 | - # Enumerate for template (1-indexed) | |
| 345 | 344 | numbered_lines = [{"num": i + 1, "text": line} for i, line in enumerate(lines)] |
| 346 | 345 | |
| 346 | + # Check if file can be rendered (wiki, markdown, html) | |
| 347 | + can_render = ext in ("wiki", "md", "markdown", "html", "htm") | |
| 348 | + view_mode = request.GET.get("mode", "source") | |
| 349 | + rendered_html = "" | |
| 350 | + if can_render and view_mode == "rendered" and not is_binary: | |
| 351 | + doc_base = "/".join(filepath.split("/")[:-1]) | |
| 352 | + if doc_base: | |
| 353 | + doc_base += "/" | |
| 354 | + rendered_html = mark_safe(_render_fossil_content(content, project_slug=slug, base_path=doc_base)) | |
| 355 | + | |
| 347 | 356 | return render( |
| 348 | 357 | request, |
| 349 | 358 | "fossil/code_file.html", |
| 350 | 359 | { |
| 351 | 360 | "project": project, |
| @@ -355,10 +364,13 @@ | ||
| 355 | 364 | "content": content, |
| 356 | 365 | "lines": numbered_lines, |
| 357 | 366 | "line_count": len(lines), |
| 358 | 367 | "is_binary": is_binary, |
| 359 | 368 | "language": ext, |
| 369 | + "can_render": can_render, | |
| 370 | + "view_mode": view_mode, | |
| 371 | + "rendered_html": rendered_html, | |
| 360 | 372 | "active_tab": "code", |
| 361 | 373 | }, |
| 362 | 374 | ) |
| 363 | 375 | |
| 364 | 376 | |
| @@ -810,10 +822,33 @@ | ||
| 810 | 822 | "activity": activity, |
| 811 | 823 | "active_tab": "timeline", |
| 812 | 824 | }, |
| 813 | 825 | ) |
| 814 | 826 | |
| 827 | + | |
| 828 | +# --- Branches --- | |
| 829 | + | |
| 830 | + | |
| 831 | +@login_required | |
| 832 | +def branch_list(request, slug): | |
| 833 | + P.PROJECT_VIEW.check(request.user) | |
| 834 | + project, fossil_repo, reader = _get_repo_and_reader(slug) | |
| 835 | + | |
| 836 | + with reader: | |
| 837 | + branches = reader.get_branches() | |
| 838 | + | |
| 839 | + return render( | |
| 840 | + request, | |
| 841 | + "fossil/branch_list.html", | |
| 842 | + { | |
| 843 | + "project": project, | |
| 844 | + "fossil_repo": fossil_repo, | |
| 845 | + "branches": branches, | |
| 846 | + "active_tab": "code", | |
| 847 | + }, | |
| 848 | + ) | |
| 849 | + | |
| 815 | 850 | |
| 816 | 851 | # --- Fossil Docs --- |
| 817 | 852 | |
| 818 | 853 | FOSSIL_SCM_SLUG = "fossil-scm" |
| 819 | 854 | |
| 820 | 855 |
| --- fossil/views.py | |
| +++ fossil/views.py | |
| @@ -339,13 +339,22 @@ | |
| 339 | for i, part in enumerate(parts): |
| 340 | file_breadcrumbs.append({"name": part, "path": "/".join(parts[: i + 1])}) |
| 341 | |
| 342 | # Split into lines for line-number display |
| 343 | lines = content.split("\n") if not is_binary else [] |
| 344 | # Enumerate for template (1-indexed) |
| 345 | numbered_lines = [{"num": i + 1, "text": line} for i, line in enumerate(lines)] |
| 346 | |
| 347 | return render( |
| 348 | request, |
| 349 | "fossil/code_file.html", |
| 350 | { |
| 351 | "project": project, |
| @@ -355,10 +364,13 @@ | |
| 355 | "content": content, |
| 356 | "lines": numbered_lines, |
| 357 | "line_count": len(lines), |
| 358 | "is_binary": is_binary, |
| 359 | "language": ext, |
| 360 | "active_tab": "code", |
| 361 | }, |
| 362 | ) |
| 363 | |
| 364 | |
| @@ -810,10 +822,33 @@ | |
| 810 | "activity": activity, |
| 811 | "active_tab": "timeline", |
| 812 | }, |
| 813 | ) |
| 814 | |
| 815 | |
| 816 | # --- Fossil Docs --- |
| 817 | |
| 818 | FOSSIL_SCM_SLUG = "fossil-scm" |
| 819 | |
| 820 |
| --- fossil/views.py | |
| +++ fossil/views.py | |
| @@ -339,13 +339,22 @@ | |
| 339 | for i, part in enumerate(parts): |
| 340 | file_breadcrumbs.append({"name": part, "path": "/".join(parts[: i + 1])}) |
| 341 | |
| 342 | # Split into lines for line-number display |
| 343 | lines = content.split("\n") if not is_binary else [] |
| 344 | numbered_lines = [{"num": i + 1, "text": line} for i, line in enumerate(lines)] |
| 345 | |
| 346 | # Check if file can be rendered (wiki, markdown, html) |
| 347 | can_render = ext in ("wiki", "md", "markdown", "html", "htm") |
| 348 | view_mode = request.GET.get("mode", "source") |
| 349 | rendered_html = "" |
| 350 | if can_render and view_mode == "rendered" and not is_binary: |
| 351 | doc_base = "/".join(filepath.split("/")[:-1]) |
| 352 | if doc_base: |
| 353 | doc_base += "/" |
| 354 | rendered_html = mark_safe(_render_fossil_content(content, project_slug=slug, base_path=doc_base)) |
| 355 | |
| 356 | return render( |
| 357 | request, |
| 358 | "fossil/code_file.html", |
| 359 | { |
| 360 | "project": project, |
| @@ -355,10 +364,13 @@ | |
| 364 | "content": content, |
| 365 | "lines": numbered_lines, |
| 366 | "line_count": len(lines), |
| 367 | "is_binary": is_binary, |
| 368 | "language": ext, |
| 369 | "can_render": can_render, |
| 370 | "view_mode": view_mode, |
| 371 | "rendered_html": rendered_html, |
| 372 | "active_tab": "code", |
| 373 | }, |
| 374 | ) |
| 375 | |
| 376 | |
| @@ -810,10 +822,33 @@ | |
| 822 | "activity": activity, |
| 823 | "active_tab": "timeline", |
| 824 | }, |
| 825 | ) |
| 826 | |
| 827 | |
| 828 | # --- Branches --- |
| 829 | |
| 830 | |
| 831 | @login_required |
| 832 | def branch_list(request, slug): |
| 833 | P.PROJECT_VIEW.check(request.user) |
| 834 | project, fossil_repo, reader = _get_repo_and_reader(slug) |
| 835 | |
| 836 | with reader: |
| 837 | branches = reader.get_branches() |
| 838 | |
| 839 | return render( |
| 840 | request, |
| 841 | "fossil/branch_list.html", |
| 842 | { |
| 843 | "project": project, |
| 844 | "fossil_repo": fossil_repo, |
| 845 | "branches": branches, |
| 846 | "active_tab": "code", |
| 847 | }, |
| 848 | ) |
| 849 | |
| 850 | |
| 851 | # --- Fossil Docs --- |
| 852 | |
| 853 | FOSSIL_SCM_SLUG = "fossil-scm" |
| 854 | |
| 855 |
| --- templates/fossil/_project_nav.html | ||
| +++ templates/fossil/_project_nav.html | ||
| @@ -4,10 +4,14 @@ | ||
| 4 | 4 | Overview |
| 5 | 5 | </a> |
| 6 | 6 | <a href="{% url 'fossil:code' slug=project.slug %}" |
| 7 | 7 | class="px-4 py-2 text-sm font-medium rounded-t-md whitespace-nowrap {% if active_tab == 'code' %}bg-gray-800 text-gray-100 border-b-2 border-brand{% else %}text-gray-400 hover:text-gray-200 hover:bg-gray-800/50{% endif %}"> |
| 8 | 8 | Code |
| 9 | + </a> | |
| 10 | + <a href="{% url 'fossil:branches' slug=project.slug %}" | |
| 11 | + class="px-4 py-2 text-sm font-medium rounded-t-md whitespace-nowrap {% if active_tab == 'branches' %}bg-gray-800 text-gray-100 border-b-2 border-brand{% else %}text-gray-400 hover:text-gray-200 hover:bg-gray-800/50{% endif %}"> | |
| 12 | + Branches | |
| 9 | 13 | </a> |
| 10 | 14 | <a href="{% url 'fossil:timeline' slug=project.slug %}" |
| 11 | 15 | class="px-4 py-2 text-sm font-medium rounded-t-md whitespace-nowrap {% if active_tab == 'timeline' %}bg-gray-800 text-gray-100 border-b-2 border-brand{% else %}text-gray-400 hover:text-gray-200 hover:bg-gray-800/50{% endif %}"> |
| 12 | 16 | Timeline |
| 13 | 17 | </a> |
| 14 | 18 | |
| 15 | 19 | ADDED templates/fossil/branch_list.html |
| --- templates/fossil/_project_nav.html | |
| +++ templates/fossil/_project_nav.html | |
| @@ -4,10 +4,14 @@ | |
| 4 | Overview |
| 5 | </a> |
| 6 | <a href="{% url 'fossil:code' slug=project.slug %}" |
| 7 | class="px-4 py-2 text-sm font-medium rounded-t-md whitespace-nowrap {% if active_tab == 'code' %}bg-gray-800 text-gray-100 border-b-2 border-brand{% else %}text-gray-400 hover:text-gray-200 hover:bg-gray-800/50{% endif %}"> |
| 8 | Code |
| 9 | </a> |
| 10 | <a href="{% url 'fossil:timeline' slug=project.slug %}" |
| 11 | class="px-4 py-2 text-sm font-medium rounded-t-md whitespace-nowrap {% if active_tab == 'timeline' %}bg-gray-800 text-gray-100 border-b-2 border-brand{% else %}text-gray-400 hover:text-gray-200 hover:bg-gray-800/50{% endif %}"> |
| 12 | Timeline |
| 13 | </a> |
| 14 | |
| 15 | DDED templates/fossil/branch_list.html |
| --- templates/fossil/_project_nav.html | |
| +++ templates/fossil/_project_nav.html | |
| @@ -4,10 +4,14 @@ | |
| 4 | Overview |
| 5 | </a> |
| 6 | <a href="{% url 'fossil:code' slug=project.slug %}" |
| 7 | class="px-4 py-2 text-sm font-medium rounded-t-md whitespace-nowrap {% if active_tab == 'code' %}bg-gray-800 text-gray-100 border-b-2 border-brand{% else %}text-gray-400 hover:text-gray-200 hover:bg-gray-800/50{% endif %}"> |
| 8 | Code |
| 9 | </a> |
| 10 | <a href="{% url 'fossil:branches' slug=project.slug %}" |
| 11 | class="px-4 py-2 text-sm font-medium rounded-t-md whitespace-nowrap {% if active_tab == 'branches' %}bg-gray-800 text-gray-100 border-b-2 border-brand{% else %}text-gray-400 hover:text-gray-200 hover:bg-gray-800/50{% endif %}"> |
| 12 | Branches |
| 13 | </a> |
| 14 | <a href="{% url 'fossil:timeline' slug=project.slug %}" |
| 15 | class="px-4 py-2 text-sm font-medium rounded-t-md whitespace-nowrap {% if active_tab == 'timeline' %}bg-gray-800 text-gray-100 border-b-2 border-brand{% else %}text-gray-400 hover:text-gray-200 hover:bg-gray-800/50{% endif %}"> |
| 16 | Timeline |
| 17 | </a> |
| 18 | |
| 19 | DDED templates/fossil/branch_list.html |
| --- a/templates/fossil/branch_list.html | ||
| +++ b/templates/fossil/branch_list.html | ||
| @@ -0,0 +1,14 @@ | ||
| 1 | +{% extends "base.html" %} | |
| 2 | +{% load fossil_filters %} | |
| 3 | +{% block title %}Branches — {{ project.name }} — Fossilrepo{% endblock %} | |
| 4 | + | |
| 5 | +{% block content %} | |
| 6 | +<h1 class="text-2xl font-bold text-gray-100 mb-2">{{ project.name }}</h1> | |
| 7 | +ble"> | |
| 8 | + <table class="min-w-full divide-y divide-gray-700"> | |
| 9 | + thead class="bg-gray-900/80"> | |
| 10 | + <tr> | |
| 11 | + <th class="px-4 py-3 text-left textext-gray-400">Branch</th> | |
| 12 | + <th class="px-4 py-3 text-left text-xs font-medium uppercase text-gray-400">Last Checkin</th> | |
| 13 | + <th class="px-4 py-3 text-left text uppercase tracking-wider text-gray-400">By</th> | |
| 14 | + <th class="px-4No branches.</tdss="px-4endblock %} |
| --- a/templates/fossil/branch_list.html | |
| +++ b/templates/fossil/branch_list.html | |
| @@ -0,0 +1,14 @@ | |
| --- a/templates/fossil/branch_list.html | |
| +++ b/templates/fossil/branch_list.html | |
| @@ -0,0 +1,14 @@ | |
| 1 | {% extends "base.html" %} |
| 2 | {% load fossil_filters %} |
| 3 | {% block title %}Branches — {{ project.name }} — Fossilrepo{% endblock %} |
| 4 | |
| 5 | {% block content %} |
| 6 | <h1 class="text-2xl font-bold text-gray-100 mb-2">{{ project.name }}</h1> |
| 7 | ble"> |
| 8 | <table class="min-w-full divide-y divide-gray-700"> |
| 9 | thead class="bg-gray-900/80"> |
| 10 | <tr> |
| 11 | <th class="px-4 py-3 text-left textext-gray-400">Branch</th> |
| 12 | <th class="px-4 py-3 text-left text-xs font-medium uppercase text-gray-400">Last Checkin</th> |
| 13 | <th class="px-4 py-3 text-left text uppercase tracking-wider text-gray-400">By</th> |
| 14 | <th class="px-4No branches.</tdss="px-4endblock %} |
+17
-3
| --- templates/fossil/code_file.html | ||
| +++ templates/fossil/code_file.html | ||
| @@ -55,17 +55,31 @@ | ||
| 55 | 55 | {% else %} |
| 56 | 56 | <a href="{% url 'fossil:code_dir' slug=project.slug dirpath=crumb.path %}" class="text-brand-light hover:text-brand">{{ crumb.name }}</a> |
| 57 | 57 | {% endif %} |
| 58 | 58 | {% endfor %} |
| 59 | 59 | </div> |
| 60 | - {% if not is_binary %} | |
| 61 | - <span class="text-xs text-gray-500">{{ line_count }} line{{ line_count|pluralize }}</span> | |
| 62 | - {% endif %} | |
| 60 | + <div class="flex items-center gap-3"> | |
| 61 | + {% if can_render %} | |
| 62 | + <div class="flex items-center gap-1 text-xs"> | |
| 63 | + <a href="?mode=source" class="px-2 py-1 rounded {% if view_mode == 'source' %}bg-brand text-white{% else %}text-gray-500 hover:text-white{% endif %}">Source</a> | |
| 64 | + <a href="?mode=rendered" class="px-2 py-1 rounded {% if view_mode == 'rendered' %}bg-brand text-white{% else %}text-gray-500 hover:text-white{% endif %}">Rendered</a> | |
| 65 | + </div> | |
| 66 | + {% endif %} | |
| 67 | + {% if not is_binary %} | |
| 68 | + <span class="text-xs text-gray-500">{{ line_count }} line{{ line_count|pluralize }}</span> | |
| 69 | + {% endif %} | |
| 70 | + </div> | |
| 63 | 71 | </div> |
| 64 | 72 | <div class="overflow-x-auto"> |
| 65 | 73 | {% if is_binary %} |
| 66 | 74 | <p class="p-4 text-sm text-gray-500">{{ content }}</p> |
| 75 | + {% elif view_mode == "rendered" and rendered_html %} | |
| 76 | + <div class="px-6 py-6"> | |
| 77 | + <div class="prose prose-invert prose-gray max-w-none"> | |
| 78 | + {{ rendered_html }} | |
| 79 | + </div> | |
| 80 | + </div> | |
| 67 | 81 | {% else %} |
| 68 | 82 | <table class="code-table"> |
| 69 | 83 | <tbody> |
| 70 | 84 | {% for line in lines %} |
| 71 | 85 | <tr class="line-row" id="L{{ line.num }}"> |
| 72 | 86 |
| --- templates/fossil/code_file.html | |
| +++ templates/fossil/code_file.html | |
| @@ -55,17 +55,31 @@ | |
| 55 | {% else %} |
| 56 | <a href="{% url 'fossil:code_dir' slug=project.slug dirpath=crumb.path %}" class="text-brand-light hover:text-brand">{{ crumb.name }}</a> |
| 57 | {% endif %} |
| 58 | {% endfor %} |
| 59 | </div> |
| 60 | {% if not is_binary %} |
| 61 | <span class="text-xs text-gray-500">{{ line_count }} line{{ line_count|pluralize }}</span> |
| 62 | {% endif %} |
| 63 | </div> |
| 64 | <div class="overflow-x-auto"> |
| 65 | {% if is_binary %} |
| 66 | <p class="p-4 text-sm text-gray-500">{{ content }}</p> |
| 67 | {% else %} |
| 68 | <table class="code-table"> |
| 69 | <tbody> |
| 70 | {% for line in lines %} |
| 71 | <tr class="line-row" id="L{{ line.num }}"> |
| 72 |
| --- templates/fossil/code_file.html | |
| +++ templates/fossil/code_file.html | |
| @@ -55,17 +55,31 @@ | |
| 55 | {% else %} |
| 56 | <a href="{% url 'fossil:code_dir' slug=project.slug dirpath=crumb.path %}" class="text-brand-light hover:text-brand">{{ crumb.name }}</a> |
| 57 | {% endif %} |
| 58 | {% endfor %} |
| 59 | </div> |
| 60 | <div class="flex items-center gap-3"> |
| 61 | {% if can_render %} |
| 62 | <div class="flex items-center gap-1 text-xs"> |
| 63 | <a href="?mode=source" class="px-2 py-1 rounded {% if view_mode == 'source' %}bg-brand text-white{% else %}text-gray-500 hover:text-white{% endif %}">Source</a> |
| 64 | <a href="?mode=rendered" class="px-2 py-1 rounded {% if view_mode == 'rendered' %}bg-brand text-white{% else %}text-gray-500 hover:text-white{% endif %}">Rendered</a> |
| 65 | </div> |
| 66 | {% endif %} |
| 67 | {% if not is_binary %} |
| 68 | <span class="text-xs text-gray-500">{{ line_count }} line{{ line_count|pluralize }}</span> |
| 69 | {% endif %} |
| 70 | </div> |
| 71 | </div> |
| 72 | <div class="overflow-x-auto"> |
| 73 | {% if is_binary %} |
| 74 | <p class="p-4 text-sm text-gray-500">{{ content }}</p> |
| 75 | {% elif view_mode == "rendered" and rendered_html %} |
| 76 | <div class="px-6 py-6"> |
| 77 | <div class="prose prose-invert prose-gray max-w-none"> |
| 78 | {{ rendered_html }} |
| 79 | </div> |
| 80 | </div> |
| 81 | {% else %} |
| 82 | <table class="code-table"> |
| 83 | <tbody> |
| 84 | {% for line in lines %} |
| 85 | <tr class="line-row" id="L{{ line.num }}"> |
| 86 |