forked from tangled.org/core
Monorepo for Tangled — https://tangled.org

appview/pages: good first issue page

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

Changed files
+301 -53
appview
pages
templates
goodfirstissues
repo
+180
appview/pages/templates/goodfirstissues/index.html
···
+
{{ define "title" }}good first issues{{ end }}
+
+
{{ define "extrameta" }}
+
<meta property="og:title" content="good first issues · tangled" />
+
<meta property="og:type" content="object" />
+
<meta property="og:url" content="https://tangled.org/goodfirstissues" />
+
<meta property="og:description" content="Find good first issues to contribute to open source projects" />
+
{{ end }}
+
+
{{ define "content" }}
+
<div class="grid grid-cols-10">
+
<header class="col-span-full md:col-span-10 px-6 py-2 mb-4">
+
<h1 class="text-2xl font-bold dark:text-white mb-1">Good First Issues</h1>
+
<p class="text-gray-600 dark:text-gray-400 mb-2">
+
Find beginner-friendly issues across all repositories to get started with open source contributions.
+
</p>
+
</header>
+
+
<div class="col-span-full md:col-span-10 space-y-6">
+
{{ if eq (len .RepoGroups) 0 }}
+
<div class="bg-white dark:bg-gray-800 drop-shadow-sm rounded p-6 md:px-10">
+
<div class="text-center py-16">
+
<div class="text-gray-500 dark:text-gray-400 mb-4">
+
{{ i "circle-dot" "w-16 h-16 mx-auto" }}
+
</div>
+
<h3 class="text-xl font-medium text-gray-900 dark:text-white mb-2">No good first issues available</h3>
+
<p class="text-gray-600 dark:text-gray-400 mb-3 max-w-md mx-auto">
+
There are currently no open issues labeled as "good-first-issue" across all repositories.
+
</p>
+
<p class="text-gray-500 dark:text-gray-500 text-sm max-w-md mx-auto">
+
Repository maintainers can add the "good-first-issue" label to beginner-friendly issues to help newcomers get started.
+
</p>
+
</div>
+
</div>
+
{{ else }}
+
{{ range .RepoGroups }}
+
<div class="mb-4 gap-1 flex flex-col drop-shadow-sm rounded bg-white dark:bg-gray-800">
+
<div class="flex px-6 pt-4 pb-2 flex-row gap-1">
+
<div class="font-medium dark:text-white flex items-center justify-between">
+
<div class="flex items-center min-w-0 flex-1 mr-2">
+
{{ if .Repo.Source }}
+
{{ i "git-fork" "w-4 h-4 mr-1.5 shrink-0" }}
+
{{ else }}
+
{{ i "book-marked" "w-4 h-4 mr-1.5 shrink-0" }}
+
{{ end }}
+
{{ $repoOwner := resolve .Repo.Did }}
+
<a href="/{{ $repoOwner }}/{{ .Repo.Name }}" class="truncate min-w-0">{{ $repoOwner }}/{{ .Repo.Name }}</a>
+
</div>
+
</div>
+
+
+
{{ if .Repo.RepoStats }}
+
<div class="text-gray-400 text-sm font-mono inline-flex gap-4 mt-auto">
+
{{ with .Repo.RepoStats.Language }}
+
<div class="flex gap-2 items-center text-sm">
+
{{ template "repo/fragments/colorBall" (dict "color" (langColor .)) }}
+
<span>{{ . }}</span>
+
</div>
+
{{ end }}
+
{{ with .Repo.RepoStats.StarCount }}
+
<div class="flex gap-1 items-center text-sm">
+
{{ i "star" "w-3 h-3 fill-current" }}
+
<span>{{ . }}</span>
+
</div>
+
{{ end }}
+
{{ with .Repo.RepoStats.IssueCount.Open }}
+
<div class="flex gap-1 items-center text-sm">
+
{{ i "circle-dot" "w-3 h-3" }}
+
<span>{{ . }}</span>
+
</div>
+
{{ end }}
+
{{ with .Repo.RepoStats.PullCount.Open }}
+
<div class="flex gap-1 items-center text-sm">
+
{{ i "git-pull-request" "w-3 h-3" }}
+
<span>{{ . }}</span>
+
</div>
+
{{ end }}
+
</div>
+
{{ end }}
+
</div>
+
+
{{ with .Repo.Description }}
+
<div class="pl-6 pb-2 text-gray-600 dark:text-gray-300 text-sm line-clamp-2">
+
{{ . | description }}
+
</div>
+
{{ end }}
+
+
{{ if gt (len .Issues) 0 }}
+
<details class="bg-white dark:bg-gray-800 group" open>
+
<summary class="py-4 px-6 text-xs list-none cursor-pointer hover:text-gray-500 hover:dark:text-gray-400">
+
{{ $s := "s" }}
+
{{ if eq (len .Issues) 1 }}
+
{{ $s = "" }}
+
{{ end }}
+
<div class="group-open:hidden flex items-center gap-2">
+
{{ i "chevrons-up-down" "w-4 h-4" }} expand {{ len .Issues }} issue{{$s}} in this repo
+
</div>
+
<div class="hidden group-open:flex items-center gap-2">
+
{{ i "chevrons-down-up" "w-4 h-4" }} hide {{ len .Issues }} issue{{$s}} in this repo
+
</div>
+
</summary>
+
<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">
+
{{ range .Issues }}
+
<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">
+
<div class="py-2 px-6">
+
<div class="flex-grow min-w-0 w-full">
+
<div class="flex text-sm items-center justify-between w-full">
+
<div class="flex items-center gap-2 min-w-0 flex-1 pr-2">
+
<span class="truncate text-sm text-gray-800 dark:text-gray-200">
+
<span class="text-gray-500 dark:text-gray-400">#{{ .IssueId }}</span>
+
{{ .Title | description }}
+
</span>
+
</div>
+
<div class="flex-shrink-0 flex items-center gap-2 text-gray-500 dark:text-gray-400">
+
<span>
+
<div class="inline-flex items-center gap-1">
+
{{ i "message-square" "w-3 h-3 md:hidden" }}
+
{{ len .Comments }}
+
<span class="hidden md:inline">comment{{ if ne (len .Comments) 1 }}s{{ end }}</span>
+
</div>
+
</span>
+
<span class="before:content-['·'] before:select-none"></span>
+
<span class="text-xs">
+
{{ template "repo/fragments/time" .Created }}
+
</span>
+
<div class="hidden md:inline-flex md:gap-1">
+
{{ $labelState := .Labels }}
+
{{ range $k, $d := $.LabelDefs }}
+
{{ range $v, $s := $labelState.GetValSet $d.AtUri.String }}
+
{{ template "labels/fragments/label" (dict "def" $d "val" $v "withPrefix" true) }}
+
{{ end }}
+
{{ end }}
+
</div>
+
</div>
+
</div>
+
</div>
+
</div>
+
</a>
+
{{ end }}
+
</div>
+
</details>
+
{{ end }}
+
</div>
+
{{ end }}
+
+
{{ if or (gt .Page.Offset 0) (eq (len .RepoGroups) .Page.Limit) }}
+
<div class="flex justify-center mt-8">
+
<div class="flex gap-2">
+
{{ if gt .Page.Offset 0 }}
+
{{ $prev := .Page.Previous }}
+
<a
+
class="btn flex items-center gap-2 no-underline hover:no-underline dark:text-white dark:hover:bg-gray-700"
+
hx-boost="true"
+
href="/goodfirstissues?offset={{ $prev.Offset }}&limit={{ $prev.Limit }}"
+
>
+
{{ i "chevron-left" "w-4 h-4" }}
+
previous
+
</a>
+
{{ else }}
+
<div></div>
+
{{ end }}
+
+
{{ if eq (len .RepoGroups) .Page.Limit }}
+
{{ $next := .Page.Next }}
+
<a
+
class="btn flex items-center gap-2 no-underline hover:no-underline dark:text-white dark:hover:bg-gray-700"
+
hx-boost="true"
+
href="/goodfirstissues?offset={{ $next.Offset }}&limit={{ $next.Limit }}"
+
>
+
next
+
{{ i "chevron-right" "w-4 h-4" }}
+
</a>
+
{{ end }}
+
</div>
+
</div>
+
{{ end }}
+
{{ end }}
+
</div>
+
</div>
+
{{ end }}
+63
appview/pages/templates/repo/issues/fragments/globalIssueListing.html
···
+
{{ define "repo/issues/fragments/globalIssueListing" }}
+
<div class="flex flex-col gap-2">
+
{{ range .Issues }}
+
<div class="rounded drop-shadow-sm bg-white px-6 py-4 dark:bg-gray-800 dark:border-gray-700">
+
<div class="pb-2 mb-3">
+
<div class="flex items-center gap-3 mb-2">
+
<a
+
href="/{{ resolve .Repo.Did }}/{{ .Repo.Name }}"
+
class="text-blue-600 dark:text-blue-400 font-medium hover:underline text-sm"
+
>
+
{{ resolve .Repo.Did }}/{{ .Repo.Name }}
+
</a>
+
</div>
+
<a
+
href="/{{ resolve .Repo.Did }}/{{ .Repo.Name }}/issues/{{ .IssueId }}"
+
class="no-underline hover:underline"
+
>
+
{{ .Title | description }}
+
<span class="text-gray-500">#{{ .IssueId }}</span>
+
</a>
+
</div>
+
<div class="text-sm text-gray-500 dark:text-gray-400 flex flex-wrap items-center gap-1">
+
{{ $bgColor := "bg-gray-800 dark:bg-gray-700" }}
+
{{ $icon := "ban" }}
+
{{ $state := "closed" }}
+
{{ if .Open }}
+
{{ $bgColor = "bg-green-600 dark:bg-green-700" }}
+
{{ $icon = "circle-dot" }}
+
{{ $state = "open" }}
+
{{ end }}
+
+
<span class="inline-flex items-center rounded px-2 py-[5px] {{ $bgColor }} text-sm">
+
{{ i $icon "w-3 h-3 mr-1.5 text-white dark:text-white" }}
+
<span class="text-white dark:text-white">{{ $state }}</span>
+
</span>
+
+
<span class="ml-1">
+
{{ template "user/fragments/picHandleLink" .Did }}
+
</span>
+
+
<span class="before:content-['·']">
+
{{ template "repo/fragments/time" .Created }}
+
</span>
+
+
<span class="before:content-['·']">
+
{{ $s := "s" }}
+
{{ if eq (len .Comments) 1 }}
+
{{ $s = "" }}
+
{{ end }}
+
<a href="/{{ resolve .Repo.Did }}/{{ .Repo.Name }}/issues/{{ .IssueId }}" class="text-gray-500 dark:text-gray-400">{{ len .Comments }} comment{{$s}}</a>
+
</span>
+
+
{{ $state := .Labels }}
+
{{ range $k, $d := $.LabelDefs }}
+
{{ range $v, $s := $state.GetValSet $d.AtUri.String }}
+
{{ template "labels/fragments/label" (dict "def" $d "val" $v "withPrefix" true) }}
+
{{ end }}
+
{{ end }}
+
</div>
+
</div>
+
{{ end }}
+
</div>
+
{{ end }}
+55
appview/pages/templates/repo/issues/fragments/issueListing.html
···
+
{{ define "repo/issues/fragments/issueListing" }}
+
<div class="flex flex-col gap-2">
+
{{ range .Issues }}
+
<div class="rounded drop-shadow-sm bg-white px-6 py-4 dark:bg-gray-800 dark:border-gray-700">
+
<div class="pb-2">
+
<a
+
href="/{{ $.RepoPrefix }}/issues/{{ .IssueId }}"
+
class="no-underline hover:underline"
+
>
+
{{ .Title | description }}
+
<span class="text-gray-500">#{{ .IssueId }}</span>
+
</a>
+
</div>
+
<div class="text-sm text-gray-500 dark:text-gray-400 flex flex-wrap items-center gap-1">
+
{{ $bgColor := "bg-gray-800 dark:bg-gray-700" }}
+
{{ $icon := "ban" }}
+
{{ $state := "closed" }}
+
{{ if .Open }}
+
{{ $bgColor = "bg-green-600 dark:bg-green-700" }}
+
{{ $icon = "circle-dot" }}
+
{{ $state = "open" }}
+
{{ end }}
+
+
<span class="inline-flex items-center rounded px-2 py-[5px] {{ $bgColor }} text-sm">
+
{{ i $icon "w-3 h-3 mr-1.5 text-white dark:text-white" }}
+
<span class="text-white dark:text-white">{{ $state }}</span>
+
</span>
+
+
<span class="ml-1">
+
{{ template "user/fragments/picHandleLink" .Did }}
+
</span>
+
+
<span class="before:content-['·']">
+
{{ template "repo/fragments/time" .Created }}
+
</span>
+
+
<span class="before:content-['·']">
+
{{ $s := "s" }}
+
{{ if eq (len .Comments) 1 }}
+
{{ $s = "" }}
+
{{ end }}
+
<a href="/{{ $.RepoPrefix }}/issues/{{ .IssueId }}" class="text-gray-500 dark:text-gray-400">{{ len .Comments }} comment{{$s}}</a>
+
</span>
+
+
{{ $state := .Labels }}
+
{{ range $k, $d := $.LabelDefs }}
+
{{ range $v, $s := $state.GetValSet $d.AtUri.String }}
+
{{ template "labels/fragments/label" (dict "def" $d "val" $v "withPrefix" true) }}
+
{{ end }}
+
{{ end }}
+
</div>
+
</div>
+
{{ end }}
+
</div>
+
{{ end }}
+2 -52
appview/pages/templates/repo/issues/issues.html
···
{{ end }}
{{ define "repoAfter" }}
-
<div class="flex flex-col gap-2 mt-2">
-
{{ range .Issues }}
-
<div class="rounded drop-shadow-sm bg-white px-6 py-4 dark:bg-gray-800 dark:border-gray-700">
-
<div class="pb-2">
-
<a
-
href="/{{ $.RepoInfo.FullName }}/issues/{{ .IssueId }}"
-
class="no-underline hover:underline"
-
>
-
{{ .Title | description }}
-
<span class="text-gray-500">#{{ .IssueId }}</span>
-
</a>
-
</div>
-
<div class="text-sm text-gray-500 dark:text-gray-400 flex flex-wrap items-center gap-1">
-
{{ $bgColor := "bg-gray-800 dark:bg-gray-700" }}
-
{{ $icon := "ban" }}
-
{{ $state := "closed" }}
-
{{ if .Open }}
-
{{ $bgColor = "bg-green-600 dark:bg-green-700" }}
-
{{ $icon = "circle-dot" }}
-
{{ $state = "open" }}
-
{{ end }}
-
-
<span class="inline-flex items-center rounded px-2 py-[5px] {{ $bgColor }} text-sm">
-
{{ i $icon "w-3 h-3 mr-1.5 text-white dark:text-white" }}
-
<span class="text-white dark:text-white">{{ $state }}</span>
-
</span>
-
-
<span class="ml-1">
-
{{ template "user/fragments/picHandleLink" .Did }}
-
</span>
-
-
<span class="before:content-['·']">
-
{{ template "repo/fragments/time" .Created }}
-
</span>
-
-
<span class="before:content-['·']">
-
{{ $s := "s" }}
-
{{ if eq (len .Comments) 1 }}
-
{{ $s = "" }}
-
{{ end }}
-
<a href="/{{ $.RepoInfo.FullName }}/issues/{{ .IssueId }}" class="text-gray-500 dark:text-gray-400">{{ len .Comments }} comment{{$s}}</a>
-
</span>
-
-
{{ $state := .Labels }}
-
{{ range $k, $d := $.LabelDefs }}
-
{{ range $v, $s := $state.GetValSet $d.AtUri.String }}
-
{{ template "labels/fragments/label" (dict "def" $d "val" $v "withPrefix" true) }}
-
{{ end }}
-
{{ end }}
-
</div>
-
</div>
-
{{ end }}
+
<div class="mt-2">
+
{{ template "repo/issues/fragments/issueListing" (dict "Issues" .Issues "RepoPrefix" .RepoInfo.FullName "LabelDefs" .LabelDefs) }}
</div>
{{ block "pagination" . }} {{ end }}
{{ end }}
+1 -1
go.mod
···
github.com/yuin/goldmark v1.7.12
github.com/yuin/goldmark-highlighting/v2 v2.0.0-20230729083705-37449abec8cc
golang.org/x/crypto v0.40.0
+
golang.org/x/exp v0.0.0-20250620022241-b7579e27df2b
golang.org/x/net v0.42.0
golang.org/x/sync v0.16.0
golang.org/x/xerrors v0.0.0-20240903120638-7835f813f4da
···
go.uber.org/atomic v1.11.0 // indirect
go.uber.org/multierr v1.11.0 // indirect
go.uber.org/zap v1.27.0 // indirect
-
golang.org/x/exp v0.0.0-20250620022241-b7579e27df2b // indirect
golang.org/x/sys v0.34.0 // indirect
golang.org/x/text v0.27.0 // indirect
golang.org/x/time v0.12.0 // indirect