257 lines
8.1 KiB
Rust
257 lines
8.1 KiB
Rust
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,
|
||
}
|
||
}
|