internal/web/templates/layout.html

195 lines · 14717 bytes

  1{{define "layout"}}<!DOCTYPE html>
  2<html lang="en">
  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">
  8<link rel="icon" href="/favicon.svg" type="image/svg+xml">
  9{{with field . "Feed"}}<link rel="alternate" type="application/atom+xml" href="{{.}}">
 10{{end}}</head>
 11<body>
 12<a class="skip" href="#content">Skip to content</a>
 13<div class="shell">
 14
 15{{$here := ""}}{{with field . "Repo"}}{{$here = printf "%s/%s" .OwnerName .Name}}{{end}}
 16<nav class="rail" aria-label="Site">
 17  <a class="brand" href="/">{{template "mark"}}<span>{{.Site}}</span></a>
 18  <div class="railbody">
 19    <div class="railtop">
 20    <form method="get" action="/search" class="railsearch" role="search">
 21      <input type="search" name="q" aria-label="Search {{.Site}}" placeholder="Search">
 22    </form>
 23    <ul class="raillist">
 24      {{if .Viewer}}<li><a href="/">Dashboard</a></li>{{end}}
 25      <li><a {{if eq (str . "Tab") "explore"}}aria-current="page" {{end}}href="/explore">Explore</a></li>
 26      {{if .Viewer}}<li><a {{if eq (str . "Tab") "notifications"}}aria-current="page" {{end}}href="/notifications">Notifications{{with .Rail.Unread}} <b class="count">{{.}}</b>{{end}}</a></li>
 27      <li><a {{if eq (str . "Tab") "bookmarks"}}aria-current="page" {{end}}href="/bookmarks">Bookmarks</a></li>
 28      <li><a class="newrepo" href="/new" aria-label="New repository" title="New repository">+</a></li>{{end}}
 29    </ul>
 30    </div>
 31    {{with .Rail.Pinned}}
 32    <div class="railgroup pinned">
 33    <p class="raillabel" id="rail-pinned">Pinned</p>
 34    <ul class="raillist" aria-labelledby="rail-pinned">
 35      {{range .}}<li><a {{if eq $here (printf "%s/%s" .Owner .Name)}}aria-current="page" {{end}}href="/{{.Owner}}/{{.Name}}"><span class="owner">{{.Owner}}/</span>{{.Name}}</a></li>
 36      {{end}}
 37    </ul>
 38    </div>
 39    {{end}}
 40    {{with .Rail.Reviews}}
 41    <div class="railgroup">
 42    <p class="raillabel" id="rail-reviews">Waiting on you <b class="count">{{len .}}</b></p>
 43    <ul class="raillist wide" aria-labelledby="rail-reviews">
 44      {{range .}}<li><a href="/{{.RepoPath}}/mrs/{{.Number}}"><b>!{{.Number}} {{.Title}}</b><span class="owner">{{.RepoPath}}</span></a></li>
 45      {{end}}
 46    </ul>
 47    </div>
 48    {{end}}
 49  </div>
 50  <ul class="raillist railpinned">
 51    {{if .Viewer}}<li><a {{if eq (str . "Tab") "account"}}aria-current="page" {{end}}href="/settings">Settings</a></li>{{end}}
 52    {{if .Admin}}<li><a {{if eq (str . "Tab") "admin"}}aria-current="page" {{end}}href="/admin">Admin</a></li>{{end}}
 53  </ul>
 54  <div class="railfoot">
 55    {{if .Viewer}}<a class="railuser" href="/{{.Viewer}}"><span class="avatar">{{initial .Viewer}}</span><span class="uname">{{.Viewer}}</span></a>
 56    <form method="post" action="/logout"><button type="submit" class="linklike">Log out</button></form>
 57    {{else}}<a href="/login">Sign in</a>{{end}}
 58  </div>
 59</nav>
 60
 61<div class="pane">
 62{{with field . "Repo"}}
 63<header class="repohead">
 64  <div class="identity">
 65    {{if field $ "RepoHome"}}<h1 class="repotitle"><a class="owner" href="/{{.OwnerName}}">{{.OwnerName}}</a><span class="sep">/</span>{{.Name}}</h1>
 66    {{else}}<p class="repotitle"><a class="owner" href="/{{.OwnerName}}">{{.OwnerName}}</a><span class="sep">/</span><a href="/{{.OwnerName}}/{{.Name}}">{{.Name}}</a></p>{{end}}
 67    {{if eq .Visibility "private"}}<span class="chip">Private</span>{{end}}
 68    {{if .Settings.Archived}}<span class="chip">Archived</span>{{end}}
 69    <span class="grow"></span>
 70    {{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}}"><span aria-hidden="true">{{if field $ "Pinned"}}★{{else}}☆{{end}}</span> {{if field $ "Pinned"}}Pinned{{else}}Pin{{end}}</button></form>
 71    <form method="post" action="/{{.OwnerName}}/{{.Name}}/watch" class="inline"><button type="submit" class="btn" aria-pressed="{{if eq (str $ "Watch") "watching"}}true{{else}}false{{end}}">{{if eq (str $ "Watch") "watching"}}Watching{{else}}Watch{{end}}</button></form>
 72    <form method="post" action="/{{.OwnerName}}/{{.Name}}/bookmark" class="inline"><button type="submit" class="btn" aria-pressed="{{if field $ "Marked"}}true{{else}}false{{end}}">{{if field $ "Marked"}}Bookmarked{{else}}Bookmark{{end}}</button></form>
 73    <form method="post" action="/{{.OwnerName}}/{{.Name}}/fork" class="inline"><button type="submit" class="btn">Fork</button></form>{{end}}
 74  </div>
 75  {{$top := topTab (str $ "Tab")}}
 76  {{/* Description, topics and metadata belong to the code tab. On task
 77       tabs only the identity row and the tab bar render, and the header
 78       is identical on every page within a tab. */}}
 79  {{if eq $top "code"}}
 80  {{if or (field $ "Desc") (field $ "Topics")}}<p class="repodesc">{{with field $ "Desc"}}{{.}}{{end}} {{with field $ "Topics"}}{{range .}}<a class="chip topic" href="/explore?q={{.}}">{{.}}</a> {{end}}{{end}}</p>{{end}}
 81  {{if or $.Repo.Settings.Website (field $ "Mirrors")}}<p class="repometa">{{with $.Repo.Settings.Website}}<a href="{{.}}" rel="nofollow">{{.}}</a>{{end}}{{range $i, $m := field $ "Mirrors"}}{{if or $i $.Repo.Settings.Website}} · {{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}}
 82  {{if $.Viewer}}<p class="toggles">Pinned shows in your rail. Watching sends every issue, request and build to your inbox. Bookmarked lists it under Bookmarks.</p>{{end}}
 83  {{end}}
 84  <nav class="tabs" aria-label="Repository">
 85    <a {{if eq $top "code"}}aria-current="page" {{end}}href="/{{.OwnerName}}/{{.Name}}">Code</a>
 86    <a {{if eq $top "issues"}}aria-current="page" {{end}}href="/{{.OwnerName}}/{{.Name}}/issues">Issues{{with field $ "OpenIssues"}}{{if .}} <i>{{.}}</i>{{end}}{{end}}</a>
 87    <a {{if eq $top "mrs"}}aria-current="page" {{end}}href="/{{.OwnerName}}/{{.Name}}/mrs">Merge requests{{with field $ "OpenMRs"}}{{if .}} <i>{{.}}</i>{{end}}{{end}}</a>
 88    <a {{if eq $top "builds"}}aria-current="page" {{end}}href="/{{.OwnerName}}/{{.Name}}/builds">Builds</a>
 89    <a {{if eq $top "releases"}}aria-current="page" {{end}}href="/{{.OwnerName}}/{{.Name}}/releases">Releases</a>
 90    {{if field $ "HasWiki"}}<a {{if eq $top "wiki"}}aria-current="page" {{end}}href="/{{.OwnerName}}/{{.Name}}/wiki">Wiki</a>{{end}}
 91    {{if field $ "CanAdmin"}}<a {{if eq $top "settings"}}aria-current="page" {{end}}href="/{{.OwnerName}}/{{.Name}}/settings">Settings</a>{{end}}
 92  </nav>
 93</header>
 94{{end}}
 95
 96<main id="content" class="content {{template "width" .}}">
 97{{template "content" .}}
 98</main>
 99
