Skip to content
This repository was archived by the owner on Aug 6, 2026. It is now read-only.

Commit f4c0f07

Browse files
authored
fix(message-editor): preserve comment anchors in pasted GitHub links (#3795)
1 parent 88bebde commit f4c0f07

3 files changed

Lines changed: 54 additions & 7 deletions

File tree

‎apps/mobile/src/lib/githubIssueUrl.ts‎

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,23 +9,28 @@ export interface ParsedGithubIssueUrl {
99
}
1010

1111
const GITHUB_ISSUE_URL_PATTERN =
12-
/^https?:\/\/github\.com\/([^/\s]+)\/([^/\s]+)\/(issues|pull)\/(\d+)(?:[/?#].*)?$/;
12+
/^https?:\/\/github\.com\/([^/\s]+)\/([^/\s]+)\/(issues|pull)\/(\d+)([/?#].*)?$/;
1313

1414
export function parseGithubIssueUrl(text: string): ParsedGithubIssueUrl | null {
1515
const trimmed = text.trim();
1616
const match = trimmed.match(GITHUB_ISSUE_URL_PATTERN);
1717
if (!match) return null;
1818

19-
const [, owner, repo, segment, rawNumber] = match;
19+
const [, owner, repo, segment, rawNumber, suffix = ""] = match;
2020
const number = Number(rawNumber);
2121
if (!Number.isInteger(number) || number <= 0) return null;
2222

2323
const kind: GithubRefKind = segment === "pull" ? "pr" : "issue";
24+
// A fragment like #discussion_r123 anchors a specific comment, so keep the
25+
// whole suffix — anchors such as #r123 only resolve on the /files subpage
26+
// they were copied from. Without a fragment the tab/query suffix is noise.
27+
const hashIndex = suffix.indexOf("#");
28+
const hasFragment = hashIndex !== -1 && hashIndex < suffix.length - 1;
2429
return {
2530
kind,
2631
owner,
2732
repo,
2833
number,
29-
normalizedUrl: `https://github.com/${owner}/${repo}/${segment}/${number}`,
34+
normalizedUrl: `https://github.com/${owner}/${repo}/${segment}/${number}${hasFragment ? suffix : ""}`,
3035
};
3136
}

‎packages/core/src/message-editor/githubIssueUrl.test.ts‎

Lines changed: 38 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,8 +55,45 @@ describe("parseGithubIssueUrl", () => {
5555
},
5656
},
5757
{
58-
name: "fragment is stripped from normalized URL",
58+
name: "issue comment fragment is preserved",
5959
input: "https://github.com/PostHog/code/issues/1808#issuecomment-123",
60+
expected: {
61+
kind: "issue",
62+
owner: "PostHog",
63+
repo: "code",
64+
number: 1808,
65+
normalizedUrl:
66+
"https://github.com/PostHog/code/issues/1808#issuecomment-123",
67+
},
68+
},
69+
{
70+
name: "PR review comment fragment is preserved",
71+
input:
72+
"https://github.com/PostHog/posthog/pull/72409#discussion_r3647131256",
73+
expected: {
74+
kind: "pr",
75+
owner: "PostHog",
76+
repo: "posthog",
77+
number: 72409,
78+
normalizedUrl:
79+
"https://github.com/PostHog/posthog/pull/72409#discussion_r3647131256",
80+
},
81+
},
82+
{
83+
name: "files tab suffix is kept when a comment fragment needs it",
84+
input: "https://github.com/PostHog/code/pull/1454/files#r3647131256",
85+
expected: {
86+
kind: "pr",
87+
owner: "PostHog",
88+
repo: "code",
89+
number: 1454,
90+
normalizedUrl:
91+
"https://github.com/PostHog/code/pull/1454/files#r3647131256",
92+
},
93+
},
94+
{
95+
name: "empty fragment is stripped from normalized URL",
96+
input: "https://github.com/PostHog/code/issues/1808#",
6097
expected: {
6198
kind: "issue",
6299
owner: "PostHog",

‎packages/core/src/message-editor/githubIssueUrl.ts‎

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,23 +11,28 @@ export interface ParsedGithubIssueUrl {
1111
}
1212

1313
const GITHUB_ISSUE_URL_PATTERN =
14-
/^https?:\/\/github\.com\/([^/\s]+)\/([^/\s]+)\/(issues|pull)\/(\d+)(?:[/?#].*)?$/;
14+
/^https?:\/\/github\.com\/([^/\s]+)\/([^/\s]+)\/(issues|pull)\/(\d+)([/?#].*)?$/;
1515

1616
export function parseGithubIssueUrl(text: string): ParsedGithubIssueUrl | null {
1717
const trimmed = text.trim();
1818
const match = trimmed.match(GITHUB_ISSUE_URL_PATTERN);
1919
if (!match) return null;
2020

21-
const [, owner, repo, segment, rawNumber] = match;
21+
const [, owner, repo, segment, rawNumber, suffix = ""] = match;
2222
const number = Number(rawNumber);
2323
if (!Number.isInteger(number) || number <= 0) return null;
2424

2525
const kind: GithubRefKind = segment === "pull" ? "pr" : "issue";
26+
// A fragment like #discussion_r123 anchors a specific comment, so keep the
27+
// whole suffix — anchors such as #r123 only resolve on the /files subpage
28+
// they were copied from. Without a fragment the tab/query suffix is noise.
29+
const hashIndex = suffix.indexOf("#");
30+
const hasFragment = hashIndex !== -1 && hashIndex < suffix.length - 1;
2631
return {
2732
kind,
2833
owner,
2934
repo,
3035
number,
31-
normalizedUrl: `https://github.com/${owner}/${repo}/${segment}/${number}`,
36+
normalizedUrl: `https://github.com/${owner}/${repo}/${segment}/${number}${hasFragment ? suffix : ""}`,
3237
};
3338
}

0 commit comments

Comments
 (0)