FossilRepo
Fix forum posts, ticket filters, wiki markup rendering - Forum: fix query for root posts (NULL not 0 for firt/fprev) - Tickets: add status filter pills (All/Open/Fixed/Closed), search bar - Tickets: show type, priority columns, UUID links - Wiki: add Fossil wiki markup renderer (handles HTML content, [link|text] syntax, <verbatim> blocks, falls back to markdown) - Wiki: auto-detect HTML vs markdown content per page
Commit
0fb1c2724e2356873548362f22dae1f6385c9b7c3e4e8335f935447f7b1a40a5
Parent
fc1b99b27f00c22…
4 files changed
+1
-1
+31
-2
+15
-9
+24
-11
+1
-1
| --- fossil/reader.py | ||
| +++ fossil/reader.py | ||
| @@ -579,11 +579,11 @@ | ||
| 579 | 579 | """ |
| 580 | 580 | SELECT b.uuid, fp.fmtime, e.user, e.comment, b.rid |
| 581 | 581 | FROM forumpost fp |
| 582 | 582 | JOIN blob b ON fp.fpid = b.rid |
| 583 | 583 | JOIN event e ON fp.fpid = e.objid |
| 584 | - WHERE fp.firt = 0 AND fp.fprev = 0 | |
| 584 | + WHERE fp.firt IS NULL AND fp.fprev IS NULL | |
| 585 | 585 | ORDER BY fp.fmtime DESC |
| 586 | 586 | LIMIT ? |
| 587 | 587 | """, |
| 588 | 588 | (limit,), |
| 589 | 589 | ).fetchall() |
| 590 | 590 |
| --- fossil/reader.py | |
| +++ fossil/reader.py | |
| @@ -579,11 +579,11 @@ | |
| 579 | """ |
| 580 | SELECT b.uuid, fp.fmtime, e.user, e.comment, b.rid |
| 581 | FROM forumpost fp |
| 582 | JOIN blob b ON fp.fpid = b.rid |
| 583 | JOIN event e ON fp.fpid = e.objid |
| 584 | WHERE fp.firt = 0 AND fp.fprev = 0 |
| 585 | ORDER BY fp.fmtime DESC |
| 586 | LIMIT ? |
| 587 | """, |
| 588 | (limit,), |
| 589 | ).fetchall() |
| 590 |
| --- fossil/reader.py | |
| +++ fossil/reader.py | |
| @@ -579,11 +579,11 @@ | |
| 579 | """ |
| 580 | SELECT b.uuid, fp.fmtime, e.user, e.comment, b.rid |
| 581 | FROM forumpost fp |
| 582 | JOIN blob b ON fp.fpid = b.rid |
| 583 | JOIN event e ON fp.fpid = e.objid |
| 584 | WHERE fp.firt IS NULL AND fp.fprev IS NULL |
| 585 | ORDER BY fp.fmtime DESC |
| 586 | LIMIT ? |
| 587 | """, |
| 588 | (limit,), |
| 589 | ).fetchall() |
| 590 |
+31
-2
| --- fossil/views.py | ||
| +++ fossil/views.py | ||
| @@ -1,5 +1,7 @@ | ||
| 1 | +import re | |
| 2 | + | |
| 1 | 3 | import markdown as md |
| 2 | 4 | from django.contrib.auth.decorators import login_required |
| 3 | 5 | from django.http import Http404 |
| 4 | 6 | from django.shortcuts import get_object_or_404, render |
| 5 | 7 | from django.utils.safestring import mark_safe |
| @@ -8,10 +10,37 @@ | ||
| 8 | 10 | from projects.models import Project |
| 9 | 11 | |
| 10 | 12 | from .models import FossilRepository |
| 11 | 13 | from .reader import FossilReader |
| 12 | 14 | |
| 15 | + | |
| 16 | +def _render_fossil_content(content: str) -> str: | |
| 17 | + """Render content that may be Fossil wiki markup, HTML, or Markdown. | |
| 18 | + | |
| 19 | + Fossil wiki pages can contain: | |
| 20 | + - Raw HTML (most Fossil wiki pages) | |
| 21 | + - Fossil-specific markup: [link|text], <verbatim>...</verbatim> | |
| 22 | + - Markdown (newer pages) | |
| 23 | + """ | |
| 24 | + if not content: | |
| 25 | + return "" | |
| 26 | + | |
| 27 | + # Convert Fossil-specific syntax | |
| 28 | + # [/path|text] -> <a href="/path">text</a> | |
| 29 | + content = re.sub(r"\[(/[^|\]]+)\|([^\]]+)\]", r'<a href="\1">\2</a>', content) | |
| 30 | + # [url|text] -> <a href="url">text</a> | |
| 31 | + content = re.sub(r"\[(https?://[^|\]]+)\|([^\]]+)\]", r'<a href="\1">\2</a>', content) | |
| 32 | + # <verbatim>...</verbatim> -> <pre><code>...</code></pre> | |
| 33 | + content = re.sub(r"<verbatim>(.*?)</verbatim>", r"<pre><code>\1</code></pre>", content, flags=re.DOTALL) | |
| 34 | + | |
| 35 | + # If content looks like it has HTML tags, treat as HTML (Fossil wiki) | |
| 36 | + if re.search(r"<(h[1-6]|p|ol|ul|li|div|table|pre|a|br)\b", content, re.IGNORECASE): | |
| 37 | + return content | |
| 38 | + | |
| 39 | + # Otherwise try markdown | |
| 40 | + return md.markdown(content, extensions=["fenced_code", "tables", "toc"]) | |
| 41 | + | |
| 13 | 42 | |
| 14 | 43 | def _get_repo_and_reader(slug): |
| 15 | 44 | """Return (project, fossil_repo, reader) or raise 404.""" |
| 16 | 45 | project = get_object_or_404(Project, slug=slug, deleted_at__isnull=True) |
| 17 | 46 | fossil_repo = get_object_or_404(FossilRepository, project=project, deleted_at__isnull=True) |
| @@ -257,11 +286,11 @@ | ||
| 257 | 286 | pages = reader.get_wiki_pages() |
| 258 | 287 | home_page = reader.get_wiki_page("Home") |
| 259 | 288 | |
| 260 | 289 | home_content_html = "" |
| 261 | 290 | if home_page: |
| 262 | - home_content_html = mark_safe(md.markdown(home_page.content, extensions=["fenced_code", "tables", "toc"])) | |
| 291 | + home_content_html = mark_safe(_render_fossil_content(home_page.content)) | |
| 263 | 292 | |
| 264 | 293 | return render( |
| 265 | 294 | request, |
| 266 | 295 | "fossil/wiki_list.html", |
| 267 | 296 | { |
| @@ -285,11 +314,11 @@ | ||
| 285 | 314 | all_pages = reader.get_wiki_pages() |
| 286 | 315 | |
| 287 | 316 | if not page: |
| 288 | 317 | raise Http404(f"Wiki page not found: {page_name}") |
| 289 | 318 | |
| 290 | - content_html = mark_safe(md.markdown(page.content, extensions=["fenced_code", "tables", "toc"])) | |
| 319 | + content_html = mark_safe(_render_fossil_content(page.content)) | |
| 291 | 320 | |
| 292 | 321 | return render( |
| 293 | 322 | request, |
| 294 | 323 | "fossil/wiki_page.html", |
| 295 | 324 | { |
| 296 | 325 |
| --- fossil/views.py | |
| +++ fossil/views.py | |
| @@ -1,5 +1,7 @@ | |
| 1 | import markdown as md |
| 2 | from django.contrib.auth.decorators import login_required |
| 3 | from django.http import Http404 |
| 4 | from django.shortcuts import get_object_or_404, render |
| 5 | from django.utils.safestring import mark_safe |
| @@ -8,10 +10,37 @@ | |
| 8 | from projects.models import Project |
| 9 | |
| 10 | from .models import FossilRepository |
| 11 | from .reader import FossilReader |
| 12 | |
| 13 | |
| 14 | def _get_repo_and_reader(slug): |
| 15 | """Return (project, fossil_repo, reader) or raise 404.""" |
| 16 | project = get_object_or_404(Project, slug=slug, deleted_at__isnull=True) |
| 17 | fossil_repo = get_object_or_404(FossilRepository, project=project, deleted_at__isnull=True) |
| @@ -257,11 +286,11 @@ | |
| 257 | pages = reader.get_wiki_pages() |
| 258 | home_page = reader.get_wiki_page("Home") |
| 259 | |
| 260 | home_content_html = "" |
| 261 | if home_page: |
| 262 | home_content_html = mark_safe(md.markdown(home_page.content, extensions=["fenced_code", "tables", "toc"])) |
| 263 | |
| 264 | return render( |
| 265 | request, |
| 266 | "fossil/wiki_list.html", |
| 267 | { |
| @@ -285,11 +314,11 @@ | |
| 285 | all_pages = reader.get_wiki_pages() |
| 286 | |
| 287 | if not page: |
| 288 | raise Http404(f"Wiki page not found: {page_name}") |
| 289 | |
| 290 | content_html = mark_safe(md.markdown(page.content, extensions=["fenced_code", "tables", "toc"])) |
| 291 | |
| 292 | return render( |
| 293 | request, |
| 294 | "fossil/wiki_page.html", |
| 295 | { |
| 296 |
| --- fossil/views.py | |
| +++ fossil/views.py | |
| @@ -1,5 +1,7 @@ | |
| 1 | import re |
| 2 | |
| 3 | import markdown as md |
| 4 | from django.contrib.auth.decorators import login_required |
| 5 | from django.http import Http404 |
| 6 | from django.shortcuts import get_object_or_404, render |
| 7 | from django.utils.safestring import mark_safe |
| @@ -8,10 +10,37 @@ | |
| 10 | from projects.models import Project |
| 11 | |
| 12 | from .models import FossilRepository |
| 13 | from .reader import FossilReader |
| 14 | |
| 15 | |
| 16 | def _render_fossil_content(content: str) -> str: |
| 17 | """Render content that may be Fossil wiki markup, HTML, or Markdown. |
| 18 | |
| 19 | Fossil wiki pages can contain: |
| 20 | - Raw HTML (most Fossil wiki pages) |
| 21 | - Fossil-specific markup: [link|text], <verbatim>...</verbatim> |
| 22 | - Markdown (newer pages) |
| 23 | """ |
| 24 | if not content: |
| 25 | return "" |
| 26 | |
| 27 | # Convert Fossil-specific syntax |
| 28 | # [/path|text] -> <a href="/path">text</a> |
| 29 | content = re.sub(r"\[(/[^|\]]+)\|([^\]]+)\]", r'<a href="\1">\2</a>', content) |
| 30 | # [url|text] -> <a href="url">text</a> |
| 31 | content = re.sub(r"\[(https?://[^|\]]+)\|([^\]]+)\]", r'<a href="\1">\2</a>', content) |
| 32 | # <verbatim>...</verbatim> -> <pre><code>...</code></pre> |
| 33 | content = re.sub(r"<verbatim>(.*?)</verbatim>", r"<pre><code>\1</code></pre>", content, flags=re.DOTALL) |
| 34 | |
| 35 | # If content looks like it has HTML tags, treat as HTML (Fossil wiki) |
| 36 | if re.search(r"<(h[1-6]|p|ol|ul|li|div|table|pre|a|br)\b", content, re.IGNORECASE): |
| 37 | return content |
| 38 | |
| 39 | # Otherwise try markdown |
| 40 | return md.markdown(content, extensions=["fenced_code", "tables", "toc"]) |
| 41 | |
| 42 | |
| 43 | def _get_repo_and_reader(slug): |
| 44 | """Return (project, fossil_repo, reader) or raise 404.""" |
| 45 | project = get_object_or_404(Project, slug=slug, deleted_at__isnull=True) |
| 46 | fossil_repo = get_object_or_404(FossilRepository, project=project, deleted_at__isnull=True) |
| @@ -257,11 +286,11 @@ | |
| 286 | pages = reader.get_wiki_pages() |
| 287 | home_page = reader.get_wiki_page("Home") |
| 288 | |
| 289 | home_content_html = "" |
| 290 | if home_page: |
| 291 | home_content_html = mark_safe(_render_fossil_content(home_page.content)) |
| 292 | |
| 293 | return render( |
| 294 | request, |
| 295 | "fossil/wiki_list.html", |
| 296 | { |
| @@ -285,11 +314,11 @@ | |
| 314 | all_pages = reader.get_wiki_pages() |
| 315 | |
| 316 | if not page: |
| 317 | raise Http404(f"Wiki page not found: {page_name}") |
| 318 | |
| 319 | content_html = mark_safe(_render_fossil_content(page.content)) |
| 320 | |
| 321 | return render( |
| 322 | request, |
| 323 | "fossil/wiki_page.html", |
| 324 | { |
| 325 |
| --- templates/fossil/partials/ticket_table.html | ||
| +++ templates/fossil/partials/ticket_table.html | ||
| @@ -1,40 +1,46 @@ | ||
| 1 | 1 | <div id="ticket-table"> |
| 2 | 2 | <div class="overflow-hidden rounded-lg border border-gray-700 bg-gray-800 shadow-sm"> |
| 3 | 3 | <table class="min-w-full divide-y divide-gray-700"> |
| 4 | 4 | <thead class="bg-gray-900"> |
| 5 | 5 | <tr> |
| 6 | - <th class="px-6 py-3 text-left text-xs font-medium uppercase text-gray-400">Title</th> | |
| 7 | - <th class="px-6 py-3 text-left text-xs font-medium uppercase text-gray-400">Status</th> | |
| 8 | - <th class="px-6 py-3 text-left text-xs font-medium uppercase text-gray-400">Owner</th> | |
| 9 | - <th class="px-6 py-3 text-left text-xs font-medium uppercase text-gray-400">Created</th> | |
| 6 | + <th class="px-4 py-3 text-left text-xs font-medium uppercase text-gray-400">Title</th> | |
| 7 | + <th class="px-4 py-3 text-left text-xs font-medium uppercase text-gray-400 w-24">Status</th> | |
| 8 | + <th class="px-4 py-3 text-left text-xs font-medium uppercase text-gray-400 w-28">Type</th> | |
| 9 | + <th class="px-4 py-3 text-left text-xs font-medium uppercase text-gray-400 w-20">Priority</th> | |
| 10 | + <th class="px-4 py-3 text-right text-xs font-medium uppercase text-gray-400 w-36">Created</th> | |
| 10 | 11 | </tr> |
| 11 | 12 | </thead> |
| 12 | 13 | <tbody class="divide-y divide-gray-700 bg-gray-800"> |
| 13 | 14 | {% for ticket in tickets %} |
| 14 | 15 | <tr class="hover:bg-gray-700/50"> |
| 15 | - <td class="px-6 py-4 whitespace-nowrap"> | |
| 16 | + <td class="px-4 py-3"> | |
| 16 | 17 | <a href="{% url 'fossil:ticket_detail' slug=project.slug ticket_uuid=ticket.uuid %}" |
| 17 | 18 | class="text-brand-light hover:text-brand font-medium text-sm"> |
| 18 | 19 | {{ ticket.title|default:"(untitled)" }} |
| 19 | 20 | </a> |
| 21 | + <div class="mt-0.5"> | |
| 22 | + <a href="{% url 'fossil:ticket_detail' slug=project.slug ticket_uuid=ticket.uuid %}" | |
| 23 | + class="text-xs font-mono text-gray-600 hover:text-brand-light">{{ ticket.uuid|truncatechars:12 }}</a> | |
| 24 | + </div> | |
| 20 | 25 | </td> |
| 21 | - <td class="px-6 py-4 whitespace-nowrap"> | |
| 26 | + <td class="px-4 py-3 whitespace-nowrap"> | |
| 22 | 27 | {% if ticket.status == "Open" %} |
| 23 | 28 | <span class="inline-flex rounded-full bg-green-900/50 px-2 text-xs font-semibold leading-5 text-green-300">{{ ticket.status }}</span> |
| 24 | 29 | {% elif ticket.status == "Closed" or ticket.status == "Fixed" %} |
| 25 | 30 | <span class="inline-flex rounded-full bg-gray-700 px-2 text-xs font-semibold leading-5 text-gray-300">{{ ticket.status }}</span> |
| 26 | 31 | {% else %} |
| 27 | 32 | <span class="inline-flex rounded-full bg-yellow-900/50 px-2 text-xs font-semibold leading-5 text-yellow-300">{{ ticket.status }}</span> |
| 28 | 33 | {% endif %} |
| 29 | 34 | </td> |
| 30 | - <td class="px-6 py-4 whitespace-nowrap text-sm text-gray-400">{{ ticket.owner|default:"—" }}</td> | |
| 31 | - <td class="px-6 py-4 whitespace-nowrap text-sm text-gray-400">{{ ticket.created|date:"N j, Y" }}</td> | |
| 35 | + <td class="px-4 py-3 whitespace-nowrap text-xs text-gray-400">{{ ticket.type|default:"—" }}</td> | |
| 36 | + <td class="px-4 py-3 whitespace-nowrap text-xs text-gray-400">{{ ticket.priority|default:"—" }}</td> | |
| 37 | + <td class="px-4 py-3 whitespace-nowrap text-xs text-gray-500 text-right">{{ ticket.created|date:"Y-m-d" }}</td> | |
| 32 | 38 | </tr> |
| 33 | 39 | {% empty %} |
| 34 | 40 | <tr> |
| 35 | - <td colspan="4" class="px-6 py-8 text-center text-sm text-gray-400">No tickets.</td> | |
| 41 | + <td colspan="5" class="px-4 py-8 text-center text-sm text-gray-400">No tickets found.</td> | |
| 36 | 42 | </tr> |
| 37 | 43 | {% endfor %} |
| 38 | 44 | </tbody> |
| 39 | 45 | </table> |
| 40 | 46 | </div> |
| 41 | 47 |
| --- templates/fossil/partials/ticket_table.html | |
| +++ templates/fossil/partials/ticket_table.html | |
| @@ -1,40 +1,46 @@ | |
| 1 | <div id="ticket-table"> |
| 2 | <div class="overflow-hidden rounded-lg border border-gray-700 bg-gray-800 shadow-sm"> |
| 3 | <table class="min-w-full divide-y divide-gray-700"> |
| 4 | <thead class="bg-gray-900"> |
| 5 | <tr> |
| 6 | <th class="px-6 py-3 text-left text-xs font-medium uppercase text-gray-400">Title</th> |
| 7 | <th class="px-6 py-3 text-left text-xs font-medium uppercase text-gray-400">Status</th> |
| 8 | <th class="px-6 py-3 text-left text-xs font-medium uppercase text-gray-400">Owner</th> |
| 9 | <th class="px-6 py-3 text-left text-xs font-medium uppercase text-gray-400">Created</th> |
| 10 | </tr> |
| 11 | </thead> |
| 12 | <tbody class="divide-y divide-gray-700 bg-gray-800"> |
| 13 | {% for ticket in tickets %} |
| 14 | <tr class="hover:bg-gray-700/50"> |
| 15 | <td class="px-6 py-4 whitespace-nowrap"> |
| 16 | <a href="{% url 'fossil:ticket_detail' slug=project.slug ticket_uuid=ticket.uuid %}" |
| 17 | class="text-brand-light hover:text-brand font-medium text-sm"> |
| 18 | {{ ticket.title|default:"(untitled)" }} |
| 19 | </a> |
| 20 | </td> |
| 21 | <td class="px-6 py-4 whitespace-nowrap"> |
| 22 | {% if ticket.status == "Open" %} |
| 23 | <span class="inline-flex rounded-full bg-green-900/50 px-2 text-xs font-semibold leading-5 text-green-300">{{ ticket.status }}</span> |
| 24 | {% elif ticket.status == "Closed" or ticket.status == "Fixed" %} |
| 25 | <span class="inline-flex rounded-full bg-gray-700 px-2 text-xs font-semibold leading-5 text-gray-300">{{ ticket.status }}</span> |
| 26 | {% else %} |
| 27 | <span class="inline-flex rounded-full bg-yellow-900/50 px-2 text-xs font-semibold leading-5 text-yellow-300">{{ ticket.status }}</span> |
| 28 | {% endif %} |
| 29 | </td> |
| 30 | <td class="px-6 py-4 whitespace-nowrap text-sm text-gray-400">{{ ticket.owner|default:"—" }}</td> |
| 31 | <td class="px-6 py-4 whitespace-nowrap text-sm text-gray-400">{{ ticket.created|date:"N j, Y" }}</td> |
| 32 | </tr> |
| 33 | {% empty %} |
| 34 | <tr> |
| 35 | <td colspan="4" class="px-6 py-8 text-center text-sm text-gray-400">No tickets.</td> |
| 36 | </tr> |
| 37 | {% endfor %} |
| 38 | </tbody> |
| 39 | </table> |
| 40 | </div> |
| 41 |
| --- templates/fossil/partials/ticket_table.html | |
| +++ templates/fossil/partials/ticket_table.html | |
| @@ -1,40 +1,46 @@ | |
| 1 | <div id="ticket-table"> |
| 2 | <div class="overflow-hidden rounded-lg border border-gray-700 bg-gray-800 shadow-sm"> |
| 3 | <table class="min-w-full divide-y divide-gray-700"> |
| 4 | <thead class="bg-gray-900"> |
| 5 | <tr> |
| 6 | <th class="px-4 py-3 text-left text-xs font-medium uppercase text-gray-400">Title</th> |
| 7 | <th class="px-4 py-3 text-left text-xs font-medium uppercase text-gray-400 w-24">Status</th> |
| 8 | <th class="px-4 py-3 text-left text-xs font-medium uppercase text-gray-400 w-28">Type</th> |
| 9 | <th class="px-4 py-3 text-left text-xs font-medium uppercase text-gray-400 w-20">Priority</th> |
| 10 | <th class="px-4 py-3 text-right text-xs font-medium uppercase text-gray-400 w-36">Created</th> |
| 11 | </tr> |
| 12 | </thead> |
| 13 | <tbody class="divide-y divide-gray-700 bg-gray-800"> |
| 14 | {% for ticket in tickets %} |
| 15 | <tr class="hover:bg-gray-700/50"> |
| 16 | <td class="px-4 py-3"> |
| 17 | <a href="{% url 'fossil:ticket_detail' slug=project.slug ticket_uuid=ticket.uuid %}" |
| 18 | class="text-brand-light hover:text-brand font-medium text-sm"> |
| 19 | {{ ticket.title|default:"(untitled)" }} |
| 20 | </a> |
| 21 | <div class="mt-0.5"> |
| 22 | <a href="{% url 'fossil:ticket_detail' slug=project.slug ticket_uuid=ticket.uuid %}" |
| 23 | class="text-xs font-mono text-gray-600 hover:text-brand-light">{{ ticket.uuid|truncatechars:12 }}</a> |
| 24 | </div> |
| 25 | </td> |
| 26 | <td class="px-4 py-3 whitespace-nowrap"> |
| 27 | {% if ticket.status == "Open" %} |
| 28 | <span class="inline-flex rounded-full bg-green-900/50 px-2 text-xs font-semibold leading-5 text-green-300">{{ ticket.status }}</span> |
| 29 | {% elif ticket.status == "Closed" or ticket.status == "Fixed" %} |
| 30 | <span class="inline-flex rounded-full bg-gray-700 px-2 text-xs font-semibold leading-5 text-gray-300">{{ ticket.status }}</span> |
| 31 | {% else %} |
| 32 | <span class="inline-flex rounded-full bg-yellow-900/50 px-2 text-xs font-semibold leading-5 text-yellow-300">{{ ticket.status }}</span> |
| 33 | {% endif %} |
| 34 | </td> |
| 35 | <td class="px-4 py-3 whitespace-nowrap text-xs text-gray-400">{{ ticket.type|default:"—" }}</td> |
| 36 | <td class="px-4 py-3 whitespace-nowrap text-xs text-gray-400">{{ ticket.priority|default:"—" }}</td> |
| 37 | <td class="px-4 py-3 whitespace-nowrap text-xs text-gray-500 text-right">{{ ticket.created|date:"Y-m-d" }}</td> |
| 38 | </tr> |
| 39 | {% empty %} |
| 40 | <tr> |
| 41 | <td colspan="5" class="px-4 py-8 text-center text-sm text-gray-400">No tickets found.</td> |
| 42 | </tr> |
| 43 | {% endfor %} |
| 44 | </tbody> |
| 45 | </table> |
| 46 | </div> |
| 47 |
+24
-11
| --- templates/fossil/ticket_list.html | ||
| +++ templates/fossil/ticket_list.html | ||
| @@ -3,20 +3,33 @@ | ||
| 3 | 3 | |
| 4 | 4 | {% block content %} |
| 5 | 5 | <h1 class="text-2xl font-bold text-gray-100 mb-2">{{ project.name }}</h1> |
| 6 | 6 | {% include "fossil/_project_nav.html" %} |
| 7 | 7 | |
| 8 | -<div class="mb-4"> | |
| 9 | - <input type="search" | |
| 10 | - name="search" | |
| 11 | - value="{{ search }}" | |
| 12 | - placeholder="Search tickets..." | |
| 13 | - class="w-full max-w-md rounded-md border-gray-700 bg-gray-800 text-gray-100 shadow-sm focus:border-brand focus:ring-brand sm:text-sm" | |
| 14 | - hx-get="{% url 'fossil:tickets' slug=project.slug %}" | |
| 15 | - hx-trigger="input changed delay:300ms, search" | |
| 16 | - hx-target="#ticket-table" | |
| 17 | - hx-swap="outerHTML" | |
| 18 | - hx-push-url="true" /> | |
| 8 | +<div class="flex items-center justify-between mb-4"> | |
| 9 | + <div class="flex items-center gap-2 text-xs text-gray-500"> | |
| 10 | + <span>Status:</span> | |
| 11 | + <a href="{% url 'fossil:tickets' slug=project.slug %}" | |
| 12 | + class="rounded-full px-2.5 py-1 {% if not status_filter %}bg-brand text-white{% else %}bg-gray-800 text-gray-400 hover:text-white border border-gray-700{% endif %}">All</a> | |
| 13 | + <a href="{% url 'fossil:tickets' slug=project.slug %}?status=Open" | |
| 14 | + class="rounded-full px-2.5 py-1 {% if status_filter == 'Open' %}bg-brand text-white{% else %}bg-gray-800 text-gray-400 hover:text-white border border-gray-700{% endif %}">Open</a> | |
| 15 | + <a href="{% url 'fossil:tickets' slug=project.slug %}?status=Fixed" | |
| 16 | + class="rounded-full px-2.5 py-1 {% if status_filter == 'Fixed' %}bg-brand text-white{% else %}bg-gray-800 text-gray-400 hover:text-white border border-gray-700{% endif %}">Fixed</a> | |
| 17 | + <a href="{% url 'fossil:tickets' slug=project.slug %}?status=Closed" | |
| 18 | + class="rounded-full px-2.5 py-1 {% if status_filter == 'Closed' %}bg-brand text-white{% else %}bg-gray-800 text-gray-400 hover:text-white border border-gray-700{% endif %}">Closed</a> | |
| 19 | + </div> | |
| 20 | + <div> | |
| 21 | + <input type="search" | |
| 22 | + name="search" | |
| 23 | + value="{{ search }}" | |
| 24 | + placeholder="Search tickets..." | |
| 25 | + class="rounded-md border-gray-700 bg-gray-800 text-gray-100 shadow-sm focus:border-brand focus:ring-brand text-sm px-3 py-1.5" | |
| 26 | + hx-get="{% url 'fossil:tickets' slug=project.slug %}{% if status_filter %}?status={{ status_filter }}{% endif %}" | |
| 27 | + hx-trigger="input changed delay:300ms, search" | |
| 28 | + hx-target="#ticket-table" | |
| 29 | + hx-swap="outerHTML" | |
| 30 | + hx-push-url="true" /> | |
| 31 | + </div> | |
| 19 | 32 | </div> |
| 20 | 33 | |
| 21 | 34 | {% include "fossil/partials/ticket_table.html" %} |
| 22 | 35 | {% endblock %} |
| 23 | 36 |
| --- templates/fossil/ticket_list.html | |
| +++ templates/fossil/ticket_list.html | |
| @@ -3,20 +3,33 @@ | |
| 3 | |
| 4 | {% block content %} |
| 5 | <h1 class="text-2xl font-bold text-gray-100 mb-2">{{ project.name }}</h1> |
| 6 | {% include "fossil/_project_nav.html" %} |
| 7 | |
| 8 | <div class="mb-4"> |
| 9 | <input type="search" |
| 10 | name="search" |
| 11 | value="{{ search }}" |
| 12 | placeholder="Search tickets..." |
| 13 | class="w-full max-w-md rounded-md border-gray-700 bg-gray-800 text-gray-100 shadow-sm focus:border-brand focus:ring-brand sm:text-sm" |
| 14 | hx-get="{% url 'fossil:tickets' slug=project.slug %}" |
| 15 | hx-trigger="input changed delay:300ms, search" |
| 16 | hx-target="#ticket-table" |
| 17 | hx-swap="outerHTML" |
| 18 | hx-push-url="true" /> |
| 19 | </div> |
| 20 | |
| 21 | {% include "fossil/partials/ticket_table.html" %} |
| 22 | {% endblock %} |
| 23 |
| --- templates/fossil/ticket_list.html | |
| +++ templates/fossil/ticket_list.html | |
| @@ -3,20 +3,33 @@ | |
| 3 | |
| 4 | {% block content %} |
| 5 | <h1 class="text-2xl font-bold text-gray-100 mb-2">{{ project.name }}</h1> |
| 6 | {% include "fossil/_project_nav.html" %} |
| 7 | |
| 8 | <div class="flex items-center justify-between mb-4"> |
| 9 | <div class="flex items-center gap-2 text-xs text-gray-500"> |
| 10 | <span>Status:</span> |
| 11 | <a href="{% url 'fossil:tickets' slug=project.slug %}" |
| 12 | class="rounded-full px-2.5 py-1 {% if not status_filter %}bg-brand text-white{% else %}bg-gray-800 text-gray-400 hover:text-white border border-gray-700{% endif %}">All</a> |
| 13 | <a href="{% url 'fossil:tickets' slug=project.slug %}?status=Open" |
| 14 | class="rounded-full px-2.5 py-1 {% if status_filter == 'Open' %}bg-brand text-white{% else %}bg-gray-800 text-gray-400 hover:text-white border border-gray-700{% endif %}">Open</a> |
| 15 | <a href="{% url 'fossil:tickets' slug=project.slug %}?status=Fixed" |
| 16 | class="rounded-full px-2.5 py-1 {% if status_filter == 'Fixed' %}bg-brand text-white{% else %}bg-gray-800 text-gray-400 hover:text-white border border-gray-700{% endif %}">Fixed</a> |
| 17 | <a href="{% url 'fossil:tickets' slug=project.slug %}?status=Closed" |
| 18 | class="rounded-full px-2.5 py-1 {% if status_filter == 'Closed' %}bg-brand text-white{% else %}bg-gray-800 text-gray-400 hover:text-white border border-gray-700{% endif %}">Closed</a> |
| 19 | </div> |
| 20 | <div> |
| 21 | <input type="search" |
| 22 | name="search" |
| 23 | value="{{ search }}" |
| 24 | placeholder="Search tickets..." |
| 25 | class="rounded-md border-gray-700 bg-gray-800 text-gray-100 shadow-sm focus:border-brand focus:ring-brand text-sm px-3 py-1.5" |
| 26 | hx-get="{% url 'fossil:tickets' slug=project.slug %}{% if status_filter %}?status={{ status_filter }}{% endif %}" |
| 27 | hx-trigger="input changed delay:300ms, search" |
| 28 | hx-target="#ticket-table" |
| 29 | hx-swap="outerHTML" |
| 30 | hx-push-url="true" /> |
| 31 | </div> |
| 32 | </div> |
| 33 | |
| 34 | {% include "fossil/partials/ticket_table.html" %} |
| 35 | {% endblock %} |
| 36 |