use actix_web::HttpResponse;
use maud::{Markup, PreEscaped, Render, html};
use super::{
Page,
components::{RepositoryChrome, RepositoryTab},
tree::Breadcrumb,
};
/// One file of one ref, as its page shows it.
pub struct FileDetail {
pub slug: String,
pub visibility: &'static str,
/// The branch the repository opens on, for the browse tab.
pub default_branch: String,
/// The revision holding the file: a branch, a tag, or a commit hash.
pub reference: String,
/// The path to the file within the revision.
pub path: String,
/// The file itself, in the only form a file's page shows.
pub display: FileDisplay,
}
/// A file's content: the two forms the forge can say something about, and
/// a refusal for the third.
pub enum FileDisplay {
/// Rendered, sanitized HTML - markdown, through [`crate::markdown`].
Rendered(String),
/// Coloured, escaped HTML - source, through [`crate::highlight`].
Coloured(String),
/// Bytes no page should pretend to read as text.
Binary,
}
pub(super) struct FileBody<'a> {
owner: &'a str,
detail: &'a FileDetail,
viewer: Option<&'a str>,
}
impl Render for FileBody<'_> {
fn render(&self) -> Markup {
let detail = self.detail;
let root = format!("/~{}/{}/tree/{}", self.owner, detail.slug, detail.reference);
// The same bytes, with none of this page around them.
let raw = format!(
"/~{}/{}/raw/{}/{}",
self.owner, detail.slug, detail.reference, detail.path
);
html! {
(RepositoryChrome {
owner: self.owner,
slug: &detail.slug,
visibility: detail.visibility,
default_branch: &detail.default_branch,
current_tab: RepositoryTab::Browse,
is_owner: self.viewer == Some(self.owner),
})
(Breadcrumb {
root: &root,
reference: &detail.reference,
path: &detail.path,
})
p class="file-tools" {
a href=(&raw) { "Raw" }
}
@match &detail.display {
// `markdown::render` sanitizes what it returns, the same as
// a readme: this is the only other place its output is
// embedded, and its safety rests on staying that way.
FileDisplay::Rendered(html) => section class="readme" { main { (PreEscaped(html)) } },
// `highlight::render` escapes the source as it colours it
// and carries the colours as inline styles, so nothing of
// ours has to know what a keyword looks like.
FileDisplay::Coloured(html) => pre class="source" { (PreEscaped(html)) },
FileDisplay::Binary => p class="empty" { "This file is binary." },
}
}
}
}
/// The page showing one file of one ref of `owner`'s repository.
pub fn file(viewer: Option<&str>, owner: &str, detail: &FileDetail) -> HttpResponse {
Page {
title: format!(
"~{owner}/{}: {}:{} - Frogue",
detail.slug, detail.reference, detail.path
),
viewer: viewer.map(str::to_string),
extra_head: None,
body: FileBody {
owner,
detail,
viewer,
},
}
.into_response()
}