import { THEME_CSS, escapeHtml, escapeAttr, containsCI, highlightAll, tabNavHTML, aiStatusBadge } from "../render/layout"; import { BUILD_ID } from "../build"; import { listAllIssues, listLabelsForIssues, type IssueWithMeta, type LabelRow } from "../store"; import { relTime } from "../render/components"; export const FEED_PAGE_SIZE = 50; function labelChip(label: LabelRow, owner: string, repo: string, state: string): string { const href = `/${encodeURIComponent(owner)}/${encodeURIComponent(repo)}/issues?state=${state}&label=${encodeURIComponent(label.name)}`; return `${escapeHtml(label.name)}`; } function issueRow(it: IssueWithMeta, q: string, state: string, labels: LabelRow[]): string { const href = `/${encodeURIComponent(it.project_owner)}/${encodeURIComponent(it.project_name)}/issues/${it.number}`; const title = q ? highlightAll(it.title, q) : escapeHtml(it.title); const projectStr = `${escapeHtml(it.project_owner)}/${escapeHtml(it.project_name)}`; const chips = labels.length ? `${labels.map((l) => labelChip(l, it.project_owner, it.project_name, state)).join("")}` : ""; const aiBadge = aiStatusBadge(it.ai_status); const modelChip = it.model ? ` · ${escapeHtml(it.model)}` : ""; return `
${title}${chips}
${projectStr} · #${it.number}${it.author ? ` · 👤 ${escapeHtml(it.author)}` : ""} · 💬 ${it.comment_count} · ${relTime(it.updated_at)}${aiBadge ? ` · ${aiBadge}` : ""}${modelChip}
`; } export async function buildIssuesFeed( state: "open" | "closed" | "all", q: string, label: string = "", viewer?: { login: string; is_admin: number } | null, ): Promise { const issues = await listAllIssues({ state, q, label, limit: FEED_PAGE_SIZE, viewerLogin: viewer?.login, viewerIsAdmin: viewer?.is_admin === 1, }); const labelMap = await listLabelsForIssues(issues.map((it) => it.id)); const matchesFirst = q ? [...issues].sort((a, b) => Number(containsCI(b.title, q)) - Number(containsCI(a.title, q))) : issues; const openActive = state === "open" ? " active" : ""; const closedActive = state === "closed" ? " active" : ""; const allActive = state === "all" ? " active" : ""; const qParam = q ? `&q=${encodeURIComponent(q)}` : ""; const labelFilterHint = label ? `
标签: ${escapeHtml(label)}
` : ""; const rows = matchesFirst.length ? matchesFirst.map((it) => issueRow(it, q, state, labelMap.get(it.id) ?? [])).join("") : `
暂无工单
`; return ` Issues · 全部
🏠
${tabNavHTML("issues", viewer ? { login: viewer.login, is_admin: viewer.is_admin } : undefined)}
${labelFilterHint}
${rows}
`; }