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