ework-web 0.10.95 → 0.10.96

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ework-web",
3
- "version": "0.10.95",
3
+ "version": "0.10.96",
4
4
  "type": "module",
5
5
  "description": "ework-web — standalone multi-project issue tracker. Local SQLite-backed, no external API dependency. Bun + TypeScript + SSR HTML.",
6
6
  "license": "MIT",
@@ -17,6 +17,7 @@ import {
17
17
  listCachedModels,
18
18
  type CommentRow,
19
19
  type IssueWithMeta,
20
+ type ProjectRow,
20
21
  } from "../store";
21
22
  import { webUrlFromClone } from "./projectUpstreams";
22
23
 
@@ -37,6 +38,22 @@ export interface IssueThreadPayload {
37
38
  comments: CommentView[];
38
39
  }
39
40
 
41
+ // Issues in a project cloned from an upstream are that upstream's issues:
42
+ // #204 in a github-mirrored repo means github.com/o/r/issues/204. Normalize
43
+ // the configured remote (https/git@/ssh/git forms) to its web base; projects
44
+ // without an upstream keep resolving refs against this ework install.
45
+ export function upstreamRefBase(project: Pick<ProjectRow, "upstream_urls">): string | null {
46
+ const url = getDefaultUpstreamUrl(project);
47
+ if (!url) return null;
48
+ let m = url.match(/^git@([^:]+):(.+)$/);
49
+ if (m?.[1] && m[2]) return `https://${m[1]}/${m[2].replace(/\.git$/, "")}`;
50
+ m = url.match(/^(?:ssh|git):\/\/(?:git@)?([^\/]+)\/(.+)$/);
51
+ if (m?.[1] && m[2]) return `https://${m[1]}/${m[2].replace(/\.git$/, "")}`;
52
+ m = url.match(/^https?:\/\/([^\/]+)\/(.+)$/);
53
+ if (m?.[1] && m[2]) return `https://${m[1]}/${m[2].replace(/\.git$/, "")}`;
54
+ return null;
55
+ }
56
+
40
57
  function toView(c: CommentRow, issuePath = ""): CommentView {
41
58
  return {
42
59
  id: c.id,
@@ -105,7 +122,7 @@ export async function buildIssueThread(
105
122
  const totalPages = Math.max(1, Math.ceil(total / PAGE_SIZE));
106
123
  const currentPage = totalPages;
107
124
  const { rows } = await listCommentsPage(issue.id, currentPage, PAGE_SIZE);
108
- const issuePath = `/${owner}/${repo}/issues/${issue.number}`;
125
+ const issuePath = `${upstreamRefBase(project) ?? `/${owner}/${repo}`}/issues/${issue.number}`;
109
126
  const views = await viewsFromComments(rows, issuePath);
110
127
  const hasOlder = currentPage > 1;
111
128
  const displayViews = orderForDisplay(views, cfg.commentSort);
@@ -188,7 +205,7 @@ export async function fetchIssuePage(
188
205
  const issue = await getIssueWithMeta(project.id, number);
189
206
  if (!issue) throw new StoreError(404, `#${number} 不存在`);
190
207
  const { rows, page: clamped } = await listCommentsPage(issue.id, page, PAGE_SIZE);
191
- const views = await viewsFromComments(rows, `/${owner}/${repo}/issues/${issue.number}`);
208
+ const views = await viewsFromComments(rows, `${upstreamRefBase(project) ?? `/${owner}/${repo}`}/issues/${issue.number}`);
192
209
  return { issue, views, currentPage: clamped, hasOlder: clamped > 1 };
193
210
  }
194
211
 
@@ -203,7 +220,7 @@ export async function fetchIssueSince(
203
220
  const issue = await getIssueWithMeta(project.id, number);
204
221
  if (!issue) throw new StoreError(404, `#${number} 不存在`);
205
222
  const rows = await listCommentsSince(issue.id, sinceISO);
206
- return await viewsFromComments(rows, `/${owner}/${repo}/issues/${issue.number}`);
223
+ return await viewsFromComments(rows, `${upstreamRefBase(project) ?? `/${owner}/${repo}`}/issues/${issue.number}`);
207
224
  }
208
225
 
209
226
  export function safeJsonEmbed(v: unknown): string {