internal/web/templates/layout.html

a7c9352033c319587613cb6825035e8d99f5871a
gitbay/internal/web/templates/layout.html history · blame · raw

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