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

appview/state: style changes to good-first-issue

Signed-off-by: oppiliappan <me@oppi.li>

oppi.li 5348986c ddd4d545

verified
Changed files
+109 -109
appview
models
pages
templates
goodfirstissues
labels
fragments
timeline
state
+14 -13
appview/models/label.go
···
return result
}
+
var (
+
LabelWontfix = fmt.Sprintf("at://%s/%s/%s", consts.TangledDid, tangled.LabelDefinitionNSID, "wontfix")
+
LabelDuplicate = fmt.Sprintf("at://%s/%s/%s", consts.TangledDid, tangled.LabelDefinitionNSID, "duplicate")
+
LabelAssignee = fmt.Sprintf("at://%s/%s/%s", consts.TangledDid, tangled.LabelDefinitionNSID, "assignee")
+
LabelGoodFirstIssue = fmt.Sprintf("at://%s/%s/%s", consts.TangledDid, tangled.LabelDefinitionNSID, "good-first-issue")
+
LabelDocumentation = fmt.Sprintf("at://%s/%s/%s", consts.TangledDid, tangled.LabelDefinitionNSID, "documentation")
+
)
+
func DefaultLabelDefs() []string {
-
rkeys := []string{
-
"wontfix",
-
"duplicate",
-
"assignee",
-
"good-first-issue",
-
"documentation",
+
return []string{
+
LabelWontfix,
+
LabelDuplicate,
+
LabelAssignee,
+
LabelGoodFirstIssue,
+
LabelDocumentation,
}
-
-
defs := make([]string, len(rkeys))
-
for i, r := range rkeys {
-
defs[i] = fmt.Sprintf("at://%s/%s/%s", consts.TangledDid, tangled.LabelDefinitionNSID, r)
-
}
-
-
return defs
}
func FetchDefaultDefs(r *idresolver.Resolver) ([]LabelDefinition, error) {
+2
appview/pages/pages.go
···
LoggedInUser *oauth.User
Timeline []models.TimelineEvent
Repos []models.Repo
+
GfiLabel *models.LabelDefinition
}
func (p *Pages) Timeline(w io.Writer, params TimelineParams) error {
···
Issues []models.Issue
RepoGroups []*models.RepoGroup
LabelDefs map[string]*models.LabelDefinition
+
GfiLabel *models.LabelDefinition
Page pagination.Page
}
+50 -62
appview/pages/templates/goodfirstissues/index.html
···
{{ 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>
+
<header class="col-span-full md:col-span-10 px-6 py-2 text-center flex flex-col items-center justify-center py-8">
+
<h1 class="scale-150 dark:text-white mb-4">
+
{{ template "labels/fragments/label" (dict "def" .GfiLabel "val" "" "withPrefix" true) }}
+
</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>
···
{{ 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 class="flex px-6 pt-4 flex-row gap-1 items-center justify-between">
+
<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>
-
</div>
{{ if .Repo.RepoStats }}
-
<div class="text-gray-400 text-sm font-mono inline-flex gap-4 mt-auto">
+
<div class="text-gray-400 text-sm font-mono inline-flex gap-4">
{{ with .Repo.RepoStats.Language }}
<div class="flex gap-2 items-center text-sm">
{{ template "repo/fragments/colorBall" (dict "color" (langColor .)) }}
···
{{ 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 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-sm">
+
{{ 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>
-
</a>
-
{{ end }}
-
</div>
-
</details>
+
</div>
+
</a>
+
{{ end }}
+
</div>
{{ end }}
</div>
{{ end }}
+1 -1
appview/pages/templates/labels/fragments/label.html
···
{{ $d := .def }}
{{ $v := .val }}
{{ $withPrefix := .withPrefix }}
-
<span class="flex items-center gap-2 font-normal normal-case rounded py-1 px-2 border border-gray-200 dark:border-gray-700 bg-white dark:bg-gray-800 text-sm">
+
<span class="w-fit flex items-center gap-2 font-normal normal-case rounded py-1 px-2 border border-gray-200 dark:border-gray-700 bg-white dark:bg-gray-800 text-sm">
{{ template "repo/fragments/colorBall" (dict "color" $d.GetColor) }}
{{ $lhs := printf "%s" $d.Name }}
+29 -27
appview/pages/templates/timeline/fragments/goodfirstissues.html
···
{{ define "timeline/fragments/goodfirstissues" }}
-
<div class="bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-700 rounded-sm mb-4">
-
<div class="px-6 py-4">
-
<div class="flex flex-col md:flex-row items-center gap-4">
-
<div class="flex-1">
-
<div class="flex items-center gap-2 mb-2">
-
<span class="text-sm text-gray-500 dark:text-gray-400">Oct&ndash;Nov 2025</span>
-
</div>
-
<h4 class="text-base font-semibold text-gray-900 dark:text-white mb-2">
-
Get your first PR merged and earn Dolly stickers! 🎉
-
</h4>
-
<p class="text-sm text-gray-600 dark:text-gray-300 mb-3">
-
Merge a PR for "good first issue" (make this a label) a and get Tangled stickers shipped free.
-
</p>
-
<a href="/goodfirstissues"
-
class="btn my-2 gap-2">
-
browse issues
-
{{ i "arrow-right" "size-4" }}
-
</a>
-
</div>
-
<div class="flex-shrink-0">
-
<div class="flex items-center gap-1 p-3 bg-gradient-to-r from-purple-50 to-pink-50 dark:from-purple-900/20 dark:to-pink-900/20 rounded border border-purple-200 dark:border-purple-700">
-
{{ template "fragments/dolly/logo" "w-6 h-6" }}
-
{{ template "fragments/dolly/silhouette" }}
-
<span class="text-xs text-purple-600 dark:text-purple-400 font-medium">Free stickers!</span>
-
</div>
-
</div>
+
{{ if .GfiLabel }}
+
<a href="/goodfirstissues" class="no-underline hover:no-underline">
+
<div class="flex items-center justify-between gap-2 bg-purple-200 dark:bg-purple-900 border border-purple-400 dark:border-purple-500 rounded mb-4 py-4 px-6 ">
+
<div class="flex-1 flex flex-col gap-2">
+
<div class="text-purple-500 dark:text-purple-400">Oct 2025</div>
+
<p>
+
Make your first contribution to an open-source project this October.
+
</p>
+
<p>
+
<em>good-first-issue</em> is a collection of issues on open-source projects
+
that are easy ways for new contributors to give back to the projects
+
they love.
+
</p>
+
<span class="flex items-center gap-2 text-purple-500 dark:text-purple-400">
+
Browse issues {{ i "arrow-right" "size-4" }}
+
</span>
+
</div>
+
<div class="hidden md:block relative px-16 scale-150">
+
<div class="relative opacity-60">
+
{{ template "labels/fragments/label" (dict "def" .GfiLabel "val" "" "withPrefix" true) }}
+
</div>
+
<div class="relative -mt-4 ml-2 opacity-80">
+
{{ template "labels/fragments/label" (dict "def" .GfiLabel "val" "" "withPrefix" true) }}
</div>
+
<div class="relative -mt-4 ml-4">
+
{{ template "labels/fragments/label" (dict "def" .GfiLabel "val" "" "withPrefix" true) }}
+
</div>
+
</div>
</div>
-
</div>
+
</a>
+
{{ end }}
{{ end }}
+5 -4
appview/state/gfi.go
···
"net/http"
"sort"
+
"github.com/bluesky-social/indigo/atproto/syntax"
"tangled.org/core/api/tangled"
"tangled.org/core/appview/db"
"tangled.org/core/appview/models"
···
}
}
-
repoGroups := make(map[string]*models.RepoGroup)
+
repoGroups := make(map[syntax.ATURI]*models.RepoGroup)
for _, issue := range goodFirstIssues {
-
repoKey := fmt.Sprintf("%s/%s", issue.Repo.Did, issue.Repo.Name)
-
if group, exists := repoGroups[repoKey]; exists {
+
if group, exists := repoGroups[issue.Repo.RepoAt()]; exists {
group.Issues = append(group.Issues, issue)
} else {
-
repoGroups[repoKey] = &models.RepoGroup{
+
repoGroups[issue.Repo.RepoAt()] = &models.RepoGroup{
Repo: issue.Repo,
Issues: []models.Issue{issue},
}
···
RepoGroups: paginatedGroups,
LabelDefs: labelDefsMap,
Page: page,
+
GfiLabel: labelDefsMap[goodFirstIssueLabel],
})
}
+8 -2
appview/state/state.go
···
return
}
-
s.pages.Timeline(w, pages.TimelineParams{
+
gfiLabel, err := db.GetLabelDefinition(s.db, db.FilterEq("at_uri", models.LabelGoodFirstIssue))
+
if err != nil {
+
// non-fatal
+
}
+
+
fmt.Println(s.pages.Timeline(w, pages.TimelineParams{
LoggedInUser: user,
Timeline: timeline,
Repos: repos,
-
})
+
GfiLabel: gfiLabel,
+
}))
}
func (s *State) UpgradeBanner(w http.ResponseWriter, r *http.Request) {