main 5041353

badge.go RawHistoryFolder

1package styles
2
3import (
4	"fmt"
5	"time"
6
7	"github.com/charmbracelet/lipgloss"
8)
9
10// Badge renders styled metadata badges.
11
12// VisitBadge renders a visit count badge.
13func (t *Theme) VisitBadge(count int) string {
14	text := fmt.Sprintf("%d visits", count)
15	if count == 1 {
16		text = "1 visit"
17	}
18	return t.BadgeMuted.Render(text)
19}
20
21// TimeBadge renders a relative time badge.
22func (t *Theme) TimeBadge(tm time.Time) string {
23	text := RelativeTime(tm)
24	return t.BadgeMuted.Render(text)
25}
26
27// DomainBadge renders a domain badge.
28func (t *Theme) DomainBadge(domain string) string {
29	return t.Badge.Render(domain)
30}
31
32// AccentBadge renders a badge with accent color.
33func (t *Theme) AccentBadge(text string) string {
34	return t.Badge.Render(text)
35}
36
37// MutedBadge renders a badge with muted colors.
38func (t *Theme) MutedBadge(text string) string {
39	return t.BadgeMuted.Render(text)
40}
41
42// StatusBadge renders a status badge with custom colors.
43func (t *Theme) StatusBadge(text string, fg, bg lipgloss.Color) string {
44	style := lipgloss.NewStyle()
45	if t != nil {
46		style = t.Badge
47	}
48	style = style.
49		Foreground(fg).
50		Background(bg).
51		Padding(0, 1)
52	return style.Render(text)
53}
54
55// RelativeTime formats a time as a human-readable relative string.
56func RelativeTime(tm time.Time) string {
57	now := time.Now()
58	diff := now.Sub(tm)
59	const (
60		hoursPerDay  = 24
61		daysPerWeek  = 7
62		daysPerMonth = 30
63		daysPerYear  = 365
64	)
65
66	switch {
67	case diff < time.Minute:
68		return "just now"
69	case diff < time.Hour:
70		mins := int(diff.Minutes())
71		if mins == 1 {
72			return "1m ago"
73		}
74		return fmt.Sprintf("%dm ago", mins)
75	case diff < hoursPerDay*time.Hour:
76		hours := int(diff.Hours())
77		if hours == 1 {
78			return "1h ago"
79		}
80		return fmt.Sprintf("%dh ago", hours)
81	case diff < time.Duration(daysPerWeek*hoursPerDay)*time.Hour:
82		days := int(diff.Hours() / hoursPerDay)
83		if days == 1 {
84			return "1d ago"
85		}
86		return fmt.Sprintf("%dd ago", days)
87	case diff < time.Duration(daysPerMonth*hoursPerDay)*time.Hour:
88		weeks := int(diff.Hours() / (hoursPerDay * daysPerWeek))
89		if weeks == 1 {
90			return "1w ago"
91		}
92		return fmt.Sprintf("%dw ago", weeks)
93	case diff < time.Duration(daysPerYear*hoursPerDay)*time.Hour:
94		months := int(diff.Hours() / (hoursPerDay * daysPerMonth))
95		if months == 1 {
96			return "1mo ago"
97		}
98		return fmt.Sprintf("%dmo ago", months)
99	default:
100		years := int(diff.Hours() / (24 * 365))
101		if years == 1 {
102			return "1y ago"
103		}
104		return fmt.Sprintf("%dy ago", years)
105	}
106}
107
108// TimeCategory returns which timeline category a time belongs to.
109type TimeCategory int
110
111const (
112	TimeCategoryToday TimeCategory = iota
113	TimeCategoryYesterday
114	TimeCategoryThisWeek
115	TimeCategoryOlder
116)
117
118// GetTimeCategory returns the category for a given time.
119func GetTimeCategory(tm time.Time) TimeCategory {
120	now := time.Now()
121	today := time.Date(now.Year(), now.Month(), now.Day(), 0, 0, 0, 0, now.Location())
122	yesterday := today.AddDate(0, 0, -1)
123	weekAgo := today.AddDate(0, 0, -7)
124
125	switch {
126	case tm.After(today) || tm.Equal(today):
127		return TimeCategoryToday
128	case tm.After(yesterday) || tm.Equal(yesterday):
129		return TimeCategoryYesterday
130	case tm.After(weekAgo):
131		return TimeCategoryThisWeek
132	default:
133		return TimeCategoryOlder
134	}
135}
136
137// TimeCategoryLabel returns the display label for a time category.
138func TimeCategoryLabel(cat TimeCategory) string {
139	switch cat {
140	case TimeCategoryToday:
141		return "Today"
142	case TimeCategoryYesterday:
143		return "Yesterday"
144	case TimeCategoryThisWeek:
145		return "This Week"
146	case TimeCategoryOlder:
147		return "Older"
148	default:
149		return "Unknown"
150	}
151}

Keyboard shortcuts

Single-key shortcuts are on. ctrl k and esc work whatever this says.

ctrl k :
go to a section, branch, tag, file or repository
?
this list
j k
move the row cursor down / up
enter
open the row under the cursor; on Files, a directory opens or closes in place
↑ ↓ ← → home end
in the file tree (focus on a row): previous / next row, close / open a directory or go to its parent / first entry, first / last row. They never open a file
l h
on Files: open a directory or a file / close a directory or go up to its parent, then to the parent page. Elsewhere: into the side panel (on a merge request, its files) / back to the content; there j k move between its controls
g g G
first / last row
/
filter the list; on Files, find a file anywhere in the repository (esc clears, enter opens the best match)
b
switch branch or tag
g s g f g c g b g m g i g r g a
tabs: summary, files, commits, branches & tags, merge requests, issues, releases, actions (when available)
g h
home
w
wide mode: use the whole width of the screen, or not (remembered)
y
copy a permalink pinned to the commit
d u x
diff: split / unified / collapse or expand all
e E
diff: the whole file in view / every file, with the lines the hunks hide
n
merge request: next note in the diff
esc
close a dialog, leave a field

Branches 8

Tags 42

↑↓ moveenter openesc close