FossilRepo

HTMX infinite scroll, copy hash buttons, clone instructions Timeline: - HTMX infinite scroll — loads next page when scrolling near bottom - Loading spinner indicator - Replaces "Load more" button Copy hash: - Reusable _copy_hash.html component with clipboard icon - Click copies full hash, shows green checkmark confirmation - Used in timeline entries (replaces plain hash link) Project overview: - Clone instructions card with copy-to-clipboard - Shows `fossil clone /path/to/slug.fossil` command

lmata 2026-04-07 00:44 trunk
Commit b139e8be7e1d8b08ebc72fa9d11d3a2ee9d3ffe2940a0ae4950101ae59a95d1b
--- a/templates/fossil/_copy_hash.html
+++ b/templates/fossil/_copy_hash.html
@@ -0,0 +1,12 @@
1
+{% comment %}
2
+Usage: {% include "fossil/_copy_hash.html" with hash=some_uuid slug=project.slug %}
3
+Shows a truncated hash with copy-to-clipboard button.
4
+{% endcomment %}
5
+<span class="inline-flex items-center gap-1" x-data="{ copied: false }">
6
+ <a href="{% url 'fossil:checkin_detail' slug=slug checkin_uuid=hash %}" class="font-mono text-xs text-brand-light hover:text-brand">{{ hash|truncatechars:10 }}</a>
7
+ <button @click="navigator.clipboard.writeText('{{ hash }}'); copied = true; setTimeout(() => copied = false, 1500)"
8
+ class="text-gray-600 hover:text-brand-light" title="Copy full hash">
9
+ <svg x-show="!copied" class="h-3 w-3" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" d="M15.75 17.25v3.375c0 .621-.504 1.125-1.125 1.125h-9.75a1.125 1.125 0 01-1.125-1.125V7.875c0-.621.504-1.125 1.125-1.125H6.75a9.06 9.06 0 011.5.124m7.5 10.376h3.375c.621 0 1.125-.504 1.125-1.125V11.25c0-4.46-3.243-8.161-7.5-8.876a9.06 9.06 0 00-1.5-.124H9.375c-.621 0-1.125.504-1.125 1.125v3.5m7.5 10.375H9.375a1.125 1.125 0 01-1.125-1.125v-9.25m12 6.625v-1.875a3.375 3.375 0 00-3.375-3.375h-1.5a1.125 1.125 0 01-1.125-1.125v-1.5a3.375 3.375 0 00-3.375-3.375H9.75" /></svg>
10
+ <svg x-show="copied" class="h-3 w-3 text-green-400" fill="none" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" style="display:none"><path stroke-linecap="round" stroke-linejoin="round" d="M4.5 12.75l6 6 9-13.5" /></svg>
11
+ </button>
12
+</span>
--- a/templates/fossil/_copy_hash.html
+++ b/templates/fossil/_copy_hash.html
@@ -0,0 +1,12 @@
 
 
 
 
 
 
 
 
 
 
 
 
--- a/templates/fossil/_copy_hash.html
+++ b/templates/fossil/_copy_hash.html
@@ -0,0 +1,12 @@
1 {% comment %}
2 Usage: {% include "fossil/_copy_hash.html" with hash=some_uuid slug=project.slug %}
3 Shows a truncated hash with copy-to-clipboard button.
4 {% endcomment %}
5 <span class="inline-flex items-center gap-1" x-data="{ copied: false }">
6 <a href="{% url 'fossil:checkin_detail' slug=slug checkin_uuid=hash %}" class="font-mono text-xs text-brand-light hover:text-brand">{{ hash|truncatechars:10 }}</a>
7 <button @click="navigator.clipboard.writeText('{{ hash }}'); copied = true; setTimeout(() => copied = false, 1500)"
8 class="text-gray-600 hover:text-brand-light" title="Copy full hash">
9 <svg x-show="!copied" class="h-3 w-3" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" d="M15.75 17.25v3.375c0 .621-.504 1.125-1.125 1.125h-9.75a1.125 1.125 0 01-1.125-1.125V7.875c0-.621.504-1.125 1.125-1.125H6.75a9.06 9.06 0 011.5.124m7.5 10.376h3.375c.621 0 1.125-.504 1.125-1.125V11.25c0-4.46-3.243-8.161-7.5-8.876a9.06 9.06 0 00-1.5-.124H9.375c-.621 0-1.125.504-1.125 1.125v3.5m7.5 10.375H9.375a1.125 1.125 0 01-1.125-1.125v-9.25m12 6.625v-1.875a3.375 3.375 0 00-3.375-3.375h-1.5a1.125 1.125 0 01-1.125-1.125v-1.5a3.375 3.375 0 00-3.375-3.375H9.75" /></svg>
10 <svg x-show="copied" class="h-3 w-3 text-green-400" fill="none" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" style="display:none"><path stroke-linecap="round" stroke-linejoin="round" d="M4.5 12.75l6 6 9-13.5" /></svg>
11 </button>
12 </span>
--- templates/fossil/partials/timeline_entries.html
+++ templates/fossil/partials/timeline_entries.html
@@ -75,11 +75,11 @@
7575
</div>
7676
7777
{# Meta: hash, user, branch #}
7878
<div class="tl-meta">
7979
{% if e.event_type == "ci" %}
80
- <a href="{% url 'fossil:checkin_detail' slug=project.slug checkin_uuid=e.uuid %}" class="tl-hash">{{ e.uuid|truncatechars:10 }}</a>
80
+ {% include "fossil/_copy_hash.html" with hash=e.uuid slug=project.slug %}
8181
{% endif %}
8282
<a href="{% url 'fossil:user_activity' slug=project.slug username=e.user %}" class="tl-user">{{ e.user }}</a>
8383
{% if e.branch %}<span class="tl-branch">{{ e.branch }}</span>{% endif %}
8484
</div>
8585
</div>
8686
--- templates/fossil/partials/timeline_entries.html
+++ templates/fossil/partials/timeline_entries.html
@@ -75,11 +75,11 @@
75 </div>
76
77 {# Meta: hash, user, branch #}
78 <div class="tl-meta">
79 {% if e.event_type == "ci" %}
80 <a href="{% url 'fossil:checkin_detail' slug=project.slug checkin_uuid=e.uuid %}" class="tl-hash">{{ e.uuid|truncatechars:10 }}</a>
81 {% endif %}
82 <a href="{% url 'fossil:user_activity' slug=project.slug username=e.user %}" class="tl-user">{{ e.user }}</a>
83 {% if e.branch %}<span class="tl-branch">{{ e.branch }}</span>{% endif %}
84 </div>
85 </div>
86
--- templates/fossil/partials/timeline_entries.html
+++ templates/fossil/partials/timeline_entries.html
@@ -75,11 +75,11 @@
75 </div>
76
77 {# Meta: hash, user, branch #}
78 <div class="tl-meta">
79 {% if e.event_type == "ci" %}
80 {% include "fossil/_copy_hash.html" with hash=e.uuid slug=project.slug %}
81 {% endif %}
82 <a href="{% url 'fossil:user_activity' slug=project.slug username=e.user %}" class="tl-user">{{ e.user }}</a>
83 {% if e.branch %}<span class="tl-branch">{{ e.branch }}</span>{% endif %}
84 </div>
85 </div>
86
--- templates/fossil/timeline.html
+++ templates/fossil/timeline.html
@@ -24,15 +24,23 @@
2424
</div>
2525
2626
{% include "fossil/partials/timeline_entries.html" %}
2727
2828
{% if entries|length == 50 %}
29
-<div class="mt-4 text-center">
30
- <a href="{% url 'fossil:timeline' slug=project.slug %}?page={{ page|add:1 }}{% if event_type %}&type={{ event_type }}{% endif %}"
31
- class="inline-flex items-center rounded-md bg-gray-800 px-4 py-2 text-sm text-gray-400 hover:text-white border border-gray-700">
32
- Load more
33
- </a>
29
+<div id="load-more" class="mt-4 text-center"
30
+ hx-get="{% url 'fossil:timeline' slug=project.slug %}?page={{ page|add:1 }}{% if event_type %}&type={{ event_type }}{% endif %}"
31
+ hx-trigger="revealed"
32
+ hx-target="#timeline-entries"
33
+ hx-swap="beforeend"
34
+ hx-select="#timeline-entries > *"
35
+ hx-indicator="#load-spinner">
36
+ <div id="load-spinner" class="htmx-indicator">
37
+ <div class="inline-flex items-center gap-2 text-sm text-gray-500">
38
+ <svg class="animate-spin h-4 w-4" fill="none" viewBox="0 0 24 24"><circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle><path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4z"></path></svg>
39
+ Loading more...
40
+ </div>
41
+ </div>
3442
</div>
3543
{% endif %}
3644
3745
<script>
3846
// Keyboard navigation: j/k to move between timeline entries
3947
--- templates/fossil/timeline.html
+++ templates/fossil/timeline.html
@@ -24,15 +24,23 @@
24 </div>
25
26 {% include "fossil/partials/timeline_entries.html" %}
27
28 {% if entries|length == 50 %}
29 <div class="mt-4 text-center">
30 <a href="{% url 'fossil:timeline' slug=project.slug %}?page={{ page|add:1 }}{% if event_type %}&type={{ event_type }}{% endif %}"
31 class="inline-flex items-center rounded-md bg-gray-800 px-4 py-2 text-sm text-gray-400 hover:text-white border border-gray-700">
32 Load more
33 </a>
 
 
 
 
 
 
 
 
34 </div>
35 {% endif %}
36
37 <script>
38 // Keyboard navigation: j/k to move between timeline entries
39
--- templates/fossil/timeline.html
+++ templates/fossil/timeline.html
@@ -24,15 +24,23 @@
24 </div>
25
26 {% include "fossil/partials/timeline_entries.html" %}
27
28 {% if entries|length == 50 %}
29 <div id="load-more" class="mt-4 text-center"
30 hx-get="{% url 'fossil:timeline' slug=project.slug %}?page={{ page|add:1 }}{% if event_type %}&type={{ event_type }}{% endif %}"
31 hx-trigger="revealed"
32 hx-target="#timeline-entries"
33 hx-swap="beforeend"
34 hx-select="#timeline-entries > *"
35 hx-indicator="#load-spinner">
36 <div id="load-spinner" class="htmx-indicator">
37 <div class="inline-flex items-center gap-2 text-sm text-gray-500">
38 <svg class="animate-spin h-4 w-4" fill="none" viewBox="0 0 24 24"><circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle><path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4z"></path></svg>
39 Loading more...
40 </div>
41 </div>
42 </div>
43 {% endif %}
44
45 <script>
46 // Keyboard navigation: j/k to move between timeline entries
47
--- templates/projects/project_detail.html
+++ templates/projects/project_detail.html
@@ -120,10 +120,23 @@
120120
<a href="{% url 'fossil:wiki' slug=project.slug %}" class="text-sm font-medium text-gray-200 hover:text-brand-light">{{ repo_stats.wiki_page_count|default:"0" }}</a>
121121
</div>
122122
</div>
123123
</div>
124124
{% endif %}
125
+
126
+ <!-- Clone instructions -->
127
+ <div class="rounded-lg bg-gray-800 border border-gray-700 p-4" x-data="{ copied: false }">
128
+ <h3 class="text-sm font-medium text-gray-300 mb-2">Clone</h3>
129
+ <div class="flex items-center gap-2">
130
+ <code class="flex-1 text-xs font-mono text-gray-400 bg-gray-900 rounded px-3 py-2 truncate">fossil clone /path/to/{{ project.slug }}.fossil</code>
131
+ <button @click="navigator.clipboard.writeText('fossil clone /path/to/{{ project.slug }}.fossil'); copied = true; setTimeout(() => copied = false, 1500)"
132
+ class="flex-shrink-0 rounded px-2 py-2 text-gray-500 hover:text-brand-light hover:bg-gray-700">
133
+ <svg x-show="!copied" 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="M15.75 17.25v3.375c0 .621-.504 1.125-1.125 1.125h-9.75a1.125 1.125 0 01-1.125-1.125V7.875c0-.621.504-1.125 1.125-1.125H6.75a9.06 9.06 0 011.5.124m7.5 10.376h3.375c.621 0 1.125-.504 1.125-1.125V11.25c0-4.46-3.243-8.161-7.5-8.876a9.06 9.06 0 00-1.5-.124H9.375c-.621 0-1.125.504-1.125 1.125v3.5m7.5 10.375H9.375a1.125 1.125 0 01-1.125-1.125v-9.25m12 6.625v-1.875a3.375 3.375 0 00-3.375-3.375h-1.5a1.125 1.125 0 01-1.125-1.125v-1.5a3.375 3.375 0 00-3.375-3.375H9.75" /></svg>
134
+ <svg x-show="copied" class="h-4 w-4 text-green-400" fill="none" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" style="display:none"><path stroke-linecap="round" stroke-linejoin="round" d="M4.5 12.75l6 6 9-13.5" /></svg>
135
+ </button>
136
+ </div>
137
+ </div>
125138
126139
{% if top_contributors %}
127140
<!-- Contributors -->
128141
<div class="rounded-lg bg-gray-800 border border-gray-700 p-4">
129142
<h3 class="text-sm font-medium text-gray-300 mb-3">Top Contributors</h3>
130143
--- templates/projects/project_detail.html
+++ templates/projects/project_detail.html
@@ -120,10 +120,23 @@
120 <a href="{% url 'fossil:wiki' slug=project.slug %}" class="text-sm font-medium text-gray-200 hover:text-brand-light">{{ repo_stats.wiki_page_count|default:"0" }}</a>
121 </div>
122 </div>
123 </div>
124 {% endif %}
 
 
 
 
 
 
 
 
 
 
 
 
 
125
126 {% if top_contributors %}
127 <!-- Contributors -->
128 <div class="rounded-lg bg-gray-800 border border-gray-700 p-4">
129 <h3 class="text-sm font-medium text-gray-300 mb-3">Top Contributors</h3>
130
--- templates/projects/project_detail.html
+++ templates/projects/project_detail.html
@@ -120,10 +120,23 @@
120 <a href="{% url 'fossil:wiki' slug=project.slug %}" class="text-sm font-medium text-gray-200 hover:text-brand-light">{{ repo_stats.wiki_page_count|default:"0" }}</a>
121 </div>
122 </div>
123 </div>
124 {% endif %}
125
126 <!-- Clone instructions -->
127 <div class="rounded-lg bg-gray-800 border border-gray-700 p-4" x-data="{ copied: false }">
128 <h3 class="text-sm font-medium text-gray-300 mb-2">Clone</h3>
129 <div class="flex items-center gap-2">
130 <code class="flex-1 text-xs font-mono text-gray-400 bg-gray-900 rounded px-3 py-2 truncate">fossil clone /path/to/{{ project.slug }}.fossil</code>
131 <button @click="navigator.clipboard.writeText('fossil clone /path/to/{{ project.slug }}.fossil'); copied = true; setTimeout(() => copied = false, 1500)"
132 class="flex-shrink-0 rounded px-2 py-2 text-gray-500 hover:text-brand-light hover:bg-gray-700">
133 <svg x-show="!copied" 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="M15.75 17.25v3.375c0 .621-.504 1.125-1.125 1.125h-9.75a1.125 1.125 0 01-1.125-1.125V7.875c0-.621.504-1.125 1.125-1.125H6.75a9.06 9.06 0 011.5.124m7.5 10.376h3.375c.621 0 1.125-.504 1.125-1.125V11.25c0-4.46-3.243-8.161-7.5-8.876a9.06 9.06 0 00-1.5-.124H9.375c-.621 0-1.125.504-1.125 1.125v3.5m7.5 10.375H9.375a1.125 1.125 0 01-1.125-1.125v-9.25m12 6.625v-1.875a3.375 3.375 0 00-3.375-3.375h-1.5a1.125 1.125 0 01-1.125-1.125v-1.5a3.375 3.375 0 00-3.375-3.375H9.75" /></svg>
134 <svg x-show="copied" class="h-4 w-4 text-green-400" fill="none" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" style="display:none"><path stroke-linecap="round" stroke-linejoin="round" d="M4.5 12.75l6 6 9-13.5" /></svg>
135 </button>
136 </div>
137 </div>
138
139 {% if top_contributors %}
140 <!-- Contributors -->
141 <div class="rounded-lg bg-gray-800 border border-gray-700 p-4">
142 <h3 class="text-sm font-medium text-gray-300 mb-3">Top Contributors</h3>
143

Keyboard Shortcuts

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