forked from tangled.org/core
this repo has no description

appview: commits page

Restyle commits in repo index and implement a separate commits page with pagination.

authored by anirudh.fi and committed by GitHub cd93d12e ebb4880d

Changed files
+364 -252
appview
knotserver
types
+2
appview/pages/pages.go
··· 293 293 LoggedInUser *auth.User 294 294 RepoInfo RepoInfo 295 295 types.RepoLogResponse 296 + Active string 296 297 } 297 298 298 299 func (p *Pages) RepoLog(w io.Writer, params RepoLogParams) error { 300 + params.Active = "overview" 299 301 return p.execute("repo/log", w, params) 300 302 } 301 303
+46 -35
appview/pages/templates/layouts/repobase.html
··· 3 3 {{ define "content" }} 4 4 <section id="repo-header" class="mb-4"> 5 5 <p class="text-xl"> 6 - <a href="/{{ .RepoInfo.OwnerWithAt }}" class="no-underline hover:underline">{{ .RepoInfo.OwnerWithAt }}</a> 7 - / 8 - <a href="/{{ .RepoInfo.FullName }}" class="no-underline hover:underline">{{ .RepoInfo.Name }}</a> 6 + <a 7 + href="/{{ .RepoInfo.OwnerWithAt }}" 8 + class="no-underline hover:underline" 9 + >{{ .RepoInfo.OwnerWithAt }}</a 10 + > 11 + / 12 + <a 13 + href="/{{ .RepoInfo.FullName }}" 14 + class="no-underline hover:underline" 15 + >{{ .RepoInfo.Name }}</a 16 + > 9 17 </p> 10 18 <span> 11 - {{ if .RepoInfo.Description }} 12 - {{ .RepoInfo.Description }} 13 - {{ else }} 14 - <span class="italic">this repo has no description</span> 15 - {{ end }} 19 + {{ if .RepoInfo.Description }} 20 + {{ .RepoInfo.Description }} 21 + {{ else }} 22 + <span class="italic">this repo has no description</span> 23 + {{ end }} 16 24 </span> 17 25 </section> 18 26 <section id="repo-links" class="min-h-screen flex flex-col"> 19 - <nav class="w-full mx-auto"> 20 - <div class="flex z-60 border-black border-b"> 21 - {{ $activeTabStyles := "border-black border-l border-r border-t border-b-0 -mb-px bg-white" }} 22 - {{ $tabs := .RepoInfo.GetTabs }} 23 - {{ range $item := $tabs }} 24 - {{ $key := index $item 0 }} 25 - {{ $value := index $item 1 }} 26 - <a 27 - href="/{{ $.RepoInfo.FullName }}{{ $value }}" 28 - class="relative -mr-px group no-underline" 29 - hx-boost="true" 30 - > 31 - <div 32 - class="px-4 py-2 mr-1 text-black min-w-[80px] text-center relative group-hover:bg-gray-200 33 - {{ if eq $.Active $key }}{{ $activeTabStyles }}{{ end }}" 34 - > 35 - {{ $key }} 36 - </div> 37 - </a> 38 - {{ end}} 39 - </div> 40 - </nav> 41 - <section 42 - class="bg-white p-6 min-h-[200px] border-l border-r border-b border-black relative z-20 w-full mx-auto"> 43 - {{ block "repoContent" . }}{{ end }} 44 - </section> 45 - {{ block "repoAfter" . }} {{ end }} 27 + <nav class="w-full mx-auto"> 28 + <div class="flex z-60 border-gray-200 border-b"> 29 + {{ $activeTabStyles := "border-gray-200 border-l border-r border-t border-b-0 -mb-px bg-white" }} 30 + {{ $tabs := .RepoInfo.GetTabs }} 31 + {{ range $item := $tabs }} 32 + {{ $key := index $item 0 }} 33 + {{ $value := index $item 1 }} 34 + <a 35 + href="/{{ $.RepoInfo.FullName }}{{ $value }}" 36 + class="relative -mr-px group no-underline" 37 + hx-boost="true" 38 + > 39 + <div 40 + class="px-4 py-2 mr-1 text-black min-w-[80px] text-center relative group-hover:bg-gray-200 41 + {{ if eq $.Active $key }} 42 + {{ $activeTabStyles }} 43 + {{ end }}" 44 + > 45 + {{ $key }} 46 + </div> 47 + </a> 48 + {{ end }} 49 + </div> 50 + </nav> 51 + <section 52 + class="bg-white p-6 min-h-[200px] border-l border-r border-b border-gray-200 relative z-20 w-full mx-auto" 53 + > 54 + {{ block "repoContent" . }}{{ end }} 55 + </section> 56 + {{ block "repoAfter" . }}{{ end }} 46 57 </section> 47 58 {{ end }} 48 59
+2 -2
appview/pages/templates/layouts/topbar.html
··· 1 1 {{ define "layouts/topbar" }} 2 2 {{ $linkstyle := "text-gray-400 hover:text-gray-900 no-underline" }} 3 - <nav class="space-x-4 mb-4 py-2 border-b border-black"> 3 + <nav class="space-x-4 mb-4 py-2 border-b border-gray-200"> 4 4 <div class="container flex justify-between p-0"> 5 5 <div id="left-items"> 6 6 <a href="/" hx-boost="true" class="{{ $linkstyle }} flex gap-2"> ··· 17 17 <summary class="{{ $linkstyle }} cursor-pointer list-none"> 18 18 {{ didOrHandle .Did .Handle }} 19 19 </summary> 20 - <div class="absolute flex flex-col right-0 mt-4 p-2 w-48 bg-white border border-black z-50"> 20 + <div class="absolute flex flex-col right-0 mt-4 p-2 w-48 bg-white border border-gray-200 z-50"> 21 21 <a href="/{{ didOrHandle .Did .Handle }}"class="{{ $linkstyle }}">profile</a> 22 22 <a href="/knots"class="{{ $linkstyle }}">knots</a> 23 23 <a href="/settings"class="{{ $linkstyle }}">settings</a>
+2 -2
appview/pages/templates/repo/commit.html
··· 63 63 {{ $last := sub (len $diff) 1 }} 64 64 {{ range $idx, $hunk := $diff }} 65 65 {{ with $hunk }} 66 - <section class="mt-4 border border-black w-full mx-auto"> 66 + <section class="mt-4 border border-gray-200 w-full mx-auto"> 67 67 <div id="file-{{ .Name.New }}"> 68 68 <div id="diff-file"> 69 69 <details open> 70 70 <summary class="list-none cursor-pointer sticky top-0"> 71 - <div id="diff-file-header" class="border-b cursor-pointer bg-white border-black flex justify-between"> 71 + <div id="diff-file-header" class="border-b cursor-pointer bg-white border-gray-200 flex justify-between"> 72 72 <div id="left-side-items" class="p-2"> 73 73 {{ if .IsNew }} 74 74 <span class="diff-type p-1 mr-1 font-mono text-sm bg-green-100 rounded text-green-700 select-none">A</span>
+103 -107
appview/pages/templates/repo/index.html
··· 1 1 {{ define "repoContent" }} 2 2 <main> 3 + <div class="flex justify-between pb-5"> 4 + <select 5 + onchange="window.location.href = '/{{ .RepoInfo.FullName }}/tree/' + this.value" 6 + class="p-1 border border-gray-200 bg-white" 7 + > 8 + <optgroup label="branches" class="bold text-sm"> 9 + {{ range .Branches }} 10 + <option 11 + value="{{ .Reference.Name }}" 12 + class="py-1" 13 + {{ if eq .Reference.Name $.Ref }} 14 + selected 15 + {{ end }} 16 + > 17 + {{ .Reference.Name }} 18 + </option> 19 + {{ end }} 20 + </optgroup> 21 + <optgroup label="tags" class="bold text-sm"> 22 + {{ range .Tags }} 23 + <option 24 + value="{{ .Reference.Name }}" 25 + class="py-1" 26 + {{ if eq .Reference.Name $.Ref }} 27 + selected 28 + {{ end }} 29 + > 30 + {{ .Reference.Name }} 31 + </option> 32 + {{ else }} 33 + <option class="py-1" disabled>no tags found</option> 34 + {{ end }} 35 + </optgroup> 36 + </select> 37 + <a 38 + href="/{{ .RepoInfo.FullName }}/commits/{{ .Ref }}" 39 + class="btn ml-2 no-underline flex items-center gap-2" 40 + > 41 + <i class="w-4 h-4" data-lucide="logs"></i> 42 + {{ .TotalCommits }} 43 + {{ if eq .TotalCommits 1 }}commit{{ else }}commits{{ end }} 44 + </a> 45 + </div> 46 + 3 47 <div class="flex gap-4"> 4 - <div id="file-tree" class="w-3/5"> 48 + <div id="file-tree" class="w-3/5 pr-2 border-r border-gray-200"> 5 49 {{ $containerstyle := "py-1" }} 6 50 {{ $linkstyle := "no-underline hover:underline" }} 7 51 8 - 9 - <div class="flex justify-end"> 10 - <select 11 - onchange="window.location.href = '/{{ .RepoInfo.FullName }}/tree/' + this.value" 12 - class="p-1 border border-gray-500 bg-white" 13 - > 14 - <optgroup label="branches" class="bold text-sm"> 15 - {{ range .Branches }} 16 - <option 17 - value="{{ .Reference.Name }}" 18 - class="py-1" 19 - {{ if eq .Reference.Name $.Ref }} 20 - selected 21 - {{ end }} 22 - > 23 - {{ .Reference.Name }} 24 - </option> 25 - {{ end }} 26 - </optgroup> 27 - <optgroup label="tags" class="bold text-sm"> 28 - {{ range .Tags }} 29 - <option 30 - value="{{ .Reference.Name }}" 31 - class="py-1" 32 - {{ if eq .Reference.Name $.Ref }} 33 - selected 34 - {{ end }} 35 - > 36 - {{ .Reference.Name }} 37 - </option> 38 - {{ else }} 39 - <option class="py-1" disabled> 40 - no tags found 41 - </option> 42 - {{ end }} 43 - </optgroup> 44 - </select> 45 - </div> 46 - 47 52 {{ range .Files }} 48 53 {{ if not .IsFile }} 49 54 <div class="{{ $containerstyle }}"> ··· 94 99 {{ end }} 95 100 {{ end }} 96 101 </div> 102 + 97 103 <div id="commit-log" class="flex-1"> 98 104 {{ range .Commits }} 99 - <div 100 - class="relative 101 - px-4 102 - py-4 103 - border-l 104 - border-black 105 - before:content-[''] 106 - before:absolute 107 - before:w-1 108 - before:h-1 109 - before:bg-black 110 - before:rounded-full 111 - before:left-[-2.2px] 112 - before:top-1/2 113 - before:-translate-y-1/2 114 - " 115 - > 116 - <div id="commit-message"> 117 - {{ $messageParts := splitN .Message "\n\n" 2 }} 118 - <div class="text-base cursor-pointer"> 119 - <div> 105 + <div class="flex flex-row items-center"> 106 + <i 107 + class="w-5 h-5 text-gray-400 align-top" 108 + data-lucide="git-commit-horizontal" 109 + ></i> 110 + <div class="relative px-4 py-4"> 111 + <div id="commit-message"> 112 + {{ $messageParts := splitN .Message "\n\n" 2 }} 113 + <div class="text-base cursor-pointer"> 120 114 <div> 121 - <a 122 - href="/{{ $.RepoInfo.FullName }}/commit/{{ .Hash.String }}" 123 - class="inline no-underline hover:underline" 124 - >{{ index $messageParts 0 }}</a 125 - > 115 + <div> 116 + <a 117 + href="/{{ $.RepoInfo.FullName }}/commit/{{ .Hash.String }}" 118 + class="inline no-underline hover:underline" 119 + >{{ index $messageParts 0 }}</a 120 + > 121 + {{ if gt (len $messageParts) 1 }} 122 + 123 + <button 124 + class="py-1/2 px-1 bg-gray-200 hover:bg-gray-400 rounded" 125 + hx-on:click="this.parentElement.nextElementSibling.classList.toggle('hidden')" 126 + > 127 + <i 128 + class="w-3 h-3" 129 + data-lucide="ellipsis" 130 + ></i> 131 + </button> 132 + {{ end }} 133 + </div> 126 134 {{ if gt (len $messageParts) 1 }} 127 - 128 - <button 129 - class="py-1/2 px-1 bg-gray-200 hover:bg-gray-400 rounded" 130 - hx-on:click="this.parentElement.nextElementSibling.classList.toggle('hidden')" 135 + <p 136 + class="hidden mt-1 text-sm cursor-text pb-2" 131 137 > 132 - <i 133 - class="w-3 h-3" 134 - data-lucide="ellipsis" 135 - ></i> 136 - </button> 138 + {{ nl2br (unwrapText (index $messageParts 1)) }} 139 + </p> 137 140 {{ end }} 138 141 </div> 139 - {{ if gt (len $messageParts) 1 }} 140 - <p 141 - class="hidden mt-1 text-sm cursor-text pb-2" 142 - > 143 - {{ nl2br (unwrapText (index $messageParts 1)) }} 144 - </p> 145 - {{ end }} 146 142 </div> 147 143 </div> 148 - </div> 149 144 150 - <div class="text-xs text-gray-500"> 151 - <span class="font-mono"> 152 - <a 153 - href="/{{ $.RepoInfo.FullName }}/commit/{{ .Hash.String }}" 154 - class="text-gray-500 no-underline hover:underline" 155 - >{{ slice .Hash.String 0 8 }}</a 156 - > 157 - </span> 158 - <span 159 - class="mx-2 before:content-['·'] before:select-none" 160 - ></span> 161 - <span> 162 - <a 163 - href="mailto:{{ .Author.Email }}" 164 - class="text-gray-500 no-underline hover:underline" 165 - >{{ .Author.Name }}</a 166 - > 167 - </span> 168 - <div 169 - class="inline-block px-1 select-none after:content-['·']" 170 - ></div> 171 - <span>{{ timeFmt .Author.When }}</span> 145 + <div class="text-xs text-gray-500"> 146 + <span class="font-mono"> 147 + <a 148 + href="/{{ $.RepoInfo.FullName }}/commit/{{ .Hash.String }}" 149 + class="text-gray-500 no-underline hover:underline" 150 + >{{ slice .Hash.String 0 8 }}</a 151 + > 152 + </span> 153 + <span 154 + class="mx-2 before:content-['·'] before:select-none" 155 + ></span> 156 + <span> 157 + <a 158 + href="mailto:{{ .Author.Email }}" 159 + class="text-gray-500 no-underline hover:underline" 160 + >{{ .Author.Name }}</a 161 + > 162 + </span> 163 + <div 164 + class="inline-block px-1 select-none after:content-['·']" 165 + ></div> 166 + <span>{{ timeFmt .Author.When }}</span> 167 + </div> 172 168 </div> 173 169 </div> 174 170 {{ end }} ··· 179 175 180 176 {{ define "repoAfter" }} 181 177 {{- if .Readme }} 182 - <section class="mt-4 p-6 border border-black w-full mx-auto"> 178 + <section class="mt-4 p-6 border border-gray-200 w-full mx-auto"> 183 179 <article class="readme"> 184 180 {{- .Readme -}} 185 181 </article> ··· 187 183 {{- end -}} 188 184 189 185 190 - <section class="mt-4 p-6 border border-black w-full mx-auto"> 186 + <section class="mt-4 p-6 border border-gray-200 w-full mx-auto"> 191 187 <strong>clone</strong> 192 188 <pre> 193 189 git clone https://tangled.sh/{{ .RepoInfo.OwnerWithAt }}/{{ .RepoInfo.Name }} </pre
+3 -3
appview/pages/templates/repo/issues/issues.html
··· 1 - {{ define "title" }}issues | {{ .RepoInfo.FullName }}{{ end }} 1 + {{ define "title" }}Issues &middot; {{ .RepoInfo.FullName }}{{ end }} 2 2 3 3 {{ define "repoContent" }} 4 4 <div class="flex justify-between items-center"> 5 - <h1 class="m-0">issues</h1> 5 + <h1 class="m-0">Issues</h1> 6 6 <div class="error" id="issues"></div> 7 7 <a 8 8 href="/{{ .RepoInfo.FullName }}/issues/new" ··· 15 15 16 16 <section id="issues" class="mt-8 space-y-4"> 17 17 {{ range .Issues }} 18 - <div class="border border-gray-200 p-4"> 18 + <div class="border border-gray-200 p-4 mx-4 hover:bg-gray-50"> 19 19 <time class="float-right text-sm"> 20 20 {{ .Created | timeFmt }} 21 21 </time>
+100 -17
appview/pages/templates/repo/log.html
··· 1 - {{define "title"}} log | {{ .RepoInfo.FullName }} {{end}} 1 + {{ define "title" }}Commits &middot; {{ .RepoInfo.FullName }}{{ end }} 2 2 3 - {{define "content"}} 3 + {{ define "repoContent" }} 4 4 5 - <h1> 6 - log | {{ .RepoInfo.FullName }} 7 - </h1> 5 + <h1>Commits</h1> 8 6 <main> 9 - <div class="log"> 10 - {{ range .Commits }} 11 - <div> 12 - <div><a href="/{{ $.RepoInfo.FullName }}/commit/{{ .Hash.String }}" class="commit-hash">{{ slice .Hash.String 0 8 }}</a></div> 13 - <pre>{{ .Message }}</pre> 7 + <div id="commit-log" class="flex-1"> 8 + {{ range .Commits }} 9 + <div class="flex flex-row justify-between items-center"> 10 + <i 11 + class="w-5 h-5 mt-5 text-gray-400 align-middle" 12 + data-lucide="git-commit-horizontal" 13 + ></i> 14 + <div 15 + class="relative w-full px-4 py-4 mt-5 mx-3 hover:bg-gray-50 border border-gray-200" 16 + > 17 + <div id="commit-message"> 18 + {{ $messageParts := splitN .Message "\n\n" 2 }} 19 + <div class="text-base cursor-pointer"> 20 + <div> 21 + <div> 22 + <a 23 + href="/{{ $.RepoInfo.FullName }}/commit/{{ .Hash.String }}" 24 + class="inline no-underline hover:underline" 25 + >{{ index $messageParts 0 }}</a 26 + > 27 + {{ if gt (len $messageParts) 1 }} 28 + 29 + <button 30 + class="py-1/2 px-1 bg-gray-200 hover:bg-gray-400 rounded" 31 + hx-on:click="this.parentElement.nextElementSibling.classList.toggle('hidden')" 32 + > 33 + <i 34 + class="w-3 h-3" 35 + data-lucide="ellipsis" 36 + ></i> 37 + </button> 38 + {{ end }} 39 + </div> 40 + {{ if gt (len $messageParts) 1 }} 41 + <p 42 + class="hidden mt-1 text-sm cursor-text pb-2" 43 + > 44 + {{ nl2br (unwrapText (index $messageParts 1)) }} 45 + </p> 46 + {{ end }} 47 + </div> 48 + </div> 49 + </div> 50 + 51 + <div class="text-xs text-gray-500"> 52 + <span class="font-mono"> 53 + <a 54 + href="/{{ $.RepoInfo.FullName }}/commit/{{ .Hash.String }}" 55 + class="text-gray-500 no-underline hover:underline" 56 + >{{ slice .Hash.String 0 8 }}</a 57 + > 58 + </span> 59 + <span 60 + class="mx-2 before:content-['·'] before:select-none" 61 + ></span> 62 + <span> 63 + <a 64 + href="mailto:{{ .Author.Email }}" 65 + class="text-gray-500 no-underline hover:underline" 66 + >{{ .Author.Name }}</a 67 + > 68 + </span> 69 + <div 70 + class="inline-block px-1 select-none after:content-['·']" 71 + ></div> 72 + <span>{{ timeFmt .Author.When }}</span> 73 + </div> 74 + </div> 75 + </div> 76 + {{ end }} 14 77 </div> 15 - <div class="commit-info"> 16 - {{ .Author.Name }} <a href="mailto:{{ .Author.Email }}" class="commit-email">{{ .Author.Email }}</a> 17 - <div>{{ .Author.When.Format "Mon, 02 Jan 2006 15:04:05 -0700" }}</div> 78 + 79 + {{ $commits_len := len .Commits }} 80 + <div class="flex justify-between mt-4 px-10"> 81 + {{ if gt .Page 1 }} 82 + <a 83 + class="btn flex items-center gap-2 no-underline" 84 + hx-boost="true" 85 + onclick="window.location.href = window.location.pathname + '?page={{ sub .Page 1 }}'" 86 + > 87 + <i data-lucide="chevron-left" class="w-4 h-4"></i> 88 + previous 89 + </a> 90 + {{ else }} 91 + <div></div> 92 + {{ end }} 93 + 94 + {{ if eq $commits_len 30 }} 95 + <a 96 + class="btn flex items-center gap-2 no-underline" 97 + hx-boost="true" 98 + onclick="window.location.href = window.location.pathname + '?page={{ add .Page 1 }}'" 99 + > 100 + next 101 + <i data-lucide="chevron-right" class="w-4 h-4"></i> 102 + </a> 103 + {{ end }} 18 104 </div> 19 - <hr /> 20 - {{ end }} 21 - </div> 22 105 </main> 23 - {{end}} 106 + {{ end }}
+16 -12
appview/pages/templates/repo/settings.html
··· 1 + {{ define "title" }}Settings &middot; {{ .RepoInfo.FullName }}{{ end }} 1 2 {{ define "repoContent" }} 2 3 <header class="font-bold text-sm mb-4">COLLABORATORS</header> 3 4 4 5 <div id="collaborator-list" class="flex flex-col gap-2 mb-2"> 5 - {{ range .Collaborators }} 6 - <div id="collaborator" class="mb-2"> 7 - <a href="/{{ didOrHandle .Did .Handle }}" class="no-underline hover:underline text-black"> 8 - {{ didOrHandle .Did .Handle }} 9 - </a> 10 - <div> 11 - <span class="text-sm text-gray-500"> 12 - {{ .Role }} 13 - </span> 14 - </div> 15 - </div> 16 - {{ end }} 6 + {{ range .Collaborators }} 7 + <div id="collaborator" class="mb-2"> 8 + <a 9 + href="/{{ didOrHandle .Did .Handle }}" 10 + class="no-underline hover:underline text-black" 11 + > 12 + {{ didOrHandle .Did .Handle }} 13 + </a> 14 + <div> 15 + <span class="text-sm text-gray-500"> 16 + {{ .Role }} 17 + </span> 18 + </div> 19 + </div> 20 + {{ end }} 17 21 </div> 18 22 19 23 {{ if .IsCollaboratorInviteAllowed }}
+1 -1
appview/pages/templates/timeline.html
··· 1 - {{ define "title" }}timeline{{ end }} 1 + {{ define "title" }}Timeline{{ end }} 2 2 3 3 {{ define "content" }} 4 4 <h1>Timeline</h1>
+13 -5
appview/state/repo.go
··· 83 83 return 84 84 } 85 85 86 + page := 1 87 + if r.URL.Query().Get("page") != "" { 88 + page, err = strconv.Atoi(r.URL.Query().Get("page")) 89 + if err != nil { 90 + page = 1 91 + } 92 + } 93 + 86 94 ref := chi.URLParam(r, "ref") 87 - resp, err := http.Get(fmt.Sprintf("http://%s/%s/%s/log/%s", f.Knot, f.OwnerDid(), f.RepoName, ref)) 95 + resp, err := http.Get(fmt.Sprintf("http://%s/%s/%s/log/%s?page=%d&per_page=30", f.Knot, f.OwnerDid(), f.RepoName, ref, page)) 88 96 if err != nil { 89 97 log.Println("failed to reach knotserver", err) 90 98 return ··· 92 100 93 101 body, err := io.ReadAll(resp.Body) 94 102 if err != nil { 95 - log.Fatalf("Error reading response body: %v", err) 103 + log.Printf("error reading response body: %v", err) 96 104 return 97 105 } 98 106 99 - var result types.RepoLogResponse 100 - err = json.Unmarshal(body, &result) 107 + var repolog types.RepoLogResponse 108 + err = json.Unmarshal(body, &repolog) 101 109 if err != nil { 102 110 log.Println("failed to parse json response", err) 103 111 return ··· 112 120 Name: f.RepoName, 113 121 SettingsAllowed: settingsAllowed(s, user, f), 114 122 }, 115 - RepoLogResponse: result, 123 + RepoLogResponse: repolog, 116 124 }) 117 125 return 118 126 }
+1 -1
appview/state/state.go
··· 793 793 r.Get("/", s.ProfilePage) 794 794 r.With(ResolveRepoKnot(s)).Route("/{repo}", func(r chi.Router) { 795 795 r.Get("/", s.RepoIndex) 796 - r.Get("/log/{ref}", s.RepoLog) 796 + r.Get("/commits/{ref}", s.RepoLog) 797 797 r.Route("/tree/{ref}", func(r chi.Router) { 798 798 r.Get("/", s.RepoIndex) 799 799 r.Get("/*", s.RepoTree)
+56 -51
input.css
··· 3 3 @tailwind utilities; 4 4 @layer base { 5 5 @font-face { 6 - font-family: 'iA Writer Quattro S'; 7 - src: url('/static/fonts/iAWriterQuattroS-Regular.ttf') format('truetype'); 8 - font-weight: normal; 9 - font-style: normal; 10 - font-display: swap; 11 - font-feature-settings: "calt" 1, "kern" 1; 6 + font-family: "iA Writer Quattro S"; 7 + src: url("/static/fonts/iAWriterQuattroS-Regular.ttf") 8 + format("truetype"); 9 + font-weight: normal; 10 + font-style: normal; 11 + font-display: swap; 12 + font-feature-settings: 13 + "calt" 1, 14 + "kern" 1; 12 15 } 13 16 @font-face { 14 - font-family: 'iA Writer Quattro S'; 15 - src: url('/static/fonts/iAWriterQuattroS-Bold.ttf') format('truetype'); 16 - font-weight: bold; 17 - font-style: normal; 18 - font-display: swap; 17 + font-family: "iA Writer Quattro S"; 18 + src: url("/static/fonts/iAWriterQuattroS-Bold.ttf") format("truetype"); 19 + font-weight: bold; 20 + font-style: normal; 21 + font-display: swap; 19 22 } 20 23 @font-face { 21 - font-family: 'iA Writer Quattro S'; 22 - src: url('/static/fonts/iAWriterQuattroS-Italic.ttf') format('truetype'); 23 - font-weight: normal; 24 - font-style: italic; 25 - font-display: swap; 24 + font-family: "iA Writer Quattro S"; 25 + src: url("/static/fonts/iAWriterQuattroS-Italic.ttf") format("truetype"); 26 + font-weight: normal; 27 + font-style: italic; 28 + font-display: swap; 26 29 } 27 30 @font-face { 28 - font-family: 'iA Writer Quattro S'; 29 - src: url('/static/fonts/iAWriterQuattroS-BoldItalic.ttf') format('truetype'); 30 - font-weight: bold; 31 - font-style: italic; 32 - font-display: swap; 31 + font-family: "iA Writer Quattro S"; 32 + src: url("/static/fonts/iAWriterQuattroS-BoldItalic.ttf") 33 + format("truetype"); 34 + font-weight: bold; 35 + font-style: italic; 36 + font-display: swap; 33 37 } 34 38 35 39 @font-face { 36 - font-family: 'iA Writer Mono S'; 37 - src: url('/static/fonts/iAWriterMonoS-Regular.ttf') format('truetype'); 38 - font-weight: normal; 39 - font-style: normal; 40 - font-display: swap; 40 + font-family: "iA Writer Mono S"; 41 + src: url("/static/fonts/iAWriterMonoS-Regular.ttf") format("truetype"); 42 + font-weight: normal; 43 + font-style: normal; 44 + font-display: swap; 41 45 } 42 46 @font-face { 43 - font-family: 'iA Writer Mono S'; 44 - src: url('/static/fonts/iAWriterMonoS-Bold.ttf') format('truetype'); 45 - font-weight: bold; 46 - font-style: normal; 47 - font-display: swap; 47 + font-family: "iA Writer Mono S"; 48 + src: url("/static/fonts/iAWriterMonoS-Bold.ttf") format("truetype"); 49 + font-weight: bold; 50 + font-style: normal; 51 + font-display: swap; 48 52 } 49 53 @font-face { 50 - font-family: 'iA Writer Mono S'; 51 - src: url('/static/fonts/iAWriterMonoS-Italic.ttf') format('truetype'); 52 - font-weight: normal; 53 - font-style: italic; 54 - font-display: swap; 54 + font-family: "iA Writer Mono S"; 55 + src: url("/static/fonts/iAWriterMonoS-Italic.ttf") format("truetype"); 56 + font-weight: normal; 57 + font-style: italic; 58 + font-display: swap; 55 59 } 56 60 @font-face { 57 - font-family: 'iA Writer Mono S'; 58 - src: url('/static/fonts/iAWriterMonoS-BoldItalic.ttf') format('truetype'); 59 - font-weight: bold; 60 - font-style: italic; 61 - font-display: swap; 61 + font-family: "iA Writer Mono S"; 62 + src: url("/static/fonts/iAWriterMonoS-BoldItalic.ttf") 63 + format("truetype"); 64 + font-weight: bold; 65 + font-style: italic; 66 + font-display: swap; 62 67 } 63 68 64 69 @font-face { ··· 81 86 } 82 87 83 88 html { 84 - letter-spacing: -0.01em; 85 - word-spacing: -0.07em; 89 + letter-spacing: -0.01em; 90 + word-spacing: -0.07em; 86 91 } 87 92 88 93 a { ··· 106 111 @apply relative z-10 inline-flex min-h-[30px] cursor-pointer items-center 107 112 justify-center bg-transparent px-2 pb-[0.2rem] text-base 108 113 text-gray-900 before:absolute before:inset-0 before:-z-10 109 - before:block before:rounded-sm before:border before:border-blue-200 110 - before:bg-white before:shadow-[0_2px_2px_0_rgba(20,20,96,0.1),inset_0_-2px_0_0_#e5edff] 111 - before:content-[''] hover:before:border-blue-300 112 - hover:before:bg-blue-50 113 - hover:before:shadow-[0_2px_2px_0_rgba(20,20,96,0.1),inset_0_-2px_0_0_#e5edff] 114 + before:block before:rounded-sm before:border before:border-gray-200 115 + before:bg-white before:shadow-[0_2px_2px_0_rgba(20,20,96,0.1),inset_0_-2px_0_0_#f5f5f5] 116 + before:content-[''] hover:before:border-gray-300 117 + hover:before:bg-gray-50 118 + hover:before:shadow-[0_2px_2px_0_rgba(20,20,96,0.1),inset_0_-2px_0_0_#f5f5f5] 114 119 focus:outline-none focus-visible:before:outline 115 - focus-visible:before:outline-4 focus-visible:before:outline-blue-500 120 + focus-visible:before:outline-4 focus-visible:before:outline-gray-500 116 121 active:before:shadow-[inset_0_2px_2px_0_rgba(20,20,96,0.1)]; 117 122 } 118 123 } 119 124 @layer utilities { 120 125 .error { 121 - @apply py-1 border-black text-black; 126 + @apply py-1 text-red-400; 122 127 } 123 128 .success { 124 - @apply py-1 border-black text-black; 129 + @apply py-1 text-black; 125 130 } 126 131 } 127 132 }
+10 -8
knotserver/routes.go
··· 51 51 } 52 52 53 53 commits, err := gr.Commits() 54 + total := len(commits) 54 55 if err != nil { 55 56 writeError(w, err.Error(), http.StatusInternalServerError) 56 57 l.Error("fetching commits", "error", err.Error()) ··· 144 145 } 145 146 146 147 resp := types.RepoIndexResponse{ 147 - IsEmpty: false, 148 - Ref: ref, 149 - Commits: commits, 150 - Description: getDescription(path), 151 - Readme: readmeContent, 152 - Files: files, 153 - Branches: bs, 154 - Tags: rtags, 148 + IsEmpty: false, 149 + Ref: ref, 150 + Commits: commits, 151 + Description: getDescription(path), 152 + Readme: readmeContent, 153 + Files: files, 154 + Branches: bs, 155 + Tags: rtags, 156 + TotalCommits: total, 155 157 } 156 158 157 159 writeJSON(w, resp)
+9 -8
types/repo.go
··· 7 7 ) 8 8 9 9 type RepoIndexResponse struct { 10 - IsEmpty bool `json:"is_empty"` 11 - Ref string `json:"ref,omitempty"` 12 - Readme template.HTML `json:"readme,omitempty"` 13 - Commits []*object.Commit `json:"commits,omitempty"` 14 - Description string `json:"description,omitempty"` 15 - Files []NiceTree `json:"files,omitempty"` 16 - Branches []Branch `json:"branches,omitempty"` 17 - Tags []*TagReference `json:"tags,omitempty"` 10 + IsEmpty bool `json:"is_empty"` 11 + Ref string `json:"ref,omitempty"` 12 + Readme template.HTML `json:"readme,omitempty"` 13 + Commits []*object.Commit `json:"commits,omitempty"` 14 + Description string `json:"description,omitempty"` 15 + Files []NiceTree `json:"files,omitempty"` 16 + Branches []Branch `json:"branches,omitempty"` 17 + Tags []*TagReference `json:"tags,omitempty"` 18 + TotalCommits int `json:"total_commits,omitempty"` 18 19 } 19 20 20 21 type RepoLogResponse struct {