shell.templ RawHistoryFolder
1// SPDX-License-Identifier: AGPL-3.0-or-later
2
3package web
4
5// Shell is the page: head, the header, the work area (content and rail), the
6// footer, the hint badge, the overlays and the key map. The content is the
7// children.
8templ Shell(l Layout, rail templ.Component) {
9 <!DOCTYPE html>
10 <html lang="en">
11 <head>
12 <meta charset="utf-8"/>
13 <meta name="viewport" content="width=device-width, initial-scale=1"/>
14 <meta name="color-scheme" content="light dark"/>
15 <title>{ l.Title }</title>
16 <link rel="icon" type="image/svg+xml" href={ templ.SafeURL(l.Assets.Icon) }/>
17 <link rel="preload" href={ templ.SafeURL(l.Assets.Font) } as="font" type="font/woff2" crossorigin/>
18 <link rel="stylesheet" href={ templ.SafeURL(l.Assets.CSS) }/>
19 <meta name="htmx-config" content='{"history":false,"includeIndicatorCSS":false,"extensions":"sse, hx-live"}'/>
20 <script src={ l.Assets.HTMX } defer></script>
21 <script src={ l.Assets.Live } defer></script>
22 <script src={ l.Assets.SSE } defer></script>
23 <script src={ l.Assets.App } defer></script>
24 <script src={ l.Assets.Files } defer></script>
25 <script src={ l.Assets.Passkey } defer></script>
26 <script src={ l.Assets.Log } defer></script>
27 </head>
28 <body
29 data-ref={ l.RefPath }
30 data-sha={ l.SHA }
31 data-sel=""
32 data-nav=""
33 if l.PathsURL != "" {
34 data-paths={ l.PathsURL }
35 data-blob={ l.BlobBase }
36 }
37 >
38 <a class="skip" href="#main">skip to content</a>
39 @Header(l)
40 <div class={ "work", templ.KV("has-rail", l.HasRail && rail != nil) }>
41 if l.Lede != "" {
42 <section class="work-lede" aria-label="Description"><p class="lede">{ l.Lede }</p></section>
43 }
44 <main id="main" tabindex="-1">
45 { children... }
46 </main>
47 if l.HasRail && rail != nil {
48 <aside class="rail" aria-label="Context">
49 @rail
50 </aside>
51 }
52 </div>
53 @Footer(l)
54 @Hint(l)
55 @OverlayHelp()
56 @OverlayRefs(l)
57 @OverlayCmd(l.Cmd)
58 @Keys()
59 </body>
60 </html>
61}
62
63// Header is the page header: the instance, the path, the ref switcher, the
64// "go to" palette and the owner; inside a repository, the tabs below.
65templ Header(l Layout) {
66 <header class="hdr">
67 <div class="hdr-top">
68 <a class="brand" href="/" data-g="h" title="home (g h)">
69 <span class="brand-mark" aria-hidden="true"></span>
70 <span class="brand-name">{ l.Instance }</span>
71 </a>
72 if len(l.Crumbs) > 0 {
73 <nav class="crumbs" aria-label="Path">
74 <ol>
75 for _, c := range l.Crumbs {
76 <li>
77 if c.Href != "" {
78 <a
79 href={ templ.SafeURL(c.Href) }
80 if c.Current {
81 aria-current="page"
82 }
83 >{ c.Label }</a>
84 } else {
85 <span
86 if c.Current {
87 aria-current="page"
88 }
89 >{ c.Label }</span>
90 }
91 </li>
92 }
93 </ol>
94 </nav>
95 }
96 <div class="hdr-acts">
97 if l.Ref != "" {
98 <a
99 class="btn ref"
100 href={ templ.SafeURL(repoHref(l.Repo, "refs")) }
101 data-fire="refs"
102 aria-keyshortcuts="b"
103 title="switch branch or tag (b)"
104 >
105 <span class="ico i-branch" aria-hidden="true"></span>
106 <span class="ref-name">{ l.Ref }</span>
107 <span class="sha">{ l.Short }</span>
108 </a>
109 }
110 <button
111 type="button"
112 class="btn search"
113 data-js
114 aria-haspopup="dialog"
115 aria-controls="panel-cmd"
116 aria-keyshortcuts="Control+K :"
117 title="go to a section, ref, file or repo (ctrl k or :)"
118 data-fire="cmd"
119 >
120 <span class="ico i-search" aria-hidden="true"></span>
121 <span class="search-label">Go to…</span>
122 <kbd aria-hidden="true">ctrl k</kbd>
123 </button>
124 <button type="button" class="btn icon" data-js data-fire="help" aria-haspopup="dialog" aria-controls="panel-help" aria-keyshortcuts="?" aria-label="Keyboard shortcuts" title="keyboard shortcuts (?)">?</button>
125 if SignedIn(ctx) {
126 <form class="owner" method="post" action="/-/logout">
127 <span class="owner-badge" title="signed in as the owner">owner</span>
128 if l.Passkeys {
129 <a class="btn" href="/-/passkeys" title="add or remove the passkeys that sign you in">Passkeys</a>
130 }
131 <button type="submit" class="btn" title="sign out of this browser">Sign out</button>
132 </form>
133 } else if l.Passkeys {
134 <a class="btn" href="/-/login" title="sign in with a passkey">Sign in</a>
135 }
136 </div>
137 </div>
138 if len(l.Sections) > 0 {
139 <nav class="tabs" aria-label="Sections">
140 <ul>
141 for _, s := range l.Sections {
142 <li>
143 <a
144 href={ templ.SafeURL(s.Href) }
145 data-key={ s.Key }
146 aria-keyshortcuts={ s.Key }
147 if s.Current {
148 aria-current="page"
149 }
150 >{ s.Label }<kbd aria-hidden="true">{ s.Key }</kbd></a>
151 </li>
152 }
153 </ul>
154 </nav>
155 }
156 </header>
157}
158
159// Footer is the version and the link to the source of the running version,
160// which the AGPL §13 asks every user of the service to be offered.
161templ Footer(l Layout) {
162 <footer class="foot">
163 <span>ermite { l.Version }</span>
164 <a href={ templ.SafeURL(l.SourceURL) } rel="noopener" title="source of this version (AGPL-3.0-or-later)">source</a>
165 <span class="foot-keys"><kbd>?</kbd> keyboard shortcuts</span>
166 </footer>
167}
168
169// Hint is the floating badge at the bottom right. It shows only what the
170// keyboard just did or what needs attention: the row position while j/k
171// move, the selected lines of a file, a flash message (permalink copied) and
172// a lost event stream. Empty, it takes no room.
173templ Hint(l Layout) {
174 <div class="hint" role="status" aria-live="polite">
175 <span class="hint-nav" :text="document.body.dataset.nav || ''"></span>
176 <span class="hint-sel" :text="document.body.dataset.sel ? document.body.dataset.sel + ' · y permalink' : ''"></span>
177 <span class="hint-flash" :text="document.body.dataset.flash || ''"></span>
178 @Live(l)
179 </div>
180}