73 lines
2.9 KiB
JavaScript
73 lines
2.9 KiB
JavaScript
/* Project card component.
|
|
* 统一 index / projects / search 三处项目卡片的渲染.
|
|
* options 控制各页面差异 (对齐重构前的三种卡片变体):
|
|
* - showSubtitle (默认 true): 显示 full_name 副标题
|
|
* - showDescription (默认 false): 显示截断描述 (projects 页)
|
|
* - showForks (默认 true): 显示 🍴 badge
|
|
* - showStar (默认 true): 显示 ⭐ badge
|
|
* - showLanguage (默认 true): 显示语言 badge
|
|
* - showUpdatedAt (默认 false): 显示 "更新于" 行 (projects 页)
|
|
* - descriptionMaxLen (默认 100)
|
|
* 点击整卡跳转 /projects/<id> (与旧内联 onclick 行为一致).
|
|
*/
|
|
|
|
import { escapeHtml, truncate, formatDate } from "../utils.js";
|
|
|
|
export function renderProjectCard(p, options = {}) {
|
|
const {
|
|
showSubtitle = true,
|
|
showDescription = false,
|
|
showForks = true,
|
|
showStar = true,
|
|
showLanguage = true,
|
|
showUpdatedAt = false,
|
|
descriptionMaxLen = 100,
|
|
} = options;
|
|
|
|
const projectId = Number.parseInt(p.id, 10);
|
|
const badges = [
|
|
showStar ? `<span class="badge bg-warning text-dark">⭐ ${p.stars ?? 0}</span>` : "",
|
|
showForks ? `<span class="badge bg-secondary">🍴 ${p.forks ?? 0}</span>` : "",
|
|
showLanguage
|
|
? `<span class="badge bg-info text-dark">${escapeHtml(p.language || "N/A")}</span>`
|
|
: "",
|
|
]
|
|
.filter(Boolean)
|
|
.join(" ");
|
|
|
|
return `
|
|
<div class="col-md-4">
|
|
<div class="card project-card h-100" data-project-id="${Number.isNaN(projectId) ? "" : projectId}">
|
|
<div class="card-body">
|
|
<h5 class="card-title">${escapeHtml(p.name)}</h5>
|
|
${
|
|
showSubtitle
|
|
? `<h6 class="card-subtitle mb-2 text-muted">${escapeHtml(p.full_name || "")}</h6>`
|
|
: ""
|
|
}
|
|
${
|
|
showDescription
|
|
? `<p class="card-text">${escapeHtml(truncate(p.description, descriptionMaxLen))}</p>`
|
|
: ""
|
|
}
|
|
<p class="card-text ${showUpdatedAt ? "mb-1" : "mb-0"}">${badges}</p>
|
|
${
|
|
showUpdatedAt
|
|
? `<small class="text-muted">更新于 ${formatDate(p.updated_at)}</small>`
|
|
: ""
|
|
}
|
|
</div>
|
|
</div>
|
|
</div>`;
|
|
}
|
|
|
|
/* Delegated click handler; one listener on document covers all cards. */
|
|
export function bindProjectCardClicks() {
|
|
document.addEventListener("click", (e) => {
|
|
const card = e.target.closest(".project-card[data-project-id]");
|
|
if (!card) return;
|
|
const id = Number.parseInt(card.dataset.projectId, 10);
|
|
if (!Number.isNaN(id)) location.href = `/projects/${id}`;
|
|
});
|
|
}
|