Monorepo for Tangled tangled.org

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

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

authored by oppi.li and committed by anirudh.fi 014c4249 4fd5dfd2

Changed files
+109 -109
appview
models
pages
templates
goodfirstissues
labels
fragments
timeline
state
+14 -13
appview/models/label.go
··· 461 461 return result 462 462 } 463 463 464 + var ( 465 + LabelWontfix = fmt.Sprintf("at://%s/%s/%s", consts.TangledDid, tangled.LabelDefinitionNSID, "wontfix") 466 + LabelDuplicate = fmt.Sprintf("at://%s/%s/%s", consts.TangledDid, tangled.LabelDefinitionNSID, "duplicate") 467 + LabelAssignee = fmt.Sprintf("at://%s/%s/%s", consts.TangledDid, tangled.LabelDefinitionNSID, "assignee") 468 + LabelGoodFirstIssue = fmt.Sprintf("at://%s/%s/%s", consts.TangledDid, tangled.LabelDefinitionNSID, "good-first-issue") 469 + LabelDocumentation = fmt.Sprintf("at://%s/%s/%s", consts.TangledDid, tangled.LabelDefinitionNSID, "documentation") 470 + ) 471 + 464 472 func DefaultLabelDefs() []string { 465 - rkeys := []string{ 466 - "wontfix", 467 - "duplicate", 468 - "assignee", 469 - "good-first-issue", 470 - "documentation", 473 + return []string{ 474 + LabelWontfix, 475 + LabelDuplicate, 476 + LabelAssignee, 477 + LabelGoodFirstIssue, 478 + LabelDocumentation, 471 479 } 472 - 473 - defs := make([]string, len(rkeys)) 474 - for i, r := range rkeys { 475 - defs[i] = fmt.Sprintf("at://%s/%s/%s", consts.TangledDid, tangled.LabelDefinitionNSID, r) 476 - } 477 - 478 - return defs 479 480 } 480 481 481 482 func FetchDefaultDefs(r *idresolver.Resolver) ([]LabelDefinition, error) {
+2
appview/pages/pages.go
··· 306 306 LoggedInUser *oauth.User 307 307 Timeline []models.TimelineEvent 308 308 Repos []models.Repo 309 + GfiLabel *models.LabelDefinition 309 310 } 310 311 311 312 func (p *Pages) Timeline(w io.Writer, params TimelineParams) error { ··· 317 318 Issues []models.Issue 318 319 RepoGroups []*models.RepoGroup 319 320 LabelDefs map[string]*models.LabelDefinition 321 + GfiLabel *models.LabelDefinition 320 322 Page pagination.Page 321 323 } 322 324
+50 -62
appview/pages/templates/goodfirstissues/index.html
··· 9 9 10 10 {{ define "content" }} 11 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> 12 + <header class="col-span-full md:col-span-10 px-6 py-2 text-center flex flex-col items-center justify-center py-8"> 13 + <h1 class="scale-150 dark:text-white mb-4"> 14 + {{ template "labels/fragments/label" (dict "def" .GfiLabel "val" "" "withPrefix" true) }} 15 + </h1> 14 16 <p class="text-gray-600 dark:text-gray-400 mb-2"> 15 17 Find beginner-friendly issues across all repositories to get started with open source contributions. 16 18 </p> ··· 35 37 {{ else }} 36 38 {{ range .RepoGroups }} 37 39 <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> 40 + <div class="flex px-6 pt-4 flex-row gap-1 items-center justify-between"> 41 + <div class="font-medium dark:text-white flex items-center justify-between"> 42 + <div class="flex items-center min-w-0 flex-1 mr-2"> 43 + {{ if .Repo.Source }} 44 + {{ i "git-fork" "w-4 h-4 mr-1.5 shrink-0" }} 45 + {{ else }} 46 + {{ i "book-marked" "w-4 h-4 mr-1.5 shrink-0" }} 47 + {{ end }} 48 + {{ $repoOwner := resolve .Repo.Did }} 49 + <a href="/{{ $repoOwner }}/{{ .Repo.Name }}" class="truncate min-w-0">{{ $repoOwner }}/{{ .Repo.Name }}</a> 50 + </div> 48 51 </div> 49 - </div> 50 52 51 53 52 54 {{ if .Repo.RepoStats }} 53 - <div class="text-gray-400 text-sm font-mono inline-flex gap-4 mt-auto"> 55 + <div class="text-gray-400 text-sm font-mono inline-flex gap-4"> 54 56 {{ with .Repo.RepoStats.Language }} 55 57 <div class="flex gap-2 items-center text-sm"> 56 58 {{ template "repo/fragments/colorBall" (dict "color" (langColor .)) }} ··· 86 88 {{ end }} 87 89 88 90 {{ 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 }} 91 + <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"> 92 + {{ range .Issues }} 93 + <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"> 94 + <div class="py-2 px-6"> 95 + <div class="flex-grow min-w-0 w-full"> 96 + <div class="flex text-sm items-center justify-between w-full"> 97 + <div class="flex items-center gap-2 min-w-0 flex-1 pr-2"> 98 + <span class="truncate text-sm text-gray-800 dark:text-gray-200"> 99 + <span class="text-gray-500 dark:text-gray-400">#{{ .IssueId }}</span> 100 + {{ .Title | description }} 101 + </span> 102 + </div> 103 + <div class="flex-shrink-0 flex items-center gap-2 text-gray-500 dark:text-gray-400"> 104 + <span> 105 + <div class="inline-flex items-center gap-1"> 106 + {{ i "message-square" "w-3 h-3 md:hidden" }} 107 + {{ len .Comments }} 108 + <span class="hidden md:inline">comment{{ if ne (len .Comments) 1 }}s{{ end }}</span> 133 109 </div> 110 + </span> 111 + <span class="before:content-['·'] before:select-none"></span> 112 + <span class="text-sm"> 113 + {{ template "repo/fragments/time" .Created }} 114 + </span> 115 + <div class="hidden md:inline-flex md:gap-1"> 116 + {{ $labelState := .Labels }} 117 + {{ range $k, $d := $.LabelDefs }} 118 + {{ range $v, $s := $labelState.GetValSet $d.AtUri.String }} 119 + {{ template "labels/fragments/label" (dict "def" $d "val" $v "withPrefix" true) }} 120 + {{ end }} 121 + {{ end }} 134 122 </div> 135 123 </div> 136 124 </div> 137 125 </div> 138 - </a> 139 - {{ end }} 140 - </div> 141 - </details> 126 + </div> 127 + </a> 128 + {{ end }} 129 + </div> 142 130 {{ end }} 143 131 </div> 144 132 {{ end }}
+1 -1
appview/pages/templates/labels/fragments/label.html
··· 2 2 {{ $d := .def }} 3 3 {{ $v := .val }} 4 4 {{ $withPrefix := .withPrefix }} 5 - <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"> 5 + <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"> 6 6 {{ template "repo/fragments/colorBall" (dict "color" $d.GetColor) }} 7 7 8 8 {{ $lhs := printf "%s" $d.Name }}
+29 -27
appview/pages/templates/timeline/fragments/goodfirstissues.html
··· 1 1 {{ define "timeline/fragments/goodfirstissues" }} 2 - <div class="bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-700 rounded-sm mb-4"> 3 - <div class="px-6 py-4"> 4 - <div class="flex flex-col md:flex-row items-center gap-4"> 5 - <div class="flex-1"> 6 - <div class="flex items-center gap-2 mb-2"> 7 - <span class="text-sm text-gray-500 dark:text-gray-400">Oct&ndash;Nov 2025</span> 8 - </div> 9 - <h4 class="text-base font-semibold text-gray-900 dark:text-white mb-2"> 10 - Get your first PR merged and earn Dolly stickers! 🎉 11 - </h4> 12 - <p class="text-sm text-gray-600 dark:text-gray-300 mb-3"> 13 - Merge a PR for "good first issue" (make this a label) a and get Tangled stickers shipped free. 14 - </p> 15 - <a href="/goodfirstissues" 16 - class="btn my-2 gap-2"> 17 - browse issues 18 - {{ i "arrow-right" "size-4" }} 19 - </a> 20 - </div> 21 - <div class="flex-shrink-0"> 22 - <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"> 23 - {{ template "fragments/dolly/logo" "w-6 h-6" }} 24 - {{ template "fragments/dolly/silhouette" }} 25 - <span class="text-xs text-purple-600 dark:text-purple-400 font-medium">Free stickers!</span> 26 - </div> 27 - </div> 2 + {{ if .GfiLabel }} 3 + <a href="/goodfirstissues" class="no-underline hover:no-underline"> 4 + <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 "> 5 + <div class="flex-1 flex flex-col gap-2"> 6 + <div class="text-purple-500 dark:text-purple-400">Oct 2025</div> 7 + <p> 8 + Make your first contribution to an open-source project this October. 9 + </p> 10 + <p> 11 + <em>good-first-issue</em> is a collection of issues on open-source projects 12 + that are easy ways for new contributors to give back to the projects 13 + they love. 14 + </p> 15 + <span class="flex items-center gap-2 text-purple-500 dark:text-purple-400"> 16 + Browse issues {{ i "arrow-right" "size-4" }} 17 + </span> 18 + </div> 19 + <div class="hidden md:block relative px-16 scale-150"> 20 + <div class="relative opacity-60"> 21 + {{ template "labels/fragments/label" (dict "def" .GfiLabel "val" "" "withPrefix" true) }} 22 + </div> 23 + <div class="relative -mt-4 ml-2 opacity-80"> 24 + {{ template "labels/fragments/label" (dict "def" .GfiLabel "val" "" "withPrefix" true) }} 28 25 </div> 26 + <div class="relative -mt-4 ml-4"> 27 + {{ template "labels/fragments/label" (dict "def" .GfiLabel "val" "" "withPrefix" true) }} 28 + </div> 29 + </div> 29 30 </div> 30 - </div> 31 + </a> 32 + {{ end }} 31 33 {{ end }}
+5 -4
appview/state/gfi.go
··· 6 6 "net/http" 7 7 "sort" 8 8 9 + "github.com/bluesky-social/indigo/atproto/syntax" 9 10 "tangled.org/core/api/tangled" 10 11 "tangled.org/core/appview/db" 11 12 "tangled.org/core/appview/models" ··· 64 65 } 65 66 } 66 67 67 - repoGroups := make(map[string]*models.RepoGroup) 68 + repoGroups := make(map[syntax.ATURI]*models.RepoGroup) 68 69 for _, issue := range goodFirstIssues { 69 - repoKey := fmt.Sprintf("%s/%s", issue.Repo.Did, issue.Repo.Name) 70 - if group, exists := repoGroups[repoKey]; exists { 70 + if group, exists := repoGroups[issue.Repo.RepoAt()]; exists { 71 71 group.Issues = append(group.Issues, issue) 72 72 } else { 73 - repoGroups[repoKey] = &models.RepoGroup{ 73 + repoGroups[issue.Repo.RepoAt()] = &models.RepoGroup{ 74 74 Repo: issue.Repo, 75 75 Issues: []models.Issue{issue}, 76 76 } ··· 134 134 RepoGroups: paginatedGroups, 135 135 LabelDefs: labelDefsMap, 136 136 Page: page, 137 + GfiLabel: labelDefsMap[goodFirstIssueLabel], 137 138 }) 138 139 }
+8 -2
appview/state/state.go
··· 270 270 return 271 271 } 272 272 273 - s.pages.Timeline(w, pages.TimelineParams{ 273 + gfiLabel, err := db.GetLabelDefinition(s.db, db.FilterEq("at_uri", models.LabelGoodFirstIssue)) 274 + if err != nil { 275 + // non-fatal 276 + } 277 + 278 + fmt.Println(s.pages.Timeline(w, pages.TimelineParams{ 274 279 LoggedInUser: user, 275 280 Timeline: timeline, 276 281 Repos: repos, 277 - }) 282 + GfiLabel: gfiLabel, 283 + })) 278 284 } 279 285 280 286 func (s *State) UpgradeBanner(w http.ResponseWriter, r *http.Request) {