Tree view and pagination

Author Mitja Felicijan <mitja.felicijan@gmail.com> 2026-05-09 15:41:48 +0200
Committer Mitja Felicijan <mitja.felicijan@gmail.com> 2026-05-09 16:42:59 +0200
Commit 47549e4059b672efa7ba5483d5854d3481e1fda8 (patch)
-rw-r--r-- hfiles.go 66
-rw-r--r-- hrepo.go 2
-rw-r--r-- models.go 12
-rw-r--r-- static/style.css 163
-rw-r--r-- templates.go 18
-rw-r--r-- views/blob.html 32
-rw-r--r-- views/commit.html 3
-rw-r--r-- views/files.html 95
-rw-r--r-- views/footer.html 10
-rw-r--r-- views/license.html 2
-rw-r--r-- views/markers.html 2
-rw-r--r-- views/nav-main.html 14
-rw-r--r-- views/readme.html 2
-rw-r--r-- views/repositories.html 2
-rw-r--r-- views/repository.html 8
-rw-r--r-- views/tree.html 21
16 files changed, 312 insertions, 140 deletions
diff --git a/hfiles.go b/hfiles.go
...
45
		}
45
		}
46
	}
46
	}
47
  
47
  
48
	var entries []TreeEntry
48
	var entries []*TreeEntry
49
	for _, entry := range tree.Entries {
49
	for _, entry := range tree.Entries {
50
		fullPath := entry.Name
50
		fullPath := entry.Name
51
		if pathValue != "" {
51
		if pathValue != "" {
...
62
			}
62
			}
63
		}
63
		}
64
  
64
  
65
		entries = append(entries, TreeEntry{
65
		entries = append(entries, &TreeEntry{
66
			Name:  entry.Name,
66
			Name:  entry.Name,
67
			Path:  fullPath,
67
			Path:  fullPath,
68
			IsDir: isDir,
68
			IsDir: isDir,
...
78
		return entries[i].Name < entries[j].Name
78
		return entries[i].Name < entries[j].Name
79
	})
79
	})
80
  
80
  
  
81
	var allEntries []*TreeEntry
  
82
	fullTree, err := commit.Tree()
  
83
	if err == nil {
  
84
		allEntries = getTreeEntries(fullTree, "", 0)
  
85
	}
  
86
  
81
	data := struct {
87
	data := struct {
82
		*RepoContext
88
		*RepoContext
83
		Entries []TreeEntry
89
		Entries    []*TreeEntry
84
		Path    string
90
		AllEntries []*TreeEntry
85
		View    string
91
		Path       string
  
92
		View       string
86
	}{
93
	}{
87
		RepoContext: ctx,
94
		RepoContext: ctx,
88
		Entries:     entries,
95
		Entries:     entries,
  
96
		AllEntries:  allEntries,
89
		Path:        pathValue,
97
		Path:        pathValue,
90
		View:        "files",
98
		View:        "files",
91
	}
99
	}
...
128
		return
136
		return
129
	}
137
	}
130
  
138
  
  
139
	var allEntries []*TreeEntry
  
140
	tree, err := commit.Tree()
  
141
	if err == nil {
  
142
		allEntries = getTreeEntries(tree, "", 0)
  
143
	}
  
144
  
131
	data := struct {
145
	data := struct {
132
		*RepoContext
146
		*RepoContext
133
		Path    string
147
		Path       string
134
		Content template.HTML
148
		Content    template.HTML
  
149
		AllEntries []*TreeEntry
135
	}{
150
	}{
136
		RepoContext: ctx,
151
		RepoContext: ctx,
137
		Path:        pathValue,
152
		Path:        pathValue,
138
		Content:     highlight(pathValue, content),
153
		Content:     highlight(pathValue, content),
  
154
		AllEntries:  allEntries,
139
	}
155
	}
140
  
156
  
141
	err = templates.ExecuteTemplate(w, "blob.html", data)
157
	err = templates.ExecuteTemplate(w, "blob.html", data)
...
280
		log.Printf("Error creating archive: %v", err)
