🐸 Frogue(Fed)

~ololduck/Frogue

Raw

//! The webui's HTML, built from Rust components.
//!
//! A page is a [`Page`]: the document shell, plus the component that makes up
//! its body. A component is any type implementing [`maud::Render`], so the
//! pieces that recur across pages are ordinary Rust types that can be shared,
//! composed and refactored like the rest of the code. Element and attribute
//! names are checked at compile time, and everything interpolated is escaped,
//! so a user name cannot turn into markup.

mod accounts;
mod comments;
mod commit;
mod commits;
mod compare;
mod components;
mod file;
mod form;
pub mod index;
mod invites;
mod patches;
pub mod profile;
mod refs;
mod remote_forge;
mod repositories;
mod ssh_keys;
mod tickets;
mod tree;

use actix_web::HttpResponse;
use maud::{DOCTYPE, Render, html};

/// The content type every page is served as, charset included so a user name
/// outside ASCII still decodes.
const HTML: &str = "text/html; charset=utf-8";

/// A complete html document: the shell every page is served inside, wrapped
/// around the component that makes up its body.
struct Page<B: Render> {
    title: String,
    /// Who is signed in, if anyone. It decides what the navigation offers;
    /// the body of the page is the same either way.
    viewer: Option<String>,
    /// Extra HTML to insert into the <head>, e.g. <link rel="alternate">.
    extra_head: Option<String>,
    body: B,
}

impl<B: Render> Page<B> {
    /// Renders the page and pairs it with the content type it is served as.
    fn into_response(self) -> HttpResponse {
        let extra = self.extra_head.as_deref().unwrap_or("");
        let document = html! {
            (DOCTYPE)
            html lang="en" {
                head {
                    meta charset="utf-8";
                    meta name="viewport" content="width=device-width";
                    title { (self.title) }
                    meta property="og:title" content=(self.title);
                    link rel="icon" href="/static/frogue-logo.svg" type="image/svg+xml";
                    link rel="stylesheet" href="/static/style.css" type="text/css";
                    (maud::PreEscaped(extra))
                }
                body {
                    header {
                        h1 { a href="/" {"🐸 Frogue" span style="color: #ccc" {"(Fed)"} }}
                        (components::Nav { viewer: self.viewer.as_deref() })
                    }
                    main { (self.body) }
                    footer {
                        p { "Powered by " a href=(env!("CARGO_PKG_REPOSITORY")) {em{"Frogue"}} " version " code{(env!("CARGO_PKG_VERSION"))}}
                    }
                }
            }
        };

        HttpResponse::Ok()
            .content_type(HTML)
            .body(document.into_string())
    }
}

// Re-export public items for backward compatibility
pub use accounts::{LoginForm, RegisterForm, login, register, register_closed};
pub use comments::CommentView;
pub use commit::{commit, commit_header, diff_stats_summary, file_diff, file_diffs};
pub use commits::commits;
pub use compare::{PatchSummary, compare, compare_form};
pub use file::{FileDetail, FileDisplay, file};
pub use index::index;
pub use invites::{InviteSummary, invites};
pub use patches::{
    PatchDetail, PatchEditForm, PatchRow, PatchesDetail, patch, patch_form, patches,
};
pub use profile::{FollowingEntry, NoteView, RepositorySummary, edit_profile, profile};
pub use refs::refs;
pub use remote_forge::{
    NewRemoteTicket, RemoteTicketRow, RemoteTicketsList, new_remote_ticket, remote_tickets,
};
pub use repositories::{
    CommitSummary, ReadmeView, RefSummary, RepositoryDetail, RepositoryForm,
    RepositorySettingsParams, new_repository, repository, repository_settings,
};
pub use ssh_keys::{SshKeyForm, SshKeySummary, ssh_keys};
pub use tickets::{
    AttachmentView, TicketDetail, TicketForm, TicketRow, TicketsDetail, ticket, ticket_form,
    tickets,
};
pub use tree::{TreeDetail, TreeEntry, tree};