Commit e6f79a5

Nick Faro committed on
Fix commit diff rendering
commit e6f79a596bcb10946139951262050143fe95d061 parent 5ef0a2d
5 changed files +201−40
Modifiedinternal/site/diff.go +139−2
@@ -3,10 +3,14 @@ package site
3 import ( 3 import (
4 "context" 4 "context"
5 "fmt" 5 "fmt"
6 "html/template"
6 "path" 7 "path"
7 "regexp" 8 "regexp"
8 "strconv" 9 "strconv"
9 "strings" 10 "strings"
11
12 "github.com/alecthomas/chroma/v2"
13 "github.com/alecthomas/chroma/v2/lexers"
10 ) 14 )
11 15
12 const ( 16 const (
@@ -27,6 +31,8 @@ type CommitView struct {
27 Commit Commit 31 Commit Commit
28 Parents []CommitParent 32 Parents []CommitParent
29 Files []DiffFile 33 Files []DiffFile
34 FileCount int
35 Binaries int
30 Additions int 36 Additions int
31 Deletions int 37 Deletions int
32 Truncated bool 38 Truncated bool
@@ -52,6 +58,8 @@ type DiffRow struct {
52 NewNumber int 58 NewNumber int
53 OldText string 59 OldText string
54 NewText string 60 NewText string
61 OldCode template.HTML
62 NewCode template.HTML
55 OldClass string 63 OldClass string
56 NewClass string 64 NewClass string
57 OldPresent bool 65 OldPresent bool
@@ -144,8 +152,15 @@ func (g *Generator) commitPage(ctx context.Context, branch string, commit Commit
144 } 152 }
145 view.Truncated = truncated 153 view.Truncated = truncated
146 if !truncated { 154 if !truncated {
147 view.Files = parsePatch(patch) 155 parsed := parsePatch(patch)
148 for _, file := range view.Files { 156 view.FileCount = len(parsed)
157 for _, file := range parsed {
158 if file.Binary {
159 view.Binaries++
160 continue
161 }
162 highlightDiffFile(&file)
163 view.Files = append(view.Files, file)
149 view.Additions += file.Additions 164 view.Additions += file.Additions
150 view.Deletions += file.Deletions 165 view.Deletions += file.Deletions
151 } 166 }
@@ -165,6 +180,128 @@ func (g *Generator) commitPage(ctx context.Context, branch string, commit Commit
165 }, nil 180 }, nil
166 } 181 }
167 182
183 func highlightDiffFile(file *DiffFile) {
184 filename := file.NewPath
185 if filename == "" {
186 filename = file.OldPath
187 }
188 lexer := lexers.Match(filename)
189 if lexer == nil {
190 setPlainDiffCode(file)
191 return
192 }
193 lexer = chroma.Coalesce(lexer)
194 for start := 0; start < len(file.Rows); {
195 if file.Rows[start].Hunk {
196 start++
197 continue
198 }
199 end := start
200 for end < len(file.Rows) && !file.Rows[end].Hunk {
201 end++
202 }
203 highlightDiffSide(file.Rows[start:end], lexer, true)
204 highlightDiffSide(file.Rows[start:end], lexer, false)
205 start = end
206 }
207 }
208
209 func setPlainDiffCode(file *DiffFile) {
210 for index := range file.Rows {
211 if file.Rows[index].OldPresent {
212 file.Rows[index].OldCode = template.HTML(template.HTMLEscapeString(file.Rows[index].OldText))
213 }
214 if file.Rows[index].NewPresent {
215 file.Rows[index].NewCode = template.HTML(template.HTMLEscapeString(file.Rows[index].NewText))
216 }
217 }
218 }
219
220 func highlightDiffSide(rows []DiffRow, lexer chroma.Lexer, old bool) {
221 var indexes []int
222 var source strings.Builder
223 for index, row := range rows {
224 present, value := row.NewPresent, row.NewText
225 if old {
226 present, value = row.OldPresent, row.OldText
227 }
228 if !present {
229 continue
230 }
231 indexes = append(indexes, index)
232 source.WriteString(value)
233 source.WriteByte('\n')
234 }
235 if len(indexes) == 0 {
236 return
237 }
238 iterator, err := lexer.Tokenise(nil, source.String())
239 if err != nil {
240 setPlainDiffSide(rows, indexes, old)
241 return
242 }
243 lines := chroma.SplitTokensIntoLines(iterator.Tokens())
244 if len(lines) != len(indexes) {
245 setPlainDiffSide(rows, indexes, old)
246 return
247 }
248 for offset, tokens := range lines {
249 formatted := formatDiffTokens(tokens)
250 if old {
251 rows[indexes[offset]].OldCode = formatted
252 } else {
253 rows[indexes[offset]].NewCode = formatted
254 }
255 }
256 }
257
258 func setPlainDiffSide(rows []DiffRow, indexes []int, old bool) {
259 for _, index := range indexes {
260 value := rows[index].NewText
261 if old {
262 value = rows[index].OldText
263 }
264 formatted := template.HTML(template.HTMLEscapeString(value))
265 if old {
266 rows[index].OldCode = formatted
267 } else {
268 rows[index].NewCode = formatted
269 }
270 }
271 }
272
273 func formatDiffTokens(tokens []chroma.Token) template.HTML {
274 var result strings.Builder
275 for _, token := range tokens {
276 value := strings.TrimSuffix(token.Value, "\n")
277 if value == "" {
278 continue
279 }
280 escaped := template.HTMLEscapeString(value)
281 class := diffTokenClass(token.Type)
282 if class == "" {
283 result.WriteString(escaped)
284 continue
285 }
286 result.WriteString(`<span class="diff-`)
287 result.WriteString(class)
288 result.WriteString(`">`)
289 result.WriteString(escaped)
290 result.WriteString(`</span>`)
291 }
292 return template.HTML(result.String())
293 }
294
295 func diffTokenClass(tokenType chroma.TokenType) string {
296 for tokenType != 0 {
297 if class, ok := chroma.StandardTypes[tokenType]; ok {
298 return class
299 }
300 tokenType = tokenType.Parent()
301 }
302 return chroma.StandardTypes[tokenType]
303 }
304
168 func parsePatch(patch []byte) []DiffFile { 305 func parsePatch(patch []byte) []DiffFile {
169 var files []DiffFile 306 var files []DiffFile
170 var current *DiffFile 307 var current *DiffFile
Modifiedinternal/site/site.go +23−9
@@ -60,6 +60,8 @@ type Generator struct {
60 submoduleCache map[string]map[string]string 60 submoduleCache map[string]map[string]string
61 commitMu sync.Mutex 61 commitMu sync.Mutex
62 generated map[string]struct{} 62 generated map[string]struct{}
63 assetOnce sync.Once
64 assetVersion string
63 } 65 }
64 66
65 type Commit struct { 67 type Commit struct {
@@ -134,6 +136,7 @@ type pageData struct {
134 IsSubmodule bool 136 IsSubmodule bool
135 SubmoduleURL string 137 SubmoduleURL string
136 SubmoduleOID string 138 SubmoduleOID string
139 AssetVersion string
137 } 140 }
138 141
139 func New(git gitutil.Git, baseURL, repoName, owner string) (*Generator, error) { 142 func New(git gitutil.Git, baseURL, repoName, owner string) (*Generator, error) {
@@ -159,12 +162,15 @@ func New(git gitutil.Git, baseURL, repoName, owner string) (*Generator, error) {
159 } 162 }
160 163
161 func (g *Generator) AssetVersion() string { 164 func (g *Generator) AssetVersion() string {
162 hash := sha256.New() 165 g.assetOnce.Do(func() {
163 _, _ = hash.Write([]byte(siteCSS)) 166 hash := sha256.New()
164 _, _ = hash.Write([]byte(g.chromaCSS())) 167 _, _ = hash.Write([]byte(siteCSS))
165 _, _ = hash.Write(gabrielMarkSVG) 168 _, _ = hash.Write([]byte(g.chromaCSS()))
166 _, _ = hash.Write(gabrielMarkPNG) 169 _, _ = hash.Write(gabrielMarkSVG)
167 return fmt.Sprintf("%x", hash.Sum(nil)) 170 _, _ = hash.Write(gabrielMarkPNG)
171 g.assetVersion = fmt.Sprintf("%x", hash.Sum(nil))
172 })
173 return g.assetVersion
168 } 174 }
169 175
170 func (g *Generator) SetAssetsCurrent(current bool) { 176 func (g *Generator) SetAssetsCurrent(current bool) {
@@ -662,11 +668,18 @@ func highlight(filename string, content []byte) (template.HTML, string, error) {
662 } 668 }
663 669
664 func (g *Generator) chromaCSS() string { 670 func (g *Generator) chromaCSS() string {
665 formatter := html.New(html.WithClasses(true)) 671 formatter := html.New(html.WithClasses(true), html.WithAllClasses(true))
666 var light, dark bytes.Buffer 672 diffFormatter := html.New(html.WithClasses(true), html.WithAllClasses(true), html.ClassPrefix("diff-"))
673 var light, diffLight, dark, diffDark bytes.Buffer
667 _ = formatter.WriteCSS(&light, styles.Get("github")) 674 _ = formatter.WriteCSS(&light, styles.Get("github"))
675 _ = diffFormatter.WriteCSS(&diffLight, styles.Get("github"))
668 _ = formatter.WriteCSS(&dark, styles.Get("github-dark")) 676 _ = formatter.WriteCSS(&dark, styles.Get("github-dark"))
669 return "\n" + light.String() + "\n@media (prefers-color-scheme: dark) {\n" + dark.String() + "}\n" 677 _ = diffFormatter.WriteCSS(&diffDark, styles.Get("github-dark"))
678 // Keep the light rules out of the dark cascade. Some dark Chroma tokens
679 // intentionally inherit the theme's base colour, so there is no explicit
680 // dark rule to override an always-on light token rule.
681 return "\n@media (prefers-color-scheme: light) {\n" + light.String() + diffLight.String() + "}\n" +
682 "@media (prefers-color-scheme: dark) {\n" + dark.String() + diffDark.String() + "}\n"
670 } 683 }
671 684
672 func (g *Generator) render(data pageData) ([]byte, error) { 685 func (g *Generator) render(data pageData) ([]byte, error) {
@@ -701,6 +714,7 @@ func (g *Generator) pageData(kind, title, branch string, summary branchSummary)
701 Languages: summary.Repository.Languages, 714 Languages: summary.Repository.Languages,
702 Contributors: summary.Repository.Contributors, 715 Contributors: summary.Repository.Contributors,
703 ContributorCount: summary.Repository.ContributorCount, 716 ContributorCount: summary.Repository.ContributorCount,
717 AssetVersion: g.AssetVersion(),
704 } 718 }
705 } 719 }
706 720
Modifiedinternal/site/site_test.go +7−1
@@ -21,6 +21,7 @@ func TestFullAndIncrementalBuild(t *testing.T) {
21 runGit(t, dir, "config", "user.email", "[email protected]") 21 runGit(t, dir, "config", "user.email", "[email protected]")
22 write(t, dir, "README.md", "# Hello bucket\n") 22 write(t, dir, "README.md", "# Hello bucket\n")
23 write(t, dir, "LICENSE", "test license\n") 23 write(t, dir, "LICENSE", "test license\n")
24 write(t, dir, "payload.bin", "binary\x00payload\n")
24 write(t, dir, "danger.html", "<!doctype html><script>alert('nope')</script>\n") 25 write(t, dir, "danger.html", "<!doctype html><script>alert('nope')</script>\n")
25 write(t, dir, "docs/guide.md", "# Guide\n\n- first\n- second\n") 26 write(t, dir, "docs/guide.md", "# Guide\n\n- first\n- second\n")
26 write(t, dir, "src/main.go", "package main\n\nfunc main() {}\n") 27 write(t, dir, "src/main.go", "package main\n\nfunc main() {}\n")
@@ -46,6 +47,9 @@ func TestFullAndIncrementalBuild(t *testing.T) {
46 assertPage(t, full, "", "data-branch-menu") 47 assertPage(t, full, "", "data-branch-menu")
47 assertPage(t, full, "", "data-copied-icon") 48 assertPage(t, full, "", "data-copied-icon")
48 assertPage(t, full, "", ".dumbforge/assets/gabriel-mark.svg") 49 assertPage(t, full, "", ".dumbforge/assets/gabriel-mark.svg")
50 assertPage(t, full, "", `.dumbforge/assets/site.css?v=`+generator.AssetVersion())
51 assertPage(t, full, ".dumbforge/assets/site.css", "@media (prefers-color-scheme: light)")
52 assertPage(t, full, ".dumbforge/assets/site.css", ".commit-list-message { overflow: hidden; color: var(--text);")
49 assertPage(t, full, "", `<span class="header-owner">owner</span><span>/</span>`) 53 assertPage(t, full, "", `<span class="header-owner">owner</span><span>/</span>`)
50 assertPageDoesNotContain(t, full, "", `href="https://example.invalid/repo.git">owner</a>`) 54 assertPageDoesNotContain(t, full, "", `href="https://example.invalid/repo.git">owner</a>`)
51 assertPage(t, full, "", "Languages") 55 assertPage(t, full, "", "Languages")
@@ -79,6 +83,7 @@ func TestFullAndIncrementalBuild(t *testing.T) {
79 assertPage(t, full, "commit/"+first, `aria-label="Side-by-side diff for README.md"`) 83 assertPage(t, full, "commit/"+first, `aria-label="Side-by-side diff for README.md"`)
80 assertPage(t, full, "commit/"+first, `class="diff-code new addition`) 84 assertPage(t, full, "commit/"+first, `class="diff-code new addition`)
81 assertPage(t, full, "commit/"+first, "A longer explanation of the initial change.") 85 assertPage(t, full, "commit/"+first, "A longer explanation of the initial change.")
86 assertPageDoesNotContain(t, full, "commit/"+first, "payload.bin")
82 assertPage(t, full, ".dumbforge/files/main.json", `"path":"src/main.go"`) 87 assertPage(t, full, ".dumbforge/files/main.json", `"path":"src/main.go"`)
83 assertPageDoesNotContain(t, full, "", "Search this repository") 88 assertPageDoesNotContain(t, full, "", "Search this repository")
84 assertPageDoesNotContain(t, full, "", ">Docs<") 89 assertPageDoesNotContain(t, full, "", ">Docs<")
@@ -107,7 +112,8 @@ func TestFullAndIncrementalBuild(t *testing.T) {
107 assertPage(t, incremental, "", `commit/`+first+`" title="initial">initial</a>`) 112 assertPage(t, incremental, "", `commit/`+first+`" title="initial">initial</a>`)
108 assertPage(t, incremental, "commits/main", "change one file") 113 assertPage(t, incremental, "commits/main", "change one file")
109 assertPage(t, incremental, "commit/"+second, `class="diff-code old deletion`) 114 assertPage(t, incremental, "commit/"+second, `class="diff-code old deletion`)
110 assertPage(t, incremental, "commit/"+second, `println(&#34;changed&#34;)`) 115 assertPage(t, incremental, "commit/"+second, `class="diff-kd"`)
116 assertPage(t, incremental, "commit/"+second, `changed`)
111 assertPage(t, incremental, "commit/"+second, `parent <a href="https://example.invalid/repo.git/commit/`+first+`"><code>`) 117 assertPage(t, incremental, "commit/"+second, `parent <a href="https://example.invalid/repo.git/commit/`+first+`"><code>`)
112 assertNoPage(t, incremental, "commit/"+first) 118 assertNoPage(t, incremental, "commit/"+first)
113 119
Modifiedinternal/site/style.go +17−12
@@ -23,9 +23,9 @@ const siteCSS = `:root {
23 --selected-bg: #ddf4ff; 23 --selected-bg: #ddf4ff;
24 --attention-bg: #fff8c5; 24 --attention-bg: #fff8c5;
25 --diff-add: #dafbe1; 25 --diff-add: #dafbe1;
26 --diff-add-number: #aceebb; 26 --diff-add-number: #ccffd8;
27 --diff-delete: #ffebe9; 27 --diff-delete: #ffebe9;
28 --diff-delete-number: #ffc1ba; 28 --diff-delete-number: #ffd7d5;
29 --diff-hunk: #ddf4ff; 29 --diff-hunk: #ddf4ff;
30 --diff-empty: #f6f8fa; 30 --diff-empty: #f6f8fa;
31 --checker-a: #f6f8fa; 31 --checker-a: #f6f8fa;
@@ -280,7 +280,8 @@ button, input { font: inherit; }
280 .commit-list-row:target:hover { background: var(--attention-bg); } 280 .commit-list-row:target:hover { background: var(--attention-bg); }
281 .commit-list-row .commit-avatar { width: 34px; height: 34px; } 281 .commit-list-row .commit-avatar { width: 34px; height: 34px; }
282 .commit-list-body { min-width: 0; display: flex; flex-direction: column; } 282 .commit-list-body { min-width: 0; display: flex; flex-direction: column; }
283 .commit-list-message { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } 283 .commit-list-message { overflow: hidden; color: var(--text); font-weight: 600; text-overflow: ellipsis; white-space: nowrap; }
284 .commit-list-message:hover, .commit-list-message:focus { color: var(--text); text-decoration: underline; }
284 .commit-list-body > span { color: var(--muted); font-size: 12px; } 285 .commit-list-body > span { color: var(--muted); font-size: 12px; }
285 .commit-list-body > span strong { color: var(--text); } 286 .commit-list-body > span strong { color: var(--text); }
286 .commit-sha { padding: 4px 8px; border: 1px solid var(--border); border-radius: 6px; color: var(--text); background: var(--canvas); font: 12px ui-monospace, SFMono-Regular, Consolas, monospace; } 287 .commit-sha { padding: 4px 8px; border: 1px solid var(--border); border-radius: 6px; color: var(--text); background: var(--canvas); font: 12px ui-monospace, SFMono-Regular, Consolas, monospace; }
@@ -291,17 +292,17 @@ button, input { font: inherit; }
291 .commit-page-heading h1 { margin: 0; font-size: 24px; font-weight: 500; } 292 .commit-page-heading h1 { margin: 0; font-size: 24px; font-weight: 500; }
292 .commit-page-heading code { padding: 3px 7px; border-radius: 6px; background: var(--neutral-muted); font: 20px ui-monospace, SFMono-Regular, Consolas, monospace; } 293 .commit-page-heading code { padding: 3px 7px; border-radius: 6px; background: var(--neutral-muted); font: 20px ui-monospace, SFMono-Regular, Consolas, monospace; }
293 .commit-summary-card { overflow: hidden; margin-bottom: 20px; border: 1px solid var(--border); border-radius: 7px; background: var(--bg); } 294 .commit-summary-card { overflow: hidden; margin-bottom: 20px; border: 1px solid var(--border); border-radius: 7px; background: var(--bg); }
294 .commit-summary-title { min-height: 58px; padding: 12px 16px; display: flex; align-items: flex-start; gap: 16px; background: var(--canvas); border-bottom: 1px solid var(--border); } 295 .commit-summary-title { min-height: 44px; padding: 8px 12px; display: flex; align-items: center; gap: 16px; border-bottom: 1px solid var(--border); }
295 .commit-summary-title > strong { min-width: 0; flex: 1; font: 14px/32px ui-monospace, SFMono-Regular, Consolas, monospace; overflow-wrap: anywhere; } 296 .commit-summary-title > strong { min-width: 0; flex: 1; font: 14px/1.5 ui-monospace, SFMono-Regular, Consolas, monospace; overflow-wrap: anywhere; }
296 .commit-copy { flex: none; } 297 .commit-copy { flex: none; margin-left: auto; }
297 .commit-copy.is-copied { color: var(--green); background: var(--success-bg); } 298 .commit-copy.is-copied { color: var(--green); background: var(--success-bg); }
298 .commit-body { margin: 0; padding: 14px 16px; overflow: auto; border-bottom: 1px solid var(--border-muted); color: var(--text); background: var(--bg); font: 13px/1.55 ui-monospace, SFMono-Regular, Consolas, monospace; white-space: pre-wrap; overflow-wrap: anywhere; } 299 .commit-body { margin: 0; padding: 14px 16px; overflow: auto; border-bottom: 1px solid var(--border-muted); color: var(--text); background: var(--bg); font: 13px/1.55 ui-monospace, SFMono-Regular, Consolas, monospace; white-space: pre-wrap; overflow-wrap: anywhere; }
299 .commit-author-row { min-height: 48px; padding: 9px 16px; display: flex; align-items: center; gap: 9px; } 300 .commit-author-row { min-height: 28px; margin: -6px 0 12px; display: flex; align-items: center; gap: 9px; }
300 .commit-author-row > span { color: var(--muted); } 301 .commit-author-row > span { color: var(--muted); }
301 .commit-author-row strong { color: var(--text); } 302 .commit-author-row strong { color: var(--text); }
302 .commit-identifiers { padding: 9px 16px; display: flex; flex-wrap: wrap; gap: 8px 20px; border-top: 1px solid var(--border-muted); color: var(--muted); font-size: 12px; } 303 .commit-identifiers { min-height: 48px; padding: 8px 12px; display: flex; flex-wrap: wrap; align-items: center; gap: 8px 20px; color: var(--muted); font-size: 12px; }
303 .commit-identifiers code { color: var(--text); font-family: ui-monospace, SFMono-Regular, Consolas, monospace; } 304 .commit-identifiers code { color: var(--text); font-family: ui-monospace, SFMono-Regular, Consolas, monospace; }
304 .diff-overview { min-height: 44px; margin-bottom: 12px; display: flex; align-items: center; gap: 12px; border-bottom: 1px solid var(--border); } 305 .diff-overview { min-height: 44px; padding: 8px 12px; display: flex; align-items: center; gap: 12px; border-top: 1px solid var(--border); }
305 .diff-overview > strong { font-size: 16px; } 306 .diff-overview > strong { font-size: 16px; }
306 .diff-overview .muted { color: var(--muted); font-size: 12px; } 307 .diff-overview .muted { color: var(--muted); font-size: 12px; }
307 .diff-totals, .diff-file-stats { margin-left: auto; display: inline-flex; align-items: center; gap: 8px; font: 12px ui-monospace, SFMono-Regular, Consolas, monospace; } 308 .diff-totals, .diff-file-stats { margin-left: auto; display: inline-flex; align-items: center; gap: 8px; font: 12px ui-monospace, SFMono-Regular, Consolas, monospace; }
@@ -322,13 +323,17 @@ button, input { font: inherit; }
322 .diff-status { flex: none; padding: 1px 6px; border: 1px solid var(--border); border-radius: 999px; color: var(--muted); font-size: 11px; font-weight: 600; } 323 .diff-status { flex: none; padding: 1px 6px; border: 1px solid var(--border); border-radius: 999px; color: var(--muted); font-size: 11px; font-weight: 600; }
323 .split-diff { width: 100%; overflow: auto; } 324 .split-diff { width: 100%; overflow: auto; }
324 .split-diff table { width: 100%; min-width: 960px; border-spacing: 0; border-collapse: collapse; table-layout: fixed; font: 12px/20px ui-monospace, SFMono-Regular, Consolas, monospace; } 325 .split-diff table { width: 100%; min-width: 960px; border-spacing: 0; border-collapse: collapse; table-layout: fixed; font: 12px/20px ui-monospace, SFMono-Regular, Consolas, monospace; }
325 .split-diff col { width: auto; } 326 .split-diff .diff-number-col { width: 50px; }
327 .split-diff .diff-code-col { width: calc(50% - 50px); }
326 .diff-line-number { width: 50px; padding: 0 8px; color: var(--muted); background: var(--bg); border-right: 1px solid var(--border-muted); text-align: right; vertical-align: top; user-select: none; } 328 .diff-line-number { width: 50px; padding: 0 8px; color: var(--muted); background: var(--bg); border-right: 1px solid var(--border-muted); text-align: right; vertical-align: top; user-select: none; }
327 .diff-line-number:nth-child(3) { border-left: 1px solid var(--border); } 329 .diff-line-number:nth-child(3) { border-left: 1px solid var(--border); }
328 .diff-line-number.addition { background: var(--diff-add-number); } 330 .diff-line-number.addition { background: var(--diff-add-number); }
329 .diff-line-number.deletion { background: var(--diff-delete-number); } 331 .diff-line-number.deletion { background: var(--diff-delete-number); }
330 .diff-code { width: calc(50% - 50px); padding: 0 10px; overflow: hidden; color: var(--text); background: var(--bg); vertical-align: top; white-space: pre; } 332 .diff-code { padding: 0 10px; overflow: hidden; color: var(--text); background: var(--bg); vertical-align: top; white-space: pre-wrap; overflow-wrap: break-word; word-break: break-word; }
331 .diff-code code { display: block; min-height: 20px; font: inherit; } 333 .diff-code code { display: block; min-height: 20px; font: inherit; white-space: pre-wrap; overflow-wrap: break-word; word-break: break-word; }
334 .diff-code code:before { width: 14px; display: inline-block; color: var(--muted); content: " "; user-select: none; }
335 .diff-code.addition code:before { color: var(--success-text); content: "+"; }
336 .diff-code.deletion code:before { color: var(--danger-text); content: "−"; }
332 .diff-code.addition { background: var(--diff-add); } 337 .diff-code.addition { background: var(--diff-add); }
333 .diff-code.deletion { background: var(--diff-delete); } 338 .diff-code.deletion { background: var(--diff-delete); }
334 .diff-code.empty { background: var(--diff-empty); } 339 .diff-code.empty { background: var(--diff-empty); }
Modifiedinternal/site/template.go +15−16
@@ -9,7 +9,7 @@ const pageTemplate = `<!doctype html>
9 <title>{{.Title}}</title> 9 <title>{{.Title}}</title>
10 <link rel="icon" type="image/png" sizes="256x256" href="{{.BaseURL}}/.dumbforge/assets/gabriel-mark.png"> 10 <link rel="icon" type="image/png" sizes="256x256" href="{{.BaseURL}}/.dumbforge/assets/gabriel-mark.png">
11 <link rel="icon" type="image/svg+xml" href="{{.BaseURL}}/.dumbforge/assets/gabriel-mark.svg"> 11 <link rel="icon" type="image/svg+xml" href="{{.BaseURL}}/.dumbforge/assets/gabriel-mark.svg">
12 <link rel="stylesheet" href="{{.BaseURL}}/.dumbforge/assets/site.css"> 12 <link rel="stylesheet" href="{{.BaseURL}}/.dumbforge/assets/site.css?v={{.AssetVersion}}">
13 </head> 13 </head>
14 <body data-base-url="{{.BaseURL}}" data-route-kind="{{.Kind}}" data-route-path="{{.Path}}" data-current-branch="{{.Branch}}" data-default-branch="{{.DefaultBranch}}" data-branches-url="{{.BranchesURL}}" data-files-url="{{.FilesURL}}"> 14 <body data-base-url="{{.BaseURL}}" data-route-kind="{{.Kind}}" data-route-path="{{.Path}}" data-current-branch="{{.Branch}}" data-default-branch="{{.DefaultBranch}}" data-branches-url="{{.BranchesURL}}" data-files-url="{{.FilesURL}}">
15 <header class="topbar"> 15 <header class="topbar">
@@ -117,28 +117,27 @@ const pageTemplate = `<!doctype html>
117 {{else if eq .Kind "commit"}} 117 {{else if eq .Kind "commit"}}
118 <section class="primary full-width commit-view"> 118 <section class="primary full-width commit-view">
119 <div class="commit-page-heading"><h1>Commit <code>{{.ViewedCommit.Commit.ShortOID}}</code></h1></div> 119 <div class="commit-page-heading"><h1>Commit <code>{{.ViewedCommit.Commit.ShortOID}}</code></h1></div>
120 <div class="commit-author-row">
121 <img class="avatar commit-avatar" src="{{.ViewedCommit.Commit.AvatarURL}}" alt="" referrerpolicy="no-referrer">
122 <span><strong>{{.ViewedCommit.Commit.Author}}</strong> committed on <time datetime="{{.ViewedCommit.Commit.DateTime}}">{{.ViewedCommit.Commit.Date}}</time></span>
123 </div>
120 <article class="commit-summary-card"> 124 <article class="commit-summary-card">
121 <div class="commit-summary-title"> 125 <div class="commit-summary-title">
122 <strong>{{.ViewedCommit.Commit.Message}}</strong> 126 <strong>{{.ViewedCommit.Commit.Message}}</strong>
123 <button class="secondary-button commit-copy" type="button" data-copy="{{.ViewedCommit.Commit.OID}}" aria-label="Copy commit ID"><span data-copy-icon>{{icon "copy"}}</span><span data-copied-icon hidden>{{icon "check"}}</span><span data-copy-label>Copy SHA</span></button>
124 </div> 127 </div>
125 {{if .ViewedCommit.Commit.Body}}<pre class="commit-body">{{.ViewedCommit.Commit.Body}}</pre>{{end}} 128 {{if .ViewedCommit.Commit.Body}}<pre class="commit-body">{{.ViewedCommit.Commit.Body}}</pre>{{end}}
126 <div class="commit-author-row">
127 <img class="avatar commit-avatar" src="{{.ViewedCommit.Commit.AvatarURL}}" alt="" referrerpolicy="no-referrer">
128 <span><strong>{{.ViewedCommit.Commit.Author}}</strong> committed on <time datetime="{{.ViewedCommit.Commit.DateTime}}">{{.ViewedCommit.Commit.Date}}</time></span>
129 </div>
130 <div class="commit-identifiers"> 129 <div class="commit-identifiers">
131 <span>commit <code>{{.ViewedCommit.Commit.OID}}</code></span> 130 <span>commit <code>{{.ViewedCommit.Commit.OID}}</code></span>
132 {{range .ViewedCommit.Parents}}<span>parent <a href="{{.URL}}"><code>{{.ShortOID}}</code></a></span>{{end}} 131 {{range .ViewedCommit.Parents}}<span>parent <a href="{{.URL}}"><code>{{.ShortOID}}</code></a></span>{{end}}
132 <button class="secondary-button commit-copy" type="button" data-copy="{{.ViewedCommit.Commit.OID}}" aria-label="Copy commit ID"><span data-copy-icon>{{icon "copy"}}</span><span data-copied-icon hidden>{{icon "check"}}</span><span data-copy-label>Copy SHA</span></button>
133 </div>
134 <div class="diff-overview">
135 {{if .ViewedCommit.Truncated}}<strong>Large commit</strong>{{else}}<strong>{{.ViewedCommit.FileCount}} changed files</strong>{{end}}
136 {{if gt (len .ViewedCommit.Parents) 1}}<span class="muted">Diffed against the first of {{len .ViewedCommit.Parents}} parents.</span>{{end}}
137 <span class="diff-totals"><b class="addition">+{{.ViewedCommit.Additions}}</b><b class="deletion">{{.ViewedCommit.Deletions}}</b></span>
133 </div> 138 </div>
134 </article> 139 </article>
135 140
136 <div class="diff-overview">
137 {{if .ViewedCommit.Truncated}}<strong>Large commit</strong>{{else}}<strong>{{len .ViewedCommit.Files}} changed files</strong>{{end}}
138 {{if gt (len .ViewedCommit.Parents) 1}}<span class="muted">Diffed against the first of {{len .ViewedCommit.Parents}} parents.</span>{{end}}
139 <span class="diff-totals"><b class="addition">+{{.ViewedCommit.Additions}}</b><b class="deletion">{{.ViewedCommit.Deletions}}</b></span>
140 </div>
141
142 {{if .ViewedCommit.Truncated}} 141 {{if .ViewedCommit.Truncated}}
143 <div class="diff-limit-notice"><strong>Diff too large to render</strong><p>This commit's patch exceeds the 8 MB static-page limit. Clone the repository to inspect the complete change.</p></div> 142 <div class="diff-limit-notice"><strong>Diff too large to render</strong><p>This commit's patch exceeds the 8 MB static-page limit. Clone the repository to inspect the complete change.</p></div>
144 {{else if .ViewedCommit.Files}} 143 {{else if .ViewedCommit.Files}}
@@ -157,18 +156,18 @@ const pageTemplate = `<!doctype html>
157 </header> 156 </header>
158 {{if .Binary}}<div class="diff-file-notice">Binary file changed.</div> 157 {{if .Binary}}<div class="diff-file-notice">Binary file changed.</div>
159 {{else if not .Rows}}<div class="diff-file-notice">{{if .Truncated}}Diff omitted because this commit exceeds the static-page render limit.{{else}}File metadata changed without textual changes.{{end}}</div> 158 {{else if not .Rows}}<div class="diff-file-notice">{{if .Truncated}}Diff omitted because this commit exceeds the static-page render limit.{{else}}File metadata changed without textual changes.{{end}}</div>
160 {{else}}<div class="split-diff"><table aria-label="Side-by-side diff for {{.DisplayPath}}"><tbody> 159 {{else}}<div class="split-diff diff-chroma"><table aria-label="Side-by-side diff for {{.DisplayPath}}"><colgroup><col class="diff-number-col"><col class="diff-code-col"><col class="diff-number-col"><col class="diff-code-col"></colgroup><tbody>
161 {{range .Rows}}{{if .Hunk}}<tr class="diff-hunk"><td colspan="4"><code>{{.Header}}</code></td></tr>{{else}}<tr> 160 {{range .Rows}}{{if .Hunk}}<tr class="diff-hunk"><td colspan="4"><code>{{.Header}}</code></td></tr>{{else}}<tr>
162 <td class="diff-line-number {{.OldClass}}">{{if .OldPresent}}{{.OldNumber}}{{end}}</td> 161 <td class="diff-line-number {{.OldClass}}">{{if .OldPresent}}{{.OldNumber}}{{end}}</td>
163 <td class="diff-code old {{.OldClass}} {{if not .OldPresent}}empty{{end}}"><code>{{if .OldPresent}}{{.OldText}}{{end}}</code></td> 162 <td class="diff-code old {{.OldClass}} {{if not .OldPresent}}empty{{end}}"><code>{{if .OldPresent}}{{.OldCode}}{{end}}</code></td>
164 <td class="diff-line-number {{.NewClass}}">{{if .NewPresent}}{{.NewNumber}}{{end}}</td> 163 <td class="diff-line-number {{.NewClass}}">{{if .NewPresent}}{{.NewNumber}}{{end}}</td>
165 <td class="diff-code new {{.NewClass}} {{if not .NewPresent}}empty{{end}}"><code>{{if .NewPresent}}{{.NewText}}{{end}}</code></td> 164 <td class="diff-code new {{.NewClass}} {{if not .NewPresent}}empty{{end}}"><code>{{if .NewPresent}}{{.NewCode}}{{end}}</code></td>
166 </tr>{{end}}{{end}} 165 </tr>{{end}}{{end}}
167 </tbody></table></div>{{if .Truncated}}<div class="diff-truncated-notice">Diff truncated after the static-page render limit. Clone the repository to inspect the complete change.</div>{{end}}{{end}} 166 </tbody></table></div>{{if .Truncated}}<div class="diff-truncated-notice">Diff truncated after the static-page render limit. Clone the repository to inspect the complete change.</div>{{end}}{{end}}
168 </article>{{end}} 167 </article>{{end}}
169 </div> 168 </div>
170 </div> 169 </div>
171 {{else}}<div class="diff-limit-notice"><strong>No file changes</strong><p>This commit has no changes relative to its first parent.</p></div>{{end}} 170 {{else}}<div class="diff-limit-notice">{{if .ViewedCommit.Binaries}}<strong>Binary changes omitted</strong><p>Binary files are not rendered in static diffs.</p>{{else}}<strong>No file changes</strong><p>This commit has no changes relative to its first parent.</p>{{end}}</div>{{end}}
172 </section> 171 </section>
173 {{else}} 172 {{else}}
174 <div class="blob-layout"> 173 <div class="blob-layout">