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

lmata 2026-04-06 14:56 trunk
Commit 88b98207cac95f86919a223cc64e8daba983cecf8f7df0fc0c4cabc35479d26f
--- docker-compose.yaml
+++ docker-compose.yaml
@@ -11,10 +11,11 @@
1111
REDIS_URL: redis://redis:6379/1
1212
CELERY_BROKER: redis://redis:6379/0
1313
EMAIL_HOST: mailpit
1414
volumes:
1515
- .:/app
16
+ - fossil-repos:/data/repos
1617
depends_on:
1718
postgres:
1819
condition: service_healthy
1920
redis:
2021
condition: service_healthy
@@ -96,5 +97,6 @@
9697
profiles:
9798
- storage
9899
99100
volumes:
100101
pgdata:
102
+ fossil-repos:
101103
--- 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
--- fossil/reader.py
+++ fossil/reader.py
@@ -290,10 +290,51 @@
290290
try:
291291
row = self.conn.execute("SELECT count(*) FROM event WHERE type='ci'").fetchone()
292292
return row[0] if row else 0
293293
except sqlite3.OperationalError:
294294
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
295336
296337
# --- Timeline ---
297338
298339
def get_timeline(self, limit: int = 50, offset: int = 0, event_type: str | None = None) -> list[TimelineEntry]:
299340
sql = """
300341
--- 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
--- fossil/urls.py
+++ fossil/urls.py
@@ -14,6 +14,7 @@
1414
path("tickets/<str:ticket_uuid>/", views.ticket_detail, name="ticket_detail"),
1515
path("wiki/", views.wiki_list, name="wiki"),
1616
path("wiki/page/<path:page_name>", views.wiki_page, name="wiki_page"),
1717
path("forum/", views.forum_list, name="forum"),
1818
path("forum/<str:thread_uuid>/", views.forum_thread, name="forum_thread"),
19
+ path("user/<str:username>/", views.user_activity, name="user_activity"),
1920
]
2021
--- 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
--- fossil/views.py
+++ fossil/views.py
@@ -558,10 +558,34 @@
558558
"thread_uuid": thread_uuid,
559559
"active_tab": "forum",
560560
},
561561
)
562562
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
+
563587
564588
# --- Helpers ---
565589
566590
567591
def _build_file_tree(files, current_dir=""):
568592
--- 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 @@
4343
<!-- Commit header -->
4444
<div class="rounded-lg bg-gray-800 border border-gray-700">
4545
<div class="px-6 py-5">
4646
<p class="text-lg text-gray-100 leading-relaxed">{{ checkin.comment }}</p>
4747
<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>
4949
<span class="text-gray-500">{{ checkin.timestamp|date:"Y-m-d H:i" }}</span>
5050
{% if checkin.branch %}
5151
<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">
5252
{{ checkin.branch }}
5353
</span>
5454
--- 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 @@
3333
</div>
3434
3535
<!-- Latest commit info -->
3636
{% if latest_commit %}
3737
<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>
3939
<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>
4040
<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>
4141
<span>{{ latest_commit.timestamp|date:"Y-m-d H:i" }}</span>
4242
</div>
4343
{% endif %}
4444
--- 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
--- templates/fossil/forum_list.html
+++ templates/fossil/forum_list.html
@@ -15,11 +15,11 @@
1515
</a>
1616
{% if post.body %}
1717
<p class="mt-1 text-sm text-gray-400 line-clamp-2">{{ post.body|truncatechars:200 }}</p>
1818
{% endif %}
1919
<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>
2121
<span>{{ post.timestamp|timesince }} ago</span>
2222
</div>
2323
</div>
2424
</div>
2525
{% empty %}
2626
--- 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 @@
1212
<div class="space-y-3">
1313
{% for post in posts %}
1414
<div class="rounded-lg bg-gray-800 border border-gray-700 {% if post.in_reply_to %}ml-8{% endif %}">
1515
<div class="px-5 py-4">
1616
<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>
1818
<span class="text-xs text-gray-500">{{ post.timestamp|timesince }} ago</span>
1919
</div>
2020
{% if post.title and forloop.first %}
2121
<h2 class="text-lg font-semibold text-gray-100 mb-3">{{ post.title }}</h2>
2222
{% endif %}
2323
--- 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 @@
5858
<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>
5959
{% else %}
6060
<p class="text-sm text-gray-100 leading-snug">{{ item.entry.comment|default:"(no comment)"|truncatechars:120 }}</p>
6161
{% endif %}
6262
<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>
6464
<span class="text-xs text-gray-600">{{ item.entry.timestamp|date:"Y-m-d H:i" }}</span>
6565
{% if item.entry.branch %}
6666
<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">
6767
{{ item.entry.branch }}
6868
</span>
6969
7070
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 %}

Keyboard Shortcuts

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