ework-web 0.10.115 → 0.10.117
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/index.ts +60 -0
- package/src/render/layout.ts +2 -2
- package/src/views/issueThread.ts +8 -7
- package/src/views/issues.ts +1 -1
package/package.json
CHANGED
package/src/index.ts
CHANGED
|
@@ -43,6 +43,7 @@ import {
|
|
|
43
43
|
updateIssueModel,
|
|
44
44
|
updateIssueRuntime,
|
|
45
45
|
listIssues,
|
|
46
|
+
listCommentsForIssue,
|
|
46
47
|
createAttachment,
|
|
47
48
|
getAttachment,
|
|
48
49
|
verifyUserPassword,
|
|
@@ -625,6 +626,65 @@ async function handle(req: Request, url: URL, ip: string, ctx: { authed: boolean
|
|
|
625
626
|
.split(/[\s,]+/).map((s) => s.trim()).filter(Boolean);
|
|
626
627
|
return json({ logins });
|
|
627
628
|
}
|
|
629
|
+
|
|
630
|
+
// Issues JSON API — read side for the ework-issue CLI (pull/open).
|
|
631
|
+
if (url.pathname === "/api/v1/issues" && req.method === "GET") {
|
|
632
|
+
const owner = url.searchParams.get("owner") ?? "";
|
|
633
|
+
const repo = url.searchParams.get("repo") ?? "";
|
|
634
|
+
if (!owner || !repo) return json({ error: "owner, repo required" }, 400);
|
|
635
|
+
const project = await getProject(owner, repo);
|
|
636
|
+
if (!project) return json({ error: "project not found" }, 404);
|
|
637
|
+
const sp = url.searchParams.get("state");
|
|
638
|
+
const state = sp === "open" || sp === "closed" ? sp : "all";
|
|
639
|
+
const rows = await listIssues(project.id, { state, limit: 200 });
|
|
640
|
+
return json({
|
|
641
|
+
project: { owner, name: repo },
|
|
642
|
+
issues: rows.map((i) => ({
|
|
643
|
+
number: i.number,
|
|
644
|
+
title: i.title,
|
|
645
|
+
state: i.state,
|
|
646
|
+
author: i.author,
|
|
647
|
+
ai_status: i.ai_status ?? "",
|
|
648
|
+
model: i.model ?? "",
|
|
649
|
+
comment_count: i.comment_count,
|
|
650
|
+
created_at: i.created_at,
|
|
651
|
+
updated_at: i.updated_at,
|
|
652
|
+
})),
|
|
653
|
+
});
|
|
654
|
+
}
|
|
655
|
+
const apiIssueDetail = url.pathname.match(/^\/api\/v1\/issues\/(\d+)$/);
|
|
656
|
+
if (apiIssueDetail && req.method === "GET") {
|
|
657
|
+
const owner = url.searchParams.get("owner") ?? "";
|
|
658
|
+
const repo = url.searchParams.get("repo") ?? "";
|
|
659
|
+
if (!owner || !repo) return json({ error: "owner, repo required" }, 400);
|
|
660
|
+
const project = await getProject(owner, repo);
|
|
661
|
+
if (!project) return json({ error: "project not found" }, 404);
|
|
662
|
+
const issue = await getIssueWithMeta(project.id, Number(apiIssueDetail[1]));
|
|
663
|
+
if (!issue) return json({ error: "issue not found" }, 404);
|
|
664
|
+
const comments = await listCommentsForIssue(issue.id);
|
|
665
|
+
return json({
|
|
666
|
+
issue: {
|
|
667
|
+
number: issue.number,
|
|
668
|
+
title: issue.title,
|
|
669
|
+
body: issue.body,
|
|
670
|
+
state: issue.state,
|
|
671
|
+
author: issue.author,
|
|
672
|
+
ai_status: issue.ai_status ?? "",
|
|
673
|
+
model: issue.model ?? "",
|
|
674
|
+
created_at: issue.created_at,
|
|
675
|
+
updated_at: issue.updated_at,
|
|
676
|
+
},
|
|
677
|
+
comments: comments.map((c) => ({
|
|
678
|
+
id: c.id,
|
|
679
|
+
author: c.author,
|
|
680
|
+
author_kind: c.author_kind ?? "",
|
|
681
|
+
model: c.model ?? "",
|
|
682
|
+
upstream_comment_id: c.upstream_comment_id ?? null,
|
|
683
|
+
created_at: c.created_at,
|
|
684
|
+
body: c.body,
|
|
685
|
+
})),
|
|
686
|
+
});
|
|
687
|
+
}
|
|
628
688
|
const evStatus = url.pathname.match(/^\/api\/v1\/repos\/([^/]+)\/([^/]+)\/issues\/(\d+)\/status$/);
|
|
629
689
|
if (evStatus && req.method === "POST") {
|
|
630
690
|
const [, owner, repo, numStr] = evStatus;
|
package/src/render/layout.ts
CHANGED
|
@@ -246,7 +246,7 @@ export function renderLayout(props: LayoutProps, inner: string, initialItems: st
|
|
|
246
246
|
<span style="opacity:.5">/</span>
|
|
247
247
|
<a href="${escapeAttr(repoIssuesHref)}" style="color:var(--header-text)">${escapeHtml(props.repoPath)}</a>
|
|
248
248
|
<span class="num">#${props.issueNumber}</span>
|
|
249
|
-
${props.upstreamUrl ? `<a href="${escapeAttr(props.upstreamUrl)}" target="_blank" rel="noopener" title="
|
|
249
|
+
${props.upstreamUrl ? `<a href="${escapeAttr(props.upstreamUrl)}" target="_blank" rel="noopener" title="跳转到上游项目" style="color:var(--header-text);text-decoration:none;font-size:.85em">↗</a>` : ""}
|
|
250
250
|
</header>
|
|
251
251
|
<div class="meta-bar">
|
|
252
252
|
<h1>${escapeHtml(props.issueTitle)}</h1>
|
|
@@ -264,7 +264,7 @@ export function renderLayout(props: LayoutProps, inner: string, initialItems: st
|
|
|
264
264
|
}).join("")}</span>` : ""}
|
|
265
265
|
${(labelsHtml || labelPickerBtn) ? `<span class="label-group">${labelsHtml}${labelPickerBtn}</span>` : ""}
|
|
266
266
|
<span class="count" id="count">…</span>
|
|
267
|
-
${props.upstreamWebUrl ? `<a class="upstream-link" href="${escapeAttr(props.upstreamWebUrl)}" target="_blank" rel="noopener noreferrer" title="
|
|
267
|
+
${props.upstreamWebUrl ? `<a class="upstream-link" href="${escapeAttr(props.upstreamWebUrl)}" target="_blank" rel="noopener noreferrer" title="跳转到上游 issue">🔗 查看上游</a>` : ""}
|
|
268
268
|
</div>
|
|
269
269
|
</div>
|
|
270
270
|
${props.descriptionHtml.trim() ? `<div class="desc-wrap">
|
package/src/views/issueThread.ts
CHANGED
|
@@ -21,7 +21,6 @@ import {
|
|
|
21
21
|
type ProjectRow,
|
|
22
22
|
} from "../store";
|
|
23
23
|
import { getConfigAll } from "../db";
|
|
24
|
-
import { webUrlFromClone } from "./projectUpstreams";
|
|
25
24
|
|
|
26
25
|
export const PAGE_SIZE = 30;
|
|
27
26
|
|
|
@@ -134,10 +133,14 @@ export async function buildIssueThread(
|
|
|
134
133
|
const payload = payloadFromComments(issue, displayViews, currentPage, hasOlder, cfg.commentSort);
|
|
135
134
|
const descriptionHtml = renderMarkdown(issue.body, "", issuePath);
|
|
136
135
|
const descriptionCollapsed = issue.body.length > 1200;
|
|
136
|
+
// 🔗 查看上游 points at the mapped upstream ISSUE (falling back to the
|
|
137
|
+
// repo root when this issue has no upstream mapping). The header ↗ is the
|
|
138
|
+
// project-level jump (see upstreamUrl below).
|
|
137
139
|
const upstreamWebUrl = (() => {
|
|
138
|
-
const
|
|
139
|
-
if (!
|
|
140
|
-
|
|
140
|
+
const base = upstreamRefBase(project);
|
|
141
|
+
if (!base) return null;
|
|
142
|
+
if (issue.upstream_issue_number) return `${base}/issues/${issue.upstream_issue_number}`;
|
|
143
|
+
return base;
|
|
141
144
|
})();
|
|
142
145
|
const labels = await listLabelsForIssue(issue.id);
|
|
143
146
|
const viewerUser = viewerLogin ? await getUserByLogin(viewerLogin) : null;
|
|
@@ -192,9 +195,7 @@ export async function buildIssueThread(
|
|
|
192
195
|
{
|
|
193
196
|
title: `${issue.title} · ${owner}/${repo}#${number}`,
|
|
194
197
|
issueTitle: issue.title,
|
|
195
|
-
upstreamUrl:
|
|
196
|
-
? `${upstreamRefBase(project)}/issues/${issue.upstream_issue_number}`
|
|
197
|
-
: undefined,
|
|
198
|
+
upstreamUrl: upstreamRefBase(project) ?? undefined,
|
|
198
199
|
repoPath: `${owner}/${repo}`,
|
|
199
200
|
issueNumber: number,
|
|
200
201
|
state: issue.state,
|
package/src/views/issues.ts
CHANGED
|
@@ -21,7 +21,7 @@ function issueRow(it: IssueWithMeta, q: string, state: string, labels: LabelRow[
|
|
|
21
21
|
const modelChip = it.model ? ` · <span class="row-model" title="模型">${escapeHtml(it.model)}</span>` : "";
|
|
22
22
|
return `<a class="row" href="${escapeAttr(href)}">
|
|
23
23
|
<div class="row-title">${title}${chips}</div>
|
|
24
|
-
<div class="row-meta">${projectStr} · #${it.number} · 💬 ${it.comment_count} · ${relTime(it.updated_at)}${aiBadge ? ` · ${aiBadge}` : ""}${modelChip}</div>
|
|
24
|
+
<div class="row-meta">${projectStr} · #${it.number}${it.author ? ` · <span class="row-author" title="创建者">👤 ${escapeHtml(it.author)}</span>` : ""} · 💬 ${it.comment_count} · ${relTime(it.updated_at)}${aiBadge ? ` · ${aiBadge}` : ""}${modelChip}</div>
|
|
25
25
|
</a>`;
|
|
26
26
|
}
|
|
27
27
|
|