Commit c5a2a4cc37

c5a2a4cc37f637b36001da7030e7cbb8a4007dde

parent: 7e688b27f5

Verified · cmc ci/build: success ci/test: success

cmc <hello@cleberg.net> · 2026-09-23 05:09 UTC

web: interface guidelines fixes

Typed-name confirm on org member remove, team member remove, team
repository revoke and Promote. The team's add/remove and grant/revoke
forms split so the confirm field sits only beside the removal.

Register email is type=email; autocomplete, autocapitalize and
spellcheck set on email, username, one-time-code, SSH key and
identifier fields. The MR close field accepts the "!" its placeholder
shows and drops inputmode=numeric. refs.html accessible names contain
the visible labels; the line-comment link names its line.

theme-color matches the rail; "…" in placeholders; tabular numbers in
td.size; .pathbar wraps long segments at every width; .refdrop contains
its scroll; .issuetitle balanced.

Closes #249
e2e/adminusersweb_test.go +4
@@ -57,6 +57,10 @@ func TestAdminUsersWeb(t *testing.T) {
5757 }
5858 post(url.Values{"field": {"enable"}, "user": {"alice"}})
5959 post(url.Values{"field": {"promote"}, "user": {"alice"}})
60 if out, _, _ := inst.ssh(t, rootKey, "", "admin", "user", "show", "alice", "--json"); strings.Contains(out, `"admin":true`) {
61 t.Fatalf("promote without a confirm took: %s", out)
62 }
63 post(url.Values{"field": {"promote"}, "user": {"alice"}, "confirm": {"alice"}})
6064 if out, _, _ := inst.ssh(t, rootKey, "", "admin", "user", "show", "alice", "--json"); !strings.Contains(out, `"admin":true`) {
6165 t.Fatalf("promote did not take: %s", out)
6266 }
e2e/mrweb_test.go +2 −1
@@ -483,7 +483,8 @@ func TestMRSupersedes(t *testing.T) {
483483 }
484484
485485 alice := inst.login(t, aliceKey)
486 if status, body := browserPost(t, alice, inst.base()+"/alice/app/mrs/1/close", url.Values{"by": {"2"}}); status != 200 {
486 // The field's placeholder is "!N", so the "!" is accepted.
487 if status, body := browserPost(t, alice, inst.base()+"/alice/app/mrs/1/close", url.Values{"by": {"!2"}}); status != 200 {
487488 t.Fatalf("close post: %d\n%s", status, body)
488489 }
489490
e2e/orgweb_test.go +33 −2
@@ -85,10 +85,32 @@ func TestOrgManagementWeb(t *testing.T) {
8585 }
8686 }
8787
88 // Revoking and removing work the same way round.
88 // Revoking and removing need the team's name typed; a bare post
89 // changes nothing.
8990 browserPost(t, alice, inst.base()+"/acme", url.Values{
9091 "field": {"team-revoke"}, "team": {"builders"}, "repo": {"acme/widget"},
9192 })
93 if out, _, _ := inst.ssh(t, aliceKey, "", "org", "team", "show", "acme", "builders", "--json"); !strings.Contains(out, `"acme/widget"`) {
94 t.Fatalf("unconfirmed revoke dropped the grant: %s", out)
95 }
96 browserPost(t, alice, inst.base()+"/acme", url.Values{
97 "field": {"team-revoke"}, "team": {"builders"}, "repo": {"acme/widget"}, "confirm": {"builders"},
98 })
99 if out, _, _ := inst.ssh(t, aliceKey, "", "org", "team", "show", "acme", "builders", "--json"); strings.Contains(out, `"acme/widget"`) {
100 t.Fatalf("confirmed revoke left the grant: %s", out)
101 }
102 browserPost(t, alice, inst.base()+"/acme", url.Values{
103 "field": {"team-remove"}, "team": {"builders"}, "user": {"bob"},
104 })
105 if out, _, _ := inst.ssh(t, aliceKey, "", "org", "team", "show", "acme", "builders", "--json"); !strings.Contains(out, `"bob"`) {
106 t.Fatalf("unconfirmed team remove took bob out: %s", out)
107 }
108 browserPost(t, alice, inst.base()+"/acme", url.Values{
109 "field": {"team-remove"}, "team": {"builders"}, "user": {"bob"}, "confirm": {"builders"},
110 })
111 if out, _, _ := inst.ssh(t, aliceKey, "", "org", "team", "show", "acme", "builders", "--json"); strings.Contains(out, `"bob"`) {
112 t.Fatalf("confirmed team remove left bob in: %s", out)
113 }
92114
93115 // Deleting the team needs its name typed; a bare post is refused and
94116 // the team stays.
@@ -108,9 +130,18 @@ func TestOrgManagementWeb(t *testing.T) {
108130 t.Fatalf("team not deleted: exit %d", code)
109131 }
110132
111 browserPost(t, alice, inst.base()+"/acme", url.Values{
133 _, body = browserPost(t, alice, inst.base()+"/acme", url.Values{
112134 "field": {"member-remove"}, "user": {"bob"},
113135 })
136 if !strings.Contains(body, "type bob to confirm") {
137 t.Fatalf("unconfirmed member remove was not refused:\n%s", body)
138 }
139 if members := orgMembers(t, inst, aliceKey); len(members) != 2 {
140 t.Fatalf("member removed without confirmation: %v", members)
141 }
142 browserPost(t, alice, inst.base()+"/acme", url.Values{
143 "field": {"member-remove"}, "user": {"bob"}, "confirm": {"bob"},
144 })
114145 if members := orgMembers(t, inst, aliceKey); len(members) != 1 {
115146 t.Fatalf("member not removed: %v", members)
116147 }
internal/httpd/adminusers.go +1 −1
@@ -105,7 +105,7 @@ func (s *Server) adminUsersSubmit(w http.ResponseWriter, r *http.Request, viewer
105105 back("unknown action")
106106 return
107107 }
108 if verb == "demote" || verb == "disable" {
108 if verb == "promote" || verb == "demote" || verb == "disable" {
109109 if ok, msg := confirmed(r, name); !ok {
110110 back(msg)
111111 return
internal/httpd/mractions.go +1 −1
@@ -87,7 +87,7 @@ func (s *Server) mrMergeSubmit(w http.ResponseWriter, r *http.Request, u store.U
8787
8888func (s *Server) mrCloseSubmit(w http.ResponseWriter, r *http.Request, u store.User) {
8989 args := []string{}
90 if by := strings.TrimSpace(r.FormValue("by")); by != "" {
90 if by := strings.TrimPrefix(strings.TrimSpace(r.FormValue("by")), "!"); by != "" {
9191 if _, err := strconv.ParseInt(by, 10, 64); err != nil {
9292 s.mrRedirect(w, r, "the superseding request is a number")
9393 return
internal/httpd/orgweb.go +12
@@ -78,6 +78,10 @@ func (s *Server) orgSubmit(w http.ResponseWriter, r *http.Request, u store.User)
7878 argv = append(argv, "--role", role)
7979 }
8080 case "member-remove":
81 if ok, msg := confirmed(r, user); !ok {
82 back(msg)
83 return
84 }
8185 argv = []string{"org", "members", "remove", owner, user}
8286 case "team-create":
8387 argv = []string{"org", "team", "create", owner, team}
@@ -90,11 +94,19 @@ func (s *Server) orgSubmit(w http.ResponseWriter, r *http.Request, u store.User)
9094 case "team-add":
9195 argv = append([]string{"org", "team", "add", owner, team}, strings.Fields(user)...)
9296 case "team-remove":
97 if ok, msg := confirmed(r, team); !ok {
98 back(msg)
99 return
100 }
93101 argv = append([]string{"org", "team", "remove", owner, team}, strings.Fields(user)...)
94102 case "team-grant":
95103 argv = []string{"org", "team", "grant", owner, team,
96104 strings.TrimSpace(r.FormValue("repo")), r.FormValue("role")}
97105 case "team-revoke":
106 if ok, msg := confirmed(r, team); !ok {
107 back(msg)
108 return
109 }
98110 argv = []string{"org", "team", "revoke", owner, team, strings.TrimSpace(r.FormValue("repo"))}
99111 default:
100112 back("unknown form")
internal/web/static/style.css +6 −6
@@ -523,7 +523,7 @@ nav.tabs a i { font-style: normal; color: var(--muted); margin-left: 4px; }
523523.pagehead h1, .listhead h1, .headrow h1, .listhead h2, .headrow h2 { margin: 0; }
524524.pagehead > p, .listhead > p, .headrow > p { margin: 0; }
525525.pagehead .grow, .listhead .spacer, .headrow .spacer { flex: 1; }
526.issuetitle { margin: 0 0 var(--sp-2); font-size: var(--fs-5); }
526.issuetitle { margin: 0 0 var(--sp-2); font-size: var(--fs-5); text-wrap: balance; }
527527.issuenumber { color: var(--muted); font-weight: 400; }
528528.issuemeta { color: var(--muted); font-size: var(--fs-2); margin: 0 0 var(--sp-4); }
529529.commithead p { margin: var(--sp-1) 0; }
@@ -905,7 +905,7 @@ table.tree td.lastcommit a {
905905table.tree td.lastcommit a:hover { color: var(--link); }
906906table.tree th.age, table.tree td.age { width: 20%; }
907907td.age, th.age { text-align: right; white-space: nowrap; color: var(--muted); font-variant-numeric: tabular-nums; }
908td.size, td.mode { color: var(--muted); white-space: nowrap; }
908td.size, td.mode { color: var(--muted); white-space: nowrap; font-variant-numeric: tabular-nums; }
909909table.tree th.size, table.tree td.size { text-align: right; }
910910table.refs td.name { width: 100%; }
911911table.refs td.sha, td.sha { color: var(--muted); }
@@ -928,8 +928,8 @@ table.keys.nowrap th, table.keys.nowrap td { white-space: nowrap; }
928928 The cell is two fixed slots, each right-aligned. A form cannot span
929929 table cells, so one column per control which would let the table
930930 align them is out without form="" plumbing, and the forms hold
931 different numbers of controls: only an admin's role form has a
932 confirm field, and the enable form has none. Sizing the slot instead
931 different numbers of controls: the enable form and the disabled
932 row's Promote have no confirm field. Sizing the slot instead
933933 of the form is what puts every button in one column and every field
934934 in another; right-aligning the cell alone left the disabled row's
935935 Promote where other rows keep their field. A slot is a field, a gap
@@ -1486,7 +1486,7 @@ a.memberchip {
14861486a.memberchip:hover { text-decoration: none; border-color: var(--link); color: var(--link); }
14871487a.memberchip .role { color: var(--muted); }
14881488
1489.pathbar { display: flex; align-items: center; gap: var(--sp-2) var(--sp-3); margin: 0 0 var(--sp-3); flex-wrap: wrap; font-size: var(--fs-2); }
1489.pathbar { display: flex; align-items: center; gap: var(--sp-2) var(--sp-3); margin: 0 0 var(--sp-3); flex-wrap: wrap; font-size: var(--fs-2); overflow-wrap: anywhere; }
14901490.pathbar .spacer { flex: 1; }
14911491.pathbar .actions { display: flex; align-items: center; gap: var(--sp-2); }
14921492/* history · blame · raw were 21px tall and "raw" 22px wide (#232) */
@@ -1608,6 +1608,7 @@ details.refmenu .refdrop {
16081608 min-width: 12rem;
16091609 max-height: 20rem;
16101610 overflow-y: auto;
1611 overscroll-behavior: contain;
16111612 background: var(--canvas);
16121613 border: 1px solid var(--line);
16131614 border-radius: var(--r-card);
@@ -1849,7 +1850,6 @@ svg.icon { vertical-align: -0.125em; }
18491850 cell padding and the card clipped the age; a long name wraps (#232) */
18501851 table.tree td.name { white-space: normal; overflow-wrap: anywhere; }
18511852 .clone pre { white-space: pre-wrap; word-break: break-all; }
1852 .pathbar { overflow-wrap: anywhere; }
18531853 .readme .cardbody, .code { padding: var(--sp-3); }
18541854 .thread { margin-left: var(--sp-3); }
18551855 .blamehunk { flex-direction: column; gap: 0; }
internal/web/templates/account.html +4 −4
@@ -32,7 +32,7 @@
3232 <label for="p-website">Website</label>
3333 <input type="text" id="p-website" name="website" value="{{.Profile.Website}}" placeholder="https://example.org">
3434 <label for="p-links">Links</label>
35 <textarea id="p-links" name="links" rows="3" placeholder="one per line: label|https://... or a bare https://... (at most 5)">{{.LinksText}}</textarea>
35 <textarea id="p-links" name="links" rows="3" placeholder="one per line: label|https://… or a bare https://… (at most 5)">{{.LinksText}}</textarea>
3636 <span class="btngroup"><button type="submit" class="btn">Save profile</button></span>
3737</form>
3838<h3>About</h3>
@@ -65,7 +65,7 @@ commands and push; a <code>git</code> key can only move git data (e.g., CI).</p>
6565 <form method="post" action="/settings" class="setform stack">
6666 <input type="hidden" name="field" value="key-add">
6767 <label for="key">Public key</label>
68 <textarea id="key" name="key" rows="3" required placeholder="ssh-ed25519 AAAA... you@machine"></textarea>
68 <textarea id="key" name="key" rows="3" required spellcheck="false" placeholder="ssh-ed25519 AAAA… you@machine"></textarea>
6969 <label for="key-label">Label</label>
7070 <input id="key-label" name="label" maxlength="64" placeholder="defaults to the key's comment">
7171 <label for="scope">Scope</label>
@@ -94,13 +94,13 @@ account and where notifications go.</p>
9494 <form method="post" action="/settings" class="setform">
9595 <input type="hidden" name="field" value="email-add">
9696 <label for="address">Address</label>
97 <input type="email" id="address" name="address" required placeholder="you@example.org">
97 <input type="email" id="address" name="address" required autocomplete="email" placeholder="you@example.org">
9898 <button type="submit" class="btn">Send code</button>
9999 </form>
100100 <form method="post" action="/settings" class="setform">
101101 <input type="hidden" name="field" value="email-verify">
102102 <label for="code">Verification code</label>
103 <input type="text" id="code" name="code" required placeholder="from the mail">
103 <input type="text" id="code" name="code" required autocomplete="one-time-code" autocapitalize="none" spellcheck="false" placeholder="from the mail">
104104 <button type="submit" class="btn">Verify</button>
105105 </form>
106106</details>
internal/web/templates/adminusers.html +1 −1
@@ -38,7 +38,7 @@
3838 <input type="hidden" name="field" value="promote">
3939 <input type="hidden" name="user" value="{{.Username}}">
4040 <input type="hidden" name="state" value="{{$.State}}">
41 <button type="submit" class="btn">Promote</button>
41 {{template "confirmfield" .Username}} <button type="submit" class="btn">Promote</button>
4242 </form>
4343 {{else}}
4444 {{/* An account that is not active cannot be promoted, but the row
internal/web/templates/builds.html +1 −1
@@ -11,7 +11,7 @@
1111 </div>{{end}}{{end}}
1212 <form method="get" class="searchform compact">
1313 <label for="ref" class="colhead">Branch</label>
14 <input type="text" id="ref" name="ref" value="{{.Filter.Ref}}" list="buildrefs">
14 <input type="text" id="ref" name="ref" value="{{.Filter.Ref}}" list="buildrefs" spellcheck="false">
1515 <datalist id="buildrefs">{{range .Refs}}<option value="{{.}}">{{end}}</datalist>
1616 <input type="hidden" name="status" value="{{.Filter.Status}}">
1717 <input type="hidden" name="job" value="{{.Filter.Job}}">
internal/web/templates/fork.html +1 −1
@@ -11,7 +11,7 @@ own.</p>
1111 <option value="{{.Viewer}}">{{.Viewer}}</option>
1212 {{range .Orgs}}<option value="{{.}}"{{if eq . $.Owner}} selected{{end}}>{{.}}</option>{{end}}
1313</select></label>
14<label>/ Name <input name="name" required maxlength="63" pattern="[a-z0-9][a-z0-9._\-]{0,62}" value="{{.Name}}" aria-describedby="forkhint"></label>
14<label>/ Name <input name="name" required maxlength="63" pattern="[a-z0-9][a-z0-9._\-]{0,62}" value="{{.Name}}" aria-describedby="forkhint" autocomplete="off" spellcheck="false"></label>
1515<span class="hint" id="forkhint">Defaults to the source name; change it to fork twice into the same owner.</span></p>
1616<p><button type="submit">Create fork</button></p>
1717</form>
internal/web/templates/issue.html +4 −4
@@ -55,8 +55,8 @@
5555 {{else}}<p class="none">none yet</p>{{end}}
5656 {{if .CanWrite}}
5757 <form method="post" action="{{$base}}/label" class="actions">
58 <input type="text" name="add" aria-label="Add labels" placeholder="add, space-separated">
59 <input type="text" name="remove" aria-label="Remove labels" placeholder="remove">
58 <input type="text" name="add" aria-label="Add labels" autocomplete="off" spellcheck="false" placeholder="add, space-separated">
59 <input type="text" name="remove" aria-label="Remove labels" autocomplete="off" spellcheck="false" placeholder="remove">
6060 <button type="submit" class="btn">Apply</button>
6161 </form>
6262 {{end}}
@@ -67,8 +67,8 @@
6767 {{else}}<p class="none">nobody yet</p>{{end}}
6868 {{if .CanWrite}}
6969 <form method="post" action="{{$base}}/assign" class="actions">
70 <input type="text" name="add" aria-label="Add assignees" placeholder="add, space-separated">
71 <input type="text" name="remove" aria-label="Remove assignees" placeholder="remove">
70 <input type="text" name="add" aria-label="Add assignees" autocomplete="off" spellcheck="false" placeholder="add, space-separated">
71 <input type="text" name="remove" aria-label="Remove assignees" autocomplete="off" spellcheck="false" placeholder="remove">
7272 <button type="submit" class="btn">Apply</button>
7373 </form>
7474 {{end}}
internal/web/templates/layout.html +2 −1
@@ -6,6 +6,7 @@
66<title>{{template "title" .}}</title>
77<link rel="stylesheet" href="/static/style.css?v={{styleVersion}}">
88<link rel="icon" href="/favicon.svg" type="image/svg+xml">
9<meta name="theme-color" content="#000000">
910{{with field . "Feed"}}<link rel="alternate" type="application/atom+xml" href="{{.}}">
1011{{end}}</head>
1112<body>
@@ -223,7 +224,7 @@
223224
224225{{/* cmtln turns a line number into the link that opens the comment form
225226 on that line. No JavaScript: the anchor travels in the query. */}}
226{{define "cmtln"}}{{if and .Viewer .Base}}<a class="cmt" title="Comment on this line" href="{{.Base}}?view=diff&amp;cpath={{.Path}}&amp;cline={{.N}}&amp;cside={{.Side}}#compose">{{.N}}</a>{{else}}{{.N}}{{end}}{{end}}
227{{define "cmtln"}}{{if and .Viewer .Base}}<a class="cmt" aria-label="Comment on line {{.N}}" title="Comment on this line" href="{{.Base}}?view=diff&amp;cpath={{.Path}}&amp;cline={{.N}}&amp;cside={{.Side}}#compose">{{.N}}</a>{{else}}{{.N}}{{end}}{{end}}
227228
228229{{/* thread renders one review thread with its reply and resolve controls.
229230 Class carries "stale" for threads whose anchor is gone. */}}
internal/web/templates/login.html +1 −1
@@ -11,7 +11,7 @@ expires in fifteen minutes.</p>
1111{{if .EmailLogin}}
1212<form method="post" action="/login">
1313 <div class="field"><label for="identifier">Username or email address</label>
14 <input type="text" id="identifier" name="identifier" autocomplete="username" required></div>
14 <input type="text" id="identifier" name="identifier" autocomplete="username" autocapitalize="none" spellcheck="false" required></div>
1515 <button type="submit">Email me a link</button>
1616</form>
1717<p>Or, from a machine with your registered key:</p>
internal/web/templates/mr.html +5 −5
@@ -107,7 +107,7 @@
107107 </form>
108108 <form method="post" action="{{$base}}/close" class="actions">
109109 <label class="vh" for="by">Closed in favour of</label>
110 <input type="text" id="by" name="by" inputmode="numeric" size="4" placeholder="!N">
110 <input type="text" id="by" name="by" size="4" autocomplete="off" placeholder="!N">
111111 <button type="submit" class="danger">Close without merging</button>
112112 </form>
113113 <form method="post" action="{{$base}}/draft" class="actions">
@@ -143,8 +143,8 @@
143143 {{else}}<p class="none">nobody yet</p>{{end}}
144144 {{if and .CanWrite (or (eq .MR.State "open") (eq .MR.State "source_gone"))}}
145145 <form method="post" action="{{$base}}/review-request" class="actions">
146 <input type="text" name="add" aria-label="Add reviewers" placeholder="add, space-separated">
147 <input type="text" name="remove" aria-label="Remove reviewers" placeholder="remove">
146 <input type="text" name="add" aria-label="Add reviewers" autocomplete="off" spellcheck="false" placeholder="add, space-separated">
147 <input type="text" name="remove" aria-label="Remove reviewers" autocomplete="off" spellcheck="false" placeholder="remove">
148148 <button type="submit" class="btn">Apply</button>
149149 </form>
150150 {{end}}
@@ -174,8 +174,8 @@
174174 {{else}}<p class="none">none yet</p>{{end}}
175175 {{if .CanWrite}}
176176 <form method="post" action="{{$base}}/label" class="actions">
177 <input type="text" name="add" aria-label="Add labels" placeholder="add, space-separated">
178 <input type="text" name="remove" aria-label="Remove labels" placeholder="remove">
177 <input type="text" name="add" aria-label="Add labels" autocomplete="off" spellcheck="false" placeholder="add, space-separated">
178 <input type="text" name="remove" aria-label="Remove labels" autocomplete="off" spellcheck="false" placeholder="remove">
179179 <button type="submit" class="btn">Apply</button>
180180 </form>
181181 {{end}}
internal/web/templates/new.html +2 −2
@@ -9,7 +9,7 @@
99 <option value="{{.Viewer}}">{{.Viewer}}</option>
1010 {{range .Orgs}}<option value="{{.}}">{{.}}</option>{{end}}
1111</select></label>
12<label>/ Name <input name="name" required maxlength="63" pattern="[a-z0-9][a-z0-9._\-]{0,62}" aria-describedby="namehint"></label>
12<label>/ Name <input name="name" required maxlength="63" pattern="[a-z0-9][a-z0-9._\-]{0,62}" aria-describedby="namehint" autocomplete="off" spellcheck="false"></label>
1313<span class="hint" id="namehint">Lowercase letters, digits, dot, dash and underscore; must start with a letter or digit; up to 63 characters.</span></p>
1414<fieldset class="segmented">
1515 <legend>Visibility</legend>
@@ -26,7 +26,7 @@
2626grants access through teams. You are its first admin.</p>
2727<form method="post" action="/new">
2828<input type="hidden" name="field" value="org-create">
29<p><label>Name <input name="name" required maxlength="63" pattern="[a-z0-9][a-z0-9._\-]{0,62}" aria-describedby="orghint"></label>
29<p><label>Name <input name="name" required maxlength="63" pattern="[a-z0-9][a-z0-9._\-]{0,62}" aria-describedby="orghint" autocomplete="off" spellcheck="false"></label>
3030<span class="hint" id="orghint">The same rules as a repository name, and it becomes a top-level path.</span></p>
3131<p><button type="submit">Create organization</button></p>
3232</form>
internal/web/templates/owner.html +27 −11
@@ -89,7 +89,7 @@
8989{{range .Members}}<tr>
9090 <td><a href="/{{.Name}}">{{.Name}}</a></td>
9191 <td>{{.Role}}</td>
92 <td class="act"><form method="post" action="/{{$org}}"><input type="hidden" name="field" value="member-remove"><input type="hidden" name="user" value="{{.Name}}"><button type="submit" class="linklike">Remove</button></form></td>
92 <td class="act"><form method="post" action="/{{$org}}"><input type="hidden" name="field" value="member-remove"><input type="hidden" name="user" value="{{.Name}}">{{template "confirmfield" .Name}} <button type="submit" class="linklike">Remove</button></form></td>
9393</tr>
9494{{end}}</table></div>
9595<details class="editbox">
@@ -97,7 +97,7 @@
9797 <form method="post" action="/{{$org}}" class="setform stack">
9898 <input type="hidden" name="field" value="member-add">
9999 <label for="member">Username</label>
100 <input type="text" id="member" name="user" required>
100 <input type="text" id="member" name="user" required autocomplete="off" spellcheck="false">
101101 <label class="none" for="memberrole">Role</label>
102102 <select id="memberrole" name="role">
103103 <option value="member">member</option>
@@ -118,25 +118,41 @@ of a team get its role on every repository it is granted.</p>
118118 <p class="meta">members: {{range .Members}}<a class="memberchip" href="/{{.}}">{{.}}</a> {{else}}<span class="none">none yet</span>{{end}}</p>
119119 <p class="meta">repositories: {{range .Grants}}<span class="memberchip"><a href="/{{.RepoPath}}">{{.RepoPath}}</a> <span class="role">{{.Role}}</span></span> {{else}}<span class="none">none yet</span>{{end}}</p>
120120 <form method="post" action="/{{$org}}" class="setform stack">
121 <input type="hidden" name="field" value="team-add">
121122 <input type="hidden" name="team" value="{{.Name}}">
122 <label class="none" for="tm-{{.Name}}">Members</label>
123 <input type="text" id="tm-{{.Name}}" name="user" placeholder="usernames, space-separated">
124 <button type="submit" name="field" value="team-add" class="btn">Add</button>
125 <button type="submit" name="field" value="team-remove" class="btn">Remove</button>
123 <label class="none" for="tm-{{.Name}}">Add members</label>
124 <input type="text" id="tm-{{.Name}}" name="user" placeholder="usernames, space-separated" autocomplete="off" spellcheck="false">
125 <button type="submit" class="btn">Add</button>
126126 </form>
127 {{if .Members}}<form method="post" action="/{{$org}}" class="setform stack">
128 <input type="hidden" name="field" value="team-remove">
129 <input type="hidden" name="team" value="{{.Name}}">
130 <label class="none" for="tmr-{{.Name}}">Remove members</label>
131 <input type="text" id="tmr-{{.Name}}" name="user" placeholder="usernames, space-separated" autocomplete="off" spellcheck="false">
132 {{template "confirmfield" .Name}}
133 <button type="submit" class="btn">Remove</button>
134 </form>{{end}}
127135 <form method="post" action="/{{$org}}" class="setform stack">
136 <input type="hidden" name="field" value="team-grant">
128137 <input type="hidden" name="team" value="{{.Name}}">
129 <label class="none" for="tr-{{.Name}}">Repository</label>
130 <input type="text" id="tr-{{.Name}}" name="repo" placeholder="owner/name">
138 <label class="none" for="tr-{{.Name}}">Grant a repository</label>
139 <input type="text" id="tr-{{.Name}}" name="repo" placeholder="owner/name" autocomplete="off" spellcheck="false">
131140 <label class="none" for="trr-{{.Name}}">Role</label>
132141 <select id="trr-{{.Name}}" name="role">
133142 <option value="read">read</option>
134143 <option value="write">write</option>
135144 <option value="admin">admin</option>
136145 </select>
137 <button type="submit" name="field" value="team-grant" class="btn">Grant</button>
138 <button type="submit" name="field" value="team-revoke" class="btn">Revoke</button>
146 <button type="submit" class="btn">Grant</button>
139147 </form>
148 {{if .Grants}}<form method="post" action="/{{$org}}" class="setform stack">
149 <input type="hidden" name="field" value="team-revoke">
150 <input type="hidden" name="team" value="{{.Name}}">
151 <label class="none" for="trv-{{.Name}}">Revoke a repository</label>
152 <input type="text" id="trv-{{.Name}}" name="repo" placeholder="owner/name" autocomplete="off" spellcheck="false">
153 {{template "confirmfield" .Name}}
154 <button type="submit" class="btn">Revoke</button>
155 </form>{{end}}
140156 <form method="post" action="/{{$org}}" class="setform">
141157 <input type="hidden" name="field" value="team-delete">
142158 <input type="hidden" name="team" value="{{.Name}}">
@@ -162,7 +178,7 @@ of a team get its role on every repository it is granted.</p>
162178 <form method="post" action="/{{$org}}" class="setform">
163179 <input type="hidden" name="field" value="org-rename">
164180 <label for="orgrename">New name</label>
165 <input type="text" id="orgrename" name="name" value="{{$org}}" required>
181 <input type="text" id="orgrename" name="name" value="{{$org}}" required autocomplete="off" spellcheck="false">
166182 <button type="submit" class="btn">Rename</button>
167183 </form>
168184 <p class="meta">Every clone URL under this organization changes with the name.</p>
internal/web/templates/refs.html +2 −2
@@ -2,8 +2,8 @@
22{{define "content"}}
33<h1>Refs</h1>
44<form method="get" action="/{{.Repo.OwnerName}}/{{.Repo.Name}}/compare" class="compareform">
5 <label>Compare <input name="base" value="{{.Repo.DefaultBranch}}" size="14" aria-label="base ref"></label>
6 <label>with <input name="head" size="14" placeholder="branch, tag or sha" aria-label="head ref"></label>
5 <label>Compare <input name="base" value="{{.Repo.DefaultBranch}}" size="14" aria-label="Compare base ref" autocomplete="off" spellcheck="false"></label>
6 <label>with <input name="head" size="14" placeholder="branch, tag or sha" aria-label="with head ref" autocomplete="off" spellcheck="false"></label>
77 <button type="submit" class="btn">Compare</button>
88</form>
99<h2>Branches</h2>
internal/web/templates/register.html +4 −4
@@ -6,9 +6,9 @@
66{{else}}<p class="lede">Open registration. Your account activates once you verify your email.</p>{{end}}
77{{if .Error}}<p class="error" role="alert">{{.Error}}</p>{{end}}
88<form method="post" action="/register" class="signupform">
9<div class="field"><label for="username">Username</label><input type="text" id="username" name="username" value="{{.Username}}" required></div>
10{{if eq .Mode "invite"}}<div class="field"><label for="invite">Invite code</label><input type="text" id="invite" name="invite" required></div>
11{{else}}<div class="field"><label for="email">Email</label><input type="text" id="email" name="email" required></div>{{end}}
9<div class="field"><label for="username">Username</label><input type="text" id="username" name="username" value="{{.Username}}" required autocomplete="username" autocapitalize="none" spellcheck="false"></div>
10{{if eq .Mode "invite"}}<div class="field"><label for="invite">Invite code</label><input type="text" id="invite" name="invite" required autocomplete="off" spellcheck="false"></div>
11{{else}}<div class="field"><label for="email">Email</label><input type="email" id="email" name="email" required autocomplete="email"></div>{{end}}
1212<div class="field"><label for="key">SSH public key</label>
1313<p class="hint">Paste the contents of your public key file, usually <code>~/.ssh/id_ed25519.pub</code>. It starts with <code>ssh-ed25519</code> or <code>ssh-rsa</code>.</p>
1414{{/* The help is a disclosure rather than a link out: a wiki page is on
@@ -19,7 +19,7 @@
1919<pre class="code" tabindex="0">ssh-keygen -t ed25519
2020cat ~/.ssh/id_ed25519.pub</pre>
2121</details>
22<textarea id="key" name="key" rows="3" required placeholder="ssh-ed25519 AAAA... you@host"></textarea></div>
22<textarea id="key" name="key" rows="3" required spellcheck="false" placeholder="ssh-ed25519 AAAA… you@host"></textarea></div>
2323<p><button type="submit">Create account</button></p>
2424</form>
2525<p class="meta">Prefer the terminal? <code>ssh git@{{.Host}} register --username you {{if eq .Mode "invite"}}--invite &lt;code&gt;{{else}}--email you@example.org{{end}}</code></p>
internal/web/templates/settings.html +1 −1
@@ -186,7 +186,7 @@
186186 <input type="hidden" name="field" value="runner-add">
187187 <label for="runner-key">Attach a runner</label>
188188 <p class="hint">Install <code>gitbay-runner</code>, run <code>gitbay-runner init</code>, and paste the key it prints. The runner builds your commits with the repository's secrets; merge requests from forks wait unless it runs with <code>-untrusted</code>.</p>
189 <textarea id="runner-key" name="key" rows="3" placeholder="ssh-ed25519 AAAA… (from gitbay-runner init)">{{index .Submitted "key"}}</textarea>
189 <textarea id="runner-key" name="key" rows="3" spellcheck="false" placeholder="ssh-ed25519 AAAA… (from gitbay-runner init)">{{index .Submitted "key"}}</textarea>
190190 <button type="submit" class="btn">Attach</button>
191191</form>
192192</section>
internal/web/templates/snippet.html +1 −1
@@ -29,7 +29,7 @@
2929{{if .CanWrite}}
3030<details class="editbox"><summary>add a file</summary>
3131<form method="post" action="/{{.Owner}}/-/snippets/{{.Snippet.PublicID}}/file" class="commentform">
32<p><input type="text" name="name" aria-label="File name" placeholder="filename" required></p>
32<p><input type="text" name="name" aria-label="File name" placeholder="filename" required autocomplete="off" spellcheck="false"></p>
3333<p><textarea name="content" aria-label="Content" rows="12" required></textarea></p>
3434<p><button type="submit" class="btn">Add file</button></p>
3535</form></details>
internal/web/templates/snippetnew.html +1 −1
@@ -4,7 +4,7 @@
44<h1>New snippet</h1>
55{{if .Error}}<p class="error" role="alert">{{.Error}}</p>{{end}}
66<form method="post" action="/{{.Owner}}/-/snippets/new" class="commentform">
7<p><input type="text" name="name" aria-label="File name" placeholder="filename" value="{{.Name}}" required></p>
7<p><input type="text" name="name" aria-label="File name" placeholder="filename" value="{{.Name}}" required autocomplete="off" spellcheck="false"></p>
88<p><input type="text" name="description" aria-label="Description" placeholder="description" value="{{.Description}}"></p>
99<p><select name="visibility" aria-label="Visibility">
1010<option value="unlisted"{{if or (eq .Visibility "unlisted") (eq .Visibility "")}} selected{{end}}>unlisted</option>