FossilRepo
Redesign timeline to match Fossil/SQLite style - Date group headers (bold date + day name, border separator) - Compact single-line entries: time | DAG | message | hash user branch - Inline layout instead of card-based (dense, scannable) - Messages truncated with ellipsis, clickable to checkin detail - Clickable hashes and usernames - Color-coded inline badges for wiki/ticket/forum events - Branch pills with brand color - Smaller, tighter DAG nodes
Commit
3091ffb3ea58aa73dc9253dd1cae651ab6f9b01fd558f8200b21a691fce495a2
Parent
671ba62f918b7f9…
1 file changed
+81
-86
| --- templates/fossil/partials/timeline_entries.html | ||
| +++ templates/fossil/partials/timeline_entries.html | ||
| @@ -1,97 +1,92 @@ | ||
| 1 | 1 | <style> |
| 2 | - .dag-col { position: relative; flex-shrink: 0; } | |
| 3 | - .dag-node { | |
| 4 | - position: absolute; top: 50%; z-index: 2; border-radius: 50%; | |
| 5 | - transform: translate(-50%, -50%); | |
| 6 | - } | |
| 7 | - .dag-node-ci { width: 12px; height: 12px; background: #DC394C; border: 2px solid #e8677a; } | |
| 8 | - .dag-node-merge { width: 12px; height: 12px; background: #DC394C; border: 2px solid #e8677a; border-radius: 2px; transform: translate(-50%, -50%) rotate(45deg); } | |
| 9 | - .dag-node-w { width: 10px; height: 10px; background: #3b82f6; border: 2px solid #60a5fa; } | |
| 10 | - .dag-node-t { width: 10px; height: 10px; background: #eab308; border: 2px solid #facc15; } | |
| 11 | - .dag-node-f { width: 10px; height: 10px; background: #a855f7; border: 2px solid #c084fc; } | |
| 12 | - .dag-node-other { width: 10px; height: 10px; background: #6b7280; border: 2px solid #9ca3af; } | |
| 13 | - .dag-vline { | |
| 14 | - position: absolute; width: 2px; top: 0; bottom: 0; | |
| 15 | - transform: translateX(-50%); | |
| 16 | - } | |
| 17 | - .dag-vline-ci { background: rgba(220,57,76,0.35); } | |
| 18 | - .dag-vline-other { background: rgba(107,114,128,0.25); } | |
| 19 | - .dag-connect { | |
| 20 | - position: absolute; top: 75%; height: 25%; | |
| 21 | - border: 0; border-bottom: 2px solid rgba(220,57,76,0.35); | |
| 22 | - border-radius: 0 0 6px 0; | |
| 23 | - z-index: 1; | |
| 24 | - } | |
| 2 | + .tl-dag { position: relative; flex-shrink: 0; } | |
| 3 | + .tl-node { | |
| 4 | + position: absolute; top: 50%; z-index: 2; border-radius: 50%; | |
| 5 | + transform: translate(-50%, -50%); width: 10px; height: 10px; | |
| 6 | + } | |
| 7 | + .tl-node-ci { background: #DC394C; border: 2px solid #e8677a; } | |
| 8 | + .tl-node-merge { background: #DC394C; border: 2px solid #e8677a; border-radius: 2px; transform: translate(-50%, -50%) rotate(45deg); } | |
| 9 | + .tl-node-w { background: #3b82f6; border: 2px solid #60a5fa; width: 8px; height: 8px; } | |
| 10 | + .tl-node-t { background: #eab308; border: 2px solid #facc15; width: 8px; height: 8px; } | |
| 11 | + .tl-node-f { background: #a855f7; border: 2px solid #c084fc; width: 8px; height: 8px; } | |
| 12 | + .tl-node-other { background: #6b7280; border: 2px solid #9ca3af; width: 8px; height: 8px; } | |
| 13 | + .tl-vline { position: absolute; width: 2px; top: 0; bottom: 0; transform: translateX(-50%); } | |
| 14 | + .tl-vline-ci { background: rgba(220,57,76,0.3); } | |
| 15 | + .tl-vline-other { background: rgba(107,114,128,0.2); } | |
| 16 | + .tl-date { font-size: 0.8rem; font-weight: 700; color: #d1d5db; padding: 8px 0 4px; border-bottom: 1px solid #374151; margin-bottom: 2px; } | |
| 17 | + .tl-row { display: flex; min-height: 28px; align-items: center; } | |
| 18 | + .tl-row:hover { background: rgba(255,255,255,0.02); } | |
| 19 | + .tl-time { width: 42px; flex-shrink: 0; text-align: right; font-size: 0.75rem; color: #6b7280; font-variant-numeric: tabular-nums; } | |
| 20 | + .tl-msg { flex: 1; min-width: 0; font-size: 0.8125rem; color: #e5e5e5; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } | |
| 21 | + .tl-msg a { color: #e5e5e5; text-decoration: none; } | |
| 22 | + .tl-msg a:hover { color: #e8677a; } | |
| 23 | + .tl-meta { display: flex; align-items: center; gap: 8px; flex-shrink: 0; padding-left: 12px; } | |
| 24 | + .tl-hash { font-family: ui-monospace, monospace; font-size: 0.7rem; color: #DC394C; text-decoration: none; } | |
| 25 | + .tl-hash:hover { color: #e8677a; text-decoration: underline; } | |
| 26 | + .tl-user { font-size: 0.7rem; color: #9ca3af; text-decoration: none; } | |
| 27 | + .tl-user:hover { color: #e8677a; } | |
| 28 | + .tl-branch { font-size: 0.65rem; padding: 1px 6px; border-radius: 9999px; background: rgba(220,57,76,0.1); border: 1px solid rgba(220,57,76,0.2); color: #e8677a; white-space: nowrap; } | |
| 29 | + .tl-badge { font-size: 0.6rem; padding: 0 4px; border-radius: 3px; font-weight: 600; } | |
| 30 | + .tl-badge-w { background: rgba(59,130,246,0.15); color: #93c5fd; } | |
| 31 | + .tl-badge-t { background: rgba(234,179,8,0.15); color: #fde047; } | |
| 32 | + .tl-badge-f { background: rgba(168,85,247,0.15); color: #d8b4fe; } | |
| 25 | 33 | </style> |
| 26 | 34 | |
| 27 | 35 | <div id="timeline-entries"> |
| 28 | 36 | {% if entries %} |
| 29 | - {% for item in entries %} | |
| 30 | - <div class="flex" style="min-height: 56px;"> | |
| 31 | - | |
| 32 | - <!-- DAG graph column --> | |
| 33 | - <div class="dag-col" style="width: {{ item.graph_width }}px;"> | |
| 34 | - <!-- Vertical rail lines --> | |
| 35 | - {% for line in item.lines %} | |
| 36 | - <div class="dag-vline dag-vline-ci" style="left: {{ line.x }}px;"></div> | |
| 37 | - {% endfor %} | |
| 38 | - | |
| 39 | - <!-- Node --> | |
| 40 | - <div class="dag-node {% if item.entry.is_merge %}dag-node-merge{% elif item.entry.event_type == 'ci' %}dag-node-ci{% elif item.entry.event_type == 'w' %}dag-node-w{% elif item.entry.event_type == 't' %}dag-node-t{% elif item.entry.event_type == 'f' %}dag-node-f{% else %}dag-node-other{% endif %}" | |
| 41 | - style="left: {{ item.node_x }}px;"></div> | |
| 42 | - | |
| 43 | - <!-- Branch/merge connector --> | |
| 37 | + {% load tz %} | |
| 38 | + {% for item in entries %} | |
| 39 | + {% with e=item.entry %} | |
| 40 | + | |
| 41 | + {# Date header — show when date changes #} | |
| 42 | + {% ifchanged e.timestamp|date:"Y-m-d" %} | |
| 43 | + <div class="tl-date">{{ e.timestamp|date:"Y-m-d, l" }}</div> | |
| 44 | + {% endifchanged %} | |
| 45 | + | |
| 46 | + <div class="tl-row"> | |
| 47 | + {# DAG column #} | |
| 48 | + <div class="tl-dag" style="width: {{ item.graph_width }}px;"> | |
| 49 | + {% for line in item.lines %} | |
| 50 | + <div class="tl-vline tl-vline-ci" style="left: {{ line.x }}px;"></div> | |
| 51 | + {% endfor %} | |
| 52 | + <div class="tl-node {% if e.is_merge %}tl-node-merge{% elif e.event_type == 'ci' %}tl-node-ci{% elif e.event_type == 'w' %}tl-node-w{% elif e.event_type == 't' %}tl-node-t{% elif e.event_type == 'f' %}tl-node-f{% else %}tl-node-other{% endif %}" | |
| 53 | + style="left: {{ item.node_x }}px;"></div> | |
| 44 | 54 | {% if item.connector %} |
| 45 | - <div class="dag-connect" | |
| 46 | - style="left: {{ item.connector.left }}px; width: {{ item.connector.width }}px; | |
| 47 | - border-left: 2px solid rgba(220,57,76,0.35); | |
| 48 | - border-right: 2px solid rgba(220,57,76,0.35);"></div> | |
| 49 | - {% endif %} | |
| 50 | - </div> | |
| 51 | - | |
| 52 | - <!-- Content --> | |
| 53 | - <div class="flex-1 py-1 min-w-0"> | |
| 54 | - <div class="rounded-lg bg-gray-800 border border-gray-700 px-4 py-2.5 hover:border-gray-600 transition-colors"> | |
| 55 | - <div class="flex items-start justify-between gap-3"> | |
| 56 | - <div class="flex-1 min-w-0"> | |
| 57 | - {% if item.entry.event_type == "ci" %} | |
| 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 | - {% endif %} | |
| 70 | - {% if item.entry.event_type == "ci" %} | |
| 71 | - <a href="{% url 'fossil:checkin_detail' slug=project.slug checkin_uuid=item.entry.uuid %}" class="text-xs font-mono text-brand-light hover:text-brand">{{ item.entry.uuid|truncatechars:10 }}</a> | |
| 72 | - {% endif %} | |
| 73 | - </div> | |
| 74 | - </div> | |
| 75 | - <div class="flex-shrink-0"> | |
| 76 | - {% if item.entry.event_type == "ci" %} | |
| 77 | - <span class="inline-flex rounded-full bg-green-900/50 px-2 py-0.5 text-xs font-semibold text-green-300">checkin</span> | |
| 78 | - {% elif item.entry.event_type == "w" %} | |
| 79 | - <span class="inline-flex rounded-full bg-blue-900/50 px-2 py-0.5 text-xs font-semibold text-blue-300">wiki</span> | |
| 80 | - {% elif item.entry.event_type == "t" %} | |
| 81 | - <span class="inline-flex rounded-full bg-yellow-900/50 px-2 py-0.5 text-xs font-semibold text-yellow-300">ticket</span> | |
| 82 | - {% elif item.entry.event_type == "f" %} | |
| 83 | - <span class="inline-flex rounded-full bg-purple-900/50 px-2 py-0.5 text-xs font-semibold text-purple-300">forum</span> | |
| 84 | - {% else %} | |
| 85 | - <span class="inline-flex rounded-full bg-gray-700 px-2 py-0.5 text-xs font-semibold text-gray-300">{{ item.entry.event_type }}</span> | |
| 86 | - {% endif %} | |
| 87 | - </div> | |
| 88 | - </div> | |
| 89 | - </div> | |
| 90 | - </div> | |
| 91 | - | |
| 92 | - </div> | |
| 55 | + <div style="position:absolute; top:75%; height:25%; left:{{ item.connector.left }}px; width:{{ item.connector.width }}px; border-bottom:2px solid rgba(220,57,76,0.3); border-left:2px solid rgba(220,57,76,0.3); border-right:2px solid rgba(220,57,76,0.3); border-radius:0 0 4px 4px; z-index:1;"></div> | |
| 56 | + {% endif %} | |
| 57 | + </div> | |
| 58 | + | |
| 59 | + {# Time #} | |
| 60 | + <div class="tl-time">{{ e.timestamp|date:"H:i" }}</div> | |
| 61 | + | |
| 62 | + {# Message #} | |
| 63 | + <div class="tl-msg" style="padding-left: 10px;"> | |
| 64 | + {% if e.event_type == "ci" %} | |
| 65 | + <a href="{% url 'fossil:checkin_detail' slug=project.slug checkin_uuid=e.uuid %}">{{ e.comment|default:"(no comment)"|truncatechars:90 }}</a> | |
| 66 | + {% elif e.event_type == "w" %} | |
| 67 | + <span class="tl-badge tl-badge-w">wiki</span> {{ e.comment|default:"(no comment)"|truncatechars:80 }} | |
| 68 | + {% elif e.event_type == "t" %} | |
| 69 | + <span class="tl-badge tl-badge-t">ticket</span> {{ e.comment|default:"(no comment)"|truncatechars:80 }} | |
| 70 | + {% elif e.event_type == "f" %} | |
| 71 | + <span class="tl-badge tl-badge-f">forum</span> {{ e.comment|default:"(no comment)"|truncatechars:80 }} | |
| 72 | + {% else %} | |
| 73 | + {{ e.comment|default:"(no comment)"|truncatechars:80 }} | |
| 74 | + {% endif %} | |
| 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 | + | |
| 87 | + {% endwith %} | |
| 93 | 88 | {% endfor %} |
| 94 | 89 | {% else %} |
| 95 | 90 | <p class="text-sm text-gray-500 py-8 text-center">No timeline entries.</p> |
| 96 | 91 | {% endif %} |
| 97 | 92 | </div> |
| 98 | 93 |
| --- templates/fossil/partials/timeline_entries.html | |
| +++ templates/fossil/partials/timeline_entries.html | |
| @@ -1,97 +1,92 @@ | |
| 1 | <style> |
| 2 | .dag-col { position: relative; flex-shrink: 0; } |
| 3 | .dag-node { |
| 4 | position: absolute; top: 50%; z-index: 2; border-radius: 50%; |
| 5 | transform: translate(-50%, -50%); |
| 6 | } |
| 7 | .dag-node-ci { width: 12px; height: 12px; background: #DC394C; border: 2px solid #e8677a; } |
| 8 | .dag-node-merge { width: 12px; height: 12px; background: #DC394C; border: 2px solid #e8677a; border-radius: 2px; transform: translate(-50%, -50%) rotate(45deg); } |
| 9 | .dag-node-w { width: 10px; height: 10px; background: #3b82f6; border: 2px solid #60a5fa; } |
| 10 | .dag-node-t { width: 10px; height: 10px; background: #eab308; border: 2px solid #facc15; } |
| 11 | .dag-node-f { width: 10px; height: 10px; background: #a855f7; border: 2px solid #c084fc; } |
| 12 | .dag-node-other { width: 10px; height: 10px; background: #6b7280; border: 2px solid #9ca3af; } |
| 13 | .dag-vline { |
| 14 | position: absolute; width: 2px; top: 0; bottom: 0; |
| 15 | transform: translateX(-50%); |
| 16 | } |
| 17 | .dag-vline-ci { background: rgba(220,57,76,0.35); } |
| 18 | .dag-vline-other { background: rgba(107,114,128,0.25); } |
| 19 | .dag-connect { |
| 20 | position: absolute; top: 75%; height: 25%; |
| 21 | border: 0; border-bottom: 2px solid rgba(220,57,76,0.35); |
| 22 | border-radius: 0 0 6px 0; |
| 23 | z-index: 1; |
| 24 | } |
| 25 | </style> |
| 26 | |
| 27 | <div id="timeline-entries"> |
| 28 | {% if entries %} |
| 29 | {% for item in entries %} |
| 30 | <div class="flex" style="min-height: 56px;"> |
| 31 | |
| 32 | <!-- DAG graph column --> |
| 33 | <div class="dag-col" style="width: {{ item.graph_width }}px;"> |
| 34 | <!-- Vertical rail lines --> |
| 35 | {% for line in item.lines %} |
| 36 | <div class="dag-vline dag-vline-ci" style="left: {{ line.x }}px;"></div> |
| 37 | {% endfor %} |
| 38 | |
| 39 | <!-- Node --> |
| 40 | <div class="dag-node {% if item.entry.is_merge %}dag-node-merge{% elif item.entry.event_type == 'ci' %}dag-node-ci{% elif item.entry.event_type == 'w' %}dag-node-w{% elif item.entry.event_type == 't' %}dag-node-t{% elif item.entry.event_type == 'f' %}dag-node-f{% else %}dag-node-other{% endif %}" |
| 41 | style="left: {{ item.node_x }}px;"></div> |
| 42 | |
| 43 | <!-- Branch/merge connector --> |
| 44 | {% if item.connector %} |
| 45 | <div class="dag-connect" |
| 46 | style="left: {{ item.connector.left }}px; width: {{ item.connector.width }}px; |
| 47 | border-left: 2px solid rgba(220,57,76,0.35); |
| 48 | border-right: 2px solid rgba(220,57,76,0.35);"></div> |
| 49 | {% endif %} |
| 50 | </div> |
| 51 | |
| 52 | <!-- Content --> |
| 53 | <div class="flex-1 py-1 min-w-0"> |
| 54 | <div class="rounded-lg bg-gray-800 border border-gray-700 px-4 py-2.5 hover:border-gray-600 transition-colors"> |
| 55 | <div class="flex items-start justify-between gap-3"> |
| 56 | <div class="flex-1 min-w-0"> |
| 57 | {% if item.entry.event_type == "ci" %} |
| 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 | {% endif %} |
| 70 | {% if item.entry.event_type == "ci" %} |
| 71 | <a href="{% url 'fossil:checkin_detail' slug=project.slug checkin_uuid=item.entry.uuid %}" class="text-xs font-mono text-brand-light hover:text-brand">{{ item.entry.uuid|truncatechars:10 }}</a> |
| 72 | {% endif %} |
| 73 | </div> |
| 74 | </div> |
| 75 | <div class="flex-shrink-0"> |
| 76 | {% if item.entry.event_type == "ci" %} |
| 77 | <span class="inline-flex rounded-full bg-green-900/50 px-2 py-0.5 text-xs font-semibold text-green-300">checkin</span> |
| 78 | {% elif item.entry.event_type == "w" %} |
| 79 | <span class="inline-flex rounded-full bg-blue-900/50 px-2 py-0.5 text-xs font-semibold text-blue-300">wiki</span> |
| 80 | {% elif item.entry.event_type == "t" %} |
| 81 | <span class="inline-flex rounded-full bg-yellow-900/50 px-2 py-0.5 text-xs font-semibold text-yellow-300">ticket</span> |
| 82 | {% elif item.entry.event_type == "f" %} |
| 83 | <span class="inline-flex rounded-full bg-purple-900/50 px-2 py-0.5 text-xs font-semibold text-purple-300">forum</span> |
| 84 | {% else %} |
| 85 | <span class="inline-flex rounded-full bg-gray-700 px-2 py-0.5 text-xs font-semibold text-gray-300">{{ item.entry.event_type }}</span> |
| 86 | {% endif %} |
| 87 | </div> |
| 88 | </div> |
| 89 | </div> |
| 90 | </div> |
| 91 | |
| 92 | </div> |
| 93 | {% endfor %} |
| 94 | {% else %} |
| 95 | <p class="text-sm text-gray-500 py-8 text-center">No timeline entries.</p> |
| 96 | {% endif %} |
| 97 | </div> |
| 98 |
| --- templates/fossil/partials/timeline_entries.html | |
| +++ templates/fossil/partials/timeline_entries.html | |
| @@ -1,97 +1,92 @@ | |
| 1 | <style> |
| 2 | .tl-dag { position: relative; flex-shrink: 0; } |
| 3 | .tl-node { |
| 4 | position: absolute; top: 50%; z-index: 2; border-radius: 50%; |
| 5 | transform: translate(-50%, -50%); width: 10px; height: 10px; |
| 6 | } |
| 7 | .tl-node-ci { background: #DC394C; border: 2px solid #e8677a; } |
| 8 | .tl-node-merge { background: #DC394C; border: 2px solid #e8677a; border-radius: 2px; transform: translate(-50%, -50%) rotate(45deg); } |
| 9 | .tl-node-w { background: #3b82f6; border: 2px solid #60a5fa; width: 8px; height: 8px; } |
| 10 | .tl-node-t { background: #eab308; border: 2px solid #facc15; width: 8px; height: 8px; } |
| 11 | .tl-node-f { background: #a855f7; border: 2px solid #c084fc; width: 8px; height: 8px; } |
| 12 | .tl-node-other { background: #6b7280; border: 2px solid #9ca3af; width: 8px; height: 8px; } |
| 13 | .tl-vline { position: absolute; width: 2px; top: 0; bottom: 0; transform: translateX(-50%); } |
| 14 | .tl-vline-ci { background: rgba(220,57,76,0.3); } |
| 15 | .tl-vline-other { background: rgba(107,114,128,0.2); } |
| 16 | .tl-date { font-size: 0.8rem; font-weight: 700; color: #d1d5db; padding: 8px 0 4px; border-bottom: 1px solid #374151; margin-bottom: 2px; } |
| 17 | .tl-row { display: flex; min-height: 28px; align-items: center; } |
| 18 | .tl-row:hover { background: rgba(255,255,255,0.02); } |
| 19 | .tl-time { width: 42px; flex-shrink: 0; text-align: right; font-size: 0.75rem; color: #6b7280; font-variant-numeric: tabular-nums; } |
| 20 | .tl-msg { flex: 1; min-width: 0; font-size: 0.8125rem; color: #e5e5e5; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } |
| 21 | .tl-msg a { color: #e5e5e5; text-decoration: none; } |
| 22 | .tl-msg a:hover { color: #e8677a; } |
| 23 | .tl-meta { display: flex; align-items: center; gap: 8px; flex-shrink: 0; padding-left: 12px; } |
| 24 | .tl-hash { font-family: ui-monospace, monospace; font-size: 0.7rem; color: #DC394C; text-decoration: none; } |
| 25 | .tl-hash:hover { color: #e8677a; text-decoration: underline; } |
| 26 | .tl-user { font-size: 0.7rem; color: #9ca3af; text-decoration: none; } |
| 27 | .tl-user:hover { color: #e8677a; } |
| 28 | .tl-branch { font-size: 0.65rem; padding: 1px 6px; border-radius: 9999px; background: rgba(220,57,76,0.1); border: 1px solid rgba(220,57,76,0.2); color: #e8677a; white-space: nowrap; } |
| 29 | .tl-badge { font-size: 0.6rem; padding: 0 4px; border-radius: 3px; font-weight: 600; } |
| 30 | .tl-badge-w { background: rgba(59,130,246,0.15); color: #93c5fd; } |
| 31 | .tl-badge-t { background: rgba(234,179,8,0.15); color: #fde047; } |
| 32 | .tl-badge-f { background: rgba(168,85,247,0.15); color: #d8b4fe; } |
| 33 | </style> |
| 34 | |
| 35 | <div id="timeline-entries"> |
| 36 | {% if entries %} |
| 37 | {% load tz %} |
| 38 | {% for item in entries %} |
| 39 | {% with e=item.entry %} |
| 40 | |
| 41 | {# Date header — show when date changes #} |
| 42 | {% ifchanged e.timestamp|date:"Y-m-d" %} |
| 43 | <div class="tl-date">{{ e.timestamp|date:"Y-m-d, l" }}</div> |
| 44 | {% endifchanged %} |
| 45 | |
| 46 | <div class="tl-row"> |
| 47 | {# DAG column #} |
| 48 | <div class="tl-dag" style="width: {{ item.graph_width }}px;"> |
| 49 | {% for line in item.lines %} |
| 50 | <div class="tl-vline tl-vline-ci" style="left: {{ line.x }}px;"></div> |
| 51 | {% endfor %} |
| 52 | <div class="tl-node {% if e.is_merge %}tl-node-merge{% elif e.event_type == 'ci' %}tl-node-ci{% elif e.event_type == 'w' %}tl-node-w{% elif e.event_type == 't' %}tl-node-t{% elif e.event_type == 'f' %}tl-node-f{% else %}tl-node-other{% endif %}" |
| 53 | style="left: {{ item.node_x }}px;"></div> |
| 54 | {% if item.connector %} |
| 55 | <div style="position:absolute; top:75%; height:25%; left:{{ item.connector.left }}px; width:{{ item.connector.width }}px; border-bottom:2px solid rgba(220,57,76,0.3); border-left:2px solid rgba(220,57,76,0.3); border-right:2px solid rgba(220,57,76,0.3); border-radius:0 0 4px 4px; z-index:1;"></div> |
| 56 | {% endif %} |
| 57 | </div> |
| 58 | |
| 59 | {# Time #} |
| 60 | <div class="tl-time">{{ e.timestamp|date:"H:i" }}</div> |
| 61 | |
| 62 | {# Message #} |
| 63 | <div class="tl-msg" style="padding-left: 10px;"> |
| 64 | {% if e.event_type == "ci" %} |
| 65 | <a href="{% url 'fossil:checkin_detail' slug=project.slug checkin_uuid=e.uuid %}">{{ e.comment|default:"(no comment)"|truncatechars:90 }}</a> |
| 66 | {% elif e.event_type == "w" %} |
| 67 | <span class="tl-badge tl-badge-w">wiki</span> {{ e.comment|default:"(no comment)"|truncatechars:80 }} |
| 68 | {% elif e.event_type == "t" %} |
| 69 | <span class="tl-badge tl-badge-t">ticket</span> {{ e.comment|default:"(no comment)"|truncatechars:80 }} |
| 70 | {% elif e.event_type == "f" %} |
| 71 | <span class="tl-badge tl-badge-f">forum</span> {{ e.comment|default:"(no comment)"|truncatechars:80 }} |
| 72 | {% else %} |
| 73 | {{ e.comment|default:"(no comment)"|truncatechars:80 }} |
| 74 | {% endif %} |
| 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 | |
| 87 | {% endwith %} |
| 88 | {% endfor %} |
| 89 | {% else %} |
| 90 | <p class="text-sm text-gray-500 py-8 text-center">No timeline entries.</p> |
| 91 | {% endif %} |
| 92 | </div> |
| 93 |