internal/web/templates/layout.html

287 lines · 25363 bytes

  1{{define "layout"}}<!DOCTYPE html>
  2<html lang="en"{{with field . "Theme"}} data-theme="{{.}}"{{end}}>
  3<head>
  4<meta charset="utf-8">
  5<meta name="viewport" content="width=device-width, initial-scale=1">
  6<title>{{template "title" .}}</title>
  7<link rel="stylesheet" href="/static/style.css?v={{styleVersion}}">
  8<link rel="icon" href="/favicon.svg" type="image/svg+xml">
  9<meta name="theme-color" content="#000000">
 10{{with field . "Feed"}}<link rel="alternate" type="application/atom+xml" href="{{.}}">
 11{{end}}</head>
 12<body>
 13<a class="skip" href="#content">Skip to content</a>
 14<div class="shell">
 15
 16<nav class="rail" aria-label="Site">
 17  <a class="brand" href="/" aria-label="{{.Site}} home">{{template "mark"}}<span class="vh">{{.Site}}</span></a>
 18  {{$items := railOptItems .}}
 19  {{/* A square is .railopt when the phone rail drops it into the More
 20       menu below. Every one of those destinations — strip, menu and
 21       the foot's own Log out link — renders off $items, so the strip
 22       and the menu can never drift out of step. */}}
 23  <ul class="raillist">
 24    {{if .Viewer}}<li>{{template "raillink" dict "Href" "/" "Icon" "home" "Name" "Dashboard" "Current" (eq (str . "Tab") "dashboard")}}</li>{{end}}
 25    <li>{{template "raillink" dict "Href" "/explore" "Icon" "compass" "Name" "Explore" "Current" (eq (str . "Tab") "explore")}}</li>
 26    <li>{{template "raillink" dict "Href" "/search" "Icon" "search" "Name" "Search" "Current" (eq (str . "Tab") "sitesearch")}}</li>
 27    {{if .Viewer}}<li>{{template "raillink" dict "Href" "/notifications" "Icon" "bell" "Name" "Notifications" "Current" (eq (str . "Tab") "notifications") "Count" .Rail.Unread}}</li>
 28    <li class="railopt">{{template "raillink" (index $items 0)}}</li>{{end}}
 29  </ul>
 30  <span class="railgap"></span>
 31  <ul class="raillist">
 32    {{if .Viewer}}<li class="railopt">{{template "raillink" (index $items 1)}}</li>
 33    {{if (index $items 2).Show}}<li class="railopt">{{template "raillink" (index $items 2)}}</li>{{end}}{{end}}
 34  </ul>
 35  <div class="railfoot">
 36    {{if .Viewer}}<details class="railmore">
 37      <summary class="railicon" aria-label="More" title="More">{{template "icon" "ellipsis"}}<span class="vh">More</span></summary>
 38      <div class="raildrop">
 39        {{range $items}}{{if .Show}}<a href="{{.Href}}">{{template "icon" .Icon}} {{.Name}}</a>{{end}}{{end}}
 40      </div>
 41    </details>
 42    <a class="railuser" href="/{{.Viewer}}" aria-label="Your profile" title="{{.Viewer}}"><span class="avatar">{{initial .Viewer}}</span></a>
 43    {{$logout := index $items 3}}<a class="railopt railicon" href="{{$logout.Href}}" aria-label="{{$logout.Name}}" title="{{$logout.Name}}">{{template "icon" $logout.Icon}}<span class="vh">{{$logout.Name}}</span></a>
 44    {{else}}<a class="railicon" href="/login" aria-label="Sign in" title="Sign in">{{template "icon" "person"}}<span class="vh">Sign in</span></a>{{end}}
 45  </div>
 46</nav>
 47
 48<div class="pane">
 49{{with field . "Repo"}}
 50<header class="repohead">
 51<div class="wrap">
 52  {{$top := topTab (str $ "Tab")}}
 53  <div class="identity">
 54    {{if field $ "RepoHome"}}<h1 class="repotitle"><a class="owner" href="/{{.OwnerName}}">{{.OwnerName}}</a><span class="sep">/</span>{{.Name}}</h1>
 55    {{else}}<p class="repotitle"><a class="owner" href="/{{.OwnerName}}">{{.OwnerName}}</a><span class="sep">/</span><a href="/{{.OwnerName}}/{{.Name}}">{{.Name}}</a></p>{{end}}
 56    {{if eq .Visibility "private"}}<span class="chip">Private</span>{{end}}
 57    {{if .Settings.Archived}}<span class="chip">Archived</span>{{end}}
 58    {{/* Description, topics and website belong to the code tab, inline
 59         with the name so the header is two rows on every page. */}}
 60    {{if eq $top "code"}}{{if or (field $ "Desc") (field $ "Topics") $.Repo.Settings.Website}}<p class="repodesc">{{with field $ "Desc"}}{{.}}{{end}} {{with field $ "Topics"}}{{range .}}<a class="chip topic" href="/explore?q={{.}}">{{.}}</a> {{end}}{{end}}{{with $.Repo.Settings.Website}}<a class="site" href="{{.}}" rel="nofollow">{{.}}</a>{{end}}</p>{{end}}{{end}}
 61    <span class="grow"></span>
 62    {{if $.Viewer}}<form method="post" action="/{{.OwnerName}}/{{.Name}}/pin" class="inline"><button type="submit" class="btn" aria-pressed="{{if field $ "Pinned"}}true{{else}}false{{end}}" title="Pinned repositories show on your dashboard"><span aria-hidden="true">{{if field $ "Pinned"}}★{{else}}☆{{end}}</span> {{if field $ "Pinned"}}Pinned{{else}}Pin{{end}}</button></form>
 63    <form method="post" action="/{{.OwnerName}}/{{.Name}}/watch" class="inline"><button type="submit" class="btn" aria-pressed="{{if ne (str $ "Watch") ""}}true{{else}}false{{end}}" title="{{if eq (str $ "Watch") "watching"}}Watching: every issue, request and build. Click to mute.{{else if eq (str $ "Watch") "muted"}}Muted: nothing from this repository. Click to stop muting.{{else}}Only what involves you. Click to watch everything.{{end}}">{{if eq (str $ "Watch") "watching"}}Watching{{else if eq (str $ "Watch") "muted"}}Muted{{else}}Watch{{end}}</button></form>
 64    <form method="post" action="/{{.OwnerName}}/{{.Name}}/bookmark" class="inline"><button type="submit" class="btn" aria-pressed="{{if field $ "Marked"}}true{{else}}false{{end}}" title="Bookmarked lists it under Bookmarks">{{if field $ "Marked"}}Bookmarked{{else}}Bookmark{{end}}</button></form>
 65    <a class="button btn" href="/{{.OwnerName}}/{{.Name}}/fork">Fork</a>{{end}}
 66  </div>
 67  {{if eq $top "code"}}{{if field $ "Mirrors"}}<p class="repometa">{{range $i, $m := field $ "Mirrors"}}{{if $i}} · {{end}}{{if eq $m.Direction "push"}}mirrors to{{else}}mirrors from{{end}} <a href="{{$m.URL}}" rel="nofollow">{{$m.Target}}</a>{{if $m.Error}}, <span class="bad">sync error: {{$m.Error}}</span>{{else if $m.Synced}}, synced {{$m.Synced}}{{end}}{{end}}</p>{{end}}{{end}}
 68  {{/* A tab's own count badge is omitted at zero (an empty <i> reads as
 69       stray punctuation next to the tab word); the dashboard pin row's
 70       count chip always shows its number, zero included, the same as
 71       every other count chip in the app. Two elements, two rules,
 72       decided once here (#270). */}}
 73  <nav class="tabs" aria-label="Repository">
 74    <a {{if eq $top "code"}}aria-current="page" {{end}}href="/{{.OwnerName}}/{{.Name}}">Code</a>
 75    <a {{if eq $top "issues"}}aria-current="page" {{end}}href="/{{.OwnerName}}/{{.Name}}/issues">Issues{{with field $ "OpenIssues"}}{{if .}} <i>{{.}}</i>{{end}}{{end}}</a>
 76    <a {{if eq $top "mrs"}}aria-current="page" {{end}}href="/{{.OwnerName}}/{{.Name}}/mrs">Merge requests{{with field $ "OpenMRs"}}{{if .}} <i>{{.}}</i>{{end}}{{end}}</a>
 77    <a {{if eq $top "builds"}}aria-current="page" {{end}}href="/{{.OwnerName}}/{{.Name}}/builds">Builds</a>
 78    <a {{if eq $top "releases"}}aria-current="page" {{end}}href="/{{.OwnerName}}/{{.Name}}/releases">Releases</a>
 79    {{if field $ "HasWiki"}}<a {{if eq $top "wiki"}}aria-current="page" {{end}}href="/{{.OwnerName}}/{{.Name}}/wiki">Wiki</a>{{end}}
 80    {{if field $ "CanAdmin"}}<a {{if eq $top "settings"}}aria-current="page" {{end}}href="/{{.OwnerName}}/{{.Name}}/settings">Settings</a>{{end}}
 81  </nav>
 82</div>
 83</header>
 84{{end}}
 85
 86<main id="content" class="content {{template "width" .}}">
 87{{template "content" .}}
 88</main>
 89
 90<footer>
 91  <p>Powered by <a href="https://gitbay.org/krz/gitbay">gitbay</a>{{with gitbayVersion}} · <code><a href="https://gitbay.org/krz/gitbay/commit/{{gitbayCommit}}">{{.}}</a></code>{{end}} · <a href="https://apps.apple.com/us/app/gitbay/id6806399095">iOS app</a> · <a href="/privacy">Privacy</a></p>
 92</footer>
 93</div>
 94</div>
 95</body>
 96</html>{{end}}
 97
 98{{/* icon draws one 16px glyph for the rail. The argument names it. The
 99     paths are stroked with currentColor, so a link's own colour and the
100     current marker carry through, and every one is hidden from the
101     accessibility tree — the link around it carries the name. */}}
102{{define "icon"}}<svg width="16" height="16" viewBox="0 0 16 16" aria-hidden="true" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">
103{{- if eq . "home"}}<path d="M2.25 7 8 2.5 13.75 7v6.25a.5.5 0 0 1-.5.5H2.75a.5.5 0 0 1-.5-.5Z"/><path d="M6.25 13.75V9.25h3.5v4.5"/>
104{{- else if eq . "search"}}<circle cx="6.75" cy="6.75" r="4.25"/><path d="m9.9 9.9 3.6 3.6"/>
105{{- else if eq . "bell"}}<path d="M4.25 7a3.75 3.75 0 0 1 7.5 0v2.25l1.25 2.25H3l1.25-2.25Z"/><path d="M6.5 11.5a1.5 1.5 0 0 0 3 0"/>
106{{- else if eq . "bookmark"}}<path d="M4 2.75h8v11L8 10.5l-4 3.25Z"/>
107{{- else if eq . "compass"}}<circle cx="8" cy="8" r="6"/><path d="m10.5 5.5-1.25 3.75-3.75 1.25 1.25-3.75Z"/>
108{{- else if eq . "plus"}}<path d="M8 3.25v9.5M3.25 8h9.5"/>
109{{- else if eq . "gear"}}<path d="M6.63 1.13L9.37 1.13L9.51 3.02L10.45 3.41L11.89 2.18L13.82 4.11L12.59 5.55L12.98 6.49L14.87 6.63L14.87 9.37L12.98 9.51L12.59 10.45L13.82 11.89L11.89 13.82L10.45 12.59L9.51 12.98L9.37 14.87L6.63 14.87L6.49 12.98L5.55 12.59L4.11 13.82L2.18 11.89L3.41 10.45L3.02 9.51L1.13 9.37L1.13 6.63L3.02 6.49L3.41 5.55L2.18 4.11L4.11 2.18L5.55 3.41L6.49 3.02Z"/><circle cx="8" cy="8" r="2.2"/>
110{{- else if eq . "shield"}}<path d="M8 1.75 13 3.5v4.25c0 3.1-2 5.4-5 6.5-3-1.1-5-3.4-5-6.5V3.5Z"/><path d="m5.9 7.75 1.6 1.6 2.85-3.1"/>
111{{- else if eq . "signout"}}<path d="M6.5 2.25H3.25a1 1 0 0 0-1 1v9.5a1 1 0 0 0 1 1H6.5"/><path d="M7.75 8h6M11.25 5.5 13.75 8l-2.5 2.5"/>
112{{- else if eq . "ellipsis"}}<circle cx="3.25" cy="8" r="1.1" fill="currentColor" stroke="none"/><circle cx="8" cy="8" r="1.1" fill="currentColor" stroke="none"/><circle cx="12.75" cy="8" r="1.1" fill="currentColor" stroke="none"/>
113{{- else if eq . "snippet"}}<path d="M3.25 2.75h6L12.75 6v7.25a.5.5 0 0 1-.5.5H3.75a.5.5 0 0 1-.5-.5Z"/><path d="M9 2.75V6h3.75"/><path d="m6.75 8.75-1.5 1.5 1.5 1.5M9.75 8.75l1.5 1.5-1.5 1.5"/>
114{{- else if eq . "person"}}<circle cx="8" cy="5.5" r="2.75"/><path d="M2.75 14a5.25 5.25 0 0 1 10.5 0"/>
115{{- else if eq . "folder"}}<path d="M1.75 3.25h4l1.5 1.75h6.5v7.5a.5.5 0 0 1-.5.5H2.25a.5.5 0 0 1-.5-.5Z"/>
116{{- else if eq . "file"}}<path d="M3.25 2.75h6L12.75 6v7.25a.5.5 0 0 1-.5.5H3.75a.5.5 0 0 1-.5-.5Z"/><path d="M9 2.75V6h3.75"/>
117{{- end}}</svg>{{end}}
118
119{{/* raillink draws one square in the rail: the icon, the name for a
120     screen reader and in the tooltip, and the bell's unread badge. */}}
121{{define "raillink"}}<a class="railicon" {{if .Current}}aria-current="page" {{end}}href="{{.Href}}" aria-label="{{.Name}}" title="{{.Name}}">{{template "icon" .Icon}}<span class="vh">{{.Name}}</span>{{with .Count}}<b class="count">{{.}}</b>{{end}}</a>{{end}}
122
123{{define "mark"}}<svg class="mark" width="19" height="19" viewBox="0 0 24 24" aria-hidden="true"><path d="M12 2.25 18.75 12H5.25z" fill="#ff6b3d"/><rect x="3" y="13.5" width="18" height="3" fill="currentColor"/><rect x="7.5" y="18" width="9" height="3" fill="currentColor"/></svg>{{end}}
124{{define "width"}}reading{{end}}
125
126{{define "reporow"}}<li>
127  <p class="reponame"><a href="/{{.OwnerName}}/{{.Name}}">{{.OwnerName}}<span class="sep">/</span><strong>{{.Name}}</strong></a>{{if eq .Visibility "private"}} <span class="chip">Private</span>{{end}}{{if .Archived}} <span class="chip">Archived</span>{{end}}</p>
128  {{if .Desc}}<p class="desc">{{.Desc}}</p>{{end}}
129  {{if .Topics}}<p class="topics">{{range .Topics}}<a class="chip topic" href="/explore?q={{.}}">{{.}}</a> {{end}}</p>{{end}}
130  <p class="meta">{{template "branchicon"}} {{.DefaultBranch}}{{if .License}} · {{.License}}{{end}}{{if .Updated}} · updated {{.Updated}}{{end}}</p>
131</li>{{end}}
132
133{{define "refmenu"}}{{if .Branches}}<details class="refmenu">
134  <summary class="refchip">{{template "branchicon"}} {{.Ref}}</summary>
135  <div class="refdrop">
136  {{range .Branches}}<a href="/{{$.Repo.OwnerName}}/{{$.Repo.Name}}/{{$.RefKind}}/{{.Name}}/{{$.DirPath}}">{{.Name}}</a>
137  {{end}}<a class="allrefs" href="/{{.Repo.OwnerName}}/{{.Repo.Name}}/refs">All refs →</a>
138  </div>
139</details>{{else}}<span class="refchip">{{template "branchicon"}} {{.Ref}}</span>{{end}}{{end}}
140
141{{define "filenav"}}<nav class="filenav" aria-label="Files">
142  <h2 class="colhead">{{.Title}}</h2>
143  <ul>
144  {{with .Parent}}<li><a class="up" href="{{.}}">..</a></li>{{end}}
145  {{range .Entries}}<li><a{{if .Dir}} class="dir"{{end}}{{if .Current}} aria-current="page"{{end}} href="{{.URL}}">{{.Name}}</a></li>
146  {{end}}</ul>
147</nav>{{end}}
148
149{{/* Every side column's groups sit in a details.sidedrop. Below 62rem
150     that disclosure is what the reader sees of the column: one closed
151     line they open. Above it style.css hides the summary and shows the
152     content whatever the open state, so the column reads as it always
153     has (#237). The facet columns name the filters in force in the
154     summary; the section navs head with the word alone. */}}
155{{define "sidecol"}}<nav class="sidecol" aria-label="Filters">
156<details class="sidedrop">
157<summary><span class="sideword">Filters</span>{{range .}}{{range .Items}}{{if .Active}} <span class="sideon">{{.Label}}</span>{{end}}{{end}}{{end}}</summary>
158  {{range .}}{{if .Items}}<div class="grp">
159  <h2 class="colhead">{{.Title}}</h2>
160  <ul>{{range .Items}}<li><a{{if .Active}} aria-current="page"{{end}} href="{{.Href}}">{{.Label}}{{if .Count}} <i>{{.Count}}</i>{{end}}</a></li>{{end}}</ul>
161  </div>{{end}}{{end}}
162</details>
163</nav>{{end}}
164
165{{define "branchicon"}}<svg class="icon" width="12" height="12" viewBox="0 0 16 16" aria-hidden="true" fill="currentColor"><path d="M9.5 3.25a2.25 2.25 0 1 1 3 2.122V6A2.5 2.5 0 0 1 10 8.5H6a1 1 0 0 0-1 1v1.128a2.251 2.251 0 1 1-1.5 0V5.372a2.25 2.25 0 1 1 1.5 0v1.836A2.493 2.493 0 0 1 6 7h4a1 1 0 0 0 1-1v-.628a2.25 2.25 0 0 1-1.5-2.122ZM4.25 12a.75.75 0 1 0 0 1.5.75.75 0 0 0 0-1.5ZM3.5 3.25a.75.75 0 1 1 1.5 0 .75.75 0 0 1-1.5 0Zm8.25-.75a.75.75 0 1 0 0 1.5.75.75 0 0 0 0-1.5Z"/></svg>{{end}}
166
167{{define "sigbadge"}}<span class="badge badge-{{.State}}" title="{{.Fingerprint}}">{{sigLabel .State}}{{if .Signer}} · {{.Signer}}{{end}}</span>{{end}}
168
169{{define "authorname"}}{{if .User}}<a class="authorlink" href="/{{.User}}" title="{{.Email}}">{{.Name}}</a>{{else}}<span title="{{.Email}}">{{.Name}}</span>{{end}}{{end}}
170
171{{/* formatpicker is the md/org choice on a body: issue and MR create.
172     The argument is the currently selected format; empty selects
173     Markdown, today's default. */}}
174{{define "formatpicker"}}<p class="branchpick">
175  <label for="format">Body markup</label>
176  <select id="format" name="format">
177    <option value="md"{{if ne . "org"}} selected{{end}}>Markdown</option>
178    <option value="org"{{if eq . "org"}} selected{{end}}>Org</option>
179  </select>
180</p>{{end}}
181
182{{/* previewbtn is the Preview submit beside a markup form's own submit.
183     It posts the form to its usual action, which renders the body and
184     hands the page back instead of writing (#235). */}}
185{{/* reactions is one item's reaction bar (reactionBar in httpd). A
186     signed-in viewer gets a button per reaction; anyone else the counts. */}}
187{{define "reactions"}}{{if .Items}}{{if .Signed}}<form method="post" action="{{.Action}}" class="reactions">{{if .Comment}}<input type="hidden" name="comment" value="{{.Comment}}">{{end}}
188{{range .Items}}<button type="submit" name="{{if .Me}}remove{{else}}add{{end}}" value="{{.Name}}" class="react{{if .Me}} mine{{end}}" aria-pressed="{{.Me}}" aria-label="{{.Label}}">{{.Emoji}}{{if .Count}} <span>{{.Count}}</span>{{end}}</button>
189{{end}}</form>{{else}}<p class="reactions">{{range .Items}}<span class="react" aria-label="{{.Label}}">{{.Emoji}} {{.Count}}</span> {{end}}</p>{{end}}{{end}}{{end}}
190
191{{define "previewbtn"}}<button type="submit" name="preview" value="1" class="btn">Preview</button>{{end}}
192
193{{/* previewblock shows a draft as it will read. The argument is the
194     rendered HTML; it sits above the textarea it came from. */}}
195{{define "previewblock"}}<div class="preview">
196  <p class="previewmark">Preview</p>
197  <div class="rendered">{{.}}</div>
198</div>{{end}}
199
200{{/* confirmfield is the typed-name check beside a destructive control.
201     The argument is the text the person must type. */}}
202{{define "confirmfield"}}<input type="text" name="confirm" aria-label="Type {{.}} to confirm" placeholder="type {{.}} to confirm" size="{{len .}}" autocomplete="off">{{end}}
203
204{{/* difffiles renders a parsed diff: one foldable section per file, with
205     line-number gutters and review threads inline. Base is the MR's
206     endpoint and Viewer the signed-in account; the commit page passes
207     neither and gets the same diff without review controls. */}}
208{{define "difffiles"}}{{$base := .Base}}{{$viewer := .Viewer}}{{$L := .L}}{{$split := false}}{{with $L}}{{$split = .Split}}{{end}}
209{{with $L}}{{if .UnifiedURL}}<p class="layoutpick">Layout: {{if .Split}}<a href="{{.UnifiedURL}}">unified</a> · <strong aria-current="true">split</strong>{{else}}<strong aria-current="true">unified</strong> · <a href="{{.SplitURL}}">split</a>{{end}}</p>{{end}}{{end}}
210{{range $fi, $f := .Files}}<details class="difffold" id="f{{$fi}}"{{if .Open}} open{{end}}>
211  <summary>
212    <span class="fpath">{{if eq .Status "renamed"}}<span class="was">{{.OldPath}} →</span> {{end}}{{.Path}}</span>
213    {{if ne .Status "modified"}}<span class="chip">{{.Status}}</span>{{end}}
214    <span class="fstat">{{if .Adds}}<span class="add">+{{.Adds}}</span>{{end}}{{if .Dels}} <span class="del">−{{.Dels}}</span>{{end}}</span>
215  </summary>
216  {{if .Binary}}<p class="none">Binary file not shown.</p>
217  {{else if $split}}<div class="tablewrap"><table class="difftable split"><colgroup><col class="cln"><col><col class="cln"><col></colgroup>
218  {{range .Rows}}{{if eq .Kind "hunk"}}<tr class="hunk"><td class="ln"></td><td class="src" colspan="3">{{.Text}}</td></tr>
219  {{else if eq .Kind "meta"}}<tr class="dmeta"><td class="ln"></td><td class="src" colspan="3">{{.Text}}</td></tr>
220  {{else}}{{$r := .}}<tr class="pair">
221    {{with .Old}}{{if eq .Class "del"}}<td class="ln del" id="f{{$fi}}-o{{.OldLine}}">{{template "cmtln" dict "N" .OldLine "Path" .Path "Side" "old" "Base" $base "Viewer" $viewer "Carry" $L.Carry}}</td><td class="src del chroma">{{if .Code}}{{.Code}}{{else}}{{.Content}}{{end}}{{with $r.OldNote}}<span class="nonl">{{.}}</span>{{end}}</td>{{else}}<td class="ln ctx old">{{.OldLine}}</td><td class="src ctx old chroma">{{if .Code}}{{.Code}}{{else}}{{.Content}}{{end}}{{with $r.OldNote}}<span class="nonl">{{.}}</span>{{end}}</td>{{end}}{{else}}<td class="ln none" aria-hidden="true"></td><td class="src none" aria-hidden="true"></td>{{end}}
222    {{with .New}}<td class="ln {{.Class}}" id="f{{$fi}}-n{{.NewLine}}">{{template "cmtln" dict "N" .NewLine "Path" .Path "Side" "new" "Base" $base "Viewer" $viewer "Carry" $L.Carry}}</td><td class="src {{.Class}} chroma">{{if .Code}}{{.Code}}{{else}}{{.Content}}{{end}}{{with $r.NewNote}}<span class="nonl">{{.}}</span>{{end}}</td>{{else}}<td class="ln none" aria-hidden="true"></td><td class="src none" aria-hidden="true"></td>{{end}}
223  </tr>
224  {{with .Compose}}<tr class="threadrow"><td colspan="4"><form id="compose" method="post" action="{{$base}}/diff-comment" class="thread composing">
225    <input type="hidden" name="path" value="{{.Path}}">
226    <input type="hidden" name="line" value="{{if eq .Class "del"}}{{.OldLine}}{{else}}{{.NewLine}}{{end}}">
227    <input type="hidden" name="side" value="{{if eq .Class "del"}}old{{else}}new{{end}}">
228    {{if ne .Class "del"}}<p><label>From line <input type="number" name="start_line" min="1" max="{{.NewLine}}" placeholder="{{.NewLine}}"></label> to {{.NewLine}}; a <code>```suggestion</code> block proposes replacement lines</p>{{end}}
229    <p><textarea name="body" aria-label="Comment on {{.Path}}" rows="3" placeholder="Comment on this line" autofocus></textarea></p>
230    <p><button type="submit" class="btn">Comment</button>
231       <button type="submit" name="pending" value="on" class="btn">Add to review</button>
232       <a href="{{$base}}?view=diff{{if $L.Carry}}&amp;layout={{$L.Carry}}{{end}}">Cancel</a></p>
233  </form></td></tr>
234  {{end}}{{range .Threads}}<tr class="threadrow"><td colspan="4">{{template "thread" dict "T" . "Base" $base "Viewer" $viewer "Class" ""}}</td></tr>
235  {{end}}{{end}}{{end}}
236  </table></div>
237  {{else}}<div class="tablewrap"><table class="difftable">
238  {{range .Lines}}{{if eq .Class "hunk"}}<tr class="hunk"><td class="ln" colspan="2"></td><td class="src">{{.Text}}</td></tr>
239  {{else if eq .Class "meta"}}<tr class="dmeta"><td class="ln" colspan="2"></td><td class="src">{{.Text}}</td></tr>
240  {{else}}<tr class="{{.Class}}" id="f{{$fi}}-{{if eq .Class "del"}}o{{.OldLine}}{{else}}n{{.NewLine}}{{end}}"><td class="ln">{{if .OldLine}}{{if eq .Class "del"}}{{template "cmtln" dict "N" .OldLine "Path" .Path "Side" "old" "Base" $base "Viewer" $viewer "Carry" $L.Carry}}{{else}}{{.OldLine}}{{end}}{{end}}</td><td class="ln">{{if .NewLine}}{{template "cmtln" dict "N" .NewLine "Path" .Path "Side" "new" "Base" $base "Viewer" $viewer "Carry" $L.Carry}}{{end}}</td><td class="src chroma">{{if .Code}}{{.Code}}{{else}}{{.Content}}{{end}}</td></tr>
241  {{if .Compose}}<tr class="threadrow"><td colspan="3"><form id="compose" method="post" action="{{$base}}/diff-comment" class="thread composing">
242    <input type="hidden" name="path" value="{{.Path}}">
243    <input type="hidden" name="line" value="{{if eq .Class "del"}}{{.OldLine}}{{else}}{{.NewLine}}{{end}}">
244    <input type="hidden" name="side" value="{{if eq .Class "del"}}old{{else}}new{{end}}">
245    {{if ne .Class "del"}}<p><label>From line <input type="number" name="start_line" min="1" max="{{.NewLine}}" placeholder="{{.NewLine}}"></label> to {{.NewLine}}; a <code>```suggestion</code> block proposes replacement lines</p>{{end}}
246    <p><textarea name="body" aria-label="Comment on {{.Path}}" rows="3" placeholder="Comment on this line" autofocus></textarea></p>
247    <p><button type="submit" class="btn">Comment</button>
248       <button type="submit" name="pending" value="on" class="btn">Add to review</button>
249       <a href="{{$base}}?view=diff{{if $L.Carry}}&amp;layout={{$L.Carry}}{{end}}">Cancel</a></p>
250  </form></td></tr>
251  {{end}}{{end}}{{range .Threads}}<tr class="threadrow"><td colspan="3">{{template "thread" dict "T" . "Base" $base "Viewer" $viewer "Class" ""}}</td></tr>
252  {{end}}{{end}}
253  </table></div>{{end}}
254</details>
255{{end}}{{end}}
256
257{{/* cmtln turns a line number into the link that opens the comment form
258     on that line. No JavaScript: the anchor travels in the query. */}}
259{{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}}{{if .Carry}}&amp;layout={{.Carry}}{{end}}#compose">{{.N}}</a>{{else}}{{.N}}{{end}}{{end}}
260
261{{/* thread renders one review thread with its reply and resolve controls.
262     Class carries "stale" for threads whose anchor is gone. */}}
263{{define "thread"}}{{$t := .T}}<div class="thread{{if $t.Resolved}} resolved{{end}}{{if $t.Pending}} pending{{end}}{{if .Class}} {{.Class}}{{end}}" id="thread-{{$t.ID}}">
264{{if $t.Pending}}<p class="threadstate">pending — only you can see this until you submit your review</p>{{end}}
265{{if or $t.Resolved (and .Class $t.Stale)}}<p class="threadstate">{{if and .Class $t.Stale}}stale{{end}}{{if $t.Resolved}}{{if and .Class $t.Stale}} · {{end}}resolved by {{$t.Resolved}}{{end}}</p>{{end}}
266{{range $i, $c := $t.Comments}}<p class="commenthead"><strong>{{.Author}}</strong> <span class="when">{{when .CreatedAt}}</span></p><div class="rendered">{{.BodyHTML}}</div>{{if and (eq $i 0) $t.Suggestion}}{{template "suggestion" dict "S" $t.Suggestion "T" $t.ID "Base" $.Base}}{{end}}{{end}}
267{{if .Viewer}}<details class="threadreply"><summary>Reply</summary>
268<form method="post" action="{{.Base}}/diff-comment">
269  <input type="hidden" name="reply" value="{{$t.ID}}">
270  <p><textarea name="body" aria-label="Reply to thread {{$t.ID}}" rows="2" placeholder="Reply as {{.Viewer}}"></textarea></p>
271  <p><button type="submit" class="btn">Reply</button></p>
272</form></details>{{end}}
273{{if $t.CanResolve}}<form method="post" action="{{.Base}}/thread" class="threadact"><input type="hidden" name="thread" value="{{$t.ID}}"><button type="submit" name="action" value="{{if $t.Resolved}}unresolve{{else}}resolve{{end}}" class="linklike">{{if $t.Resolved}}Reopen thread{{else}}Resolve thread{{end}}</button></form>{{end}}
274</div>{{end}}
275
276{{/* suggestion renders the lines a thread's suggestion replaces and the
277     ones it proposes, with the apply button, or the command to run where
278     the server cannot sign the commit. */}}
279{{define "suggestion"}}{{$s := .S}}<div class="suggestion">
280<p class="threadstate">{{if $s.Outdated}}outdated suggestion: {{$s.Reason}}{{else}}suggested change{{end}}</p>
281<div class="tablewrap"><table class="difftable">
282{{range $s.Old}}<tr class="del"><td class="ln">{{.N}}</td><td class="src">{{.Text}}</td></tr>
283{{end}}{{range $s.New}}<tr class="add"><td class="ln">{{.N}}</td><td class="src">{{.Text}}</td></tr>
284{{end}}</table></div>
285{{if and $s.CanApply (not $s.Outdated)}}{{if $s.Local}}<p class="threadstate">This repository requires signed commits, which the server cannot make. Apply it from a clone: <code>{{$s.Command}}</code></p>
286{{else}}<form method="post" action="{{.Base}}/suggestion" class="threadact"><input type="hidden" name="thread" value="{{.T}}"><button type="submit" class="btn">Apply suggestion</button></form>
287{{end}}{{end}}</div>{{end}}