296
		log.Printf("Error creating archive: %v", err)
281
	}
297
	}
282
}
298
}
  
299
  
  
300
func getTreeEntries(tree *object.Tree, prefix string, depth int) []*TreeEntry {
  
301
	var entries []*TreeEntry
  
302
	for _, entry := range tree.Entries {
  
303
		fullPath := entry.Name
  
304
		if prefix != "" {
  
305
			fullPath = prefix + "/" + entry.Name
  
306
		}
  
307
  
  
308
		isDir := entry.Mode.IsFile() == false
  
309
		te := &TreeEntry{
  
310
			Name:  entry.Name,
  
311
			Path:  fullPath,
  
312
			IsDir: isDir,
  
313
			Mode:  entry.Mode.String(),
  
314
			Depth: depth,
  
315
		}
  
316
  
  
317
		if isDir {
  
318
			subTree, err := tree.Tree(entry.Name)
  
319
			if err == nil {
  
320
				te.Children = getTreeEntries(subTree, fullPath, depth+1)
  
321
			}
  
322
		}
  
323
		entries = append(entries, te)
  
324
	}
  
325
  
  
326
	sort.Slice(entries, func(i, j int) bool {
  
327
		if entries[i].IsDir != entries[j].IsDir {
  
328
			return entries[i].IsDir
  
329
		}
  
330
		return entries[i].Name < entries[j].Name
  
331
	})
  
332
  
  
333
	return entries
  
334
}
diff --git a/hrepo.go b/hrepo.go
...
35
  
35
  
36
	totalCommitsKey := ctx.Repo.Name + ":" + ctx.Hash.String()
36
	totalCommitsKey := ctx.Repo.Name + ":" + ctx.Hash.String()
37
	var totalCommits int
37
	var totalCommits int
38
	if val, ok := repoMetadataCache.Load(totalCommitsKey); ok {
38
	if val, ok := repoMetadataCache.Load(totalCommitsKey); ok && val.(RepoMetadata).TotalCommits > 0 {
39
		totalCommits = val.(RepoMetadata).TotalCommits
39
		totalCommits = val.(RepoMetadata).TotalCommits
40
	} else {
40
	} else {
41
		cIter, err := ctx.GitRepo.Log(&git.LogOptions{From: ctx.Hash})
41
		cIter, err := ctx.GitRepo.Log(&git.LogOptions{From: ctx.Hash})
...
diff --git a/models.go b/models.go
...
56
}
56
}
57
  
57
  
58
type TreeEntry struct {
58
type TreeEntry struct {
59
	Name  string
59
	Name     string
60
	Path  string
60
	Path     string
61
	IsDir bool
61
	IsDir    bool
62
	Size  int64
62
	Size     int64
63
	Mode  string
63
	Mode     string
  
64
	Depth    int
  
65
	Children []*TreeEntry
64
}
66
}
65
  
67
  
66
type RepoContext struct {
68
type RepoContext struct {
...
diff --git a/static/style.css b/static/style.css
...
81
	text-decoration: underline;
81
	text-decoration: underline;
82
}
82
}
83
  
83
  
84
pre { 
84
pre {
85
	margin: 0; 
85
	margin: 0;
86
	overflow-x: auto; 
86
	overflow-x: auto;
87
	scrollbar-width: none;
  
88
	white-space: pre;
87
	white-space: pre;
89
}
  
90
pre::-webkit-scrollbar { 
  
91
	display: none;
  
92
}
88
}
93
  
89
  
94
blockquote {
90
blockquote {
...
128
  
124
  
129
.nav-main {
125
.nav-main {
130
	display: flex;
126
	display: flex;
  
127
	justify-content: space-between;
  
128
	align-items: center;
  
129
}
  
130
  
  
131
.nav-main-left {
  
132
	display: flex;
131
	gap: 0.4em;
133
	gap: 0.4em;
  
134
	align-items: center;
  
135
}
  
136
  
  
137
.nav-main-right {
  
138
	display: flex;
  
139
	gap: 1em;
  
140
	font-size: small;
132
}
141
}
133
  
142
  
134
.nav-repository {
143
.nav-repository {
...
141
	}
150
	}
142
}
151
}
143
  
