🐸 Frogue(Fed)

~ololduck/Frogue

Raw

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()
}