Sign in

Search issue titles and bodies, change requests and comments. Private repositories you cannot read are not included.

vole
Clone

Read only, no credentials:

https://rodent.software/git/vole.git

Write over SSH with a registered key:

git clone ssh://git@0.0.0.0:2222/vole.git

A free and open-source forge that lives in your Git repo.

Code

392 commit(s) on main

Merge change request 01M70WES5ZZDJ6RP226WB11BBZ: Let people rename and remove their own passkeys keenan · 1h ago 4b8c9e1d
history blame raw 2.6 KiB
1package web
2
3// The two token inputs: labels and assignees. Both are a set of names the chip UI
4// (static/app.js) writes into hidden form fields, so the parsing and normalising
5// of that set lives here rather than beside either issue page.
6
7import (
8 "sort"
9
10 "rodent.software/vole/forge/access"
11 "rodent.software/vole/forge/repo"
12 "rodent.software/vole/metadata"
13)
14
15// tokenPickerData is the small view model behind the token input. It is used for
16// labels and assignees: Selected seeds the current set, All is what to suggest,
17// and Item names the hidden form field the chip UI writes.
18type tokenPickerData struct {
19 Selected []string
20 All []string
21 Item string
22 Placeholder string
23}
24
25func tokenPicker(selected, all []string, item, placeholder string) tokenPickerData {
26 return tokenPickerData{Selected: selected, All: all, Item: item, Placeholder: placeholder}
27}
28
29// assignableAccounts lists active accounts that can see this repository. A
30// read-only account is still a valid assignee: the point is that they can read
31// the issue they were given, even if they cannot comment on it.
32func (w *Web) assignableAccounts(m *repo.Meta) []string {
33 if w.Ident == nil {
34 return nil
35 }
36 acl, err := m.ACL()
37 if err != nil {
38 return nil
39 }
40 snap := w.Ident.Snapshot()
41 if snap == nil {
42 return nil
43 }
44 var names []string
45 for _, acct := range snap.ByName {
46 if acct.Active() && acl.Can(acct.Name, access.Read) {
47 names = append(names, acct.Name)
48 }
49 }
50 sort.Strings(names)
51 return names
52}
53
54// unknownNames reports names that are not in the allowed set. It keeps the
55// browser from storing an assignee nobody can resolve.
56func unknownNames(want, allowed []string) []string {
57 set := map[string]bool{}
58 for _, n := range allowed {
59 set[n] = true
60 }
61 var bad []string
62 for _, n := range want {
63 if !set[n] {
64 bad = append(bad, n)
65 }
66 }
67 return bad
68}
69
70// mergeNames returns the union of two name lists, sorted, without duplicates.
71func mergeNames(a, b []string) []string {
72 set := map[string]bool{}
73 for _, n := range a {
74 set[n] = true
75 }
76 for _, n := range b {
77 set[n] = true
78 }
79 return metadata.SortedNames(set)
80}
81
82// normalizeLabelSet is what the browser writes: NormalizeLabels plus dedupe, so
83// checking two spellings of the same label cannot store it twice.
84func normalizeLabelSet(in []string) []string {
85 set := map[string]bool{}
86 for _, v := range metadata.NormalizeLabels(in) {
87 set[v] = true
88 }
89 return metadata.SortedNames(set)
90}
91
92// sameStrings compares the normalised label lists a writer would store.
93func sameStrings(a, b []string) bool {
94 if len(a) != len(b) {
95 return false
96 }
97 for i := range a {
98 if a[i] != b[i] {
99 return false
100 }
101 }
102 return true
103}
104