152
  
  
153
.content-layout {
  
154
	display: flex;
  
155
	gap: 1em;
  
156
	min-width: 0;
  
157
	align-items: flex-start;
  
158
}
  
159
  
  
160
.tree-view {
  
161
	width: 250px;
  
162
	flex-shrink: 0;
  
163
	border: 1px solid var(--border);
  
164
	font-size: small;
  
165
	padding: 0.5em 0;
  
166
	align-self: flex-start;
  
167
}
  
168
  
  
169
.tree-entry {
  
170
	display: block;
  
171
	padding: 2px 10px;
  
172
	white-space: nowrap;
  
173
	overflow: hidden;
  
174
	text-overflow: ellipsis;
  
175
	color: #000000;
  
176
	text-decoration: none;
  
177
}
  
178
  
  
179
.tree-entry:hover {
  
180
	background: var(--table-row-hover-bg);
  
181
	text-decoration: none;
  
182
}
  
183
  
  
184
.tree-entry.active {
  
185
	font-weight: bold;
  
186
	background: var(--bg);
  
187
	color: var(--link);
  
188
}
  
189
  
  
190
.tree-entry.directory {
  
191
	color: #000000;
  
192
}
  
193
  
  
194
.code-container {
  
195
	flex-grow: 1;
  
196
	display: flex;
  
197
	flex-direction: column;
  
198
	border: 1px solid var(--border);
  
199
	min-width: 0;
  
200
}
  
201
  
  
202
.code-header {
  
203
	display: flex;
  
204
	justify-content: space-between;
  
205
	align-items: center;
  
206
	padding: 0.5em 1em;
  
207
	background: var(--table-header-bg);
  
208
	border-bottom: 1px solid var(--border);
  
209
	position: sticky;
  
210
	top: 0;
  
211
	z-index: 10;
  
212
	font-size: small;
  
213
	font-family: monospace;
  
214
}
  
215
  
144
.code-view {
216
.code-view {
145
	border: 1px solid var(--border);
217
	border: none;
  
218
}
  
219
  
  
220
.code-view pre { background: none !important; }
  
221
  
  
222
.code-view code > span > span:first-child {
  
223
	color: var(--ln-fg);
  
224
	background: var(--ln-bg);
  
225
	border-right: 1px solid var(--border);
  
226
	font-size: smaller;
146
}
227
}
147
  
228
  
