Skip to main content

max / makenotwork

13.2 KB · 370 lines History Blame Raw
1 //! Templates for the public git source browser, issues, and repo settings.
2
3 use std::collections::HashMap;
4 use std::sync::Arc;
5
6 use askama::Template;
7
8 use crate::auth::SessionUser;
9 use crate::git;
10 use crate::routes::git::annotations_view::PersonalAnnotation;
11 use crate::routes::git::notes_view::{AnnotationRow, CommitNote, NamespaceSummary};
12 use crate::types::{Item, Project, Version};
13
14 use super::super::CsrfTokenOption;
15
16 /// An item paired with its versions, for release display on the git repo page.
17 pub struct ReleaseItem {
18 pub item: Item,
19 pub versions: Vec<Version>,
20 }
21
22 /// Repository overview: file tree at HEAD + README.
23 #[derive(Template)]
24 #[template(path = "pages/git/repo.html")]
25 pub struct GitRepoTemplate {
26 pub csrf_token: CsrfTokenOption,
27 pub session_user: Option<SessionUser>,
28 pub owner: String,
29 pub repo_name: String,
30 pub description: Option<String>,
31 pub current_ref: String,
32 pub refs: Vec<git::RefInfo>,
33 pub tree_items: Vec<git::TreeItem>,
34 pub readme_html: Option<String>,
35 pub host_url: Arc<str>,
36 /// Hostname for SSH clone URLs (e.g., "git.makenot.work"). Hidden when `None`.
37 pub git_ssh_host: Option<String>,
38 /// Linked project, if this repo is associated with a public project.
39 pub linked_project: Option<Project>,
40 /// Public items with versions from the linked project (releases).
41 pub release_items: Vec<ReleaseItem>,
42 /// Number of open issues for the nav bar badge.
43 pub open_issue_count: i64,
44 /// Whether this repository has `refs/replace/*` mappings, which links the
45 /// replace page from the overview. Almost always false, and shown here
46 /// rather than in the nav so no repository grows a permanently empty tab.
47 pub has_replacements: bool,
48 /// Whether the current viewer is the repo owner (for settings link).
49 pub is_owner: bool,
50 /// Whether the viewer has muted this repo's issue notifications. `None`
51 /// when logged out, which hides the control: there is nothing to mute for
52 /// somebody we could not mail anyway.
53 pub issues_muted: Option<bool>,
54 /// Repository id, for the mute form's hidden field.
55 pub repo_id: String,
56 pub active_tab: &'static str,
57 }
58
59 /// Subdirectory listing with breadcrumb navigation.
60 #[derive(Template)]
61 #[template(path = "pages/git/tree.html")]
62 pub struct GitTreeTemplate {
63 pub csrf_token: CsrfTokenOption,
64 pub session_user: Option<SessionUser>,
65 pub owner: String,
66 pub repo_name: String,
67 pub current_ref: String,
68 pub refs: Vec<git::RefInfo>,
69 pub path: String,
70 pub parent_path: String,
71 /// Whether we're in a subdirectory (for ".." link and path joining).
72 pub in_subdir: bool,
73 pub breadcrumbs: Vec<git::Breadcrumb>,
74 pub tree_items: Vec<git::TreeItem>,
75 pub open_issue_count: i64,
76 pub is_owner: bool,
77 pub active_tab: &'static str,
78 }
79
80 /// File viewer with syntax highlighting and line numbers.
81 #[derive(Template)]
82 #[template(path = "pages/git/file.html")]
83 pub struct GitFileTemplate {
84 pub csrf_token: CsrfTokenOption,
85 pub session_user: Option<SessionUser>,
86 pub owner: String,
87 pub repo_name: String,
88 pub current_ref: String,
89 pub refs: Vec<git::RefInfo>,
90 pub file_path: String,
91 pub filename: String,
92 pub breadcrumbs: Vec<git::Breadcrumb>,
93 pub file_size: String,
94 pub line_count: usize,
95 pub is_binary: bool,
96 pub highlighted_lines: Vec<String>,
97 /// Notes on the blob this view is showing. A note on a file annotates the
98 /// object, so it follows the content rather than the path.
99 pub notes: Vec<CommitNote>,
100 pub open_issue_count: i64,
101 pub is_owner: bool,
102 pub active_tab: &'static str,
103 }
104
105 /// Commit log with pagination.
106 #[derive(Template)]
107 #[template(path = "pages/git/commits.html")]
108 pub struct GitCommitsTemplate {
109 pub csrf_token: CsrfTokenOption,
110 pub session_user: Option<SessionUser>,
111 pub owner: String,
112 pub repo_name: String,
113 pub current_ref: String,
114 pub refs: Vec<git::RefInfo>,
115 pub commits: Vec<git::CommitInfo>,
116 /// Commits on this page that carry a note, mapped to how many namespaces
117 /// annotate them. One batched lookup per page, never one per row.
118 pub annotated: HashMap<String, usize>,
119 pub page: usize,
120 pub has_more: bool,
121 pub open_issue_count: i64,
122 /// Whether the current viewer is the repo owner (for settings link).
123 pub is_owner: bool,
124 pub active_tab: &'static str,
125 }
126
127 /// Commit detail page with inline diffs.
128 #[derive(Template)]
129 #[template(path = "pages/git/commit.html")]
130 pub struct GitCommitDetailTemplate {
131 pub csrf_token: CsrfTokenOption,
132 pub session_user: Option<SessionUser>,
133 pub owner: String,
134 pub repo_name: String,
135 pub current_ref: String,
136 pub refs: Vec<git::RefInfo>,
137 pub detail: git::CommitDetail,
138 /// Signature state. `Unsigned` for almost every commit, and rendered as
139 /// nothing rather than as a warning: an unsigned commit is normal.
140 pub signature: git::signing::SignatureStatus,
141 /// Notes on this commit, one entry per `refs/notes/*` namespace that has
142 /// one. Empty for the ordinary unannotated commit.
143 pub notes: Vec<CommitNote>,
144 /// Whether this reader may add, edit or remove a note here: the repo owner
145 /// or a collaborator who can push. False for everyone signed out.
146 pub can_write_notes: bool,
147 /// The note just saved was merged with somebody else's concurrent edit, so
148 /// what is on screen is not what was typed. Set from the redirect that
149 /// follows a write, since the page cannot work it out from the repository.
150 pub notes_merged: bool,
151 /// Whether this reader may write a personal annotation: signed in, in good
152 /// standing, not a sandbox account. Unrelated to `can_write_notes`, which
153 /// asks about pushing to THIS repository.
154 pub can_annotate: bool,
155 /// The reader's own annotation on this commit, as written, for the form to
156 /// load. Empty when there is none.
157 pub annotation_source: String,
158 /// Every annotation the signed-in reader has on this commit, from their own
159 /// annotation repository, rendered. Usually the one they wrote here, and
160 /// more when the same commit is served by a fork they also read. Empty for
161 /// everyone signed out, and never populated for anybody but the reader:
162 /// nobody else sees these.
163 pub personal_annotations: Vec<PersonalAnnotation>,
164 /// The annotation just saved was merged with a concurrent edit of the same
165 /// note from another session.
166 pub annotation_merged: bool,
167 pub diff_files: Vec<git::DiffFile>,
168 pub total_files: usize,
169 pub total_additions: usize,
170 pub total_deletions: usize,
171 pub open_issue_count: i64,
172 pub is_owner: bool,
173 pub active_tab: &'static str,
174 }
175
176 /// Every annotation this account has written, newest first.
177 ///
178 /// The only page an orphan can be reached from: its target has no commit page
179 /// anywhere any more, so nothing else links to it.
180 #[derive(Template)]
181 #[template(path = "pages/git/annotations.html")]
182 pub struct GitAnnotationsTemplate {
183 pub csrf_token: CsrfTokenOption,
184 pub session_user: Option<SessionUser>,
185 pub annotations: Vec<PersonalAnnotation>,
186 /// 1-based page.
187 pub page: usize,
188 pub has_more: bool,
189 /// Every annotation the account has, orphans included, since they are
190 /// shown and nothing collects them.
191 pub total: i64,
192 }
193
194 /// The repo's Notes tab: every `refs/notes/*` namespace with its count, and a
195 /// feed of the annotations in the selected one.
196 #[derive(Template)]
197 #[template(path = "pages/git/notes.html")]
198 pub struct GitNotesTemplate {
199 pub csrf_token: CsrfTokenOption,
200 pub session_user: Option<SessionUser>,
201 pub owner: String,
202 pub repo_name: String,
203 pub current_ref: String,
204 pub refs: Vec<git::RefInfo>,
205 pub namespaces: Vec<NamespaceSummary>,
206 /// Namespace the feed belongs to. `None` for a repository with no notes.
207 pub selected: Option<String>,
208 pub rows: Vec<AnnotationRow>,
209 /// Notes in the selected namespace, which exceeds `rows.len()` when the feed
210 /// was capped.
211 pub total: usize,
212 /// The search these rows answer, echoed back into the box.
213 pub search: String,
214 pub commits_only: bool,
215 /// 1-based page of the feed. Named apart from `page` because Askama would
216 /// otherwise shadow nothing usefully and the template reads better for it.
217 pub page_number: usize,
218 pub has_more: bool,
219 /// Whether the rows came from the Postgres index. False means the
220 /// repository was walked, which cannot search or page, so the controls are
221 /// not offered.
222 pub from_index: bool,
223 pub open_issue_count: i64,
224 pub is_owner: bool,
225 pub active_tab: &'static str,
226 }
227
228 /// The repo's Tags tab: every tag, with the annotation an annotated tag
229 /// carries. The message on an annotated tag is a release note the author
230 /// already wrote and no forge shows.
231 #[derive(Template)]
232 #[template(path = "pages/git/tags.html")]
233 pub struct GitTagsTemplate {
234 pub csrf_token: CsrfTokenOption,
235 pub session_user: Option<SessionUser>,
236 pub owner: String,
237 pub repo_name: String,
238 pub current_ref: String,
239 pub refs: Vec<git::RefInfo>,
240 pub tags: Vec<git::TagInfo>,
241 pub open_issue_count: i64,
242 pub is_owner: bool,
243 pub active_tab: &'static str,
244 }
245
246 /// The repository's `refs/replace/*` mappings.
247 ///
248 /// Reachable by URL on every repository and linked from the overview only when
249 /// there is something to see, because the overwhelming majority of repositories
250 /// have no replacements and a permanently empty tab is noise.
251 #[derive(Template)]
252 #[template(path = "pages/git/replace.html")]
253 pub struct GitReplaceTemplate {
254 pub csrf_token: CsrfTokenOption,
255 pub session_user: Option<SessionUser>,
256 pub owner: String,
257 pub repo_name: String,
258 pub current_ref: String,
259 pub refs: Vec<git::RefInfo>,
260 pub replacements: Vec<git::Replacement>,
261 pub open_issue_count: i64,
262 pub is_owner: bool,
263 pub active_tab: &'static str,
264 }
265
266 /// Blame view for a single file.
267 #[derive(Template)]
268 #[template(path = "pages/git/blame.html")]
269 pub struct GitBlameTemplate {
270 pub csrf_token: CsrfTokenOption,
271 pub session_user: Option<SessionUser>,
272 pub owner: String,
273 pub repo_name: String,
274 pub current_ref: String,
275 pub refs: Vec<git::RefInfo>,
276 pub file_path: String,
277 pub filename: String,
278 pub breadcrumbs: Vec<git::Breadcrumb>,
279 pub blame_lines: Vec<git::BlameLine>,
280 /// Commits on this page that carry a note, mapped to how many namespaces
281 /// annotate them. Asked once per distinct commit, not once per line.
282 pub annotated: HashMap<String, usize>,
283 pub open_issue_count: i64,
284 pub is_owner: bool,
285 pub active_tab: &'static str,
286 }
287
288 /// Per-file commit history with breadcrumb context.
289 #[derive(Template)]
290 #[template(path = "pages/git/file_log.html")]
291 pub struct GitFileLogTemplate {
292 pub csrf_token: CsrfTokenOption,
293 pub session_user: Option<SessionUser>,
294 pub owner: String,
295 pub repo_name: String,
296 pub current_ref: String,
297 pub refs: Vec<git::RefInfo>,
298 pub file_path: String,
299 pub filename: String,
300 pub breadcrumbs: Vec<git::Breadcrumb>,
301 pub commits: Vec<git::CommitInfo>,
302 /// Commits on this page that carry a note, mapped to how many namespaces
303 /// annotate them. One batched lookup per page, never one per row.
304 pub annotated: HashMap<String, usize>,
305 pub page: usize,
306 pub has_more: bool,
307 pub open_issue_count: i64,
308 pub is_owner: bool,
309 pub active_tab: &'static str,
310 }
311
312 // Git Issues
313
314 /// Issue list page with status tabs and search (read-only).
315 #[derive(Template)]
316 #[template(path = "pages/git/issues.html")]
317 pub struct GitIssueListTemplate {
318 pub csrf_token: CsrfTokenOption,
319 pub session_user: Option<SessionUser>,
320 pub owner: String,
321 pub repo_name: String,
322 pub current_ref: String,
323 pub issues: Vec<crate::db::DbIssueWithMeta>,
324 pub open_count: i64,
325 pub closed_count: i64,
326 pub current_status: String,
327 pub search_query: String,
328 pub current_page: i64,
329 pub total_pages: i64,
330 /// Whether the current viewer is the repo owner (for settings link).
331 pub is_owner: bool,
332 /// Email address for submitting new issues.
333 pub email_address: String,
334 }
335
336 /// Issue detail page with comments (read-only).
337 #[derive(Template)]
338 #[template(path = "pages/git/issue.html")]
339 pub struct GitIssueDetailTemplate {
340 pub csrf_token: CsrfTokenOption,
341 pub session_user: Option<SessionUser>,
342 pub owner: String,
343 pub repo_name: String,
344 pub current_ref: String,
345 pub issue: crate::db::DbIssue,
346 pub author_username: String,
347 pub comments: Vec<crate::db::DbIssueCommentWithAuthor>,
348 pub is_owner: bool,
349 pub open_issue_count: i64,
350 /// Email address for submitting new issues.
351 pub email_address: String,
352 }
353
354 /// Repository settings page (owner only).
355 #[derive(Template)]
356 #[template(path = "pages/git/settings.html")]
357 pub struct GitRepoSettingsTemplate {
358 pub csrf_token: CsrfTokenOption,
359 pub session_user: Option<SessionUser>,
360 pub owner: String,
361 pub repo_name: String,
362 pub current_ref: String,
363 pub repo: crate::db::DbGitRepo,
364 pub open_issue_count: i64,
365 /// Owner's projects for the link dropdown.
366 pub projects: Vec<crate::db::DbProject>,
367 /// ID of the currently linked project as a string (for dropdown comparison).
368 pub linked_project_id: String,
369 }
370