Prepare Gotcha 1.0 for release

This commit is contained in:
Georg Bauer
2026-08-03 17:54:07 +02:00
parent 1c4a6efd13
commit 46cca8fdd2
60 changed files with 7916 additions and 7173 deletions

View File

@@ -0,0 +1,256 @@
use super::{helpers::*, *};
pub fn issue_page(details: IssueDetails) -> IssuePage {
IssuePage {
title: details
.issue
.title
.clone()
.unwrap_or_else(|| "Untitled issue".into()),
state: work_item_state(details.issue.state.as_deref()),
meta: issue_meta(&details.issue),
milestone: issue_milestone(&details.issue),
labels: details
.issue
.labels
.as_deref()
.unwrap_or_default()
.iter()
.filter_map(label_row)
.collect(),
body: details
.issue
.body
.filter(|body| !body.is_empty())
.unwrap_or_else(|| "No description provided.".into()),
comments: details
.comments
.iter()
.map(|comment| comment_row(comment, details.viewer_id))
.collect(),
has_more: details.has_more,
}
}
pub fn issue_editor_page(data: IssueEditorData) -> IssueEditorPage {
let selected_labels: std::collections::BTreeSet<_> = data
.issue
.as_ref()
.and_then(|issue| issue.labels.as_deref())
.unwrap_or_default()
.iter()
.filter_map(|label| label.id)
.collect();
let selected_milestone = data
.issue
.as_ref()
.and_then(|issue| issue.milestone.as_ref())
.and_then(|milestone| milestone.id);
let mut labels: Vec<_> = data
.labels
.into_iter()
.filter_map(|label| {
let id = label.id?;
Some(IssueEditorLabel {
id,
name: label.name?,
selected: selected_labels.contains(&id),
})
})
.collect();
labels.sort_by_key(|label| label.name.to_lowercase());
let mut milestones: Vec<_> = data
.milestones
.into_iter()
.filter(|milestone| {
milestone.state.as_deref() != Some("closed") || milestone.id == selected_milestone
})
.filter_map(|milestone| {
let id = milestone.id?;
Some(IssueEditorMilestone {
id,
title: milestone.title?,
selected: Some(id) == selected_milestone,
})
})
.collect();
milestones.sort_by_key(|milestone| milestone.title.to_lowercase());
IssueEditorPage {
title: data
.issue
.as_ref()
.and_then(|issue| issue.title.clone())
.unwrap_or_default(),
body: data
.issue
.as_ref()
.and_then(|issue| issue.body.clone())
.unwrap_or_default(),
due_date: data
.issue
.as_ref()
.and_then(|issue| issue.due_date.as_deref())
.and_then(parse_api_date),
closed: data.issue.as_ref().and_then(|issue| issue.state.as_deref()) == Some("closed"),
labels,
milestones,
}
}
pub fn pull_page(details: PullDetails) -> PullPage {
let pull = &details.pull;
let author = pull
.user
.as_ref()
.and_then(|user| user.login.as_deref())
.unwrap_or("unknown");
let head = pull
.head
.as_ref()
.and_then(|branch| branch.label.as_deref().or(branch.r#ref.as_deref()))
.unwrap_or("head");
let base = pull
.base
.as_ref()
.and_then(|branch| branch.label.as_deref().or(branch.r#ref.as_deref()))
.unwrap_or("base");
let state = pull_state(pull);
PullPage {
title: pull
.title
.clone()
.unwrap_or_else(|| "Untitled pull request".into()),
state: work_item_state(pull.state.as_deref()),
meta: format!(
"#{} · {state} · {author} · {head} → {base}\n{} files · +{} {} · {} comments",
pull.number.unwrap_or_default(),
pull.changed_files.unwrap_or_default(),
pull.additions.unwrap_or_default(),
pull.deletions.unwrap_or_default(),
pull.comments.unwrap_or_default(),
),
body: pull
.body
.clone()
.filter(|body| !body.is_empty())
.unwrap_or_else(|| "No description provided.".into()),
files_ref: pull_files_ref(pull),
files: details.files.iter().filter_map(file_row).collect(),
comments: details
.comments
.iter()
.map(|comment| comment_row(comment, None))
.collect(),
has_more: details.has_more,
}
}
pub fn file_rows(files: Vec<models::ChangedFile>) -> Vec<FileRow> {
files.iter().filter_map(file_row).collect()
}
pub fn commit_file_rows(files: Vec<models::CommitAffectedFiles>) -> Vec<FileRow> {
files
.into_iter()
.filter_map(|file| {
Some(FileRow {
path: file.filename?,
status: file.status.unwrap_or_else(|| "modified".into()),
})
})
.collect()
}
pub fn commit_details_page(commit: models::Commit, branch: Option<String>) -> CommitDetailsPage {
let details = commit.commit.as_deref();
let message = details
.and_then(|details| details.message.as_deref())
.unwrap_or("Commit");
let mut lines = message.lines();
let title = lines.next().unwrap_or("Commit").to_string();
let description = lines.collect::<Vec<_>>().join("\n").trim().to_string();
let author = details
.and_then(|details| details.author.as_deref())
.and_then(commit_user)
.or_else(|| {
commit
.author
.as_deref()
.and_then(|author| author.login.clone())
});
let committer = details
.and_then(|details| details.committer.as_deref())
.and_then(commit_user)
.or_else(|| {
commit
.committer
.as_deref()
.and_then(|committer| committer.login.clone())
});
let date = details
.and_then(|details| details.committer.as_deref())
.and_then(|committer| committer.date.as_deref())
.or_else(|| {
details
.and_then(|details| details.author.as_deref())
.and_then(|author| author.date.as_deref())
})
.or(commit.created.as_deref());
let files = commit.files.unwrap_or_default();
let mut metadata = Vec::new();
if let Some(author) = author {
metadata.push(metadata_row("Author", author, false));
}
if let Some(committer) = committer {
metadata.push(metadata_row("Committer", committer, false));
}
if date.is_some() {
metadata.push(metadata_row("Committed", compact_date(date), false));
}
if let Some(branch) = branch.filter(|branch| !branch.is_empty()) {
metadata.push(metadata_row("Branch", branch, false));
}
metadata.push(metadata_row(
"Commit",
commit.sha.clone().unwrap_or_else(|| "unknown".into()),
true,
));
let verification = details.and_then(|details| details.verification.as_deref());
metadata.push(metadata_row(
"Signature",
match verification.filter(|verification| {
verification
.signature
.as_deref()
.is_some_and(|signature| !signature.is_empty())
}) {
Some(verification) if verification.verified.unwrap_or(false) => "Verified".into(),
Some(_) => "Unverified".into(),
None => "Unsigned".into(),
},
false,
));
CommitDetailsPage {
title,
description,
metadata,
files: commit_file_rows(files),
}
}
fn commit_user(user: &models::CommitUser) -> Option<String> {
match (user.name.as_deref(), user.email.as_deref()) {
(Some(name), Some(email)) => Some(format!("{name} <{email}>")),
(Some(name), None) => Some(name.into()),
(None, Some(email)) => Some(email.into()),
(None, None) => None,
}
}
fn metadata_row(label: &str, value: String, monospaced: bool) -> CommitMetadataRow {
CommitMetadataRow {
label: label.into(),
value,
monospaced,
}
}