Commit 27e5426f8f
Verified · cmc
internal/web/static/style.css +66 −1
| @@ -530,7 +530,14 @@ code.fullsha { color: var(--muted); overflow-wrap: anywhere; } | ||
| 530 | 530 | .empty-note { color: var(--muted); } |
| 531 | 531 | .crumbs { color: var(--muted); font-size: var(--fs-2); } |
| 532 | 532 | .desc, td.desc { color: var(--muted); } |
| 533 | .error { color: var(--bad); } | |
| 533 | .error { | |
| 534 | color: var(--fg); | |
| 535 | border: 1px solid var(--bad); | |
| 536 | border-left-width: 3px; | |
| 537 | border-radius: var(--r-sm); | |
| 538 | padding: var(--sp-2) var(--sp-3); | |
| 539 | margin-bottom: var(--sp-4); | |
| 540 | } | |
| 534 | 541 | .meta { color: var(--muted); font-size: var(--fs-1); } |
| 535 | 542 | svg.icon { vertical-align: -0.125em; } |
| 536 | 543 | .lede { font-size: var(--fs-3); margin: var(--sp-2) 0; } |
| @@ -970,6 +977,64 @@ input:focus, textarea:focus, select:focus { border-color: var(--accent); } | ||
| 970 | 977 | input[type="radio"], input[type="checkbox"] { accent-color: var(--fill); } |
| 971 | 978 | label:has(> input[type="radio"]), label:has(> input[type="checkbox"]), |
| 972 | 979 | label:has(> select) { display: inline-flex; align-items: center; gap: var(--sp-2); } |
| 980 | ||
| 981 | /* Shape carries the meaning on these two and nothing else does: a circle | |
| 982 | takes one answer, a square takes any number. It survives colour being | |
| 983 | removed — greyscale, high contrast, colour blindness — so the native | |
| 984 | shapes stay and only the colour joins the system. Drawing them by hand | |
| 985 | would align the corners at the cost of the one cue that always works. */ | |
| 986 | input[type="radio"], input[type="checkbox"] { accent-color: var(--fill); } | |
| 987 | ||
| 988 | /* a segmented control for a small, mutually exclusive set: real radios | |
| 989 | underneath, so arrow keys and screen readers behave as they always did */ | |
| 990 | fieldset.segmented { border: 0; margin: 0 0 var(--sp-4); padding: 0; } | |
| 991 | fieldset.segmented legend { | |
| 992 | padding: 0; | |
| 993 | margin-bottom: var(--sp-2); | |
| 994 | color: var(--muted); | |
| 995 | font-size: var(--fs-2); | |
| 996 | } | |
| 997 | fieldset.segmented .options { display: inline-flex; } | |
| 998 | fieldset.segmented label { gap: 0; } | |
| 999 | fieldset.segmented input { | |
| 1000 | position: absolute; | |
| 1001 | width: 1px; | |
| 1002 | height: 1px; | |
| 1003 | opacity: 0; | |
| 1004 | pointer-events: none; | |
| 1005 | } | |
| 1006 | fieldset.segmented span { | |
| 1007 | display: block; | |
| 1008 | padding: var(--sp-2) var(--sp-4); | |
| 1009 | border: 1px solid var(--line); | |
| 1010 | font-size: var(--fs-2); | |
| 1011 | cursor: pointer; | |
| 1012 | } | |
| 1013 | fieldset.segmented label + label span { border-left: 0; } | |
| 1014 | fieldset.segmented label:first-child span { border-radius: var(--r-sm) 0 0 var(--r-sm); } | |
| 1015 | fieldset.segmented label:last-child span { border-radius: 0 var(--r-sm) var(--r-sm) 0; } | |
| 1016 | fieldset.segmented span:hover { border-color: var(--muted); } | |
| 1017 | fieldset.segmented input:checked + span { | |
| 1018 | background: var(--fill); | |
| 1019 | border-color: var(--fill); | |
| 1020 | color: #ffffff; | |
| 1021 | font-weight: 600; | |
| 1022 | } | |
| 1023 | fieldset.segmented input:focus-visible + span { | |
| 1024 | outline: 2px solid var(--accent); | |
| 1025 | outline-offset: 2px; | |
| 1026 | } | |
| 1027 | ||
| 1028 | /* remaining input states */ | |
| 1029 | ::placeholder { color: var(--muted); opacity: 1; } | |
| 1030 | ::selection { background: var(--fill); color: #ffffff; } | |
| 1031 | /* :user-invalid, not :invalid — a required field is invalid the moment | |
| 1032 | the page loads, and colouring it red before anyone has typed is a | |
| 1033 | scolding, not a hint */ | |
| 1034 | input:user-invalid, textarea:user-invalid { | |
| 1035 | border-color: var(--bad); | |
| 1036 | } | |
| 1037 | .hint { display: block; color: var(--muted); font-size: var(--fs-1); margin-top: var(--sp-1); } | |
| 973 | 1038 | button { |
| 974 | 1039 | background: var(--fill); |
| 975 | 1040 | color: #ffffff; |
internal/web/templates/edit.html +1 −1
| @@ -1,7 +1,7 @@ | ||
| 1 | 1 | {{define "title"}}edit {{.Path}} · {{.Repo.OwnerName}}/{{.Repo.Name}}{{end}} |
| 2 | 2 | {{define "content"}} |
| 3 | 3 | <h1>edit {{.Repo.OwnerName}}/{{.Repo.Name}} : {{.Path}} @ {{.Ref}}</h1> |
| 4 | {{if .Error}}<p class="error">{{.Error}}</p>{{end}} | |
| 4 | {{if .Error}}<p class="error" role="alert">{{.Error}}</p>{{end}} | |
| 5 | 5 | <form method="post" action="/{{.Repo.OwnerName}}/{{.Repo.Name}}/edit/{{.Ref}}/{{.Path}}"> |
| 6 | 6 | <p><textarea name="content" aria-label="File contents" rows="24" style="width:100%" spellcheck="false">{{.Content}}</textarea></p> |
| 7 | 7 | <p><input name="message" aria-label="Commit message" placeholder="commit message" style="width:60%"> |
internal/web/templates/login.html +1 −1
| @@ -1,7 +1,7 @@ | ||
| 1 | 1 | {{define "title"}}login · {{.Site}}{{end}} |
| 2 | 2 | {{define "content"}} |
| 3 | 3 | <h1>Log in</h1> |
| 4 | {{if .Error}}<p class="error">{{.Error}}</p>{{end}} | |
| 4 | {{if .Error}}<p class="error" role="alert">{{.Error}}</p>{{end}} | |
| 5 | 5 | <p>Browser sessions are minted over SSH — there is no password. From a machine |
| 6 | 6 | with your registered key:</p> |
| 7 | 7 | <pre class="message">ssh git@{{.Site}} web login</pre> |
internal/web/templates/new.html +10 −4
| @@ -1,15 +1,21 @@ | ||
| 1 | 1 | {{define "title"}}new repository · {{.Site}}{{end}} |
| 2 | 2 | {{define "content"}} |
| 3 | 3 | <h1>New repository</h1> |
| 4 | {{if .Error}}<p class="error">{{.Error}}</p>{{end}} | |
| 4 | {{if .Error}}<p class="error" role="alert">{{.Error}}</p>{{end}} | |
| 5 | 5 | <form method="post" action="/new"> |
| 6 | 6 | <p><label>Owner <select name="owner"> |
| 7 | 7 | <option value="{{.Viewer}}">{{.Viewer}}</option> |
| 8 | 8 | {{range .Orgs}}<option value="{{.}}">{{.}}</option>{{end}} |
| 9 | 9 | </select></label> |
| 10 | <label>/ Name <input name="name" required pattern="[a-z0-9][a-z0-9._-]*"></label></p> | |
| 11 | <p><label><input type="radio" name="visibility" value="public" checked> Public</label> | |
| 12 | <label><input type="radio" name="visibility" value="private"> Private</label></p> | |
| 10 | <label>/ Name <input name="name" required maxlength="63" pattern="[a-z0-9][a-z0-9._\-]{0,62}" aria-describedby="namehint"></label> | |
| 11 | <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> | |
| 12 | <fieldset class="segmented"> | |
| 13 | <legend>Visibility</legend> | |
| 14 | <div class="options"> | |
| 15 | <label><input type="radio" name="visibility" value="public" checked><span>Public</span></label> | |
| 16 | <label><input type="radio" name="visibility" value="private"><span>Private</span></label> | |
| 17 | </div> | |
| 18 | </fieldset> | |
| 13 | 19 | <p><button type="submit">Create repository</button></p> |
| 14 | 20 | </form> |
| 15 | 21 | {{end}} |
internal/web/templates/register.html +1 −1
| @@ -4,7 +4,7 @@ | ||
| 4 | 4 | <h1>Create an account</h1> |
| 5 | 5 | {{if eq .Mode "invite"}}<p class="lede">This instance is invite-only: you need an invite code from an admin.</p> |
| 6 | 6 | {{else}}<p class="lede">Open registration — your account activates once you verify your email.</p>{{end}} |
| 7 | {{if .Error}}<p class="error">{{.Error}}</p>{{end}} | |
| 7 | {{if .Error}}<p class="error" role="alert">{{.Error}}</p>{{end}} | |
| 8 | 8 | <form method="post" action="/register" class="signupform"> |
| 9 | 9 | <p><label>username<br><input type="text" name="username" value="{{.Username}}" required autofocus></label></p> |
| 10 | 10 | {{if eq .Mode "invite"}}<p><label>invite code<br><input type="text" name="invite" required></label></p> |
internal/web/templates/search.html +1 −1
| @@ -5,7 +5,7 @@ | ||
| 5 | 5 | <input type="search" name="q" aria-label="Search file contents" value="{{.Query}}" placeholder="search file contents on {{.Ref}}" autofocus> |
| 6 | 6 | <button type="submit">search</button> |
| 7 | 7 | </form> |
| 8 | {{if .QueryErr}}<p class="error">{{.QueryErr}}</p> | |
| 8 | {{if .QueryErr}}<p class="error" role="alert">{{.QueryErr}}</p> | |
| 9 | 9 | {{else if .Query}} |
| 10 | 10 | {{if .Matches}} |
| 11 | 11 | <p class="meta">{{len .Matches}} match{{if ne (len .Matches) 1}}es{{end}}{{if .Capped}} (capped — refine the query){{end}} on <code>{{.Ref}}</code></p> |