FossilRepo
Add contributor profiles, clickable usernames everywhere - New user activity page: /fossil/user/<username>/ Shows checkin count, ticket changes, wiki edits, forum posts Lists recent checkins with clickable hashes - All usernames now clickable across every Fossil view: timeline, checkin detail, code browser, forum list, forum thread - Add fossil-repos Docker volume so .fossil files persist across rebuilds
Commit
88b98207cac95f86919a223cc64e8daba983cecf8f7df0fc0c4cabc35479d26f
Parent
2b89620ac041378…
10 files changed
+2
+41
+1
+24
+1
-1
+1
-1
+1
-1
+1
-1
+1
-1
+98
~
docker-compose.yaml
~
fossil/reader.py
~
fossil/urls.py
~
fossil/views.py
~
templates/fossil/checkin_detail.html
~
templates/fossil/code_browser.html
~
templates/fossil/forum_list.html
~
templates/fossil/forum_thread.html
~
templates/fossil/partials/timeline_entries.html
+
templates/fossil/user_activity.html
+2
| --- docker-compose.yaml | ||
| +++ docker-compose.yaml | ||
| @@ -11,10 +11,11 @@ | ||
| 11 | 11 | REDIS_URL: redis://redis:6379/1 |
| 12 | 12 | CELERY_BROKER: redis://redis:6379/0 |
| 13 | 13 | EMAIL_HOST: mailpit |
| 14 | 14 | volumes: |
| 15 | 15 | - .:/app |
| 16 | + - fossil-repos:/data/repos | |
| 16 | 17 | depends_on: |
| 17 | 18 | postgres: |
| 18 | 19 | condition: service_healthy |
| 19 | 20 | redis: |
| 20 | 21 | condition: service_healthy |
| @@ -96,5 +97,6 @@ | ||
| 96 | 97 | profiles: |
| 97 | 98 | - storage |
| 98 | 99 | |
| 99 | 100 | volumes: |
| 100 | 101 | pgdata: |
| 102 | + fossil-repos: | |
| 101 | 103 |
| --- docker-compose.yaml | |
| +++ docker-compose.yaml | |
| @@ -11,10 +11,11 @@ | |
| 11 | REDIS_URL: redis://redis:6379/1 |
| 12 | CELERY_BROKER: redis://redis:6379/0 |
| 13 | EMAIL_HOST: mailpit |
| 14 | volumes: |
| 15 | - .:/app |
| 16 | depends_on: |
| 17 | postgres: |
| 18 | condition: service_healthy |
| 19 | redis: |
| 20 | condition: service_healthy |
| @@ -96,5 +97,6 @@ | |
| 96 | profiles: |
| 97 | - storage |
| 98 | |
| 99 | volumes: |
| 100 | pgdata: |
| 101 |
| --- docker-compose.yaml | |
| +++ docker-compose.yaml | |
| @@ -11,10 +11,11 @@ | |
| 11 | REDIS_URL: redis://redis:6379/1 |
| 12 | CELERY_BROKER: redis://redis:6379/0 |
| 13 | EMAIL_HOST: mailpit |
| 14 | volumes: |
| 15 | - .:/app |
| 16 | - fossil-repos:/data/repos |
| 17 | depends_on: |
| 18 | postgres: |
| 19 | condition: service_healthy |
| 20 | redis: |
| 21 | condition: service_healthy |
| @@ -96,5 +97,6 @@ | |
| 97 | profiles: |
| 98 | - storage |
| 99 | |
| 100 | volumes: |
| 101 | pgdata: |
| 102 | fossil-repos: |
| 103 |
+41
| --- fossil/reader.py | ||
| +++ fossil/reader.py | ||
| @@ -290,10 +290,51 @@ | ||
| 290 | 290 | try: |
| 291 | 291 | row = self.conn.execute("SELECT count(*) FROM event WHERE type='ci'").fetchone() |
| 292 | 292 | return row[0] if row else 0 |
| 293 | 293 | except sqlite3.OperationalError: |
| 294 | 294 | return 0 |
| 295 | + | |
| 296 | + # --- User Activity --- | |
| 297 | + | |
| 298 | + def get_user_activity(self, username: str, limit: int = 50) -> dict: | |
| 299 | + """Get activity summary for a specific user.""" | |
| 300 | + result = {"checkins": [], "checkin_count": 0, "ticket_count": 0, "wiki_count": 0, "forum_count": 0} | |
| 301 | + try: | |
| 302 | + # Checkin count | |
| 303 | + row = self.conn.execute("SELECT count(*) FROM event WHERE user=? AND type='ci'", (username,)).fetchone() | |
| 304 | + result["checkin_count"] = row[0] if row else 0 | |
| 305 | + | |
| 306 | + # Recent checkins | |
| 307 | + rows = self.conn.execute( | |
| 308 | + "SELECT blob.uuid, event.mtime, event.comment FROM event " | |
| 309 | + "JOIN blob ON event.objid=blob.rid WHERE event.user=? AND event.type='ci' " | |
| 310 | + "ORDER BY event.mtime DESC LIMIT ?", | |
| 311 | + (username, limit), | |
| 312 | + ).fetchall() | |
| 313 | + for r in rows: | |
| 314 | + result["checkins"].append( | |
| 315 | + { | |
| 316 | + "uuid": r["uuid"], | |
| 317 | + "timestamp": _julian_to_datetime(r["mtime"]), | |
| 318 | + "comment": r["comment"] or "", | |
| 319 | + } | |
| 320 | + ) | |
| 321 | + | |
| 322 | + # Wiki edit count | |
| 323 | + row = self.conn.execute("SELECT count(*) FROM event WHERE user=? AND type='w'", (username,)).fetchone() | |
| 324 | + result["wiki_count"] = row[0] if row else 0 | |
| 325 | + | |
| 326 | + # Forum post count | |
| 327 | + row = self.conn.execute("SELECT count(*) FROM event WHERE user=? AND type='f'", (username,)).fetchone() | |
| 328 | + result["forum_count"] = row[0] if row else 0 | |
| 329 | + | |
| 330 | + # Ticket-related event count | |
| 331 | + row = self.conn.execute("SELECT count(*) FROM event WHERE user=? AND type='t'", (username,)).fetchone() | |
| 332 | + result["ticket_count"] = row[0] if row else 0 | |
| 333 | + except sqlite3.OperationalError: | |
| 334 | + pass | |
| 335 | + return result | |
| 295 | 336 | |
| 296 | 337 | # --- Timeline --- |
| 297 | 338 | |
| 298 | 339 | def get_timeline(self, limit: int = 50, offset: int = 0, event_type: str | None = None) -> list[TimelineEntry]: |
| 299 | 340 | sql = """ |
| 300 | 341 |
| --- fossil/reader.py | |
| +++ fossil/reader.py | |
| @@ -290,10 +290,51 @@ | |
| 290 | try: |
| 291 | row = self.conn.execute("SELECT count(*) FROM event WHERE type='ci'").fetchone() |
| 292 | return row[0] if row else 0 |
| 293 | except sqlite3.OperationalError: |
| 294 | return 0 |
| 295 | |
| 296 | # --- Timeline --- |
| 297 | |
| 298 | def get_timeline(self, limit: int = 50, offset: int = 0, event_type: str | None = None) -> list[TimelineEntry]: |
| 299 | sql = """ |
| 300 |
| --- fossil/reader.py | |
| +++ fossil/reader.py | |
| @@ -290,10 +290,51 @@ | |
| 290 | try: |
| 291 | row = self.conn.execute("SELECT count(*) FROM event WHERE type='ci'").fetchone() |
| 292 | return row[0] if row else 0 |
| 293 | except sqlite3.OperationalError: |
| 294 | return 0 |
| 295 | |
| 296 | # --- User Activity --- |
| 297 | |
| 298 | def get_user_activity(self, username: str, limit: int = 50) -> dict: |
| 299 | """Get activity summary for a specific user.""" |
| 300 | result = {"checkins": [], "checkin_count": 0, "ticket_count": 0, "wiki_count": 0, "forum_count": 0} |
| 301 | try: |
| 302 | # Checkin count |
| 303 | row = self.conn.execute("SELECT count(*) FROM event WHERE user=? AND type='ci'", (username,)).fetchone() |
| 304 | result["checkin_count"] = row[0] if row else 0 |
| 305 | |
| 306 | # Recent checkins |
| 307 | rows = self.conn.execute( |
| 308 | "SELECT blob.uuid, event.mtime, event.comment FROM event " |
| 309 | "JOIN blob ON event.objid=blob.rid WHERE event.user=? AND event.type='ci' " |
| 310 | "ORDER BY event.mtime DESC LIMIT ?", |
| 311 | (username, limit), |
| 312 | ).fetchall() |
| 313 | for r in rows: |
| 314 | result["checkins"].append( |
| 315 | { |
| 316 | "uuid": r["uuid"], |
| 317 | "timestamp": _julian_to_datetime(r["mtime"]), |
| 318 | "comment": r["comment"] or "", |
| 319 | } |
| 320 | ) |
| 321 | |
| 322 | # Wiki edit count |
| 323 | row = self.conn.execute("SELECT count(*) FROM event WHERE user=? AND type='w'", (username,)).fetchone() |
| 324 | result["wiki_count"] = row[0] if row else 0 |
| 325 | |
| 326 | # Forum post count |
| 327 | row = self.conn.execute("SELECT count(*) FROM event WHERE user=? AND type='f'", (username,)).fetchone() |
| 328 | result["forum_count"] = row[0] if row else 0 |
| 329 | |
| 330 | # Ticket-related event count |
| 331 | row = self.conn.execute("SELECT count(*) FROM event WHERE user=? AND type='t'", (username,)).fetchone() |
| 332 | result["ticket_count"] = row[0] if row else 0 |
| 333 | except sqlite3.OperationalError: |
| 334 | pass |
| 335 | return result |
| 336 | |
| 337 | # --- Timeline --- |
| 338 | |
| 339 | def get_timeline(self, limit: int = 50, offset: int = 0, event_type: str | None = None) -> list[TimelineEntry]: |
| 340 | sql = """ |
| 341 |
+1
| --- fossil/urls.py | ||
| +++ fossil/urls.py | ||
| @@ -14,6 +14,7 @@ | ||
| 14 | 14 | path("tickets/<str:ticket_uuid>/", views.ticket_detail, name="ticket_detail"), |
| 15 | 15 | path("wiki/", views.wiki_list, name="wiki"), |
| 16 | 16 | path("wiki/page/<path:page_name>", views.wiki_page, name="wiki_page"), |
| 17 | 17 | path("forum/", views.forum_list, name="forum"), |
| 18 | 18 | path("forum/<str:thread_uuid>/", views.forum_thread, name="forum_thread"), |
| 19 | + path("user/<str:username>/", views.user_activity, name="user_activity"), | |
| 19 | 20 | ] |
| 20 | 21 |
| --- fossil/urls.py | |
| +++ fossil/urls.py | |
| @@ -14,6 +14,7 @@ | |
| 14 | path("tickets/<str:ticket_uuid>/", views.ticket_detail, name="ticket_detail"), |
| 15 | path("wiki/", views.wiki_list, name="wiki"), |
| 16 | path("wiki/page/<path:page_name>", views.wiki_page, name="wiki_page"), |
| 17 | path("forum/", views.forum_list, name="forum"), |
| 18 | path("forum/<str:thread_uuid>/", views.forum_thread, name="forum_thread"), |
| 19 | ] |
| 20 |
| --- fossil/urls.py | |
| +++ fossil/urls.py | |
| @@ -14,6 +14,7 @@ | |
| 14 | path("tickets/<str:ticket_uuid>/", views.ticket_detail, name="ticket_detail"), |
| 15 | path("wiki/", views.wiki_list, name="wiki"), |
| 16 | path("wiki/page/<path:page_name>", views.wiki_page, name="wiki_page"), |
| 17 | path("forum/", views.forum_list, name="forum"), |
| 18 | path("forum/<str:thread_uuid>/", views.forum_thread, name="forum_thread"), |
| 19 | path("user/<str:username>/", views.user_activity, name="user_activity"), |
| 20 | ] |
| 21 |
+24
| --- fossil/views.py | ||
| +++ fossil/views.py | ||
| @@ -558,10 +558,34 @@ | ||
| 558 | 558 | "thread_uuid": thread_uuid, |
| 559 | 559 | "active_tab": "forum", |
| 560 | 560 | }, |
| 561 | 561 | ) |
| 562 | 562 | |
| 563 | + | |
| 564 | +# --- User Activity --- | |
| 565 | + | |
| 566 | + | |
| 567 | +@login_required | |
| 568 | +def user_activity(request, slug, username): | |
| 569 | + P.PROJECT_VIEW.check(request.user) | |
| 570 | + project, fossil_repo, reader = _get_repo_and_reader(slug) | |
| 571 | + | |
| 572 | + with reader: | |
| 573 | + activity = reader.get_user_activity(username) | |
| 574 | + | |
| 575 | + return render( | |
| 576 | + request, | |
| 577 | + "fossil/user_activity.html", | |
| 578 | + { | |
| 579 | + "project": project, | |
| 580 | + "fossil_repo": fossil_repo, | |
| 581 | + "username": username, | |
| 582 | + "activity": activity, | |
| 583 | + "active_tab": "timeline", | |
| 584 | + }, | |
| 585 | + ) | |
| 586 | + | |
| 563 | 587 | |
| 564 | 588 | # --- Helpers --- |
| 565 | 589 | |
| 566 | 590 | |
| 567 | 591 | def _build_file_tree(files, current_dir=""): |
| 568 | 592 |
| --- fossil/views.py | |
| +++ fossil/views.py | |
| @@ -558,10 +558,34 @@ | |
| 558 | "thread_uuid": thread_uuid, |
| 559 | "active_tab": "forum", |
| 560 | }, |
| 561 | ) |
| 562 | |
| 563 | |
| 564 | # --- Helpers --- |
| 565 | |
| 566 | |
| 567 | def _build_file_tree(files, current_dir=""): |
| 568 |
| --- fossil/views.py | |
| +++ fossil/views.py | |
| @@ -558,10 +558,34 @@ | |
| 558 | "thread_uuid": thread_uuid, |
| 559 | "active_tab": "forum", |
| 560 | }, |
| 561 | ) |
| 562 | |
| 563 | |
| 564 | # --- User Activity --- |
| 565 | |
| 566 | |
| 567 | @login_required |
| 568 | def user_activity(request, slug, username): |
| 569 | P.PROJECT_VIEW.check(request.user) |
| 570 | project, fossil_repo, reader = _get_repo_and_reader(slug) |
| 571 | |
| 572 | with reader: |
| 573 | activity = reader.get_user_activity(username) |
| 574 | |
| 575 | return render( |
| 576 | request, |
| 577 | "fossil/user_activity.html", |
| 578 | { |
| 579 | "project": project, |
| 580 | "fossil_repo": fossil_repo, |
| 581 | "username": username, |
| 582 | "activity": activity, |
| 583 | "active_tab": "timeline", |
| 584 | }, |
| 585 | ) |
| 586 | |
| 587 | |
| 588 | # --- Helpers --- |
| 589 | |
| 590 | |
| 591 | def _build_file_tree(files, current_dir=""): |
| 592 |
| --- templates/fossil/checkin_detail.html | ||
| +++ templates/fossil/checkin_detail.html | ||
| @@ -43,11 +43,11 @@ | ||
| 43 | 43 | <!-- Commit header --> |
| 44 | 44 | <div class="rounded-lg bg-gray-800 border border-gray-700"> |
| 45 | 45 | <div class="px-6 py-5"> |
| 46 | 46 | <p class="text-lg text-gray-100 leading-relaxed">{{ checkin.comment }}</p> |
| 47 | 47 | <div class="mt-3 flex items-center gap-4 flex-wrap text-sm"> |
| 48 | - <span class="font-medium text-gray-200">{{ checkin.user }}</span> | |
| 48 | + <a href="{% url 'fossil:user_activity' slug=project.slug username=checkin.user %}" class="font-medium text-gray-200 hover:text-brand-light">{{ checkin.user }}</a> | |
| 49 | 49 | <span class="text-gray-500">{{ checkin.timestamp|date:"Y-m-d H:i" }}</span> |
| 50 | 50 | {% if checkin.branch %} |
| 51 | 51 | <span class="inline-flex items-center rounded-md bg-brand/10 border border-brand/20 px-2 py-0.5 text-xs text-brand-light"> |
| 52 | 52 | {{ checkin.branch }} |
| 53 | 53 | </span> |
| 54 | 54 |
| --- templates/fossil/checkin_detail.html | |
| +++ templates/fossil/checkin_detail.html | |
| @@ -43,11 +43,11 @@ | |
| 43 | <!-- Commit header --> |
| 44 | <div class="rounded-lg bg-gray-800 border border-gray-700"> |
| 45 | <div class="px-6 py-5"> |
| 46 | <p class="text-lg text-gray-100 leading-relaxed">{{ checkin.comment }}</p> |
| 47 | <div class="mt-3 flex items-center gap-4 flex-wrap text-sm"> |
| 48 | <span class="font-medium text-gray-200">{{ checkin.user }}</span> |
| 49 | <span class="text-gray-500">{{ checkin.timestamp|date:"Y-m-d H:i" }}</span> |
| 50 | {% if checkin.branch %} |
| 51 | <span class="inline-flex items-center rounded-md bg-brand/10 border border-brand/20 px-2 py-0.5 text-xs text-brand-light"> |
| 52 | {{ checkin.branch }} |
| 53 | </span> |
| 54 |
| --- templates/fossil/checkin_detail.html | |
| +++ templates/fossil/checkin_detail.html | |
| @@ -43,11 +43,11 @@ | |
| 43 | <!-- Commit header --> |
| 44 | <div class="rounded-lg bg-gray-800 border border-gray-700"> |
| 45 | <div class="px-6 py-5"> |
| 46 | <p class="text-lg text-gray-100 leading-relaxed">{{ checkin.comment }}</p> |
| 47 | <div class="mt-3 flex items-center gap-4 flex-wrap text-sm"> |
| 48 | <a href="{% url 'fossil:user_activity' slug=project.slug username=checkin.user %}" class="font-medium text-gray-200 hover:text-brand-light">{{ checkin.user }}</a> |
| 49 | <span class="text-gray-500">{{ checkin.timestamp|date:"Y-m-d H:i" }}</span> |
| 50 | {% if checkin.branch %} |
| 51 | <span class="inline-flex items-center rounded-md bg-brand/10 border border-brand/20 px-2 py-0.5 text-xs text-brand-light"> |
| 52 | {{ checkin.branch }} |
| 53 | </span> |
| 54 |
| --- templates/fossil/code_browser.html | ||
| +++ templates/fossil/code_browser.html | ||
| @@ -33,11 +33,11 @@ | ||
| 33 | 33 | </div> |
| 34 | 34 | |
| 35 | 35 | <!-- Latest commit info --> |
| 36 | 36 | {% if latest_commit %} |
| 37 | 37 | <div class="flex items-center gap-3 mt-2 text-xs text-gray-500"> |
| 38 | - <span class="font-medium text-gray-300">{{ latest_commit.user }}</span> | |
| 38 | + <a href="{% url 'fossil:user_activity' slug=project.slug username=latest_commit.user %}" class="font-medium text-gray-300 hover:text-brand-light">{{ latest_commit.user }}</a> | |
| 39 | 39 | <a href="{% url 'fossil:checkin_detail' slug=project.slug checkin_uuid=latest_commit.uuid %}" class="text-gray-400 truncate hover:text-brand-light">{{ latest_commit.comment|truncatechars:80 }}</a> |
| 40 | 40 | <a href="{% url 'fossil:checkin_detail' slug=project.slug checkin_uuid=latest_commit.uuid %}" class="font-mono text-brand-light hover:text-brand">{{ latest_commit.uuid|truncatechars:10 }}</a> |
| 41 | 41 | <span>{{ latest_commit.timestamp|date:"Y-m-d H:i" }}</span> |
| 42 | 42 | </div> |
| 43 | 43 | {% endif %} |
| 44 | 44 |
| --- templates/fossil/code_browser.html | |
| +++ templates/fossil/code_browser.html | |
| @@ -33,11 +33,11 @@ | |
| 33 | </div> |
| 34 | |
| 35 | <!-- Latest commit info --> |
| 36 | {% if latest_commit %} |
| 37 | <div class="flex items-center gap-3 mt-2 text-xs text-gray-500"> |
| 38 | <span class="font-medium text-gray-300">{{ latest_commit.user }}</span> |
| 39 | <a href="{% url 'fossil:checkin_detail' slug=project.slug checkin_uuid=latest_commit.uuid %}" class="text-gray-400 truncate hover:text-brand-light">{{ latest_commit.comment|truncatechars:80 }}</a> |
| 40 | <a href="{% url 'fossil:checkin_detail' slug=project.slug checkin_uuid=latest_commit.uuid %}" class="font-mono text-brand-light hover:text-brand">{{ latest_commit.uuid|truncatechars:10 }}</a> |
| 41 | <span>{{ latest_commit.timestamp|date:"Y-m-d H:i" }}</span> |
| 42 | </div> |
| 43 | {% endif %} |
| 44 |
| --- templates/fossil/code_browser.html | |
| +++ templates/fossil/code_browser.html | |
| @@ -33,11 +33,11 @@ | |
| 33 | </div> |
| 34 | |
| 35 | <!-- Latest commit info --> |
| 36 | {% if latest_commit %} |
| 37 | <div class="flex items-center gap-3 mt-2 text-xs text-gray-500"> |
| 38 | <a href="{% url 'fossil:user_activity' slug=project.slug username=latest_commit.user %}" class="font-medium text-gray-300 hover:text-brand-light">{{ latest_commit.user }}</a> |
| 39 | <a href="{% url 'fossil:checkin_detail' slug=project.slug checkin_uuid=latest_commit.uuid %}" class="text-gray-400 truncate hover:text-brand-light">{{ latest_commit.comment|truncatechars:80 }}</a> |
| 40 | <a href="{% url 'fossil:checkin_detail' slug=project.slug checkin_uuid=latest_commit.uuid %}" class="font-mono text-brand-light hover:text-brand">{{ latest_commit.uuid|truncatechars:10 }}</a> |
| 41 | <span>{{ latest_commit.timestamp|date:"Y-m-d H:i" }}</span> |
| 42 | </div> |
| 43 | {% endif %} |
| 44 |
+1
-1
| --- templates/fossil/forum_list.html | ||
| +++ templates/fossil/forum_list.html | ||
| @@ -15,11 +15,11 @@ | ||
| 15 | 15 | </a> |
| 16 | 16 | {% if post.body %} |
| 17 | 17 | <p class="mt-1 text-sm text-gray-400 line-clamp-2">{{ post.body|truncatechars:200 }}</p> |
| 18 | 18 | {% endif %} |
| 19 | 19 | <div class="mt-2 flex items-center gap-3 text-xs text-gray-500"> |
| 20 | - <span class="font-medium text-gray-400">{{ post.user }}</span> | |
| 20 | + <a href="{% url 'fossil:user_activity' slug=project.slug username=post.user %}" class="font-medium text-gray-400 hover:text-brand-light">{{ post.user }}</a> | |
| 21 | 21 | <span>{{ post.timestamp|timesince }} ago</span> |
| 22 | 22 | </div> |
| 23 | 23 | </div> |
| 24 | 24 | </div> |
| 25 | 25 | {% empty %} |
| 26 | 26 |
| --- templates/fossil/forum_list.html | |
| +++ templates/fossil/forum_list.html | |
| @@ -15,11 +15,11 @@ | |
| 15 | </a> |
| 16 | {% if post.body %} |
| 17 | <p class="mt-1 text-sm text-gray-400 line-clamp-2">{{ post.body|truncatechars:200 }}</p> |
| 18 | {% endif %} |
| 19 | <div class="mt-2 flex items-center gap-3 text-xs text-gray-500"> |
| 20 | <span class="font-medium text-gray-400">{{ post.user }}</span> |
| 21 | <span>{{ post.timestamp|timesince }} ago</span> |
| 22 | </div> |
| 23 | </div> |
| 24 | </div> |
| 25 | {% empty %} |
| 26 |
| --- templates/fossil/forum_list.html | |
| +++ templates/fossil/forum_list.html | |
| @@ -15,11 +15,11 @@ | |
| 15 | </a> |
| 16 | {% if post.body %} |
| 17 | <p class="mt-1 text-sm text-gray-400 line-clamp-2">{{ post.body|truncatechars:200 }}</p> |
| 18 | {% endif %} |
| 19 | <div class="mt-2 flex items-center gap-3 text-xs text-gray-500"> |
| 20 | <a href="{% url 'fossil:user_activity' slug=project.slug username=post.user %}" class="font-medium text-gray-400 hover:text-brand-light">{{ post.user }}</a> |
| 21 | <span>{{ post.timestamp|timesince }} ago</span> |
| 22 | </div> |
| 23 | </div> |
| 24 | </div> |
| 25 | {% empty %} |
| 26 |
| --- templates/fossil/forum_thread.html | ||
| +++ templates/fossil/forum_thread.html | ||
| @@ -12,11 +12,11 @@ | ||
| 12 | 12 | <div class="space-y-3"> |
| 13 | 13 | {% for post in posts %} |
| 14 | 14 | <div class="rounded-lg bg-gray-800 border border-gray-700 {% if post.in_reply_to %}ml-8{% endif %}"> |
| 15 | 15 | <div class="px-5 py-4"> |
| 16 | 16 | <div class="flex items-center justify-between mb-2"> |
| 17 | - <span class="text-sm font-medium text-gray-200">{{ post.user }}</span> | |
| 17 | + <a href="{% url 'fossil:user_activity' slug=project.slug username=post.user %}" class="text-sm font-medium text-gray-200 hover:text-brand-light">{{ post.user }}</a> | |
| 18 | 18 | <span class="text-xs text-gray-500">{{ post.timestamp|timesince }} ago</span> |
| 19 | 19 | </div> |
| 20 | 20 | {% if post.title and forloop.first %} |
| 21 | 21 | <h2 class="text-lg font-semibold text-gray-100 mb-3">{{ post.title }}</h2> |
| 22 | 22 | {% endif %} |
| 23 | 23 |
| --- templates/fossil/forum_thread.html | |
| +++ templates/fossil/forum_thread.html | |
| @@ -12,11 +12,11 @@ | |
| 12 | <div class="space-y-3"> |
| 13 | {% for post in posts %} |
| 14 | <div class="rounded-lg bg-gray-800 border border-gray-700 {% if post.in_reply_to %}ml-8{% endif %}"> |
| 15 | <div class="px-5 py-4"> |
| 16 | <div class="flex items-center justify-between mb-2"> |
| 17 | <span class="text-sm font-medium text-gray-200">{{ post.user }}</span> |
| 18 | <span class="text-xs text-gray-500">{{ post.timestamp|timesince }} ago</span> |
| 19 | </div> |
| 20 | {% if post.title and forloop.first %} |
| 21 | <h2 class="text-lg font-semibold text-gray-100 mb-3">{{ post.title }}</h2> |
| 22 | {% endif %} |
| 23 |
| --- templates/fossil/forum_thread.html | |
| +++ templates/fossil/forum_thread.html | |
| @@ -12,11 +12,11 @@ | |
| 12 | <div class="space-y-3"> |
| 13 | {% for post in posts %} |
| 14 | <div class="rounded-lg bg-gray-800 border border-gray-700 {% if post.in_reply_to %}ml-8{% endif %}"> |
| 15 | <div class="px-5 py-4"> |
| 16 | <div class="flex items-center justify-between mb-2"> |
| 17 | <a href="{% url 'fossil:user_activity' slug=project.slug username=post.user %}" class="text-sm font-medium text-gray-200 hover:text-brand-light">{{ post.user }}</a> |
| 18 | <span class="text-xs text-gray-500">{{ post.timestamp|timesince }} ago</span> |
| 19 | </div> |
| 20 | {% if post.title and forloop.first %} |
| 21 | <h2 class="text-lg font-semibold text-gray-100 mb-3">{{ post.title }}</h2> |
| 22 | {% endif %} |
| 23 |
| --- templates/fossil/partials/timeline_entries.html | ||
| +++ templates/fossil/partials/timeline_entries.html | ||
| @@ -58,11 +58,11 @@ | ||
| 58 | 58 | <a href="{% url 'fossil:checkin_detail' slug=project.slug checkin_uuid=item.entry.uuid %}" class="text-sm text-gray-100 leading-snug hover:text-brand-light">{{ item.entry.comment|default:"(no comment)"|truncatechars:120 }}</a> |
| 59 | 59 | {% else %} |
| 60 | 60 | <p class="text-sm text-gray-100 leading-snug">{{ item.entry.comment|default:"(no comment)"|truncatechars:120 }}</p> |
| 61 | 61 | {% endif %} |
| 62 | 62 | <div class="mt-1 flex items-center gap-3 flex-wrap"> |
| 63 | - <span class="text-xs text-gray-400">{{ item.entry.user }}</span> | |
| 63 | + <a href="{% url 'fossil:user_activity' slug=project.slug username=item.entry.user %}" class="text-xs text-gray-400 hover:text-brand-light">{{ item.entry.user }}</a> | |
| 64 | 64 | <span class="text-xs text-gray-600">{{ item.entry.timestamp|date:"Y-m-d H:i" }}</span> |
| 65 | 65 | {% if item.entry.branch %} |
| 66 | 66 | <span class="inline-flex items-center rounded-md bg-brand/10 border border-brand/20 px-1.5 py-0.5 text-xs text-brand-light"> |
| 67 | 67 | {{ item.entry.branch }} |
| 68 | 68 | </span> |
| 69 | 69 | |
| 70 | 70 | ADDED templates/fossil/user_activity.html |
| --- templates/fossil/partials/timeline_entries.html | |
| +++ templates/fossil/partials/timeline_entries.html | |
| @@ -58,11 +58,11 @@ | |
| 58 | <a href="{% url 'fossil:checkin_detail' slug=project.slug checkin_uuid=item.entry.uuid %}" class="text-sm text-gray-100 leading-snug hover:text-brand-light">{{ item.entry.comment|default:"(no comment)"|truncatechars:120 }}</a> |
| 59 | {% else %} |
| 60 | <p class="text-sm text-gray-100 leading-snug">{{ item.entry.comment|default:"(no comment)"|truncatechars:120 }}</p> |
| 61 | {% endif %} |
| 62 | <div class="mt-1 flex items-center gap-3 flex-wrap"> |
| 63 | <span class="text-xs text-gray-400">{{ item.entry.user }}</span> |
| 64 | <span class="text-xs text-gray-600">{{ item.entry.timestamp|date:"Y-m-d H:i" }}</span> |
| 65 | {% if item.entry.branch %} |
| 66 | <span class="inline-flex items-center rounded-md bg-brand/10 border border-brand/20 px-1.5 py-0.5 text-xs text-brand-light"> |
| 67 | {{ item.entry.branch }} |
| 68 | </span> |
| 69 | |
| 70 | DDED templates/fossil/user_activity.html |
| --- templates/fossil/partials/timeline_entries.html | |
| +++ templates/fossil/partials/timeline_entries.html | |
| @@ -58,11 +58,11 @@ | |
| 58 | <a href="{% url 'fossil:checkin_detail' slug=project.slug checkin_uuid=item.entry.uuid %}" class="text-sm text-gray-100 leading-snug hover:text-brand-light">{{ item.entry.comment|default:"(no comment)"|truncatechars:120 }}</a> |
| 59 | {% else %} |
| 60 | <p class="text-sm text-gray-100 leading-snug">{{ item.entry.comment|default:"(no comment)"|truncatechars:120 }}</p> |
| 61 | {% endif %} |
| 62 | <div class="mt-1 flex items-center gap-3 flex-wrap"> |
| 63 | <a href="{% url 'fossil:user_activity' slug=project.slug username=item.entry.user %}" class="text-xs text-gray-400 hover:text-brand-light">{{ item.entry.user }}</a> |
| 64 | <span class="text-xs text-gray-600">{{ item.entry.timestamp|date:"Y-m-d H:i" }}</span> |
| 65 | {% if item.entry.branch %} |
| 66 | <span class="inline-flex items-center rounded-md bg-brand/10 border border-brand/20 px-1.5 py-0.5 text-xs text-brand-light"> |
| 67 | {{ item.entry.branch }} |
| 68 | </span> |
| 69 | |
| 70 | DDED templates/fossil/user_activity.html |
| --- a/templates/fossil/user_activity.html | ||
| +++ b/templates/fossil/user_activity.html | ||
| @@ -0,0 +1,98 @@ | ||
| 1 | +{% extends "base.html" %} | |
| 2 | +{% block title %}{{ username }} — {{ project.name }} — Fossilrepo{% endblock %} | |
| 3 | + | |
| 4 | +{% block content %} | |
| 5 | +<h1 class="text-2xl font-bold text-gray-100 mb-2">{{ project.name }}</h1> | |
| 6 | +{% include "fos<div class="grid grid-cols-1 gap-6 lg:grid-cols-3"> | |
| 7 | + <!-- Main content --> | |
| 8 | + <div class="lg:col-span-2"> | |
| 9 | + <div class="rounded-lg bg-gray-800 border border-gray-700"> | |
| 10 | + <div class="px-5 py-4 border-b border-gray-700"> | |
| 11 | + <h2 class="text-xl font-bold text-gray-100">{{ username }}</h2> | |
| 12 | + <p class="mt-1 text-sm text-gray-400">Contributor activity in {{ project.name }}</p> | |
| 13 | + </div> | |
| 14 | + | |
| 15 | + <!-- Recent checkins --> | |
| 16 | + <div class="divide-y divide-gray-700"> | |
| 17 | + {% for commit in activity.checkins %} | |
| 18 | + <div class="px-5 py-3 flex items-start gap-3 hover:bg-gray-700/30"> | |
| 19 | + <div class="flex-shrink-0 mt-1"> | |
| 20 | + <div class="w-2.5 h-2.5 rounded-full bg-brand"></div> | |
| 21 | + </div> | |
| 22 | + <div class="flex-1 min-w-0"> | |
| 23 | + <a href="{% url 'fossil:checkin_detail' slug=project.slug checkin_uuid=commit.uuid %}" | |
| 24 | + class="text-sm text-gray-200 hover:text-brand-light">{{ commit.comment|truncatechars:100 }}</a> | |
| 25 | + <div class="mt-0.5 flex items-center gap-3 text-xs text-gray-500"> | |
| 26 | + <a href="{% url 'fossil:checkin_detail' slug=project.slug checkin_uuid=commit.uuid %}" | |
| 27 | + class="font-mono text-brand-light hover:text-brand">{{ commit.uuid|truncatechars:10 }}</a> | |
| 28 | + <span>{{ commit.timestamp|timesince }} ago</span> | |
| 29 | + </div> | |
| 30 | + </div> | |
| 31 | + </div> | |
| 32 | + {% empty %} | |
| 33 | + <p class="px-5 py-8 text-sm text-gray-500 text-center">No checkins by this user.</p> | |
| 34 | + {% endfor %} | |
| 35 | + </div> | |
| 36 | + </div> | |
| 37 | + </div> | |
| 38 | + | |
| 39 | + <!-- Sidebar stats --> | |
| 40 | + <div> | |
| 41 | + <div class="rounded-lg bg-gray-800 border border-gray-700 p-4"> | |
| 42 | + <h3 class="text-sm font-medium text-gray-300 mb-3">Contributions</h3> | |
| 43 | + <div class="space-y-1"> | |
| 44 | + <a href="{% url2"> | |
| 45 | +-2.5 rounded-full bgv> | |
| 46 | + </div> | |
| 47 | + </div> | |
| 48 | + | |
| 49 | + <!-- Si-w-0"> | |
| 50 | + <a href="{% url 'fossil:checkin_detail' slug=project.slug checkin_uuid=commit.uuid %}" | |
| 51 | + class="text-sm text-gray-200 hover:text-brand-light">{{ commit.comment|truncatechars:100 } <div class="divide-y divide-gray-700"> | |
| 52 | + {% for commit in activity.checkins %} | |
| 53 | + <div class="px-5 py-3 flex items-start gap-3 hover:bg-gray-700/30"> | |
| 54 | + <div class="flex-shrink-0div> | |
| 55 | +-2.5 rounded-full bgv> | |
| 56 | + </div> | |
| 57 | + </div> | |
| 58 | + | |
| 59 | + <!-- Si-w-0"> | |
| 60 | + <a href="{% url 'fossil:checkin_detail' slug=project.slug checkin_uuid=commit.uuid %}" | |
| 61 | + class="text-sm text-gray-200 hover:text-brand-light">{{ commit.comment|truncatechars:100 }}</a> | |
| 62 | + <div class="mt-0.5 flex items-center gap-3 text-xs text-gray-500"> | |
| 63 | + <a href="{% url 'fossil:checkin_detail' slug=project.slug checkin_uuid=commit.uuid %}" | |
| 64 | + class="font-mono text-brand-light hover:text-brand">{{ commit.uuid|truncatechars:10 }}</a> | |
| 65 | + <span>{{ commit.timestamp|timesince }} ago</span> | |
| 66 | + </div> | |
| 67 | + </div> | |
| 68 | + </div> | |
| 69 | + {% empty %} | |
| 70 | + <p class="px-5 py-8div> | |
| 71 | +-2.5 rounded-full bgv> | |
| 72 | + </div> | |
| 73 | + </div> | |
| 74 | + | |
| 75 | + <!-- Si-w-0"> | |
| 76 | + <a href="{% url 'fossil:checkin_detail' slug=project.slug checkin_uuid=commit.uuid %}" | |
| 77 | + class="text-sm text-gray-200 hover:text-brand-light">{{ commit.comment|truncatechars:100 }nter justify-between rounded-md px-2 py-1.5 hover:bg-gray-700/50"> | |
| 78 | + <span class="flex items-center gap-2 text-sm text-gray-400"> | |
| 79 | + <svg class="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" d="M12 6v6h4.5m4.5 0a9 9 0 11-18 0 9 9 0 0118 0z" /></svg> | |
| 80 | + Checkins | |
| 81 | + div> | |
| 82 | +-2.5 rounded-full bgv> | |
| 83 | + </div> | |
| 84 | + </div> | |
| 85 | + | |
| 86 | + <!-- Silex items-center gap-2 text-sm text-gray-400"> | |
| 87 | + <svg class="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" d="M7.5 8.25h9m-9 3H12m-9.75 1.51c0 1.6 1.123 2.994 2.707 3.227 1.129.166 2.27.293 3.423.379.35.026.67.21.865.501L12 21l2.755-4.133a1.14 1.14 0 01.865-.501 48.172 48.172 0 003.423-.379c1.584-.233 2.707-1.626 2.707-3.228V6.741c0-1.602-1.123-2.995-2.707-3.228A48.394 48.394 0 0012 3c-2.392 0-4.744.175-7.043.513C3.373 3.746 2.25 5.14 2.25 6.741v6.018z" /></svg> | |
| 88 | + Forum posts | |
| 89 | + </span> | |
| 90 | + <span class="text-sm font-medium text-gray-200">{{ activity.forum_count }}</span> | |
| 91 | + </a> | |
| 92 | + </div> | |
| 93 | + </div> | |
| 94 | +div></div> | |
| 95 | + | |
| 96 | + <!-- Sidebar stats --> | |
| 97 | + </div> | |
| 98 | +{% endblock %} |
| --- a/templates/fossil/user_activity.html | |
| +++ b/templates/fossil/user_activity.html | |
| @@ -0,0 +1,98 @@ | |
| --- a/templates/fossil/user_activity.html | |
| +++ b/templates/fossil/user_activity.html | |
| @@ -0,0 +1,98 @@ | |
| 1 | {% extends "base.html" %} |
| 2 | {% block title %}{{ username }} — {{ project.name }} — Fossilrepo{% endblock %} |
| 3 | |
| 4 | {% block content %} |
| 5 | <h1 class="text-2xl font-bold text-gray-100 mb-2">{{ project.name }}</h1> |
| 6 | {% include "fos<div class="grid grid-cols-1 gap-6 lg:grid-cols-3"> |
| 7 | <!-- Main content --> |
| 8 | <div class="lg:col-span-2"> |
| 9 | <div class="rounded-lg bg-gray-800 border border-gray-700"> |
| 10 | <div class="px-5 py-4 border-b border-gray-700"> |
| 11 | <h2 class="text-xl font-bold text-gray-100">{{ username }}</h2> |
| 12 | <p class="mt-1 text-sm text-gray-400">Contributor activity in {{ project.name }}</p> |
| 13 | </div> |
| 14 | |
| 15 | <!-- Recent checkins --> |
| 16 | <div class="divide-y divide-gray-700"> |
| 17 | {% for commit in activity.checkins %} |
| 18 | <div class="px-5 py-3 flex items-start gap-3 hover:bg-gray-700/30"> |
| 19 | <div class="flex-shrink-0 mt-1"> |
| 20 | <div class="w-2.5 h-2.5 rounded-full bg-brand"></div> |
| 21 | </div> |
| 22 | <div class="flex-1 min-w-0"> |
| 23 | <a href="{% url 'fossil:checkin_detail' slug=project.slug checkin_uuid=commit.uuid %}" |
| 24 | class="text-sm text-gray-200 hover:text-brand-light">{{ commit.comment|truncatechars:100 }}</a> |
| 25 | <div class="mt-0.5 flex items-center gap-3 text-xs text-gray-500"> |
| 26 | <a href="{% url 'fossil:checkin_detail' slug=project.slug checkin_uuid=commit.uuid %}" |
| 27 | class="font-mono text-brand-light hover:text-brand">{{ commit.uuid|truncatechars:10 }}</a> |
| 28 | <span>{{ commit.timestamp|timesince }} ago</span> |
| 29 | </div> |
| 30 | </div> |
| 31 | </div> |
| 32 | {% empty %} |
| 33 | <p class="px-5 py-8 text-sm text-gray-500 text-center">No checkins by this user.</p> |
| 34 | {% endfor %} |
| 35 | </div> |
| 36 | </div> |
| 37 | </div> |
| 38 | |
| 39 | <!-- Sidebar stats --> |
| 40 | <div> |
| 41 | <div class="rounded-lg bg-gray-800 border border-gray-700 p-4"> |
| 42 | <h3 class="text-sm font-medium text-gray-300 mb-3">Contributions</h3> |
| 43 | <div class="space-y-1"> |
| 44 | <a href="{% url2"> |
| 45 | -2.5 rounded-full bgv> |
| 46 | </div> |
| 47 | </div> |
| 48 | |
| 49 | <!-- Si-w-0"> |
| 50 | <a href="{% url 'fossil:checkin_detail' slug=project.slug checkin_uuid=commit.uuid %}" |
| 51 | class="text-sm text-gray-200 hover:text-brand-light">{{ commit.comment|truncatechars:100 } <div class="divide-y divide-gray-700"> |
| 52 | {% for commit in activity.checkins %} |
| 53 | <div class="px-5 py-3 flex items-start gap-3 hover:bg-gray-700/30"> |
| 54 | <div class="flex-shrink-0div> |
| 55 | -2.5 rounded-full bgv> |
| 56 | </div> |
| 57 | </div> |
| 58 | |
| 59 | <!-- Si-w-0"> |
| 60 | <a href="{% url 'fossil:checkin_detail' slug=project.slug checkin_uuid=commit.uuid %}" |
| 61 | class="text-sm text-gray-200 hover:text-brand-light">{{ commit.comment|truncatechars:100 }}</a> |
| 62 | <div class="mt-0.5 flex items-center gap-3 text-xs text-gray-500"> |
| 63 | <a href="{% url 'fossil:checkin_detail' slug=project.slug checkin_uuid=commit.uuid %}" |
| 64 | class="font-mono text-brand-light hover:text-brand">{{ commit.uuid|truncatechars:10 }}</a> |
| 65 | <span>{{ commit.timestamp|timesince }} ago</span> |
| 66 | </div> |
| 67 | </div> |
| 68 | </div> |
| 69 | {% empty %} |
| 70 | <p class="px-5 py-8div> |
| 71 | -2.5 rounded-full bgv> |
| 72 | </div> |
| 73 | </div> |
| 74 | |
| 75 | <!-- Si-w-0"> |
| 76 | <a href="{% url 'fossil:checkin_detail' slug=project.slug checkin_uuid=commit.uuid %}" |
| 77 | class="text-sm text-gray-200 hover:text-brand-light">{{ commit.comment|truncatechars:100 }nter justify-between rounded-md px-2 py-1.5 hover:bg-gray-700/50"> |
| 78 | <span class="flex items-center gap-2 text-sm text-gray-400"> |
| 79 | <svg class="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" d="M12 6v6h4.5m4.5 0a9 9 0 11-18 0 9 9 0 0118 0z" /></svg> |
| 80 | Checkins |
| 81 | div> |
| 82 | -2.5 rounded-full bgv> |
| 83 | </div> |
| 84 | </div> |
| 85 | |
| 86 | <!-- Silex items-center gap-2 text-sm text-gray-400"> |
| 87 | <svg class="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" d="M7.5 8.25h9m-9 3H12m-9.75 1.51c0 1.6 1.123 2.994 2.707 3.227 1.129.166 2.27.293 3.423.379.35.026.67.21.865.501L12 21l2.755-4.133a1.14 1.14 0 01.865-.501 48.172 48.172 0 003.423-.379c1.584-.233 2.707-1.626 2.707-3.228V6.741c0-1.602-1.123-2.995-2.707-3.228A48.394 48.394 0 0012 3c-2.392 0-4.744.175-7.043.513C3.373 3.746 2.25 5.14 2.25 6.741v6.018z" /></svg> |
| 88 | Forum posts |
| 89 | </span> |
| 90 | <span class="text-sm font-medium text-gray-200">{{ activity.forum_count }}</span> |
| 91 | </a> |
| 92 | </div> |
| 93 | </div> |
| 94 | div></div> |
| 95 | |
| 96 | <!-- Sidebar stats --> |
| 97 | </div> |
| 98 | {% endblock %} |