148
.diff-table { 
229
.files-view {
149
	table-layout: fixed;
230
	flex-grow: 1;
  
231
	overflow: auto;
  
232
	min-width: 0;
  
233
  
  
234
	a { color: initial; }
  
235
}
  
236
  
  
237
.diff-table {
150
	width: 100%;
238
	width: 100%;
151
	border-collapse: collapse;
239
	border-collapse: collapse;
152
	font-family: monospace;
240
	font-family: monospace;
153
  
241
  
154
	td { 
242
	td {
155
		padding: 0 2px;
243
		padding: 0 2px;
156
		border: 0;
244
		border: 0;
157
		overflow: hidden;
245
		overflow: hidden;
...
182
	.diff-add { background-color: var(--diff-add-bg); color: var(--diff-add-fg); }
270
	.diff-add { background-color: var(--diff-add-bg); color: var(--diff-add-fg); }
183
	.diff-del { background-color: var(--diff-del-bg); color: var(--diff-del-fg); }
271
	.diff-del { background-color: var(--diff-del-bg); color: var(--diff-del-fg); }
184
	.diff-mod { background-color: var(--diff-mod-bg); color: var(--diff-mod-fg); }
272
	.diff-mod { background-color: var(--diff-mod-bg); color: var(--diff-mod-fg); }
185
	.diff-gap { 
273
	.diff-gap {
186
		background-color: var(--diff-gap-bg);
274
		background-color: var(--diff-gap-bg);
187
		color: var(--diff-gap-fg);
275
		color: var(--diff-gap-fg);
188
		border-top: 1px solid var(--border);
276
		border-top: 1px solid var(--border);
189
		border-bottom: 1px solid var(--border);
277
		border-bottom: 1px solid var(--border);
190
	}
278
	}
191
	.diff-fname { 
279
	.diff-fname {
192
		background-color: var(--diff-fname-bg);
280
		background-color: var(--diff-fname-bg);
193
		color: var(--diff-fname-fg);
281
		color: var(--diff-fname-fg);
194
		border-top: 1px solid var(--border);
282
		border-top: 1px solid var(--border);
...
199
	}
287
	}
200
}
288
}
201
  
289
  
  
290
.tree-view details summary {
  
291
	list-style: none;
  
292
	cursor: pointer;
  
293
	outline: none;
  
294
}
  
295
  
  
296
.tree-view details summary::-webkit-details-marker {
  
297
	display: none;
  
298
}
  
299
  
  
300
.tree-view details summary::before {
  
301
	content: "▸";
  
302
	display: inline-block;
  
303
	width: 1em;
  
304
	color: #000000;
  
305
}
  
306
  
  
307
.tree-view details[open] > summary::before {
  
308
	content: "▾";
  
309
}
  
310
  
  
311
.tree-view details {
  
312
	display: block;
  
313
}
  
314
  
202
.file-diffs {
315
.file-diffs {
203
	display: flex;
316
	display: flex;
204
	flex-direction: column;
317
	flex-direction: column;
...
223
		color: var(--fg-muted);
336
		color: var(--fg-muted);
224
		font-size: small;
337
		font-size: small;
225
		user-select: none;
338
		user-select: none;
226
	}
  
227
}
  
228
  
  
229
.code-view {
  
230
	pre { background: none !important; }
  
231
	code > span > span:first-child {
  
232
		color: var(--ln-fg);
  
233
		background: var(--ln-bg);
  
234
		border-right: 1px solid var(--border);
  
235
		font-size: smaller;
  
236
	}
339
	}
237
}
340
}
238
  
341
  
...
367
	color: var(--fg-muted);
470
	color: var(--fg-muted);
368
}
471
}
369
  
472
  
370
footer {
473
.footer {
371
	display: flex;
474
	margin-top: 2em;
372
	justify-content: space-between;
475
	padding-top: 1em;
373
	align-items: center;
476
	text-align: center;
374
	color: var(--fg-muted);
477
	color: var(--fg-muted);
375
	font-size: small;
478
	font-size: small;
376
  
  
377
	.footer-rss {
  
378
		display: flex;
  
379
		gap: 1em;
  
380
	}
  
381
}
479
}
382
  
  
diff --git a/templates.go b/templates.go
...
42
	"float64": func(i int) float64 {
42
	"float64": func(i int) float64 {
43
		return float64(i)
43
		return float64(i)
44
	},
44
	},
  
45
	"int": func(f float64) int {
  
46
		return int(f)
  
47
	},
45
	"multiply": func(a, b float64) float64 {
48
	"multiply": func(a, b float64) float64 {
46
		return a * b
49
		return a * b
47
	},
50
	},
...
50
			return 0
53
			return 0
51
		}
54
		}
52
		return a / b
55
		return a / b
  
56
	},
  
57
	"hasPrefix": strings.HasPrefix,
  
58
	"dict": func(values ...interface{}) (map[string]interface{}, error) {
  
59
		if len(values)%2 != 0 {
  
60
			return nil, fmt.Errorf("invalid dict call")
  
61
		}
  
62
		dict := make(map[string]interface{}, len(values)/2)
  
63
		for i := 0; i < len(values); i += 2 {
  
64
			key, ok := values[i].(string)
  
65
			if !ok {
  
66
				return nil, fmt.Errorf("dict keys must be strings")
  
67
			}
  
68
			dict[key] = values[i+1]
  
69
		}
  
70
		return dict, nil
53
	},
71
	},
