Skip to content
Clone
80f9e01
184 lines 7.5 KB Rust
use axum::extract::{Path, Query, State};
use axum::response::Response;
use gitbaby::commit::types::TreeEntryMode;
use maud::html;
use crate::handler::html::error::html_err;
use crate::handler::html::{
HtmlRepoPath, first_line, iso_time, relative_time, resolve_view, response_html, short_sha,
};
use crate::service::GitService;
use crate::share::access::BasicAuth;
#[derive(Debug, Clone, serde::Deserialize, utoipa::IntoParams)]
pub struct TreeRowQuery {
#[serde(default)]
pub revision: Option<String>,
#[serde(default)]
pub path: Option<String>,
#[serde(default)]
pub name: Option<String>,
}
#[utoipa::path(
get,
path = "/repos/{space_name}/{repo_name}/html/tree-row",
tag = "html",
description = "渲染目录列表中单个条目的 HTML 行片段。",
operation_id = "render_tree_row",
params(HtmlRepoPath, TreeRowQuery),
responses(
(status = 200, description = "tree row html fragment", content_type = "text/html"),
(status = 404, description = "repo, ref or path not found")
)
)]
pub async fn render_tree_row(
State(service): State<GitService>,
Path(path): Path<HtmlRepoPath>,
Query(q): Query<TreeRowQuery>,
auth: BasicAuth,
) -> Response {
let view = match resolve_view(service.clone(), path, auth, q.revision, q.path, false).await {
Ok(v) => v,
Err(resp) => return resp,
};
let ctx = view.ctx;
let revision = view.revision;
let parent_clean = view.clean_path;
let name = match q.name.as_deref() {
Some(n) if !n.is_empty() && n != ".." && !n.contains('/') && !n.contains('\\') => {
n.to_string()
}
_ => return html_err(404, "name invalid"),
};
if name.is_empty() || name.chars().count() > crate::handler::html::MAX_NAME_LEN {
return html_err(404, "name invalid");
}
let lookup_path = if parent_clean.is_empty() {
format!("{name}/")
} else {
format!("{parent_clean}/{name}/")
};
let lookup_file = if parent_clean.is_empty() {
name.clone()
} else {
format!("{parent_clean}/{name}")
};
let rows = match ctx
.service
.tree_with_commits_html(&ctx.ctx, &revision, &parent_clean)
.await
{
Ok(r) => r,
Err(crate::share::errors::GitError::NotFound) => {
return html_err(404, "ref not found");
}
Err(e) => {
tracing::warn!(error = %e, "tree-row internal");
return html_err(500, "internal");
}
};
let matched = rows.iter().find(|r| {
let entry_path = if r.entry.mode == TreeEntryMode::Tree {
format!("{}/", r.entry.name)
} else {
r.entry.name.clone()
};
if parent_clean.is_empty() {
r.entry.name == name || entry_path == lookup_path
} else {
entry_path == lookup_file || entry_path == lookup_path
}
});
let row = match matched {
Some(r) => r,
None => return html_err(404, "entry not found"),
};
let mode = match row.entry.mode {
TreeEntryMode::Tree => "tree",
TreeEntryMode::Submodule => "submodule",
TreeEntryMode::Commit => "commit",
TreeEntryMode::Symlink => "symlink",
TreeEntryMode::Exec => "exec",
TreeEntryMode::Blob => "blob",
};
let oid_str = row.entry.oid.to_string();
let short_oid = short_sha(&oid_str);
let sha = row.latest.id.to_string();
let short_sha_str = short_sha(&sha);
let subject = first_line(&row.latest.message);
let author = row.latest.author.name.clone();
let iso = iso_time(row.latest.author.time);
let rel = relative_time(row.latest.author.time);
let commit_url = format!("/{}/{}/commit/{}", ctx.space_name, ctx.repo_name, sha);
let blob_url = format!(
"/{}/{}/blob/{}/{}",
ctx.space_name, ctx.repo_name, revision, lookup_path
);
let href = if mode == "tree" || mode == "submodule" {
format!(
"/{}/{}/tree/{}/{}",
ctx.space_name, ctx.repo_name, revision, lookup_path
)
} else {
blob_url.clone()
};
let base = "git-tree-row border-b border-zinc-100 hover:bg-zinc-50";
let cls = if ctx.dto.is_archived {
format!("{base} opacity-60")
} else {
base.to_string()
};
let markup = html! {
tr class=(cls)
data-component="git-tree-row"
data-space=(ctx.space_name)
data-repo=(ctx.repo_name)
data-revision=(revision)
data-path=(lookup_path)
data-mode=(mode)
data-name=(name.as_str())
data-oid=(short_oid)
data-commit=(short_sha_str)
data-archived=(if ctx.dto.is_archived { "true" } else { "false" }) {
td class="w-8 px-2 py-1 text-zinc-400" {
@if mode == "tree" {
svg class="h-4 w-4" viewBox="0 0 16 16" fill="currentColor" xmlns="http://www.w3.org/2000/svg" {
path d="M1.75 1A1.75 1.75 0 000 2.75v10.5C0 14.216.784 15 1.75 15h12.5A1.75 1.75 0 0016 13.25V2.75A1.75 1.75 0 0014.25 1H1.75zM1.5 2.75a.25.25 0 01.25-.25h12.5a.25.25 0 01.25.25v.857l-4.61 4.61a.25.25 0 01-.354 0L5.61 4.354a.25.25 0 00-.354 0L1.5 8.111V2.75zm0 7.268l3.405-3.405 2.79 2.79a.75.75 0 001.06 0l3.245-3.245V13.25a.25.25 0 01-.25.25H1.75a.25.25 0 01-.25-.25V10.018z" {}
}
} @else if mode == "submodule" {
svg class="h-4 w-4" viewBox="0 0 16 16" fill="currentColor" xmlns="http://www.w3.org/2000/svg" {
path d="M4.25 2.5a.75.75 0 000 1.5h7.5a.75.75 0 000-1.5h-7.5zm0 4a.75.75 0 000 1.5h7.5a.75.75 0 000-1.5h-7.5zm0 4a.75.75 0 000 1.5h4.5a.75.75 0 000-1.5h-4.5z" {}
}
} @else if mode == "symlink" {
svg class="h-4 w-4" viewBox="0 0 16 16" fill="currentColor" xmlns="http://www.w3.org/2000/svg" {
path d="M2.5 4.5v7a1 1 0 001 1h7a1 1 0 001-1v-3.379a1.5 1.5 0 00-.44-1.061L8.94 5.94a1.5 1.5 0 00-1.061-.439H3.5a1 1 0 00-1 1z" {}
}
} @else if mode == "exec" {
svg class="h-4 w-4" viewBox="0 0 16 16" fill="currentColor" xmlns="http://www.w3.org/2000/svg" {
path d="M2.75 2.5a.25.25 0 00-.25.25v10.5c0 .138.112.25.25.25h10.5a.25.25 0 00.25-.25V2.75a.25.25 0 00-.25-.25H2.75z" {}
}
} @else {
svg class="h-4 w-4" viewBox="0 0 16 16" fill="currentColor" xmlns="http://www.w3.org/2000/svg" {
path d="M2 1.75C2 .784 2.784 0 3.75 0h6.586c.464 0 .909.184 1.237.513l2.914 2.914c.329.328.513.773.513 1.237v9.586A1.75 1.75 0 0113.25 16h-9.5A1.75 1.75 0 012 14.25V1.75z" {}
}
}
}
td class="px-2 py-1 font-mono" {
a class="text-zinc-900 hover:underline" href=(href) { (name) }
}
td class="px-2 py-1" {
a class="flex items-center gap-2 text-zinc-700 hover:text-zinc-900"
href=(commit_url)
title=(format!("{}\n{}\n{}\n{}", sha, subject, author, iso)) {
code class="rounded bg-zinc-100 px-1 text-xs" { (short_sha_str) }
span class="truncate max-w-md text-xs" { (subject) }
}
}
td class="px-2 py-1 text-right text-zinc-500 text-xs" data-time=(row.latest.author.time.unix_timestamp()) {
(rel)
}
}
};
response_html(markup, 30)
}