forked from tangled.org/core
Monorepo for Tangled

appview/pages: good first issue page

Signed-off-by: Anirudh Oppiliappan <anirudh@tangled.org>

authored by anirudh.fi and committed by oppi.li 972b9798 0ef988d4

Changed files
+301 -53
appview
pages
templates
goodfirstissues
repo
+180
appview/pages/templates/goodfirstissues/index.html
···
··· 1 + {{ define "title" }}good first issues{{ end }} 2 + 3 + {{ define "extrameta" }} 4 + <meta property="og:title" content="good first issues · tangled" /> 5 + <meta property="og:type" content="object" /> 6 + <meta property="og:url" content="https://tangled.org/goodfirstissues" /> 7 + <meta property="og:description" content="Find good first issues to contribute to open source projects" /> 8 + {{ end }} 9 + 10 + {{ define "content" }} 11 + <div class="grid grid-cols-10"> 12 + <header class="col-span-full md:col-span-10 px-6 py-2 mb-4"> 13 + <h1 class="text-2xl font-bold dark:text-white mb-1">Good First Issues</h1> 14 + <p class="text-gray-600 dark:text-gray-400 mb-2"> 15 + Find beginner-friendly issues across all repositories to get started with open source contributions. 16 + </p> 17 + </header> 18 + 19 + <div class="col-span-full md:col-span-10 space-y-6"> 20 + {{ if eq (len .RepoGroups) 0 }} 21 + <div class="bg-white dark:bg-gray-800 drop-shadow-sm rounded p-6 md:px-10"> 22 + <div class="text-center py-16"> 23 + <div class="text-gray-500 dark:text-gray-400 mb-4"> 24 + {{ i "circle-dot" "w-16 h-16 mx-auto" }} 25 + </div> 26 + <h3 class="text-xl font-medium text-gray-900 dark:text-white mb-2">No good first issues available</h3> 27 + <p class="text-gray-600 dark:text-gray-400 mb-3 max-w-md mx-auto"> 28 + There are currently no open issues labeled as "good-first-issue" across all repositories. 29 + </p> 30 + <p class="text-gray-500 dark:text-gray-500 text-sm max-w-md mx-auto"> 31 + Repository maintainers can add the "good-first-issue" label to beginner-friendly issues to help newcomers get started. 32 + </p> 33 + </div> 34 + </div> 35 + {{ else }} 36 + {{ range .RepoGroups }} 37 + <div class="mb-4 gap-1 flex flex-col drop-shadow-sm rounded bg-white dark:bg-gray-800"> 38 + <div class="flex px-6 pt-4 pb-2 flex-row gap-1"> 39 + <div class="font-medium dark:text-white flex items-center justify-between"> 40 + <div class="flex items-center min-w-0 flex-1 mr-2"> 41 + {{ if .Repo.Source }} 42 + {{ i "git-fork" "w-4 h-4 mr-1.5 shrink-0" }} 43 + {{ else }} 44 + {{ i "book-marked" "w-4 h-4 mr-1.5 shrink-0" }} 45 + {{ end }} 46 + {{ $repoOwner := resolve .Repo.Did }} 47 + <a href="/{{ $repoOwner }}/{{ .Repo.Name }}" class="truncate min-w-0">{{ $repoOwner }}/{{ .Repo.Name }}</a> 48 + </div> 49 + </div> 50 + 51 + 52 + {{ if .Repo.RepoStats }} 53 + <div class="text-gray-400 text-sm font-mono inline-flex gap-4 mt-auto"> 54 + {{ with .Repo.RepoStats.Language }} 55 + <div class="flex gap-2 items-center text-sm"> 56 + {{ template "repo/fragments/colorBall" (dict "color" (langColor .)) }} 57 + <span>{{ . }}</span> 58 + </div> 59 + {{ end }} 60 + {{ with .Repo.RepoStats.StarCount }} 61 + <div class="flex gap-1 items-center text-sm"> 62 + {{ i "star" "w-3 h-3 fill-current" }} 63 + <span>{{ . }}</span> 64 + </div> 65 + {{ end }} 66 + {{ with .Repo.RepoStats.IssueCount.Open }} 67 + <div class="flex gap-1 items-center text-sm"> 68 + {{ i "circle-dot" "w-3 h-3" }} 69 + <span>{{ . }}</span> 70 + </div> 71 + {{ end }} 72 + {{ with .Repo.RepoStats.PullCount.Open }} 73 + <div class="flex gap-1 items-center text-sm"> 74 + {{ i "git-pull-request" "w-3 h-3" }} 75 + <span>{{ . }}</span> 76 + </div> 77 + {{ end }} 78 + </div> 79 + {{ end }} 80 + </div> 81 + 82 + {{ with .Repo.Description }} 83 + <div class="pl-6 pb-2 text-gray-600 dark:text-gray-300 text-sm line-clamp-2"> 84 + {{ . | description }} 85 + </div> 86 + {{ end }} 87 + 88 + {{ if gt (len .Issues) 0 }} 89 + <details class="bg-white dark:bg-gray-800 group" open> 90 + <summary class="py-4 px-6 text-xs list-none cursor-pointer hover:text-gray-500 hover:dark:text-gray-400"> 91 + {{ $s := "s" }} 92 + {{ if eq (len .Issues) 1 }} 93 + {{ $s = "" }} 94 + {{ end }} 95 + <div class="group-open:hidden flex items-center gap-2"> 96 + {{ i "chevrons-up-down" "w-4 h-4" }} expand {{ len .Issues }} issue{{$s}} in this repo 97 + </div> 98 + <div class="hidden group-open:flex items-center gap-2"> 99 + {{ i "chevrons-down-up" "w-4 h-4" }} hide {{ len .Issues }} issue{{$s}} in this repo 100 + </div> 101 + </summary> 102 + <div class="grid grid-cols-1 rounded-b border-b border-t border-gray-200 dark:border-gray-900 divide-y divide-gray-200 dark:divide-gray-900"> 103 + {{ range .Issues }} 104 + <a href="/{{ resolve .Repo.Did }}/{{ .Repo.Name }}/issues/{{ .IssueId }}" class="no-underline hover:no-underline hover:bg-gray-100/25 hover:dark:bg-gray-700/25"> 105 + <div class="py-2 px-6"> 106 + <div class="flex-grow min-w-0 w-full"> 107 + <div class="flex text-sm items-center justify-between w-full"> 108 + <div class="flex items-center gap-2 min-w-0 flex-1 pr-2"> 109 + <span class="truncate text-sm text-gray-800 dark:text-gray-200"> 110 + <span class="text-gray-500 dark:text-gray-400">#{{ .IssueId }}</span> 111 + {{ .Title | description }} 112 + </span> 113 + </div> 114 + <div class="flex-shrink-0 flex items-center gap-2 text-gray-500 dark:text-gray-400"> 115 + <span> 116 + <div class="inline-flex items-center gap-1"> 117 + {{ i "message-square" "w-3 h-3 md:hidden" }} 118 + {{ len .Comments }} 119 + <span class="hidden md:inline">comment{{ if ne (len .Comments) 1 }}s{{ end }}</span> 120 + </div> 121 + </span> 122 + <span class="before:content-['·'] before:select-none"></span> 123 + <span class="text-xs"> 124 + {{ template "repo/fragments/time" .Created }} 125 + </span> 126 + <div class="hidden md:inline-flex md:gap-1"> 127 + {{ $labelState := .Labels }} 128 + {{ range $k, $d := $.LabelDefs }} 129 + {{ range $v, $s := $labelState.GetValSet $d.AtUri.String }} 130 + {{ template "labels/fragments/label" (dict "def" $d "val" $v "withPrefix" true) }} 131 + {{ end }} 132 + {{ end }} 133 + </div> 134 + </div> 135 + </div> 136 + </div> 137 + </div> 138 + </a> 139 + {{ end }} 140 + </div> 141 + </details> 142 + {{ end }} 143 + </div> 144 + {{ end }} 145 + 146 + {{ if or (gt .Page.Offset 0) (eq (len .RepoGroups) .Page.Limit) }} 147 + <div class="flex justify-center mt-8"> 148 + <div class="flex gap-2"> 149 + {{ if gt .Page.Offset 0 }} 150 + {{ $prev := .Page.Previous }} 151 + <a 152 + class="btn flex items-center gap-2 no-underline hover:no-underline dark:text-white dark:hover:bg-gray-700" 153 + hx-boost="true" 154 + href="/goodfirstissues?offset={{ $prev.Offset }}&limit={{ $prev.Limit }}" 155 + > 156 + {{ i "chevron-left" "w-4 h-4" }} 157 + previous 158 + </a> 159 + {{ else }} 160 + <div></div> 161 + {{ end }} 162 + 163 + {{ if eq (len .RepoGroups) .Page.Limit }} 164 + {{ $next := .Page.Next }} 165 + <a 166 + class="btn flex items-center gap-2 no-underline hover:no-underline dark:text-white dark:hover:bg-gray-700" 167 + hx-boost="true" 168 + href="/goodfirstissues?offset={{ $next.Offset }}&limit={{ $next.Limit }}" 169 + > 170 + next 171 + {{ i "chevron-right" "w-4 h-4" }} 172 + </a> 173 + {{ end }} 174 + </div> 175 + </div> 176 + {{ end }} 177 + {{ end }} 178 + </div> 179 + </div> 180 + {{ end }}
+63
appview/pages/templates/repo/issues/fragments/globalIssueListing.html
···
··· 1 + {{ define "repo/issues/fragments/globalIssueListing" }} 2 + <div class="flex flex-col gap-2"> 3 + {{ range .Issues }} 4 + <div class="rounded drop-shadow-sm bg-white px-6 py-4 dark:bg-gray-800 dark:border-gray-700"> 5 + <div class="pb-2 mb-3"> 6 + <div class="flex items-center gap-3 mb-2"> 7 + <a 8 + href="/{{ resolve .Repo.Did }}/{{ .Repo.Name }}" 9 + class="text-blue-600 dark:text-blue-400 font-medium hover:underline text-sm" 10 + > 11 + {{ resolve .Repo.Did }}/{{ .Repo.Name }} 12 + </a> 13 + </div> 14 + <a 15 + href="/{{ resolve .Repo.Did }}/{{ .Repo.Name }}/issues/{{ .IssueId }}" 16 + class="no-underline hover:underline" 17 + > 18 + {{ .Title | description }} 19 + <span class="text-gray-500">#{{ .IssueId }}</span> 20 + </a> 21 + </div> 22 + <div class="text-sm text-gray-500 dark:text-gray-400 flex flex-wrap items-center gap-1"> 23 + {{ $bgColor := "bg-gray-800 dark:bg-gray-700" }} 24 + {{ $icon := "ban" }} 25 + {{ $state := "closed" }} 26 + {{ if .Open }} 27 + {{ $bgColor = "bg-green-600 dark:bg-green-700" }} 28 + {{ $icon = "circle-dot" }} 29 + {{ $state = "open" }} 30 + {{ end }} 31 + 32 + <span class="inline-flex items-center rounded px-2 py-[5px] {{ $bgColor }} text-sm"> 33 + {{ i $icon "w-3 h-3 mr-1.5 text-white dark:text-white" }} 34 + <span class="text-white dark:text-white">{{ $state }}</span> 35 + </span> 36 + 37 + <span class="ml-1"> 38 + {{ template "user/fragments/picHandleLink" .Did }} 39 + </span> 40 + 41 + <span class="before:content-['·']"> 42 + {{ template "repo/fragments/time" .Created }} 43 + </span> 44 + 45 + <span class="before:content-['·']"> 46 + {{ $s := "s" }} 47 + {{ if eq (len .Comments) 1 }} 48 + {{ $s = "" }} 49 + {{ end }} 50 + <a href="/{{ resolve .Repo.Did }}/{{ .Repo.Name }}/issues/{{ .IssueId }}" class="text-gray-500 dark:text-gray-400">{{ len .Comments }} comment{{$s}}</a> 51 + </span> 52 + 53 + {{ $state := .Labels }} 54 + {{ range $k, $d := $.LabelDefs }} 55 + {{ range $v, $s := $state.GetValSet $d.AtUri.String }} 56 + {{ template "labels/fragments/label" (dict "def" $d "val" $v "withPrefix" true) }} 57 + {{ end }} 58 + {{ end }} 59 + </div> 60 + </div> 61 + {{ end }} 62 + </div> 63 + {{ end }}
+55
appview/pages/templates/repo/issues/fragments/issueListing.html
···
··· 1 + {{ define "repo/issues/fragments/issueListing" }} 2 + <div class="flex flex-col gap-2"> 3 + {{ range .Issues }} 4 + <div class="rounded drop-shadow-sm bg-white px-6 py-4 dark:bg-gray-800 dark:border-gray-700"> 5 + <div class="pb-2"> 6 + <a 7 + href="/{{ $.RepoPrefix }}/issues/{{ .IssueId }}" 8 + class="no-underline hover:underline" 9 + > 10 + {{ .Title | description }} 11 + <span class="text-gray-500">#{{ .IssueId }}</span> 12 + </a> 13 + </div> 14 + <div class="text-sm text-gray-500 dark:text-gray-400 flex flex-wrap items-center gap-1"> 15 + {{ $bgColor := "bg-gray-800 dark:bg-gray-700" }} 16 + {{ $icon := "ban" }} 17 + {{ $state := "closed" }} 18 + {{ if .Open }} 19 + {{ $bgColor = "bg-green-600 dark:bg-green-700" }} 20 + {{ $icon = "circle-dot" }} 21 + {{ $state = "open" }} 22 + {{ end }} 23 + 24 + <span class="inline-flex items-center rounded px-2 py-[5px] {{ $bgColor }} text-sm"> 25 + {{ i $icon "w-3 h-3 mr-1.5 text-white dark:text-white" }} 26 + <span class="text-white dark:text-white">{{ $state }}</span> 27 + </span> 28 + 29 + <span class="ml-1"> 30 + {{ template "user/fragments/picHandleLink" .Did }} 31 + </span> 32 + 33 + <span class="before:content-['·']"> 34 + {{ template "repo/fragments/time" .Created }} 35 + </span> 36 + 37 + <span class="before:content-['·']"> 38 + {{ $s := "s" }} 39 + {{ if eq (len .Comments) 1 }} 40 + {{ $s = "" }} 41 + {{ end }} 42 + <a href="/{{ $.RepoPrefix }}/issues/{{ .IssueId }}" class="text-gray-500 dark:text-gray-400">{{ len .Comments }} comment{{$s}}</a> 43 + </span> 44 + 45 + {{ $state := .Labels }} 46 + {{ range $k, $d := $.LabelDefs }} 47 + {{ range $v, $s := $state.GetValSet $d.AtUri.String }} 48 + {{ template "labels/fragments/label" (dict "def" $d "val" $v "withPrefix" true) }} 49 + {{ end }} 50 + {{ end }} 51 + </div> 52 + </div> 53 + {{ end }} 54 + </div> 55 + {{ end }}
+2 -52
appview/pages/templates/repo/issues/issues.html
··· 37 {{ end }} 38 39 {{ define "repoAfter" }} 40 - <div class="flex flex-col gap-2 mt-2"> 41 - {{ range .Issues }} 42 - <div class="rounded drop-shadow-sm bg-white px-6 py-4 dark:bg-gray-800 dark:border-gray-700"> 43 - <div class="pb-2"> 44 - <a 45 - href="/{{ $.RepoInfo.FullName }}/issues/{{ .IssueId }}" 46 - class="no-underline hover:underline" 47 - > 48 - {{ .Title | description }} 49 - <span class="text-gray-500">#{{ .IssueId }}</span> 50 - </a> 51 - </div> 52 - <div class="text-sm text-gray-500 dark:text-gray-400 flex flex-wrap items-center gap-1"> 53 - {{ $bgColor := "bg-gray-800 dark:bg-gray-700" }} 54 - {{ $icon := "ban" }} 55 - {{ $state := "closed" }} 56 - {{ if .Open }} 57 - {{ $bgColor = "bg-green-600 dark:bg-green-700" }} 58 - {{ $icon = "circle-dot" }} 59 - {{ $state = "open" }} 60 - {{ end }} 61 - 62 - <span class="inline-flex items-center rounded px-2 py-[5px] {{ $bgColor }} text-sm"> 63 - {{ i $icon "w-3 h-3 mr-1.5 text-white dark:text-white" }} 64 - <span class="text-white dark:text-white">{{ $state }}</span> 65 - </span> 66 - 67 - <span class="ml-1"> 68 - {{ template "user/fragments/picHandleLink" .Did }} 69 - </span> 70 - 71 - <span class="before:content-['·']"> 72 - {{ template "repo/fragments/time" .Created }} 73 - </span> 74 - 75 - <span class="before:content-['·']"> 76 - {{ $s := "s" }} 77 - {{ if eq (len .Comments) 1 }} 78 - {{ $s = "" }} 79 - {{ end }} 80 - <a href="/{{ $.RepoInfo.FullName }}/issues/{{ .IssueId }}" class="text-gray-500 dark:text-gray-400">{{ len .Comments }} comment{{$s}}</a> 81 - </span> 82 - 83 - {{ $state := .Labels }} 84 - {{ range $k, $d := $.LabelDefs }} 85 - {{ range $v, $s := $state.GetValSet $d.AtUri.String }} 86 - {{ template "labels/fragments/label" (dict "def" $d "val" $v "withPrefix" true) }} 87 - {{ end }} 88 - {{ end }} 89 - </div> 90 - </div> 91 - {{ end }} 92 </div> 93 {{ block "pagination" . }} {{ end }} 94 {{ end }}
··· 37 {{ end }} 38 39 {{ define "repoAfter" }} 40 + <div class="mt-2"> 41 + {{ template "repo/issues/fragments/issueListing" (dict "Issues" .Issues "RepoPrefix" .RepoInfo.FullName "LabelDefs" .LabelDefs) }} 42 </div> 43 {{ block "pagination" . }} {{ end }} 44 {{ end }}
+1 -1
go.mod
··· 43 github.com/yuin/goldmark v1.7.12 44 github.com/yuin/goldmark-highlighting/v2 v2.0.0-20230729083705-37449abec8cc 45 golang.org/x/crypto v0.40.0 46 golang.org/x/net v0.42.0 47 golang.org/x/sync v0.16.0 48 golang.org/x/xerrors v0.0.0-20240903120638-7835f813f4da ··· 168 go.uber.org/atomic v1.11.0 // indirect 169 go.uber.org/multierr v1.11.0 // indirect 170 go.uber.org/zap v1.27.0 // indirect 171 - golang.org/x/exp v0.0.0-20250620022241-b7579e27df2b // indirect 172 golang.org/x/sys v0.34.0 // indirect 173 golang.org/x/text v0.27.0 // indirect 174 golang.org/x/time v0.12.0 // indirect
··· 43 github.com/yuin/goldmark v1.7.12 44 github.com/yuin/goldmark-highlighting/v2 v2.0.0-20230729083705-37449abec8cc 45 golang.org/x/crypto v0.40.0 46 + golang.org/x/exp v0.0.0-20250620022241-b7579e27df2b 47 golang.org/x/net v0.42.0 48 golang.org/x/sync v0.16.0 49 golang.org/x/xerrors v0.0.0-20240903120638-7835f813f4da ··· 169 go.uber.org/atomic v1.11.0 // indirect 170 go.uber.org/multierr v1.11.0 // indirect 171 go.uber.org/zap v1.27.0 // indirect 172 golang.org/x/sys v0.34.0 // indirect 173 golang.org/x/text v0.27.0 // indirect 174 golang.org/x/time v0.12.0 // indirect