+13
-1
appview/db/issues.go
+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
+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
+6
-6
appview/pages/pages.go
···
877
877
}
878
878
879
879
type RepoSingleIssueParams struct {
880
-
LoggedInUser *oauth.User
881
-
RepoInfo repoinfo.RepoInfo
882
-
Active string
883
-
Issue *db.Issue
884
-
CommentList []db.CommentListItem
885
-
IssueOwnerHandle string
880
+
LoggedInUser *oauth.User
881
+
RepoInfo repoinfo.RepoInfo
882
+
Active string
883
+
Issue *db.Issue
884
+
CommentList []db.CommentListItem
885
+
LabelDefs map[string]*db.LabelDefinition
886
886
887
887
OrderedReactionKinds []db.ReactionKind
888
888
Reactions map[db.ReactionKind]int
+119
appview/pages/templates/repo/fragments/addLabelModal.html
+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
+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