54
	"humanize": func(size int64) string {
72
	"humanize": func(size int64) string {
55
		const unit = 1024
73
		const unit = 1024
...
diff --git a/views/blob.html b/views/blob.html
...
11
		<main>
11
		<main>
12
			{{template "nav-repository.html" .}}
12
			{{template "nav-repository.html" .}}
13
  
13
  
14
			<div class="breadcrumbs">
14
			<div class="content-layout">
15
				<span>Path:</span>
15
				<div class="tree-view">
16
				<a href="/r/{{.Repo.Name}}/files/?ref={{.CurrentRef}}">{{.Repo.Name}}</a>
16
					{{template "tree" dict "Entries" .AllEntries "RepoName" .Repo.Name "CurrentRef" .CurrentRef "CurrentPath" .Path}}
17
				{{$parts := split .Path "/"}}
17
				</div>
18
				{{$path := ""}}
18
				<div class="code-container">
19
				{{range $i, $part := $parts}}
19
					<div class="code-header">
20
					/
20
						<span class="file-path">{{.Path}}</span>
21
					{{$path = join $path $part}}
21
						<a href="/r/{{.Repo.Name}}/raw/{{.Path}}?ref={{.CurrentRef}}" class="raw-link">raw</a>
22
					{{if eq (add $i 1) (len $parts)}}
22
					</div>
23
						{{$part}}
23
					<div class="code-view">{{.Content}}</div>
24
						(<a href="/r/{{$.Repo.Name}}/raw/{{$.Path}}?ref={{$.CurrentRef}}">raw</a>)
24
				</div>
25
					{{else}}
  
26
						<a href="/r/{{$.Repo.Name}}/files/{{$path}}?ref={{$.CurrentRef}}">{{$part}}</a>
  
27
					{{end}}
  
28
				{{end}}
  
29
			</div>
  
30
  
  
31
			<div class="blob-content">
  
32
				<div class="code-view">{{.Content}}</div>
  
33
			</div>
25
			</div>
34
		</main>
26
		</main>
35
  
  
36
		{{template "footer.html" .}}
27
		{{template "footer.html" .}}
37
	</body>
28
	</body>
38
</html>
29
</html>
  
30
  
diff --git a/views/commit.html b/views/commit.html
...
10
  
10
  
11
		<main>
11
		<main>
12
			{{template "nav-repository.html" .}}
12
			{{template "nav-repository.html" .}}
13
	
  
14
			<div>
13
			<div>
15
				{{$lines := split .Commit.Message "\n"}}
14
				{{$lines := split .Commit.Message "\n"}}
16
				<h3 class="margin-none padding-none">{{index $lines 0}}</h3>
15
				<h3 class="margin-none padding-none">{{index $lines 0}}</h3>
...
126
				{{end}}
125
				{{end}}
127
			</div>
126
			</div>
128
		</main>
127
		</main>
129
  
  
130
		{{template "footer.html" .}}
128
		{{template "footer.html" .}}
131
	</body>
129
	</body>
132
</html>
130
</html>
  
131
  
diff --git a/views/files.html b/views/files.html
...
11
		<main>
11
		<main>
12
			{{template "nav-repository.html" .}}
12
			{{template "nav-repository.html" .}}
13
  
13
  
14
			<div class="breadcrumbs">
14
			<div class="content-layout">
15
				<span>Path:</span>
15
				<div class="tree-view">
16
				{{if .Path}}
16
					{{template "tree" dict "Entries" .AllEntries "RepoName" .Repo.Name "CurrentRef" .CurrentRef "CurrentPath" .Path}}
17
					<a href="/r/{{.Repo.Name}}/files/?ref={{.CurrentRef}}">{{.Repo.Name}}</a>
17
				</div>
18
					{{$parts := split .Path "/"}}
  
19
					{{$path := ""}}
  
20
					{{range $i, $part := $parts}}
  
21
						/
  
22
						{{$path = join $path $part}}
  
23
						{{if eq (add $i 1) (len $parts)}}
  
24
							{{$part}}
  
25
							(<a href="/r/{{$.Repo.Name}}/archive/{{$.Path}}?ref={{$.CurrentRef}}">download</a>)
  
26
						{{else}}
  
27
							<a href="/r/{{$.Repo.Name}}/files/{{$path}}?ref={{$.CurrentRef}}">{{$part}}</a>
  
28
						{{end}}
  
29
					{{end}}
  
30
				{{else}}
  
31
					{{.Repo.Name}}
  
32
				{{end}}
  
33
			</div>
  
34
  
18
  
35
			<table>
19
				<div class="files-view">
36
				<thead>
20
					<table>
37
					<tr>
21
						<thead>
38
						<th width="20"></th>
22
							<tr>
39
						<th>Name</th>
23
								<th width="20"></th>
40
						<th width="100" class="text-right">Mode</th>
24
								<th>Name</th>
41
						<th width="100" class="text-right">Size</th>
25
								<th width="100" class="text-right">Mode</th>
42
						<th width="50" class="text-center">Raw</th>
26
								<th width="100" class="text-right">Size</th>
43
					</tr>
27
								<th width="50" class="text-center">Raw</th>
44
				</thead>
28
							</tr>
45
				<tbody>
29
						</thead>
46
					{{if .Path}}
30
						<tbody>
47
					<tr>
31
							{{if .Path}}
48
						<td></td>
32
							<tr>
49
						<td colspan="4"><a href="/r/{{.Repo.Name}}/files/{{parent .Path}}?ref={{.CurrentRef}}">..</a></td>
33
								<td></td>
50
					</tr>
34
								<td colspan="4"><a href="/r/{{.Repo.Name}}/files/{{parent .Path}}?ref={{.CurrentRef}}">..</a></td>
51
					{{end}}
35
							</tr>
52
					{{range .Entries}}
36
							{{end}}
53
					<tr>
37
							{{range .Entries}}
54
						<td class="filetype text-center">{{if .IsDir}}d{{else}}f{{end}}</td>
38
							<tr>
55
						<td>
39
								<td class="filetype text-center">{{if .IsDir}}{{template "icon-directory" .}}{{else}}{{template "icon-file" .}}{{end}}</td>
56
							{{if .IsDir}}
40
								<td>
57
							<a href="/r/{{$.Repo.Name}}/files/{{.Path}}?ref={{$.CurrentRef}}">{{.Name}}/</a>
41
									{{if .IsDir}}
58
							{{else}}
42
									<a href="/r/{{$.Repo.Name}}/files/{{.Path}}?ref={{$.CurrentRef}}">{{.Name}}/</a>
59
							<a href="/r/{{$.Repo.Name}}/blob/{{.Path}}?ref={{$.CurrentRef}}">{{.Name}}</a>
43
									{{else}}
  
44
									<a href="/r/{{$.Repo.Name}}/blob/{{.Path}}?ref={{$.CurrentRef}}">{{.Name}}</a>
  
45
									{{end}}
  
46
								</td>
  
47
								<td class="text-right"><code>{{.Mode}}</code></td>
  
48
								<td class="text-right">{{if not .IsDir}}<code>{{humanize .Size}}</code>{{end}}</td>
  
49
								<td class="text-center">{{if not .IsDir}}<a href="/r/{{$.Repo.Name}}/raw/{{.Path}}?ref={{$.CurrentRef}}">raw</a>{{end}}</td>
  
50
							</tr>
60
							{{end}}
51
							{{end}}
61
						</td>
52
						</tbody>
62
						<td class="text-right"><code>{{.Mode}}</code></td>
53
					</table>
63
						<td class="text-right">{{if not .IsDir}}<code>{{humanize .Size}}</code>{{end}}</td>
54
				</div>
64
						<td class="text-center">{{if not .IsDir}}<a href="/r/{{$.Repo.Name}}/raw/{{.Path}}?ref={{$.CurrentRef}}">raw</a>{{end}}</td>
55
			</div>
65
					</tr>
  
66
					{{end}}
  
67
				</tbody>
  
68
			</table>
  
69
		</main>
56
		</main>
70
  
  
71
		{{template "footer.html" .}}
57
		{{template "footer.html" .}}
72
	</body>
58
	</body>
73
</html>
59
</html>
  
60
  
diff --git a/views/footer.html b/views/footer.html
1
<footer class="footer">
1
<footer class="footer">
2
	<div class="footer-rss">
2
	Powered by <a href="https://git.mitjafelicijan.com/r/bbgit">bbgit</a>
3
		{{if .Repo}}
  
4
		<a href="/r/{{.Repo.Name}}/commits.rss?ref={{.CurrentRef}}" title="Commits RSS">Commits RSS</a>
  
5
		<a href="/r/{{.Repo.Name}}/tags.rss" title="Tags RSS">Tags RSS</a>
  
6
		{{end}}
  
7
	</div>
  
8
	<div class="powered-by">
  
9
		Powered by <a href="https://git.mitjafelicijan.com/r/bbgit">bbgit</a>
  
10
	</div>
  
11
</footer>
3
</footer>
diff --git a/views/license.html b/views/license.html
...
15
				{{.Content}}
15
				{{.Content}}
16
			</div>
16
			</div>
17
		</main>
17
		</main>
18
  
  
19
		{{template "footer.html" .}}
18
		{{template "footer.html" .}}
20
	</body>
19
	</body>
21
</html>
20
</html>
  
21
  
diff --git a/views/markers.html b/views/markers.html
...
34
				</tbody>
34
				</tbody>
35
			</table>
35
			</table>
36
		</main>
36
		</main>
37
  
  
38
		{{template "footer.html" .}}
37
		{{template "footer.html" .}}
39
	</body>
38
	</body>
40
</html>
39
</html>
  
40
  
diff --git a/views/nav-main.html b/views/nav-main.html
1
<nav class="nav-main">
1
<nav class="nav-main">
2
	<a href="/">Repositories</a>
2
	<div class="nav-main-left">
  
3
		<a href="/">Repositories</a>
  
4
		{{if .Repo}}
  
5
		<span>»</span>
  
6
		<div><span class="text-bold">{{.Repo.Name}}</span>: {{.Repo.Description}}</div>
  
7
		{{end}}
  
8
	</div>
3
	{{if .Repo}}
9
	{{if .Repo}}
4
	<span>»</span>
10
	<div class="nav-main-right">
5
	<div><span class="text-bold">{{.Repo.Name}}</span>: {{.Repo.Description}}</div>
11
		<a href="/r/{{.Repo.Name}}/commits.rss?ref={{.CurrentRef}}" title="Commits RSS">Commits RSS</a>
  
12
		<a href="/r/{{.Repo.Name}}/tags.rss" title="Tags RSS">Tags RSS</a>
  
13
	</div>
6
	{{end}}
14
	{{end}}
7
</nav>
15
</nav>
diff --git a/views/readme.html b/views/readme.html
...
15
				{{.Content}}
15
				{{.Content}}
16
			</div>
16
			</div>
17
		</main>
17
		</main>
18
  
  
19
		{{template "footer.html" .}}
18
		{{template "footer.html" .}}
20
	</body>
19
	</body>
21
</html>
20
</html>
  
21
  
diff --git a/views/repositories.html b/views/repositories.html
...
37
				</tbody>
37
				</tbody>
38
			</table>
38
			</table>
39
		</main>
39
		</main>
40
  
  
41
		{{template "footer.html" .}}
40
		{{template "footer.html" .}}
42
	</body>
41
	</body>
43
</html>
42
</html>
  
43
  
diff --git a/views/repository.html b/views/repository.html
...
40
					<tr>
40
					<tr>
41
						<th width="110">Date</th>
41
						<th width="110">Date</th>
42
						<th>Commit message</th>
42
						<th>Commit message</th>
  
43
						<th width="70"></th>
43
						<th width="200">Author</th>
44
						<th width="200">Author</th>
44
						<th width="90" class="text-right">Lines</th>
45
						<th width="90" class="text-right">Lines</th>
45
					</tr>
46
					</tr>
...
49
					<tr>
50
					<tr>
50
						<td>{{.AuthorDate.Format "2006-01-02"}}</td>
51
						<td>{{.AuthorDate.Format "2006-01-02"}}</td>
51
						<td><a href="/r/{{$.Repo.Name}}/c/{{.Hash}}">{{truncate .Message 80}}</a></td>
52
						<td><a href="/r/{{$.Repo.Name}}/c/{{.Hash}}">{{truncate .Message 80}}</a></td>
  
53
						<td class="text-center">
  
54
							<a href="/r/{{$.Repo.Name}}/files/?ref={{.Hash}}" title="Browse repository at this point">browse</a>
  
55
						</td>
52
						<td>{{.AuthorName}}</td>
56
						<td>{{.AuthorName}}</td>
53
						<td class="text-right">
57
						<td class="text-right">
54
							<span class="stats-add">+{{.Additions}}</span>/<span class="stats-del">-{{.Deletions}}</span>
58
							<span class="stats-add">+{{.Additions}}</span>/<span class="stats-del">-{{.Deletions}}</span>
...
56
					</tr>
60
					</tr>
57
					{{else}}
61
					{{else}}
58
					<tr>
62
					<tr>
59
						<td colspan="4">No commits found.</td>
63
						<td colspan="5">No commits found.</td>
60
					</tr>
64
					</tr>
61
					{{end}}
65
					{{end}}
62
				</tbody>
66
				</tbody>
...
86
			</div>
90
			</div>
87
			{{end}}
91
			{{end}}
88
		</main>
92
		</main>
89
  
  
90
		{{template "footer.html" .}}
93
		{{template "footer.html" .}}
91
	</body>
94
	</body>
92
</html>
95
</html>
  
96
  
diff --git a/views/tree.html b/views/tree.html
  
1
{{define "icon-file"}}<svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" viewBox="0 0 512 512" style="vertical-align: middle; margin-right: 4px;"><path fill="#000000" d="M334.627 16H48v480h424V153.373ZM440 166.627V168H320V48h1.373ZM80 464V48h208v152h152v264Z"/></svg>{{end}}
  
2
{{define "icon-directory"}}<svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" viewBox="0 0 512 512" style="vertical-align: middle; margin-right: 4px;"><path fill="#000000" d="M472 472H40a24.03 24.03 0 0 1-24-24V64a24.03 24.03 0 0 1 24-24h186.667a23.935 23.935 0 0 1 22.154 14.77L269.333 104H472a24.03 24.03 0 0 1 24 24v320a24.03 24.03 0 0 1-24 24M48 440h416V136H248l-26.667-64H48Z"/></svg>{{end}}
  
3
  
  
4
{{define "tree"}}
  
5
	{{range .Entries}}
  
6
	{{if .IsDir}}
  
7
		<details class="tree-directory" {{if hasPrefix $.CurrentPath .Path}}open{{end}}>
  
8
			<summary class="tree-entry directory" style="padding-left: {{add (int (multiply (float64 .Depth) 15.0)) 10}}px">
  
9
				<a href="/r/{{$.RepoName}}/files/{{.Path}}?ref={{$.CurrentRef}}" class="text-reset">{{template "icon-directory" .}} {{.Name}}</a>
  
10
			</summary>
  
11
			{{template "tree" dict "Entries" .Children "RepoName" $.RepoName "CurrentRef" $.CurrentRef "CurrentPath" $.CurrentPath}}
  
12
		</details>
  
13
	{{else}}
  
14
		<a href="/r/{{$.RepoName}}/blob/{{.Path}}?ref={{$.CurrentRef}}"
  
15
			class="tree-entry {{if eq .Path $.CurrentPath}}active{{end}}"
  
16
			style="padding-left: {{add (int (multiply (float64 .Depth) 15.0)) 25}}px">
  
17
			{{template "icon-file" .}} {{.Name}}
  
18
		</a>
  
19
	{{end}}
  
20
	{{end}}
  
21
{{end}}