internal/web/templates/layout.html
193 lines · 14626 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 pinned">
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&cpath={{.Path}}&cline={{.N}}&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}}