shell.templ RawHistoryFolder
1// SPDX-License-Identifier: AGPL-3.0-or-later
2
3package web
4
5// Shell is the page: head, the two bars, the work area (content and rail),
6// the overlays and the key map. The content is the children.
7templ Shell(l Layout, rail templ.Component) {
8 <!DOCTYPE html>
9 <html lang="en">
10 <head>
11 <meta charset="utf-8"/>
12 <meta name="viewport" content="width=device-width, initial-scale=1"/>
13 <meta name="color-scheme" content="light dark"/>
14 <title>{ l.Title }</title>
15 <link rel="icon" type="image/svg+xml" href={ templ.SafeURL(l.Assets.Icon) }/>
16 <link rel="stylesheet" href={ templ.SafeURL(l.Assets.CSS) }/>
17 <script src={ l.Assets.HTMX } defer></script>
18 <script src={ l.Assets.Live } defer></script>
19 <script src={ l.Assets.SSE } defer></script>
20 <script src={ l.Assets.App } defer></script>
21 </head>
22 <body data-ref={ l.RefPath } data-sha={ l.SHA } data-sel="">
23 <a class="skip" href="#main">skip to content</a>
24 @Topbar(l)
25 <div class={ "work", templ.KV("has-rail", l.HasRail), templ.KV("read", l.Read) }>
26 <main id="main" tabindex="-1" data-y="0" hx-on:scroll="this.dataset.y = Math.round(this.scrollTop / 60)">
27 { children... }
28 </main>
29 if l.HasRail && rail != nil {
30 <aside class="rail" aria-label="Context">
31 <details class="rail-d" open data-init="" hx-live="if (!data.init) { data.init = '1'; this.open = matchMedia('(min-width: 1100px)').matches }">
32 <summary>{ l.RailSum }</summary>
33 @rail
34 </details>
35 </aside>
36 }
37 </div>
38 @OverlayHelp()
39 @OverlayRefs(l)
40 @OverlayCmd(l.Cmd)
41 @Statusbar(l)
42 @Keys()
43 </body>
44 </html>
45}
46
47// Topbar is the top bar, neferbar style: the one accent block (instance)
48// fading into the black bar, the path, the repo sections, the ref switcher.
49templ Topbar(l Layout) {
50 <header class="bar top">
51 <a class="blk" href="/" data-g="h" title="home (g h)">{ l.Instance }</a><span class="fade" aria-hidden="true"></span>
52 <nav class="crumbs" aria-label="Path">
53 <ol>
54 for _, c := range l.Crumbs {
55 <li>
56 if c.Href != "" {
57 <a
58 href={ templ.SafeURL(c.Href) }
59 if c.Current {
60 aria-current="page"
61 }
62 >{ c.Label }</a>
63 } else {
64 <span aria-current="page">{ c.Label }</span>
65 }
66 </li>
67 }
68 </ol>
69 </nav>
70 if len(l.Sections) > 0 {
71 <nav class="secs" aria-label="Sections">
72 <ul>
73 for _, s := range l.Sections {
74 <li>
75 <a
76 href={ templ.SafeURL(s.Href) }
77 data-key={ s.Key }
78 aria-keyshortcuts={ "g " + s.Key }
79 if s.Current {
80 aria-current="page"
81 }
82 >{ s.Key }{ s.Post }</a>
83 </li>
84 }
85 </ul>
86 </nav>
87 }
88 if l.Ref != "" {
89 <button
90 type="button"
91 class="ref"
92 aria-haspopup="dialog"
93 aria-controls="panel-refs"
94 aria-keyshortcuts="b"
95 title="switch ref (b)"
96 hx-on:click="document.body.dispatchEvent(new CustomEvent('k-refs'))"
97 >{ l.Ref }<span class="sha">{ "@" + l.Short }</span></button>
98 }
99 </header>
100}
101
102// Statusbar is the bottom bar: mode, context, the ":" command prompt, the
103// source link (AGPL §13), hints, live state and the cursor position. Plain
104// text only: the top bar carries the accent.
105templ Statusbar(l Layout) {
106 <footer class="bar status" aria-label="Status">
107 <span class="mode">{ l.Status.Mode }</span>
108 <span class="st-ctx">{ l.Status.Context }</span>
109 <button
110 type="button"
111 class="st-cmd"
112 aria-haspopup="dialog"
113 aria-controls="panel-cmd"
114 aria-keyshortcuts=":"
115 hx-on:click="document.body.dispatchEvent(new CustomEvent('k-cmd'))"
116 ><span class="hi">:</span> go to…</button>
117 if l.Status.Blob {
118 <span class="st-sel" :text="document.body.dataset.sel ? document.body.dataset.sel + ' · y permalink' : 'click a line number · shift-click extends'">click a line number · shift-click extends</span>
119 }
120 <span class="st-flash" aria-live="polite" :text="document.body.dataset.flash || ''"></span>
121 <span class="st-hints">{ l.Status.Hints }</span>
122 @Footer(l)
123 @Live(l)
124 if l.Status.Cursor {
125 <span
126 class="st-pos num"
127 title="cursor position"
128 :text="(() => { let all = q('[data-rows] > :not([hidden])').arr(); let i = all.findIndex(r => r.hasAttribute('data-cursor')); return (i + 1) + '/' + all.length })()"
129 >{ cursorText(l.Status.Count) }</span>
130 }
131 </footer>
132}
133
134// Footer is the version and the link to the source of the running version,
135// which the AGPL §13 asks every user of the service to be offered.
136templ Footer(l Layout) {
137 <span class="st-src"><span class="st-ver">ermite { l.Version } · </span><a href={ templ.SafeURL(l.SourceURL) } rel="noopener" title="source of this version (AGPL-3.0-or-later)">source</a></span>
138}