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 LoggedInUser *auth.User 294 RepoInfo RepoInfo 295 types.RepoLogResponse 296 } 297 298 func (p *Pages) RepoLog(w io.Writer, params RepoLogParams) error { 299 return p.execute("repo/log", w, params) 300 } 301
··· 293 LoggedInUser *auth.User 294 RepoInfo RepoInfo 295 types.RepoLogResponse 296 + Active string 297 } 298 299 func (p *Pages) RepoLog(w io.Writer, params RepoLogParams) error { 300 + params.Active = "overview" 301 return p.execute("repo/log", w, params) 302 } 303
+46 -35
appview/pages/templates/layouts/repobase.html
··· 3 {{ define "content" }} 4 <section id="repo-header" class="mb-4"> 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> 9 </p> 10 <span> 11 - {{ if .RepoInfo.Description }} 12 - {{ .RepoInfo.Description }} 13 - {{ else }} 14 - <span class="italic">this repo has no description</span> 15 - {{ end }} 16 </span> 17 </section> 18 <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 }} 46 </section> 47 {{ end }} 48
··· 3 {{ define "content" }} 4 <section id="repo-header" class="mb-4"> 5 <p class="text-xl"> 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 + > 17 </p> 18 <span> 19 + {{ if .RepoInfo.Description }} 20 + {{ .RepoInfo.Description }} 21 + {{ else }} 22 + <span class="italic">this repo has no description</span> 23 + {{ end }} 24 </span> 25 </section> 26 <section id="repo-links" class="min-h-screen flex flex-col"> 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 }} 57 </section> 58 {{ end }} 59
+2 -2
appview/pages/templates/layouts/topbar.html
··· 1 {{ define "layouts/topbar" }} 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"> 4 <div class="container flex justify-between p-0"> 5 <div id="left-items"> 6 <a href="/" hx-boost="true" class="{{ $linkstyle }} flex gap-2"> ··· 17 <summary class="{{ $linkstyle }} cursor-pointer list-none"> 18 {{ didOrHandle .Did .Handle }} 19 </summary> 20 - <div class="absolute flex flex-col right-0 mt-4 p-2 w-48 bg-white border border-black z-50"> 21 <a href="/{{ didOrHandle .Did .Handle }}"class="{{ $linkstyle }}">profile</a> 22 <a href="/knots"class="{{ $linkstyle }}">knots</a> 23 <a href="/settings"class="{{ $linkstyle }}">settings</a>
··· 1 {{ define "layouts/topbar" }} 2 {{ $linkstyle := "text-gray-400 hover:text-gray-900 no-underline" }} 3 + <nav class="space-x-4 mb-4 py-2 border-b border-gray-200"> 4 <div class="container flex justify-between p-0"> 5 <div id="left-items"> 6 <a href="/" hx-boost="true" class="{{ $linkstyle }} flex gap-2"> ··· 17 <summary class="{{ $linkstyle }} cursor-pointer list-none"> 18 {{ didOrHandle .Did .Handle }} 19 </summary> 20 + <div class="absolute flex flex-col right-0 mt-4 p-2 w-48 bg-white border border-gray-200 z-50"> 21 <a href="/{{ didOrHandle .Did .Handle }}"class="{{ $linkstyle }}">profile</a> 22 <a href="/knots"class="{{ $linkstyle }}">knots</a> 23 <a href="/settings"class="{{ $linkstyle }}">settings</a>
+2 -2
appview/pages/templates/repo/commit.html
··· 63 {{ $last := sub (len $diff) 1 }} 64 {{ range $idx, $hunk := $diff }} 65 {{ with $hunk }} 66 - <section class="mt-4 border border-black w-full mx-auto"> 67 <div id="file-{{ .Name.New }}"> 68 <div id="diff-file"> 69 <details open> 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"> 72 <div id="left-side-items" class="p-2"> 73 {{ if .IsNew }} 74 <span class="diff-type p-1 mr-1 font-mono text-sm bg-green-100 rounded text-green-700 select-none">A</span>
··· 63 {{ $last := sub (len $diff) 1 }} 64 {{ range $idx, $hunk := $diff }} 65 {{ with $hunk }} 66 + <section class="mt-4 border border-gray-200 w-full mx-auto"> 67 <div id="file-{{ .Name.New }}"> 68 <div id="diff-file"> 69 <details open> 70 <summary class="list-none cursor-pointer sticky top-0"> 71 + <div id="diff-file-header" class="border-b cursor-pointer bg-white border-gray-200 flex justify-between"> 72 <div id="left-side-items" class="p-2"> 73 {{ if .IsNew }} 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 {{ define "repoContent" }} 2 <main> 3 <div class="flex gap-4"> 4 - <div id="file-tree" class="w-3/5"> 5 {{ $containerstyle := "py-1" }} 6 {{ $linkstyle := "no-underline hover:underline" }} 7 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 {{ range .Files }} 48 {{ if not .IsFile }} 49 <div class="{{ $containerstyle }}"> ··· 94 {{ end }} 95 {{ end }} 96 </div> 97 <div id="commit-log" class="flex-1"> 98 {{ 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> 120 <div> 121 - <a 122 - href="/{{ $.RepoInfo.FullName }}/commit/{{ .Hash.String }}" 123 - class="inline no-underline hover:underline" 124 - >{{ index $messageParts 0 }}</a 125 - > 126 {{ 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')" 131 > 132 - <i 133 - class="w-3 h-3" 134 - data-lucide="ellipsis" 135 - ></i> 136 - </button> 137 {{ end }} 138 </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 </div> 147 </div> 148 - </div> 149 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> 172 </div> 173 </div> 174 {{ end }} ··· 179 180 {{ define "repoAfter" }} 181 {{- if .Readme }} 182 - <section class="mt-4 p-6 border border-black w-full mx-auto"> 183 <article class="readme"> 184 {{- .Readme -}} 185 </article> ··· 187 {{- end -}} 188 189 190 - <section class="mt-4 p-6 border border-black w-full mx-auto"> 191 <strong>clone</strong> 192 <pre> 193 git clone https://tangled.sh/{{ .RepoInfo.OwnerWithAt }}/{{ .RepoInfo.Name }} </pre
··· 1 {{ define "repoContent" }} 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 + 47 <div class="flex gap-4"> 48 + <div id="file-tree" class="w-3/5 pr-2 border-r border-gray-200"> 49 {{ $containerstyle := "py-1" }} 50 {{ $linkstyle := "no-underline hover:underline" }} 51 52 {{ range .Files }} 53 {{ if not .IsFile }} 54 <div class="{{ $containerstyle }}"> ··· 99 {{ end }} 100 {{ end }} 101 </div> 102 + 103 <div id="commit-log" class="flex-1"> 104 {{ range .Commits }} 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"> 114 <div> 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> 134 {{ if gt (len $messageParts) 1 }} 135 + <p 136 + class="hidden mt-1 text-sm cursor-text pb-2" 137 > 138 + {{ nl2br (unwrapText (index $messageParts 1)) }} 139 + </p> 140 {{ end }} 141 </div> 142 </div> 143 </div> 144 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> 168 </div> 169 </div> 170 {{ end }} ··· 175 176 {{ define "repoAfter" }} 177 {{- if .Readme }} 178 + <section class="mt-4 p-6 border border-gray-200 w-full mx-auto"> 179 <article class="readme"> 180 {{- .Readme -}} 181 </article> ··· 183 {{- end -}} 184 185 186 + <section class="mt-4 p-6 border border-gray-200 w-full mx-auto"> 187 <strong>clone</strong> 188 <pre> 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 }} 2 3 {{ define "repoContent" }} 4 <div class="flex justify-between items-center"> 5 - <h1 class="m-0">issues</h1> 6 <div class="error" id="issues"></div> 7 <a 8 href="/{{ .RepoInfo.FullName }}/issues/new" ··· 15 16 <section id="issues" class="mt-8 space-y-4"> 17 {{ range .Issues }} 18 - <div class="border border-gray-200 p-4"> 19 <time class="float-right text-sm"> 20 {{ .Created | timeFmt }} 21 </time>
··· 1 + {{ define "title" }}Issues &middot; {{ .RepoInfo.FullName }}{{ end }} 2 3 {{ define "repoContent" }} 4 <div class="flex justify-between items-center"> 5 + <h1 class="m-0">Issues</h1> 6 <div class="error" id="issues"></div> 7 <a 8 href="/{{ .RepoInfo.FullName }}/issues/new" ··· 15 16 <section id="issues" class="mt-8 space-y-4"> 17 {{ range .Issues }} 18 + <div class="border border-gray-200 p-4 mx-4 hover:bg-gray-50"> 19 <time class="float-right text-sm"> 20 {{ .Created | timeFmt }} 21 </time>
+100 -17
appview/pages/templates/repo/log.html
··· 1 - {{define "title"}} log | {{ .RepoInfo.FullName }} {{end}} 2 3 - {{define "content"}} 4 5 - <h1> 6 - log | {{ .RepoInfo.FullName }} 7 - </h1> 8 <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> 14 </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> 18 </div> 19 - <hr /> 20 - {{ end }} 21 - </div> 22 </main> 23 - {{end}}
··· 1 + {{ define "title" }}Commits &middot; {{ .RepoInfo.FullName }}{{ end }} 2 3 + {{ define "repoContent" }} 4 5 + <h1>Commits</h1> 6 <main> 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 }} 77 </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 }} 104 </div> 105 </main> 106 + {{ end }}
+16 -12
appview/pages/templates/repo/settings.html
··· 1 {{ define "repoContent" }} 2 <header class="font-bold text-sm mb-4">COLLABORATORS</header> 3 4 <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 }} 17 </div> 18 19 {{ if .IsCollaboratorInviteAllowed }}
··· 1 + {{ define "title" }}Settings &middot; {{ .RepoInfo.FullName }}{{ end }} 2 {{ define "repoContent" }} 3 <header class="font-bold text-sm mb-4">COLLABORATORS</header> 4 5 <div id="collaborator-list" class="flex flex-col gap-2 mb-2"> 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 }} 21 </div> 22 23 {{ if .IsCollaboratorInviteAllowed }}
+1 -1
appview/pages/templates/timeline.html
··· 1 - {{ define "title" }}timeline{{ end }} 2 3 {{ define "content" }} 4 <h1>Timeline</h1>
··· 1 + {{ define "title" }}Timeline{{ end }} 2 3 {{ define "content" }} 4 <h1>Timeline</h1>
+13 -5
appview/state/repo.go
··· 83 return 84 } 85 86 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)) 88 if err != nil { 89 log.Println("failed to reach knotserver", err) 90 return ··· 92 93 body, err := io.ReadAll(resp.Body) 94 if err != nil { 95 - log.Fatalf("Error reading response body: %v", err) 96 return 97 } 98 99 - var result types.RepoLogResponse 100 - err = json.Unmarshal(body, &result) 101 if err != nil { 102 log.Println("failed to parse json response", err) 103 return ··· 112 Name: f.RepoName, 113 SettingsAllowed: settingsAllowed(s, user, f), 114 }, 115 - RepoLogResponse: result, 116 }) 117 return 118 }
··· 83 return 84 } 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 + 94 ref := chi.URLParam(r, "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)) 96 if err != nil { 97 log.Println("failed to reach knotserver", err) 98 return ··· 100 101 body, err := io.ReadAll(resp.Body) 102 if err != nil { 103 + log.Printf("error reading response body: %v", err) 104 return 105 } 106 107 + var repolog types.RepoLogResponse 108 + err = json.Unmarshal(body, &repolog) 109 if err != nil { 110 log.Println("failed to parse json response", err) 111 return ··· 120 Name: f.RepoName, 121 SettingsAllowed: settingsAllowed(s, user, f), 122 }, 123 + RepoLogResponse: repolog, 124 }) 125 return 126 }
+1 -1
appview/state/state.go
··· 793 r.Get("/", s.ProfilePage) 794 r.With(ResolveRepoKnot(s)).Route("/{repo}", func(r chi.Router) { 795 r.Get("/", s.RepoIndex) 796 - r.Get("/log/{ref}", s.RepoLog) 797 r.Route("/tree/{ref}", func(r chi.Router) { 798 r.Get("/", s.RepoIndex) 799 r.Get("/*", s.RepoTree)
··· 793 r.Get("/", s.ProfilePage) 794 r.With(ResolveRepoKnot(s)).Route("/{repo}", func(r chi.Router) { 795 r.Get("/", s.RepoIndex) 796 + r.Get("/commits/{ref}", s.RepoLog) 797 r.Route("/tree/{ref}", func(r chi.Router) { 798 r.Get("/", s.RepoIndex) 799 r.Get("/*", s.RepoTree)
+56 -51
input.css
··· 3 @tailwind utilities; 4 @layer base { 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; 12 } 13 @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; 19 } 20 @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; 26 } 27 @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; 33 } 34 35 @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; 41 } 42 @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; 48 } 49 @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; 55 } 56 @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; 62 } 63 64 @font-face { ··· 81 } 82 83 html { 84 - letter-spacing: -0.01em; 85 - word-spacing: -0.07em; 86 } 87 88 a { ··· 106 @apply relative z-10 inline-flex min-h-[30px] cursor-pointer items-center 107 justify-center bg-transparent px-2 pb-[0.2rem] text-base 108 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 focus:outline-none focus-visible:before:outline 115 - focus-visible:before:outline-4 focus-visible:before:outline-blue-500 116 active:before:shadow-[inset_0_2px_2px_0_rgba(20,20,96,0.1)]; 117 } 118 } 119 @layer utilities { 120 .error { 121 - @apply py-1 border-black text-black; 122 } 123 .success { 124 - @apply py-1 border-black text-black; 125 } 126 } 127 }
··· 3 @tailwind utilities; 4 @layer base { 5 @font-face { 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; 15 } 16 @font-face { 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; 22 } 23 @font-face { 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; 29 } 30 @font-face { 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; 37 } 38 39 @font-face { 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; 45 } 46 @font-face { 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; 52 } 53 @font-face { 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; 59 } 60 @font-face { 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; 67 } 68 69 @font-face { ··· 86 } 87 88 html { 89 + letter-spacing: -0.01em; 90 + word-spacing: -0.07em; 91 } 92 93 a { ··· 111 @apply relative z-10 inline-flex min-h-[30px] cursor-pointer items-center 112 justify-center bg-transparent px-2 pb-[0.2rem] text-base 113 text-gray-900 before:absolute before:inset-0 before:-z-10 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] 119 focus:outline-none focus-visible:before:outline 120 + focus-visible:before:outline-4 focus-visible:before:outline-gray-500 121 active:before:shadow-[inset_0_2px_2px_0_rgba(20,20,96,0.1)]; 122 } 123 } 124 @layer utilities { 125 .error { 126 + @apply py-1 text-red-400; 127 } 128 .success { 129 + @apply py-1 text-black; 130 } 131 } 132 }
+10 -8
knotserver/routes.go
··· 51 } 52 53 commits, err := gr.Commits() 54 if err != nil { 55 writeError(w, err.Error(), http.StatusInternalServerError) 56 l.Error("fetching commits", "error", err.Error()) ··· 144 } 145 146 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, 155 } 156 157 writeJSON(w, resp)
··· 51 } 52 53 commits, err := gr.Commits() 54 + total := len(commits) 55 if err != nil { 56 writeError(w, err.Error(), http.StatusInternalServerError) 57 l.Error("fetching commits", "error", err.Error()) ··· 145 } 146 147 resp := types.RepoIndexResponse{ 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, 157 } 158 159 writeJSON(w, resp)
+9 -8
types/repo.go
··· 7 ) 8 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"` 18 } 19 20 type RepoLogResponse struct {
··· 7 ) 8 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"` 18 + TotalCommits int `json:"total_commits,omitempty"` 19 } 20 21 type RepoLogResponse struct {