v0.1.0 3f267b5

diff.templ RawHistoryFolder

1// SPDX-License-Identifier: AGPL-3.0-or-later
2
3package web
4
5// Commit is a commit: the message and facts, the diff tools, the diff.
6templ Commit(l Layout, p CommitPage) {
7	@Shell(l, commitRail(p)) {
8		<header class="show">
9			<h1>{ p.Subject }</h1>
10			if p.Body != "" {
11				<pre class="msg">{ p.Body }</pre>
12			}
13			<dl class="kv show-kv">
14				<dt>commit</dt>
15				<dd>{ p.SHA }</dd>
16				<dt>author</dt>
17				<dd>@personView(p.Author)</dd>
18				if p.Committer != nil {
19					<dt>committer</dt>
20					<dd>@personView(*p.Committer)</dd>
21				}
22				<dt>
23					if p.Merge {
24						merge of
25					} else {
26						parent
27					}
28				</dt>
29				<dd>
30					if p.Root {
31						none (root commit)
32					}
33					for i, par := range p.Parents {
34						if i > 0 {
35							{ " + " }
36						}
37						<a href={ templ.SafeURL(par.Href) }>{ par.Label }</a>
38					}
39					if p.Merge {
40						{ " · diff against the first parent" }
41					}
42				</dd>
43				if len(p.Refs) > 0 {
44					<dt>refs</dt>
45					<dd>
46						for _, r := range p.Refs {
47							[{ r }]{ " " }
48						}
49					</dd>
50				}
51			</dl>
52		</header>
53		<div class="diff-tools" role="toolbar" aria-label="Diff view">
54			<span class="dim">{ plural(p.NFiles, "file", "files") } · <span class="plus">+{ itoa(p.Adds) }</span> <span class="minus">-{ itoa(p.Dels) }</span></span>
55			<span class="grow"></span>
56			@viewLink(p, "unified", "[u]nified", "u", p.UnifiedHref)
57			@viewLink(p, "split", "[d] split", "d", p.SplitHref)
58			<button type="button" aria-keyshortcuts="x" hx-on:click="document.body.dispatchEvent(new CustomEvent('k-collapse'))">[x] collapse all</button>
59		</div>
60		if p.Invisible {
61			<p class="notice">
62				this diff holds invisible characters (direction controls, zero-width characters). They are shown as
63				<span class="inv">&lt;U+XXXX&gt;</span> markers, never applied.
64			</p>
65		}
66		if p.Note != "" {
67			<p class="notice">{ p.Note }</p>
68		}
69		<div
70			id="diffs"
71			if p.Both {
72				data-diff={ p.View }
73			} else {
74				data-view={ p.View }
75			}
76			if p.Both && !p.Pinned {
77				hx-live="this.dataset.diff = sessionStorage.getItem('diff') || this.dataset.diff"
78			}
79		>
80			for _, f := range p.Files {
81				@DiffFileView(f, p)
82			}
83			if len(p.Files) == 0 {
84				<p class="note">no changed files{ " " }
85					if p.Merge {
86						against the first parent.
87					} else {
88						in this commit.
89					}
90				</p>
91			}
92		</div>
93	}
94}
95
96// viewLink is a button of the diff tools. It is a link, so that the split
97// layout works without JavaScript; with it, and when both layouts are in the
98// page, a click only flips the layout.
99templ viewLink(p CommitPage, view, label, key, href string) {
100	<a
101		href={ templ.SafeURL(href) }
102		class="vbtn"
103		data-view={ view }
104		aria-keyshortcuts={ key }
105		if p.View == view {
106			aria-current="true"
107		}
108		if p.Both {
109			hx-on:click="event.preventDefault(); document.body.dispatchEvent(new CustomEvent('k-' + this.dataset.view))"
110			:aria-current={ "q('#diffs').attr['data-diff'] === '" + view + "' ? 'true' : false" }
111		}
112	>{ label }</a>
113}
114
115templ personView(pr Person) {
116	{ pr.Name }
117	if pr.WhenAt != "" {
118		{ " · " }<time datetime={ pr.WhenAt } title={ pr.Abs }>{ pr.When }</time>{ " · " + pr.Abs }
119	}
120}
121
122templ commitRail(p CommitPage) {
123	<section aria-labelledby="r-files">
124		<h2 id="r-files">files <span>{ itoa(len(p.Files)) }</span></h2>
125		@FileList(p.Files)
126	</section>
127}
128
129// FileList is the table of contents of a diff: status letter, path, +n -m
130// and an ASCII bar. The row of the file in view is marked.
131templ FileList(files []DiffFile) {
132	<ol class="files" aria-label="Changed files">
133		for _, f := range files {
134			<li>
135				<a
136					href={ templ.SafeURL("#" + f.ID) }
137					:aria-current={ "(() => { let all = q('.dfile').arr(); let cur = all.filter(f => f.getBoundingClientRect().top < innerHeight / 3).at(-1) || all[0]; return cur?.id === '" + f.ID + "' ? 'true' : false })()" }
138					hx-on:click="document.getElementById(this.hash.slice(1)).open = true"
139				>
140					<span class="st" title={ f.Status }>{ f.Status }</span>
141					<span class="p" title={ f.Path }>{ f.Path }</span>
142					<span class="n"><span class="plus">+{ itoa(f.Add) }</span> <span class="minus">-{ itoa(f.Del) }</span></span>
143					<span class="b" aria-hidden="true"><span class="plus">{ f.Bar.Add }</span><span class="minus">{ f.Bar.Del }</span>{ f.Bar.Rest }</span>
144				</a>
145			</li>
146		}
147	</ol>
148}
149
150// DiffFileView is one file of the diff: a <details> (per-file collapse). The
151// unified and the split tables are both in it; #diffs[data-diff] shows one.
152// Every changed line keeps its +/- marker, so a change is never conveyed by
153// colour alone.
154templ DiffFileView(f DiffFile, p CommitPage) {
155	<details
156		class="dfile"
157		id={ f.ID }
158		data-file
159		if f.Open {
160			open
161		}
162	>
163		<summary>
164			<span class="st">{ f.Status }</span>{ " " }<span class="p">{ f.Path }</span>{ " " }<span class="plus">+{ itoa(f.Add) }</span>{ " " }<span class="minus">-{ itoa(f.Del) }</span>
165			if f.Note != "" {
166				{ " " }<span class="note">{ f.Note }</span>
167			}
168		</summary>
169		if len(f.Hunks) > 0 {
170			<div class="dscroll">
171				if p.ShowUnified {
172					<table class="dt uni" aria-label={ f.Path + ", unified" }>
173						for _, h := range f.Hunks {
174							<tbody>
175								<tr class="hunk"><td colspan="4">{ h.Header }</td></tr>
176								for _, ln := range h.Lines {
177									<tr class={ ln.Kind }>
178										<td class="ln">{ ln.Old }</td>
179										<td class="ln">{ ln.New }</td>
180										<td class="m">{ ln.Mark }</td>
181										<td class="tx">@codeLine(ln)</td>
182									</tr>
183								}
184							</tbody>
185						}
186					</table>
187				}
188				if p.ShowSplit {
189					<table class="dt spl" aria-label={ f.Path + ", split" }>
190						for _, h := range f.Hunks {
191							<tbody>
192								<tr class="hunk"><td colspan="6">{ h.Header }</td></tr>
193								for _, row := range h.Split {
194									<tr>
195										@splitSide(row.L, true)
196										@splitSide(row.R, false)
197									</tr>
198								}
199							</tbody>
200						}
201					</table>
202				}
203			</div>
204		} else {
205			<p class="note pad">
206				if f.Note != "" {
207					{ f.Note }
208				} else {
209					no textual change.
210				}
211			</p>
212		}
213	</details>
214}
215
216// splitSide is one half of a row of the split view; a side without a line is
217// an empty cell.
218templ splitSide(ln *DiffLine, old bool) {
219	if ln == nil {
220		<td class="ln none"></td>
221		<td class="m none"></td>
222		<td class="tx none"></td>
223	} else {
224		<td class={ "ln", ln.Kind }>
225			if old {
226				{ ln.Old }
227			} else {
228				{ ln.New }
229			}
230		</td>
231		<td class={ "m", ln.Kind }>{ ln.Mark }</td>
232		<td class={ "tx", ln.Kind }>@codeLine(*ln)</td>
233	}
234}

Keyboard shortcuts

Single-key shortcuts are on. ctrl k and esc work whatever this says.

ctrl k :
go to a section, branch, tag, file or repository
?
this list
j k
move the row cursor down / up
enter
open the row under the cursor; on Files, a directory opens or closes in place
↑ ↓ ← → home end
in the file tree (focus on a row): previous / next row, close / open a directory or go to its parent / first entry, first / last row. They never open a file
l h
on Files: open a directory or a file / close a directory or go up to its parent, then to the parent page. Elsewhere: into the side panel (on a merge request, its files) / back to the content; there j k move between its controls
g g G
first / last row
/
filter the list; on Files, find a file anywhere in the repository (esc clears, enter opens the best match)
b
switch branch or tag
g s g f g c g b g m g i g r g a
tabs: summary, files, commits, branches & tags, merge requests, issues, releases, actions (when available)
g h
home
w
wide mode: use the whole width of the screen, or not (remembered)
y
copy a permalink pinned to the commit
d u x
diff: split / unified / collapse or expand all
e E
diff: the whole file in view / every file, with the lines the hunks hide
n
merge request: next note in the diff
esc
close a dialog, leave a field

Branches 2

Tags 45

↑↓ moveenter openesc close