diff --git a/hfiles.go b/hfiles.go index b63ed42168ea089eae8656d5615dd5d095965b23..67b279fa4a0d3d2a67c05126698c56ae1fc87b54 100644 --- a/hfiles.go +++ b/hfiles.go @@ -45,7 +45,7 @@ return } } - var entries []TreeEntry + var entries []*TreeEntry for _, entry := range tree.Entries { fullPath := entry.Name if pathValue != "" { @@ -62,7 +62,7 @@ size = blob.Size } } - entries = append(entries, TreeEntry{ + entries = append(entries, &TreeEntry{ Name: entry.Name, Path: fullPath, IsDir: isDir, @@ -78,14 +78,22 @@ } return entries[i].Name < entries[j].Name }) + var allEntries []*TreeEntry + fullTree, err := commit.Tree() + if err == nil { + allEntries = getTreeEntries(fullTree, "", 0) + } + data := struct { *RepoContext - Entries []TreeEntry - Path string - View string + Entries []*TreeEntry + AllEntries []*TreeEntry + Path string + View string }{ RepoContext: ctx, Entries: entries, + AllEntries: allEntries, Path: pathValue, View: "files", } @@ -128,14 +136,22 @@ http.Error(w, fmt.Sprintf("Error reading file: %v", err), http.StatusInternalServerError) return } + var allEntries []*TreeEntry + tree, err := commit.Tree() + if err == nil { + allEntries = getTreeEntries(tree, "", 0) + } + data := struct { *RepoContext - Path string - Content template.HTML + Path string + Content template.HTML + AllEntries []*TreeEntry }{ RepoContext: ctx, Path: pathValue, Content: highlight(pathValue, content), + AllEntries: allEntries, } err = templates.ExecuteTemplate(w, "blob.html", data) @@ -280,3 +296,39 @@ if err != nil { log.Printf("Error creating archive: %v", err) } } + +func getTreeEntries(tree *object.Tree, prefix string, depth int) []*TreeEntry { + var entries []*TreeEntry + for _, entry := range tree.Entries { + fullPath := entry.Name + if prefix != "" { + fullPath = prefix + "/" + entry.Name + } + + isDir := entry.Mode.IsFile() == false + te := &TreeEntry{ + Name: entry.Name, + Path: fullPath, + IsDir: isDir, + Mode: entry.Mode.String(), + Depth: depth, + } + + if isDir { + subTree, err := tree.Tree(entry.Name) + if err == nil { + te.Children = getTreeEntries(subTree, fullPath, depth+1) + } + } + entries = append(entries, te) + } + + sort.Slice(entries, func(i, j int) bool { + if entries[i].IsDir != entries[j].IsDir { + return entries[i].IsDir + } + return entries[i].Name < entries[j].Name + }) + + return entries +} diff --git a/hrepo.go b/hrepo.go index 43e3d8c9af0627885bec73d1c36182255ff89a7c..cffa75094a0cb7baf961e6bc33a0f0aa0381f644 100644 --- a/hrepo.go +++ b/hrepo.go @@ -35,7 +35,7 @@ pageSize := 30 totalCommitsKey := ctx.Repo.Name + ":" + ctx.Hash.String() var totalCommits int - if val, ok := repoMetadataCache.Load(totalCommitsKey); ok { + if val, ok := repoMetadataCache.Load(totalCommitsKey); ok && val.(RepoMetadata).TotalCommits > 0 { totalCommits = val.(RepoMetadata).TotalCommits } else { cIter, err := ctx.GitRepo.Log(&git.LogOptions{From: ctx.Hash}) diff --git a/models.go b/models.go index 10af77c7c1a298d3e9e4e1c872f7b34e0dfa571a..a4519d7bca9571e18cc5fbf7500a72f4c943797a 100644 --- a/models.go +++ b/models.go @@ -56,11 +56,13 @@ Repositories []Repository } type TreeEntry struct { - Name string - Path string - IsDir bool - Size int64 - Mode string + Name string + Path string + IsDir bool + Size int64 + Mode string + Depth int + Children []*TreeEntry } type RepoContext struct { diff --git a/static/style.css b/static/style.css index 9beb36a43377d6ee03d6f0e00ce587bb63393dcc..72ef35f00f5c06cfa38827458d5b2866c7197a92 100644 --- a/static/style.css +++ b/static/style.css @@ -81,14 +81,10 @@ a:hover { text-decoration: underline; } -pre { - margin: 0; - overflow-x: auto; - scrollbar-width: none; +pre { + margin: 0; + overflow-x: auto; white-space: pre; -} -pre::-webkit-scrollbar { - display: none; } blockquote { @@ -128,7 +124,20 @@ .filetype { font-size: small; color: var(--fg-muted); } .nav-main { display: flex; + justify-content: space-between; + align-items: center; +} + +.nav-main-left { + display: flex; gap: 0.4em; + align-items: center; +} + +.nav-main-right { + display: flex; + gap: 1em; + font-size: small; } .nav-repository { @@ -141,17 +150,96 @@ gap: 0.75em; } } +.content-layout { + display: flex; + gap: 1em; + min-width: 0; + align-items: flex-start; +} + +.tree-view { + width: 250px; + flex-shrink: 0; + border: 1px solid var(--border); + font-size: small; + padding: 0.5em 0; + align-self: flex-start; +} + +.tree-entry { + display: block; + padding: 2px 10px; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + color: #000000; + text-decoration: none; +} + +.tree-entry:hover { + background: var(--table-row-hover-bg); + text-decoration: none; +} + +.tree-entry.active { + font-weight: bold; + background: var(--bg); + color: var(--link); +} + +.tree-entry.directory { + color: #000000; +} + +.code-container { + flex-grow: 1; + display: flex; + flex-direction: column; + border: 1px solid var(--border); + min-width: 0; +} + +.code-header { + display: flex; + justify-content: space-between; + align-items: center; + padding: 0.5em 1em; + background: var(--table-header-bg); + border-bottom: 1px solid var(--border); + position: sticky; + top: 0; + z-index: 10; + font-size: small; + font-family: monospace; +} + .code-view { - border: 1px solid var(--border); + border: none; +} + +.code-view pre { background: none !important; } + +.code-view code > span > span:first-child { + color: var(--ln-fg); + background: var(--ln-bg); + border-right: 1px solid var(--border); + font-size: smaller; } -.diff-table { - table-layout: fixed; +.files-view { + flex-grow: 1; + overflow: auto; + min-width: 0; + + a { color: initial; } +} + +.diff-table { width: 100%; border-collapse: collapse; font-family: monospace; - td { + td { padding: 0 2px; border: 0; overflow: hidden; @@ -182,13 +270,13 @@ .diff-add { background-color: var(--diff-add-bg); color: var(--diff-add-fg); } .diff-del { background-color: var(--diff-del-bg); color: var(--diff-del-fg); } .diff-mod { background-color: var(--diff-mod-bg); color: var(--diff-mod-fg); } - .diff-gap { + .diff-gap { background-color: var(--diff-gap-bg); color: var(--diff-gap-fg); border-top: 1px solid var(--border); border-bottom: 1px solid var(--border); } - .diff-fname { + .diff-fname { background-color: var(--diff-fname-bg); color: var(--diff-fname-fg); border-top: 1px solid var(--border); @@ -199,6 +287,31 @@ font-weight: bold; } } +.tree-view details summary { + list-style: none; + cursor: pointer; + outline: none; +} + +.tree-view details summary::-webkit-details-marker { + display: none; +} + +.tree-view details summary::before { + content: "▸"; + display: inline-block; + width: 1em; + color: #000000; +} + +.tree-view details[open] > summary::before { + content: "▾"; +} + +.tree-view details { + display: block; +} + .file-diffs { display: flex; flex-direction: column; @@ -223,16 +336,6 @@ .summary { color: var(--fg-muted); font-size: small; user-select: none; - } -} - -.code-view { - pre { background: none !important; } - code > span > span:first-child { - color: var(--ln-fg); - background: var(--ln-bg); - border-right: 1px solid var(--border); - font-size: smaller; } } @@ -367,16 +470,10 @@ .language-percentage { color: var(--fg-muted); } -footer { - display: flex; - justify-content: space-between; - align-items: center; +.footer { + margin-top: 2em; + padding-top: 1em; + text-align: center; color: var(--fg-muted); font-size: small; - - .footer-rss { - display: flex; - gap: 1em; - } } - diff --git a/templates.go b/templates.go index e1ea3fbcd852be64771e83f707794770e6fdc11d..5f3026275fa09d81b1564fe5e785b57daac3be36 100644 --- a/templates.go +++ b/templates.go @@ -42,6 +42,9 @@ }, "float64": func(i int) float64 { return float64(i) }, + "int": func(f float64) int { + return int(f) + }, "multiply": func(a, b float64) float64 { return a * b }, @@ -50,6 +53,21 @@ if b == 0 { return 0 } return a / b + }, + "hasPrefix": strings.HasPrefix, + "dict": func(values ...interface{}) (map[string]interface{}, error) { + if len(values)%2 != 0 { + return nil, fmt.Errorf("invalid dict call") + } + dict := make(map[string]interface{}, len(values)/2) + for i := 0; i < len(values); i += 2 { + key, ok := values[i].(string) + if !ok { + return nil, fmt.Errorf("dict keys must be strings") + } + dict[key] = values[i+1] + } + return dict, nil }, "humanize": func(size int64) string { const unit = 1024 diff --git a/views/blob.html b/views/blob.html index daee5f1c51dc88fd7debdaa7aff6cd597f150f96..53f15fede2a433daa09b3527c7f34b8422a03f19 100644 --- a/views/blob.html +++ b/views/blob.html @@ -11,28 +11,20 @@
{{template "nav-repository.html" .}} - - -
-
{{.Content}}
+
+
+ {{template "tree" dict "Entries" .AllEntries "RepoName" .Repo.Name "CurrentRef" .CurrentRef "CurrentPath" .Path}} +
+
+
+ {{.Path}} + raw +
+
{{.Content}}
+
- {{template "footer.html" .}} + diff --git a/views/commit.html b/views/commit.html index 1c6f6b8462f229157b09ebc67e23a6d0ffd44506..4b66cc6e4c0f999750e4a5ecda51cb93b3e2a1a0 100644 --- a/views/commit.html +++ b/views/commit.html @@ -10,7 +10,6 @@ {{template "nav-main.html" .}}
{{template "nav-repository.html" .}} -
{{$lines := split .Commit.Message "\n"}}

{{index $lines 0}}

@@ -126,7 +125,7 @@ {{end}} {{end}}
- {{template "footer.html" .}} + diff --git a/views/files.html b/views/files.html index ebdde68784f4a19eb260b26fbcbd5100c7a50b46..e04ffc013242e94e97291d5d172e90a16423c82b 100644 --- a/views/files.html +++ b/views/files.html @@ -11,63 +11,50 @@
{{template "nav-repository.html" .}} - +
+
+ {{template "tree" dict "Entries" .AllEntries "RepoName" .Repo.Name "CurrentRef" .CurrentRef "CurrentPath" .Path}} +
- - - - - - - - - - - - {{if .Path}} - - - - - {{end}} - {{range .Entries}} - - - +
NameModeSizeRaw
..
{{if .IsDir}}d{{else}}f{{end}} - {{if .IsDir}} - {{.Name}}/ - {{else}} - {{.Name}} +
+ + + + + + + + + + + + {{if .Path}} + + + + + {{end}} + {{range .Entries}} + + + + + + + {{end}} - - - - - - {{end}} - -
NameModeSizeRaw
..
{{if .IsDir}}{{template "icon-directory" .}}{{else}}{{template "icon-file" .}}{{end}} + {{if .IsDir}} + {{.Name}}/ + {{else}} + {{.Name}} + {{end}} + {{.Mode}}{{if not .IsDir}}{{humanize .Size}}{{end}}{{if not .IsDir}}raw{{end}}
{{.Mode}}{{if not .IsDir}}{{humanize .Size}}{{end}}{{if not .IsDir}}raw{{end}}
+
+
+
- {{template "footer.html" .}} + diff --git a/views/footer.html b/views/footer.html index c1238cc81da6727c0995eebae6e1881014df49f0..1c9f894ba626422a5627ba138a4662b7f8de9fff 100644 --- a/views/footer.html +++ b/views/footer.html @@ -1,11 +1,3 @@ diff --git a/views/license.html b/views/license.html index ddc38b3ccc8d0c4cc75acf8d984969003e3265e6..4ce620ed1b628b359f87291b3a3aa80aeab6c01e 100644 --- a/views/license.html +++ b/views/license.html @@ -15,7 +15,7 @@
{{.Content}}
- {{template "footer.html" .}} + diff --git a/views/markers.html b/views/markers.html index add4341dc673a96170e586b9e99c55cc2415fd1c..f4e029fa3905b492cb5642c513b00966fadc5da1 100644 --- a/views/markers.html +++ b/views/markers.html @@ -34,7 +34,7 @@ {{end}} - {{template "footer.html" .}} + diff --git a/views/nav-main.html b/views/nav-main.html index 26f4c77465ee3b55d53cc68a77462ecdf122f69f..2480b15cbe920a8998815d651ea826ca4fd39c2a 100644 --- a/views/nav-main.html +++ b/views/nav-main.html @@ -1,7 +1,15 @@ diff --git a/views/readme.html b/views/readme.html index 08617f55dc74478791cb98411959e2b1f40abc9d..da61aca99f9900f813f71d0a779f13d6254461a0 100644 --- a/views/readme.html +++ b/views/readme.html @@ -15,7 +15,7 @@
{{.Content}}
- {{template "footer.html" .}} + diff --git a/views/repositories.html b/views/repositories.html index 79e4bee43cb8371de1d47332644d7de6796b496e..be83c5d3978ffa149f213dc75a74af5311004fe5 100644 --- a/views/repositories.html +++ b/views/repositories.html @@ -37,7 +37,7 @@ {{end}} - {{template "footer.html" .}} + diff --git a/views/repository.html b/views/repository.html index b148aa12a3a5bd57d6d0297228f55c15fdb8f60a..0d4de1f90711358a5074f8882825affb29fee337 100644 --- a/views/repository.html +++ b/views/repository.html @@ -40,6 +40,7 @@ Date Commit message + Author Lines @@ -49,6 +50,9 @@ {{range .Commits}} {{.AuthorDate.Format "2006-01-02"}} {{truncate .Message 80}} + + browse + {{.AuthorName}} +{{.Additions}}/-{{.Deletions}} @@ -56,7 +60,7 @@ {{else}} - No commits found. + No commits found. {{end}} @@ -86,7 +90,7 @@ {{end}} {{end}} - {{template "footer.html" .}} + diff --git a/views/tree.html b/views/tree.html new file mode 100644 index 0000000000000000000000000000000000000000..2d3f34d778a2d9c98961bb1a2e5b1173112ea98c --- /dev/null +++ b/views/tree.html @@ -0,0 +1,21 @@ +{{define "icon-file"}}{{end}} +{{define "icon-directory"}}{{end}} + +{{define "tree"}} + {{range .Entries}} + {{if .IsDir}} +
+ + {{template "icon-directory" .}} {{.Name}} + + {{template "tree" dict "Entries" .Children "RepoName" $.RepoName "CurrentRef" $.CurrentRef "CurrentPath" $.CurrentPath}} +
+ {{else}} + + {{template "icon-file" .}} {{.Name}} + + {{end}} + {{end}} +{{end}}