forked from tangled.org/core
Monorepo for Tangled

appview/issues: display labels on issues

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

oppi.li 59a421c1 596cada3

verified
Changed files
+179 -9
appview
db
issues
pages
templates
repo
fragments
issues
+13 -1
appview/db/issues.go
··· 30 30 // optionally, populate this when querying for reverse mappings 31 31 // like comment counts, parent repo etc. 32 32 Comments []IssueComment 33 + Labels LabelState 33 34 Repo *Repo 34 35 } 35 36 ··· 371 372 372 373 // collect comments 373 374 issueAts := slices.Collect(maps.Keys(issueMap)) 375 + 374 376 comments, err := GetIssueComments(e, FilterIn("issue_at", issueAts)) 375 377 if err != nil { 376 378 return nil, fmt.Errorf("failed to query comments: %w", err) 377 379 } 378 - 379 380 for i := range comments { 380 381 issueAt := comments[i].IssueAt 381 382 if issue, ok := issueMap[issueAt]; ok { 382 383 issue.Comments = append(issue.Comments, comments[i]) 384 + } 385 + } 386 + 387 + // collect allLabels for each issue 388 + allLabels, err := GetLabels(e, FilterIn("subject", issueAts)) 389 + if err != nil { 390 + return nil, fmt.Errorf("failed to query labels: %w", err) 391 + } 392 + for issueAt, labels := range allLabels { 393 + if issue, ok := issueMap[issueAt.String()]; ok { 394 + issue.Labels = labels 383 395 } 384 396 } 385 397
+13
appview/issues/issues.go
··· 92 92 userReactions = db.GetReactionStatusMap(rp.db, user.Did, issue.AtUri()) 93 93 } 94 94 95 + labelDefs, err := db.GetLabelDefinitions(rp.db, db.FilterIn("at_uri", f.Repo.Labels)) 96 + if err != nil { 97 + log.Println("failed to fetch labels", err) 98 + rp.pages.Error503(w) 99 + return 100 + } 101 + 102 + defs := make(map[string]*db.LabelDefinition) 103 + for _, l := range labelDefs { 104 + defs[l.AtUri().String()] = &l 105 + } 106 + 95 107 rp.pages.RepoSingleIssue(w, pages.RepoSingleIssueParams{ 96 108 LoggedInUser: user, 97 109 RepoInfo: f.RepoInfo(user), ··· 100 112 OrderedReactionKinds: db.OrderedReactionKinds, 101 113 Reactions: reactionCountMap, 102 114 UserReacted: userReactions, 115 + LabelDefs: defs, 103 116 }) 104 117 } 105 118
+6 -6
appview/pages/pages.go
··· 897 897 } 898 898 899 899 type RepoSingleIssueParams struct { 900 - LoggedInUser *oauth.User 901 - RepoInfo repoinfo.RepoInfo 902 - Active string 903 - Issue *db.Issue 904 - CommentList []db.CommentListItem 905 - IssueOwnerHandle string 900 + LoggedInUser *oauth.User 901 + RepoInfo repoinfo.RepoInfo 902 + Active string 903 + Issue *db.Issue 904 + CommentList []db.CommentListItem 905 + LabelDefs map[string]*db.LabelDefinition 906 906 907 907 OrderedReactionKinds []db.ReactionKind 908 908 Reactions map[db.ReactionKind]int
+119
appview/pages/templates/repo/fragments/addLabelModal.html
··· 1 + {{ define "repo/fragments/addLabelModal" }} 2 + {{ $root := .root }} 3 + {{ $subject := .subject }} 4 + {{ $state := .state }} 5 + {{ with $root }} 6 + <form 7 + hx-put="/{{ .RepoInfo.FullName }}/labels/perform" 8 + hx-on::after-request="if(event.detail.successful) this.reset()" 9 + hx-indicator="#spinner" 10 + hx-swap="none" 11 + class="flex flex-col gap-4" 12 + > 13 + <p class="text-gray-500 dark:text-gray-400">Add, remove or update labels.</p> 14 + 15 + <input class="hidden" name="repo" value="{{ .RepoInfo.RepoAt.String }}"> 16 + <input class="hidden" name="subject" value="{{ $subject }}"> 17 + 18 + <div class="flex flex-col gap-2 max-h-64 overflow-y-auto"> 19 + {{ $id := 0 }} 20 + {{ range $k, $valset := $state.Inner }} 21 + {{ $d := index $root.LabelDefs $k }} 22 + {{ range $v, $s := $valset }} 23 + <div class="grid grid-cols-2 cursor-pointer rounded"> 24 + <label class="w-full flex items-center gap-2"> 25 + <input type="checkbox" name="op-{{$id}}" value="add" checked> 26 + {{ template "labels/fragments/labelDef" $d }} 27 + </label> 28 + {{ template "valueTypeInput" (dict "valueType" $d.ValueType "value" $v "key" $k) }} 29 + <input type="hidden" name="operand-key" value="{{ $k }}"> 30 + {{ $id = add $id 1 }} 31 + </div> 32 + {{ end }} 33 + {{ end }} 34 + 35 + {{ range $k, $d := $root.LabelDefs }} 36 + {{ if not ($state.ContainsLabel $k) }} 37 + <div class="grid grid-cols-2 cursor-pointer rounded"> 38 + <label class="w-full flex items-center gap-2"> 39 + <input type="checkbox" name="op-{{$id}}" value="add"> 40 + {{ template "labels/fragments/labelDef" $d }} 41 + </label> 42 + {{ template "valueTypeInput" (dict "valueType" $d.ValueType "value" "" "key" $k) }} 43 + <input type="hidden" name="operand-key" value="{{ $k }}"> 44 + {{ $id = add $id 1 }} 45 + </div> 46 + {{ end }} 47 + {{ end }} 48 + </div> 49 + 50 + <div class="flex gap-2 pt-2"> 51 + <button 52 + type="button" 53 + popovertarget="add-label-modal" 54 + popovertargetaction="hide" 55 + class="btn w-1/2 flex items-center gap-2 text-red-500 hover:text-red-700 dark:text-red-400 dark:hover:text-red-300" 56 + > 57 + {{ i "x" "size-4" }} cancel 58 + </button> 59 + <button type="submit" class="btn w-1/2 flex items-center"> 60 + <span class="inline-flex gap-2 items-center">{{ i "check" "size-4" }} save</span> 61 + <span id="spinner" class="group"> 62 + {{ i "loader-circle" "ml-2 w-4 h-4 animate-spin hidden group-[.htmx-request]:inline" }} 63 + </span> 64 + </button> 65 + </div> 66 + <div id="add-label-error" class="text-red-500 dark:text-red-400"></div> 67 + </form> 68 + {{ end }} 69 + {{ end }} 70 + 71 + {{ define "valueTypeInput" }} 72 + {{ $valueType := .valueType }} 73 + {{ $value := .value }} 74 + {{ $key := .key }} 75 + 76 + {{ if $valueType.IsEnumType }} 77 + {{ template "enumTypeInput" $ }} 78 + {{ else if $valueType.IsBool }} 79 + {{ template "boolTypeInput" $ }} 80 + {{ else if $valueType.IsInt }} 81 + {{ template "intTypeInput" $ }} 82 + {{ else if $valueType.IsString }} 83 + {{ template "stringTypeInput" $ }} 84 + {{ else if $valueType.IsNull }} 85 + {{ template "nullTypeInput" $ }} 86 + {{ end }} 87 + {{ end }} 88 + 89 + {{ define "enumTypeInput" }} 90 + {{ $valueType := .valueType }} 91 + {{ $value := .value }} 92 + <select name="operand-val" class="w-full p-1 rounded border border-gray-300 bg-white dark:bg-gray-800 dark:text-white dark:border-gray-600"> 93 + {{ range $valueType.Enum }} 94 + <option value="{{.}}" {{ if eq $value . }} selected {{ end }}>{{.}}</option> 95 + {{ end }} 96 + </select> 97 + {{ end }} 98 + 99 + {{ define "boolTypeInput" }} 100 + {{ $value := .value }} 101 + <select name="operand-val" class="w-full p-1 rounded border border-gray-300 bg-white dark:bg-gray-800 dark:text-white dark:border-gray-600"> 102 + <option value="true" {{ if $value }} selected {{ end }}>true</option> 103 + <option value="false" {{ if not $value }} selected {{ end }}>false</option> 104 + </select> 105 + {{ end }} 106 + 107 + {{ define "intTypeInput" }} 108 + {{ $value := .value }} 109 + <input class="p-1 w-full" type="number" name="operand-val" value="{{$value}}" max="100"> 110 + {{ end }} 111 + 112 + {{ define "stringTypeInput" }} 113 + {{ $value := .value }} 114 + <input class="p-1 w-full" type="text" name="operand-val" value="{{$value}}"> 115 + {{ end }} 116 + 117 + {{ define "nullTypeInput" }} 118 + <input class="p-1" type="hidden" name="operand-val" value="null"> 119 + {{ end }}
+28 -2
appview/pages/templates/repo/issues/issue.html
··· 15 15 {{ if .Issue.Body }} 16 16 <article id="body" class="mt-4 prose dark:prose-invert">{{ .Issue.Body | markdown }}</article> 17 17 {{ end }} 18 - {{ template "issueReactions" . }} 18 + <div class="flex flex-wrap gap-2 items-stretch"> 19 + {{ template "issueReactions" . }} 20 + {{ template "issueLabels" . }} 21 + </div> 19 22 </section> 20 23 {{ end }} 21 24 ··· 86 89 {{ end }} 87 90 88 91 {{ define "issueReactions" }} 89 - <div class="flex items-center gap-2 mt-2"> 92 + <div class="flex items-center gap-2"> 90 93 {{ template "repo/fragments/reactionsPopUp" .OrderedReactionKinds }} 91 94 {{ range $kind := .OrderedReactionKinds }} 92 95 {{ ··· 98 101 "ThreadAt" $.Issue.AtUri) 99 102 }} 100 103 {{ end }} 104 + </div> 105 + {{ end }} 106 + 107 + {{ define "issueLabels" }} 108 + {{ range $k, $valset := $.Issue.Labels.Inner }} 109 + {{ $d := index $.LabelDefs $k }} 110 + {{ range $v, $s := $valset }} 111 + {{ template "labels/fragments/label" (dict "def" $d "val" $v) }} 112 + {{ end }} 113 + {{ end }} 114 + 115 + <button 116 + class="btn text-gray-500 dark:text-gray-400" 117 + popovertarget="add-label-modal" 118 + {{ if not (or .RepoInfo.Roles.IsOwner .RepoInfo.Roles.IsCollaborator) }}disabled{{ end }} 119 + popovertargetaction="toggle"> 120 + {{ i "plus" "size-4" }} 121 + </button> 122 + <div 123 + id="add-label-modal" 124 + popover 125 + class="bg-white w-full sm:w-96 dark:bg-gray-800 p-6 rounded border border-gray-200 dark:border-gray-700 drop-shadow dark:text-white backdrop:bg-gray-400/50 dark:backdrop:bg-gray-800/50"> 126 + {{ template "repo/fragments/addLabelModal" (dict "root" $ "subject" $.Issue.AtUri.String "state" $.Issue.Labels) }} 101 127 </div> 102 128 {{ end }} 103 129