194c44c
use axum::extract::{Path, Query, State};
use axum::response::Response;
use maud::html;
use crate::handler::html::{HtmlRepoPath, archived_class, resolve_view, response_html};
use crate::service::GitService;
use crate::share::access::BasicAuth;
#[derive(Debug, Clone, serde::Deserialize, utoipa::IntoParams)]
pub struct BlobHeaderQuery {
#[serde(default)]
pub revision: Option<String>,
#[serde(default)]
pub path: Option<String>,
}
#[utoipa::path(
get,
path = "/repos/{space_name}/{repo_name}/html/blob-header",
tag = "html",
description = "渲染文件页头的 Raw、Blame 和 History 操作链接。",
operation_id = "render_blob_header",
params(HtmlRepoPath, BlobHeaderQuery),
responses(
(status = 200, description = "blob header html fragment", content_type = "text/html"),
(status = 404, description = "repo, ref or blob not found")
)
)]
pub async fn render_blob_header(
State(service): State<GitService>,
Path(path): Path<HtmlRepoPath>,
Query(q): Query<BlobHeaderQuery>,
auth: BasicAuth,
) -> Response {
let view = match resolve_view(service.clone(), path, auth, q.revision, q.path, true).await {
Ok(v) => v,
Err(resp) => return resp,
};
let ctx = view.ctx;
let revision = view.revision;
let clean_path = view.clean_path;
let base =
"git-blob-header flex items-center justify-between border-b border-zinc-200 px-3 py-2";
let cls = archived_class(&ctx.dto, base);
let filename = clean_path.rsplit('/').next().unwrap_or(&clean_path);
let space = ctx.space_name.clone();
let repo = ctx.repo_name.clone();
let raw_url = format!("/{space}/{repo}/blob/raw/{revision}/{clean_path}");
let blame_url = format!("/{space}/{repo}/blame/{revision}/{clean_path}");
let history_url = format!("/{space}/{repo}/commits/{revision}/{clean_path}");
let markup = html! {
div class=(cls)
data-component="git-blob-header"
data-space=(space)
data-repo=(repo)
data-revision=(revision)
data-path=(clean_path.as_str())
data-archived=(if ctx.dto.is_archived { "true" } else { "false" }) {
div class="flex items-center gap-2" {
svg class="h-4 w-4 text-zinc-500" viewBox="0 0 16 16" fill="currentColor" xmlns="http://www.w3.org/2000/svg" {
path d="M2 2.5A2.5 2.5 0 014.5 0h8.75a.75.75 0 01.75.75v12.5a.75.75 0 01-.75.75h-2.5a.75.75 0 110-1.5h1.75v-2h-8a1 1 0 00-.714 1.7.75.75 0 01-1.072 1.05A2.495 2.495 0 012 11.5zm10.5-1V9h-8c-.356 0-.694.074-1 .208V2.5a1 1 0 011-1h8z" {}
}
span class="font-mono text-sm font-medium text-zinc-700" { (filename) }
}
div class="flex items-center gap-1.5" {
a class="rounded border border-zinc-200 bg-white px-2 py-0.5 text-xs text-zinc-700 hover:bg-zinc-50"
href=(raw_url)
target="_blank"
rel="noopener noreferrer" {
"Raw"
}
a class="rounded border border-zinc-200 bg-white px-2 py-0.5 text-xs text-zinc-700 hover:bg-zinc-50"
href=(blame_url) {
"Blame"
}
a class="rounded border border-zinc-200 bg-white px-2 py-0.5 text-xs text-zinc-700 hover:bg-zinc-50"
href=(history_url) {
"History"
}
}
}
};
response_html(markup, 30)
}