100<footer>
101  <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="/privacy">Privacy</a></p>
102</footer>
103</div>
104</div>
105</body>
106</html>{{end}}
107
108{{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}}
109{{define "width"}}reading{{end}}
110
111{{define "reporow"}}<li>
112  <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>
113  {{if .Desc}}<p class="desc">{{.Desc}}</p>{{end}}
114  {{if .Topics}}<p class="topics">{{range .Topics}}<a class="chip topic" href="/explore?q={{.}}">{{.}}</a> {{end}}</p>{{end}}
115  <p class="meta">{{template "branchicon"}} {{.DefaultBranch}}{{if .License}} · {{.License}}{{end}}{{if .Updated}} · updated {{.Updated}}{{end}}</p>
116</li>{{end}}
117
118{{define "refmenu"}}{{if .Branches}}<details class="refmenu">
119  <summary class="refchip">{{template "branchicon"}} {{.Ref}}</summary>
120  <div class="refdrop">
121  {{range .Branches}}<a href="/{{$.Repo.OwnerName}}/{{$.Repo.Name}}/{{$.RefKind}}/{{.Name}}/{{$.DirPath}}">{{.Name}}</a>
122  {{end}}<a class="allrefs" href="/{{.Repo.OwnerName}}/{{.Repo.Name}}/refs">All refs →</a>
123  </div>
124</details>{{else}}<span class="refchip">{{template "branchicon"}} {{.Ref}}</span>{{end}}{{end}}
125
126{{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}}
127
128{{define "sigbadge"}}<span class="badge badge-{{.State}}" title="{{.Fingerprint}}">{{sigLabel .State}}{{if .Signer}} · {{.Signer}}{{end}}</span>{{end}}
129
130{{define "authorname"}}{{if .User}}<a class="authorlink" href="/{{.User}}" title="{{.Email}}">{{.Name}}</a>{{else}}<span title="{{.Email}}">{{.Name}}</span>{{end}}{{end}}
131
132{{/* formatpicker is the md/org choice on a body: issue and MR create, and
133     a profile's about text. The argument is the currently selected
134     format; empty selects Markdown, today's default. */}}
135{{define "formatpicker"}}<p class="branchpick">
136  <label for="format">Body markup</label>
137  <select id="format" name="format">
138    <option value="md"{{if ne . "org"}} selected{{end}}>Markdown</option>
139    <option value="org"{{if eq . "org"}} selected{{end}}>Org</option>
140  </select>
141</p>{{end}}
142
143{{/* confirmfield is the typed-name check beside a destructive control.
144     The argument is the text the person must type. */}}
145{{define "confirmfield"}}<input type="text" name="confirm" aria-label="Type {{.}} to confirm" placeholder="type {{.}} to confirm" size="{{len .}}" autocomplete="off">{{end}}
146
147{{/* difffiles renders a parsed diff: one foldable section per file, with
148     line-number gutters and review threads inline. Base is the MR's
149     endpoint and Viewer the signed-in account; the commit page passes
150     neither and gets the same diff without review controls. */}}
151{{define "difffiles"}}{{$base := .Base}}{{$viewer := .Viewer}}
152{{range $fi, $f := .Files}}<details class="difffold" id="f{{$fi}}"{{if .Open}} open{{end}}>
153  <summary>
154    <span class="fpath">{{if eq .Status "renamed"}}<span class="was">{{.OldPath}} →</span> {{end}}{{.Path}}</span>
155    {{if ne .Status "modified"}}<span class="chip">{{.Status}}</span>{{end}}
156    <span class="fstat">{{if .Adds}}<span class="add">+{{.Adds}}</span>{{end}}{{if .Dels}} <span class="del">−{{.Dels}}</span>{{end}}</span>
157  </summary>
158  {{if .Binary}}<p class="none">Binary file not shown.</p>
159  {{else}}<div class="tablewrap"><table class="difftable">
160  {{range .Lines}}{{if eq .Class "hunk"}}<tr class="hunk"><td class="ln" colspan="2"></td><td class="src">{{.Text}}</td></tr>
161  {{else if eq .Class "meta"}}<tr class="dmeta"><td class="ln" colspan="2"></td><td class="src">{{.Text}}</td></tr>
162  {{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}}{{else}}{{.OldLine}}{{end}}{{end}}</td><td class="ln">{{if .NewLine}}{{template "cmtln" dict "N" .NewLine "Path" .Path "Side" "new" "Base" $base "Viewer" $viewer}}{{end}}</td><td class="src chroma">{{if .Code}}{{.Code}}{{else}}{{.Content}}{{end}}</td></tr>
163  {{if .Compose}}<tr class="threadrow"><td colspan="3"><form id="compose" method="post" action="{{$base}}/diff-comment" class="thread composing">
164    <input type="hidden" name="path" value="{{.Path}}">
165    <input type="hidden" name="line" value="{{if eq .Class "del"}}{{.OldLine}}{{else}}{{.NewLine}}{{end}}">
166    <input type="hidden" name="side" value="{{if eq .Class "del"}}old{{else}}new{{end}}">
167    <p><textarea name="body" aria-label="Comment on {{.Path}}" rows="3" placeholder="Comment on this line" autofocus></textarea></p>
168    <p><button type="submit" class="btn">Comment</button>
169       <button type="submit" name="pending" value="on" class="btn">Add to review</button>
170       <a href="{{$base}}?view=diff">Cancel</a></p>
171  </form></td></tr>
172  {{end}}{{end}}{{range .Threads}}<tr class="threadrow"><td colspan="3">{{template "thread" dict "T" . "Base" $base "Viewer" $viewer "Class" ""}}</td></tr>
173  {{end}}{{end}}
174  </table></div>{{end}}
175</details>
176{{end}}{{end}}
177
178{{/* cmtln turns a line number into the link that opens the comment form
179     on that line. No JavaScript: the anchor travels in the query. */}}
180{{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}}
181
182{{/* thread renders one review thread with its reply and resolve controls.
183     Class carries "stale" for threads whose anchor is gone. */}}
184{{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}}">
185{{if $t.Pending}}<p class="threadstate">pending — only you can see this until you submit your review</p>{{end}}
186{{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}}
187{{range $t.Comments}}<p class="commenthead"><strong>{{.Author}}</strong> <span class="when">{{when .CreatedAt}}</span></p><div class="rendered">{{.BodyHTML}}</div>{{end}}
188{{if .Viewer}}<details class="threadreply"><summary>Reply</summary>
189<form method="post" action="{{.Base}}/diff-comment">
190  <input type="hidden" name="reply" value="{{$t.ID}}">
191  <p><textarea name="body" aria-label="Reply to thread {{$t.ID}}" rows="2" placeholder="Reply as {{.Viewer}}"></textarea></p>
192  <p><button type="submit" class="btn">Reply</button></p>
193</form></details>{{end}}
194{{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}}
195</div>{{end}}