8 files changed,
+694,
-54
1@@ -0,0 +1,533 @@
2+# Client-Side Time Humanization Implementation Plan
3+
4+> **For Claude:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task.
5+
6+**Goal:** Replace server-side time humanization with client-side JavaScript to ensure relative times remain accurate as pages age.
7+
8+**Architecture:** Add ISO 8601 timestamp fields to data structures, update templates to include `data-time` attributes with default MMM dd fallback text, and add inline JavaScript to the base layout that queries for these elements and updates their text content dynamically with relative times (minutes/hours/days ago).
9+
10+**Tech Stack:** Go templates, vanilla JavaScript (ES5-compatible for broad browser support), ISO 8601 timestamps.
11+
12+---
13+
14+## Prerequisites
15+
16+- The codebase is a Go static site generator using embedded templates
17+- Templates are in `html/*.tmpl` files
18+- Current time humanization uses `humanize.Time()` from Go
19+- Output is static HTML files
20+
21+---
22+
23+## Task 1: Add ISO Timestamp Fields to Data Structures
24+
25+**Files:**
26+- Modify: `main.go:121-131` (CommitData struct)
27+- Modify: `main.go:133-149` (TreeItem struct)
28+- Modify: `issues.go:14-27` (IssueData struct)
29+- Modify: `issues.go:29-35` (CommentData struct)
30+
31+**Step 1: Add WhenISO field to CommitData struct**
32+
33+Add field after `WhenStr`:
34+
35+```go
36+type CommitData struct {
37+ SummaryStr string
38+ URL template.URL
39+ WhenStr string
40+ WhenISO string // ISO 8601 format for JavaScript parsing
41+ HumanWhenStr string
42+ AuthorStr string
43+ ShortID string
44+ ParentID string
45+ Refs []*RefInfo
46+ *git.Commit
47+}
48+```
49+
50+**Step 2: Add WhenISO field to TreeItem struct**
51+
52+Add field after `When`:
53+
54+```go
55+type TreeItem struct {
56+ IsTextFile bool
57+ IsDir bool
58+ Size string
59+ NumLines int
60+ Name string
61+ Icon string
62+ Path string
63+ URL template.URL
64+ CommitID string
65+ CommitURL template.URL
66+ Summary string
67+ When string
68+ WhenISO string // ISO 8601 format for JavaScript parsing
69+ Author *git.Signature
70+ Entry *git.TreeEntry
71+ Crumbs []*Breadcrumb
72+}
73+```
74+
75+**Step 3: Add CreatedAtISO field to IssueData struct**
76+
77+```go
78+type IssueData struct {
79+ ID string
80+ FullID string
81+ Title string
82+ Status string
83+ Author string
84+ CreatedAt string
85+ CreatedAtISO string // ISO 8601 format for JavaScript parsing
86+ HumanDate string
87+ Labels []string
88+ CommentCount int
89+ Description template.HTML
90+ Comments []CommentData
91+ URL template.URL
92+}
93+```
94+
95+**Step 4: Add CreatedAtISO field to CommentData struct**
96+
97+```go
98+type CommentData struct {
99+ Author string
100+ CreatedAt string
101+ CreatedAtISO string // ISO 8601 format for JavaScript parsing
102+ HumanDate string
103+ Body template.HTML
104+}
105+```
106+
107+**Step 5: Commit**
108+
109+```bash
110+jj commit -m "feat: add ISO timestamp fields to data structures"
111+```
112+
113+---
114+
115+## Task 2: Populate ISO Timestamp Fields in CommitData
116+
117+**Files:**
118+- Modify: `main.go:1157-1168`
119+
120+**Step 1: Update commit data population**
121+
122+Find the `CommitData` initialization around line 1157 and add `WhenISO`:
123+
124+```go
125+cd := &CommitData{
126+ ParentID: parentID,
127+ URL: c.getCommitURL(commit.ID.String()),
128+ ShortID: getShortID(commit.ID.String()),
129+ SummaryStr: commit.Summary(),
130+ AuthorStr: commit.Author.Name,
131+ WhenStr: commit.Author.When.Format(time.DateOnly),
132+ WhenISO: commit.Author.When.Format(time.RFC3339),
133+ HumanWhenStr: humanizeTime(commit.Author.When),
134+ Commit: commit,
135+ Refs: tags,
136+}
137+```
138+
139+**Step 2: Commit**
140+
141+```bash
142+jj commit -m "feat: populate WhenISO field for commits"
143+```
144+
145+---
146+
147+## Task 3: Populate ISO Timestamp Fields in TreeItem
148+
149+**Files:**
150+- Modify: `main.go:1001-1055` (NewTreeItem function)
151+
152+**Step 1: Update TreeItem population**
153+
154+Find where `item.When` is set (around line 1032) and add `WhenISO`:
155+
156+```go
157+if len(lastCommits) > 0 {
158+ lc := lastCommits[0]
159+ item.CommitURL = tw.Config.getCommitURL(lc.ID.String())
160+ item.CommitID = getShortID(lc.ID.String())
161+ item.Summary = lc.Summary()
162+ item.When = lc.Author.When.Format(time.DateOnly)
163+ item.WhenISO = lc.Author.When.Format(time.RFC3339)
164+ item.Author = lc.Author
165+}
166+```
167+
168+**Step 2: Commit**
169+
170+```bash
171+jj commit -m "feat: populate WhenISO field for tree items"
172+```
173+
174+---
175+
176+## Task 4: Populate ISO Timestamp Fields in IssueData and CommentData
177+
178+**Files:**
179+- Modify: `issues.go:95-130`
180+
181+**Step 1: Update CommentData population**
182+
183+Find where `comments` slice is built and add `CreatedAtISO`:
184+
185+```go
186+comments = append(comments, CommentData{
187+ Author: comment.Author.Name(),
188+ CreatedAt: comment.FormatTime(),
189+ CreatedAtISO: comment.FormatTimeRFC3339(),
190+ HumanDate: comment.FormatTimeRel(),
191+ Body: c.renderMarkdown(comment.Message),
192+})
193+```
194+
195+**Note:** `FormatTimeRFC3339()` may not exist on the git-bug comment type. Check if available, otherwise use:
196+```go
197+CreatedAtISO: comment.UnixTime.Format(time.RFC3339),
198+```
199+
200+Or check what methods are available on the comment object.
201+
202+**Step 2: Update IssueData population**
203+
204+Find where `IssueData` is created (around line 116-130) and add `CreatedAtISO`:
205+
206+```go
207+issue := &IssueData{
208+ ID: getShortID(fullID),
209+ FullID: fullID,
210+ Title: snap.Title,
211+ Status: snap.Status.String(),
212+ Author: snap.Author.Name(),
213+ CreatedAt: snap.CreateTime.Format("Mon Jan 2 15:04:05 2006 -0700"),
214+ CreatedAtISO: snap.CreateTime.Format(time.RFC3339),
215+ HumanDate: humanizeTime(snap.CreateTime),
216+ Labels: labels,
217+ CommentCount: commentCount,
218+ Description: description,
219+ Comments: comments,
220+ URL: c.getIssueURL(fullID),
221+}
222+```
223+
224+**Step 3: Commit**
225+
226+```bash
227+jj commit -m "feat: populate ISO timestamp fields for issues and comments"
228+```
229+
230+---
231+
232+## Task 5: Update Header Partial Template
233+
234+**Files:**
235+- Modify: `html/header.partial.tmpl:34-36`
236+
237+**Step 1: Update last commit bar time display**
238+
239+Replace:
240+```html
241+<div class="last-commit-bar__time" title="{{.LastCommit.WhenStr}}">
242+ {{.LastCommit.HumanWhenStr}}
243+</div>
244+```
245+
246+With:
247+```html
248+<div class="last-commit-bar__time" title="{{.LastCommit.WhenStr}}">
249+ <span class="human-time" data-time="{{.LastCommit.WhenISO}}">{{.LastCommit.WhenStr}}</span>
250+</div>
251+```
252+
253+**Step 2: Commit**
254+
255+```bash
256+jj commit -m "feat: add data-time attribute to header timestamp"
257+```
258+
259+---
260+
261+## Task 6: Update Tree Page Template
262+
263+**Files:**
264+- Modify: `html/tree.page.tmpl:38`
265+
266+**Step 1: Update file listing timestamp**
267+
268+Replace:
269+```html
270+<a href="{{.CommitURL}}" title="{{.Summary}}">{{.When}}</a>
271+```
272+
273+With:
274+```html
275+<a href="{{.CommitURL}}" title="{{.Summary}}">
276+ <span class="human-time" data-time="{{.WhenISO}}">{{.When}}</span>
277+</a>
278+```
279+
280+**Step 2: Commit**
281+
282+```bash
283+jj commit -m "feat: add data-time attribute to tree view timestamps"
284+```
285+
286+---
287+
288+## Task 7: Update Issues List Template
289+
290+**Files:**
291+- Modify: `html/issues_list.page.tmpl:52`
292+
293+**Step 1: Update issue list timestamp**
294+
295+Replace:
296+```html
297+<span class="issue-date" title="{{.CreatedAt}}">{{.HumanDate}}</span>
298+```
299+
300+With:
301+```html
302+<span class="issue-date" title="{{.CreatedAt}}">
303+ <span class="human-time" data-time="{{.CreatedAtISO}}">{{.CreatedAt}}</span>
304+</span>
305+```
306+
307+**Step 2: Commit**
308+
309+```bash
310+jj commit -m "feat: add data-time attribute to issues list timestamps"
311+```
312+
313+---
314+
315+## Task 8: Update Issue Detail Template
316+
317+**Files:**
318+- Modify: `html/issue_detail.page.tmpl:16,30,43`
319+
320+**Step 1: Update issue header timestamp (line 16)**
321+
322+Replace:
323+```html
324+<span class="issue-date" title="{{.Issue.CreatedAt}}">{{.Issue.HumanDate}}</span>
325+```
326+
327+With:
328+```html
329+<span class="issue-date" title="{{.Issue.CreatedAt}}">
330+ <span class="human-time" data-time="{{.Issue.CreatedAtISO}}">{{.Issue.CreatedAt}}</span>
331+</span>
332+```
333+
334+**Step 2: Update issue description timestamp (line 30)**
335+
336+Replace:
337+```html
338+<span class="issue-comment__date" title="{{.Issue.CreatedAt}}">{{.Issue.HumanDate}}</span>
339+```
340+
341+With:
342+```html
343+<span class="issue-comment__date" title="{{.Issue.CreatedAt}}">
344+ <span class="human-time" data-time="{{.Issue.CreatedAtISO}}">{{.Issue.CreatedAt}}</span>
345+</span>
346+```
347+
348+**Step 3: Update comment timestamp (line 43)**
349+
350+Replace:
351+```html
352+<span class="issue-comment__date" title="{{.CreatedAt}}">{{.HumanDate}}</span>
353+```
354+
355+With:
356+```html
357+<span class="issue-comment__date" title="{{.CreatedAt}}">
358+ <span class="human-time" data-time="{{.CreatedAtISO}}">{{.CreatedAt}}</span>
359+</span>
360+```
361+
362+**Step 4: Commit**
363+
364+```bash
365+jj commit -m "feat: add data-time attributes to issue detail timestamps"
366+```
367+
368+---
369+
370+## Task 9: Add JavaScript to Base Layout
371+
372+**Files:**
373+- Modify: `html/base.layout.tmpl:15-21`
374+
375+**Step 1: Add inline JavaScript before closing body tag**
376+
377+Add this script before the closing `</body>` tag:
378+
379+```html
380+<script>
381+(function() {
382+ var MINUTE_MS = 60000;
383+ var HOUR_MS = 3600000;
384+ var DAY_MS = 86400000;
385+ var MONTH_MS = 30 * DAY_MS;
386+
387+ function updateTimes() {
388+ var elements = document.querySelectorAll('[data-time]');
389+ var now = new Date();
390+ var minDiffMs = Infinity;
391+
392+ elements.forEach(function(el) {
393+ var date = new Date(el.getAttribute('data-time'));
394+ var diffMs = now - date;
395+
396+ // Track the smallest difference for interval calculation
397+ if (diffMs < minDiffMs && diffMs >= 0) {
398+ minDiffMs = diffMs;
399+ }
400+
401+ var diffMins = Math.floor(diffMs / MINUTE_MS);
402+ var diffHours = Math.floor(diffMs / HOUR_MS);
403+ var diffDays = Math.floor(diffMs / DAY_MS);
404+
405+ var text;
406+ if (diffMins < 1) {
407+ text = 'just now';
408+ } else if (diffMins < 60) {
409+ text = diffMins + ' minute' + (diffMins === 1 ? '' : 's') + ' ago';
410+ } else if (diffHours < 24) {
411+ text = diffHours + ' hour' + (diffHours === 1 ? '' : 's') + ' ago';
412+ } else if (diffDays < 30) {
413+ text = diffDays + ' day' + (diffDays === 1 ? '' : 's') + ' ago';
414+ } else {
415+ // Keep default MMM dd format (already in element text)
416+ return;
417+ }
418+ el.textContent = text;
419+ });
420+
421+ return minDiffMs;
422+ }
423+
424+ function scheduleUpdate() {
425+ var minDiffMs = updateTimes();
426+ var intervalMs;
427+
428+ // Determine interval based on smallest time difference
429+ if (minDiffMs < HOUR_MS) {
430+ // Smallest diff is in minutes - update every minute
431+ intervalMs = MINUTE_MS;
432+ } else if (minDiffMs < DAY_MS) {
433+ // Smallest diff is in hours - update every hour
434+ intervalMs = HOUR_MS;
435+ } else if (minDiffMs < MONTH_MS) {
436+ // Smallest diff is in days - update every day
437+ intervalMs = DAY_MS;
438+ } else {
439+ // All timestamps are > 30 days, no updates needed
440+ return;
441+ }
442+
443+ setTimeout(scheduleUpdate, intervalMs);
444+ }
445+
446+ scheduleUpdate();
447+})();
448+</script>
449+```
450+
451+**Rationale:** This approach dynamically adjusts the update interval based on the granularity needed:
452+- If the most recent timestamp is < 1 hour old โ update every minute (to track "X minutes ago")
453+- If the most recent timestamp is 1-24 hours old โ update every hour (to track "X hours ago")
454+- If the most recent timestamp is 1-30 days old โ update every day (to track "X days ago")
455+- If all timestamps are > 30 days โ no interval needed (static dates don't change)
456+
457+**Step 2: Commit**
458+
459+```bash
460+jj commit -m "feat: add client-side time humanization JavaScript"
461+```
462+
463+---
464+
465+## Task 10: Build and Test
466+
467+**Files:**
468+- Run: Build commands
469+- Verify: Output HTML in testdata.site/
470+
471+**Step 1: Build the project**
472+
473+```bash
474+cd /home/btburke/projects/pgit
475+go build -o pgit .
476+```
477+
478+Expected: Successful build with no errors.
479+
480+**Step 2: Regenerate test site**
481+
482+```bash
483+./pgit --repo ./testdata --out ./testdata.site --revs main,branch-c --home-url https://test.com/test --clone-url https://test.com/test/test2 --issues
484+```
485+
486+Expected: Site generates successfully.
487+
488+**Step 3: Verify HTML output**
489+
490+Check `testdata.site/index.html`:
491+- Should contain `<span class="human-time" data-time="2026-04-...">`
492+- Should contain the inline JavaScript
493+
494+Check `testdata.site/issues/open/index.html`:
495+- Should contain `data-time` attributes on issue timestamps
496+
497+Check `testdata.site/logs/main/index.html`:
498+- Should NOT have data-time (log page uses WhenStr without humanization currently - this is acceptable)
499+
500+**Step 4: Manual browser test**
501+
502+Open `testdata.site/index.html` in a browser:
503+- The timestamp should show relative time (e.g., "2 days ago") if the commit is recent
504+- Or it should show the default date format ("2026-04-07") if older than 30 days
505+- View page source to verify `data-time` attributes are present
506+
507+**Step 5: Commit**
508+
509+```bash
510+jj commit -m "chore: regenerate test site with client-side time humanization"
511+```
512+
513+---
514+
515+## Summary
516+
517+This plan implements client-side time humanization by:
518+
519+1. Adding ISO 8601 timestamp fields to all data structures that display times
520+2. Populating those fields using `time.RFC3339` format
521+3. Wrapping time display elements with `<span class="human-time" data-time="...">`
522+4. Adding vanilla JavaScript to the base layout that:
523+ - Queries all `[data-time]` elements
524+ - Parses the ISO timestamp
525+ - Calculates relative time (minutes/hours/days ago)
526+ - Updates text content dynamically
527+ - Dynamically schedules updates based on the smallest time difference on the page
528+ - Uses minute/hour/day intervals as appropriate to minimize unnecessary work
529+
530+**Edge cases handled:**
531+- Times < 1 minute show "just now"
532+- Times > 30 days keep the default MMM dd format
533+- No-JS users see the default date format (graceful degradation)
534+- Uses ES5-compatible syntax for broad browser support
+69,
-0
1@@ -18,6 +18,75 @@
2 <main>{{template "content" .}}</main>
3 <hr />
4 <footer>{{template "footer" .}}</footer>
5+ <script>
6+ (function() {
7+ var MINUTE_MS = 60000;
8+ var HOUR_MS = 3600000;
9+ var DAY_MS = 86400000;
10+ var MONTH_MS = 30 * DAY_MS;
11+
12+ function updateTimes() {
13+ var elements = document.querySelectorAll('[data-time]');
14+ var now = new Date();
15+ var minDiffMs = Infinity;
16+
17+ elements.forEach(function(el) {
18+ var date = new Date(el.getAttribute('data-time'));
19+ var diffMs = now - date;
20+
21+ // Track the smallest difference for interval calculation
22+ if (diffMs < minDiffMs && diffMs >= 0) {
23+ minDiffMs = diffMs;
24+ }
25+
26+ var diffMins = Math.floor(diffMs / MINUTE_MS);
27+ var diffHours = Math.floor(diffMs / HOUR_MS);
28+ var diffDays = Math.floor(diffMs / DAY_MS);
29+
30+ var text;
31+ if (diffMins < 1) {
32+ text = 'just now';
33+ } else if (diffMins < 60) {
34+ text = diffMins + ' minute' + (diffMins === 1 ? '' : 's') + ' ago';
35+ } else if (diffHours < 24) {
36+ text = diffHours + ' hour' + (diffHours === 1 ? '' : 's') + ' ago';
37+ } else if (diffDays < 30) {
38+ text = diffDays + ' day' + (diffDays === 1 ? '' : 's') + ' ago';
39+ } else {
40+ // Keep default MMM dd format (already in element text)
41+ return;
42+ }
43+ el.textContent = text;
44+ });
45+
46+ return minDiffMs;
47+ }
48+
49+ function scheduleUpdate() {
50+ var minDiffMs = updateTimes();
51+ var intervalMs;
52+
53+ // Determine interval based on smallest time difference
54+ if (minDiffMs < HOUR_MS) {
55+ // Smallest diff is in minutes - update every minute
56+ intervalMs = MINUTE_MS;
57+ } else if (minDiffMs < DAY_MS) {
58+ // Smallest diff is in hours - update every hour
59+ intervalMs = HOUR_MS;
60+ } else if (minDiffMs < MONTH_MS) {
61+ // Smallest diff is in days - update every day
62+ intervalMs = DAY_MS;
63+ } else {
64+ // All timestamps are > 30 days, no updates needed
65+ return;
66+ }
67+
68+ setTimeout(scheduleUpdate, intervalMs);
69+ }
70+
71+ scheduleUpdate();
72+ })();
73+ </script>
74 </body>
75 </html>
76 {{end}}
+2,
-2
1@@ -31,8 +31,8 @@
2 <span> · </span>
3 <a href="{{.LastCommit.URL}}">{{.LastCommit.SummaryStr}}</a>
4 </div>
5- <div class="last-commit-bar__time" title="{{.LastCommit.WhenStr}}">
6- {{.LastCommit.HumanWhenStr}}
7+ <div class="last-commit-bar__time">
8+ <span class="human-time" data-time="{{.LastCommit.WhenISO}}">{{.LastCommit.WhenDisplay}}</span>
9 </div>
10 </div>
11 {{end}}
+9,
-3
1@@ -13,7 +13,9 @@
2 <div class="issue-detail__meta">
3 <span class="issue-id">#{{.Issue.ID}}</span>
4 <span class="issue-author">opened by {{.Issue.Author}}</span>
5- <span class="issue-date" title="{{.Issue.CreatedAt}}">{{.Issue.HumanDate}}</span>
6+ <span class="issue-date">
7+ <span class="human-time" data-time="{{.Issue.CreatedAtISO}}">{{.Issue.CreatedAtDisp}}</span>
8+ </span>
9 </div>
10 {{if .Issue.Labels}}
11 <div class="issue-detail__labels">
12@@ -27,7 +29,9 @@
13 <div class="issue-comment">
14 <div class="issue-comment__header">
15 <span class="issue-comment__author">{{.Issue.Author}}</span>
16- <span class="issue-comment__date" title="{{.Issue.CreatedAt}}">{{.Issue.HumanDate}}</span>
17+ <span class="issue-comment__date">
18+ <span class="human-time" data-time="{{.Issue.CreatedAtISO}}">{{.Issue.CreatedAtDisp}}</span>
19+ </span>
20 </div>
21 <div class="issue-comment__body markdown">{{.Issue.Description}}</div>
22 </div>
23@@ -40,7 +44,9 @@
24 <div class="issue-comment">
25 <div class="issue-comment__header">
26 <span class="issue-comment__author">{{.Author}}</span>
27- <span class="issue-comment__date" title="{{.CreatedAt}}">{{.HumanDate}}</span>
28+ <span class="issue-comment__date">
29+ <span class="human-time" data-time="{{.CreatedAtISO}}">{{.CreatedAtDisp}}</span>
30+ </span>
31 </div>
32 <div class="issue-comment__body markdown">{{.Body}}</div>
33 </div>
+3,
-1
1@@ -49,7 +49,9 @@
2 <span class="issue-meta">#{{.ID}}{{if gt .CommentCount 0}} ยท {{.CommentCount}} comment{{if ne .CommentCount 1}}s{{end}}{{end}}</span>
3 </div>
4 <div class="issue-item__stats">
5- <span class="issue-date" title="{{.CreatedAt}}">{{.HumanDate}}</span>
6+ <span class="issue-date">
7+ <span class="human-time" data-time="{{.CreatedAtISO}}">{{.CreatedAtDisp}}</span>
8+ </span>
9 {{if .Labels}}
10 <span class="issue-labels-text">{{range $i, $label := .Labels}}{{if $i}}, {{end}}{{$label}}{{end}}</span>
11 {{end}}
+3,
-1
1@@ -35,7 +35,9 @@
2 {{if $.Repo.HideTreeLastCommit}}
3 {{else}}
4 <div class="file-list__commit">
5- <a href="{{.CommitURL}}" title="{{.Summary}}">{{.When}}</a>
6+ <a href="{{.CommitURL}}" title="{{.Summary}}">
7+ <span class="human-time" data-time="{{.WhenISO}}">{{.WhenDisplay}}</span>
8+ </a>
9 </div>
10 {{end}}
11 <div class="file-list__size">
+43,
-32
1@@ -5,6 +5,7 @@ import (
2 "html/template"
3 "net/url"
4 "path/filepath"
5+ "time"
6
7 "github.com/git-bug/git-bug/entities/bug"
8 "github.com/git-bug/git-bug/repository"
9@@ -12,26 +13,30 @@ import (
10
11 // IssueData represents a git-bug issue for templates
12 type IssueData struct {
13- ID string
14- FullID string
15- Title string
16- Status string // "open" or "closed"
17- Author string
18- CreatedAt string
19- HumanDate string
20- Labels []string
21- CommentCount int // excludes original description
22- Description template.HTML
23- Comments []CommentData
24- URL template.URL
25+ ID string
26+ FullID string
27+ Title string
28+ Status string // "open" or "closed"
29+ Author string
30+ CreatedAt string
31+ CreatedAtISO string
32+ CreatedAtDisp string
33+ HumanDate string
34+ Labels []string
35+ CommentCount int // excludes original description
36+ Description template.HTML
37+ Comments []CommentData
38+ URL template.URL
39 }
40
41 // CommentData represents a comment on an issue
42 type CommentData struct {
43- Author string
44- CreatedAt string
45- HumanDate string
46- Body template.HTML
47+ Author string
48+ CreatedAt string
49+ CreatedAtISO string
50+ CreatedAtDisp string
51+ HumanDate string
52+ Body template.HTML
53 }
54
55 // IssuesListPageData for list pages (open, closed, by label)
56@@ -98,11 +103,15 @@ func (c *Config) loadIssues() ([]*IssueData, error) {
57 // Skip the original description
58 continue
59 }
60+ // Parse RFC1123 format and convert to ISO 8601 (UTC) for JavaScript
61+ createdAtTime, _ := time.Parse("Mon Jan 2 15:04:05 2006 -0700", comment.FormatTime())
62 comments = append(comments, CommentData{
63- Author: comment.Author.Name(),
64- CreatedAt: comment.FormatTime(),
65- HumanDate: comment.FormatTimeRel(),
66- Body: c.renderMarkdown(comment.Message),
67+ Author: comment.Author.Name(),
68+ CreatedAt: comment.FormatTime(),
69+ CreatedAtISO: createdAtTime.UTC().Format(time.RFC3339),
70+ CreatedAtDisp: formatDateForDisplay(createdAtTime),
71+ HumanDate: comment.FormatTimeRel(),
72+ Body: c.renderMarkdown(comment.Message),
73 })
74 }
75
76@@ -114,18 +123,20 @@ func (c *Config) loadIssues() ([]*IssueData, error) {
77
78 fullID := b.Id().String()
79 issue := &IssueData{
80- ID: getShortID(fullID),
81- FullID: fullID,
82- Title: snap.Title,
83- Status: snap.Status.String(),
84- Author: snap.Author.Name(),
85- CreatedAt: snap.CreateTime.Format("Mon Jan 2 15:04:05 2006 -0700"),
86- HumanDate: humanizeTime(snap.CreateTime),
87- Labels: labels,
88- CommentCount: commentCount,
89- Description: description,
90- Comments: comments,
91- URL: c.getIssueURL(fullID),
92+ ID: getShortID(fullID),
93+ FullID: fullID,
94+ Title: snap.Title,
95+ Status: snap.Status.String(),
96+ Author: snap.Author.Name(),
97+ CreatedAt: snap.CreateTime.Format("Mon Jan 2 15:04:05 2006 -0700"),
98+ CreatedAtISO: snap.CreateTime.UTC().Format(time.RFC3339),
99+ CreatedAtDisp: formatDateForDisplay(snap.CreateTime),
100+ HumanDate: humanizeTime(snap.CreateTime),
101+ Labels: labels,
102+ CommentCount: commentCount,
103+ Description: description,
104+ Comments: comments,
105+ URL: c.getIssueURL(fullID),
106 }
107 issues = append(issues, issue)
108 }
M
main.go
+32,
-15
1@@ -122,6 +122,8 @@ type CommitData struct {
2 SummaryStr string
3 URL template.URL
4 WhenStr string
5+ WhenISO string
6+ WhenDisplay string
7 HumanWhenStr string
8 AuthorStr string
9 ShortID string
10@@ -131,21 +133,23 @@ type CommitData struct {
11 }
12
13 type TreeItem struct {
14- IsTextFile bool
15- IsDir bool
16- Size string
17- NumLines int
18- Name string
19- Icon string
20- Path string
21- URL template.URL
22- CommitID string
23- CommitURL template.URL
24- Summary string
25- When string
26- Author *git.Signature
27- Entry *git.TreeEntry
28- Crumbs []*Breadcrumb
29+ IsTextFile bool
30+ IsDir bool
31+ Size string
32+ NumLines int
33+ Name string
34+ Icon string
35+ Path string
36+ URL template.URL
37+ CommitID string
38+ CommitURL template.URL
39+ Summary string
40+ When string
41+ WhenISO string
42+ WhenDisplay string
43+ Author *git.Signature
44+ Entry *git.TreeEntry
45+ Crumbs []*Breadcrumb
46 }
47
48 type DiffRender struct {
49@@ -451,6 +455,15 @@ func humanizeTime(t time.Time) string {
50 return humanize.Time(t)
51 }
52
53+// formatDateForDisplay returns a static date format for non-JS display:
54+// "Jan 2" for dates less than a year ago, "Jan 2006" for older dates
55+func formatDateForDisplay(t time.Time) string {
56+ if time.Since(t).Hours() > 365*24 {
57+ return t.Format("Jan 2006")
58+ }
59+ return t.Format("Jan 2")
60+}
61+
62 func repoName(root string) string {
63 _, file := filepath.Split(root)
64 return file
65@@ -1030,6 +1043,8 @@ func (tw *TreeWalker) NewTreeItem(entry *git.TreeEntry, curpath string, crumbs [
66 item.CommitID = getShortID(lc.ID.String())
67 item.Summary = lc.Summary()
68 item.When = lc.Author.When.Format(time.DateOnly)
69+ item.WhenISO = lc.Author.When.UTC().Format(time.RFC3339)
70+ item.WhenDisplay = formatDateForDisplay(lc.Author.When)
71 item.Author = lc.Author
72 }
73 }
74@@ -1161,6 +1176,8 @@ func (c *Config) writeRevision(repo *git.Repository, pageData *PageData, refs []
75 SummaryStr: commit.Summary(),
76 AuthorStr: commit.Author.Name,
77 WhenStr: commit.Author.When.Format(time.DateOnly),
78+ WhenISO: commit.Author.When.UTC().Format(time.RFC3339),
79+ WhenDisplay: formatDateForDisplay(commit.Author.When),
80 HumanWhenStr: humanizeTime(commit.Author.When),
81 Commit: commit,
82 Refs: tags,