mirror of
https://github.com/paperclipai/paperclip.git
synced 2026-10-22 00:30:19 +02:00
PAP-2118 fix markdown issue path linkification
Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
1 parent
40cb008225
commit
879fde73f6
3 files changed
+42
-3
No files matched your search
@@ -173,6 +173,20 @@ describe("MarkdownBody", () => {
|
||||
expect(html).not.toContain("paperclip-mention-chip--issue");
|
||||
});
|
||||
|
||||
it("linkifies plain internal issue paths in markdown text", () => {
|
||||
const html = renderMarkdown("See /issues/PAP-1179 and /PAP/issues/pap-1180 for context.", [
|
||||
{ identifier: "PAP-1179", status: "blocked" },
|
||||
{ identifier: "PAP-1180", status: "done" },
|
||||
]);
|
||||
|
||||
expect(html).toContain('href="/issues/PAP-1179"');
|
||||
expect(html).toContain('href="/issues/PAP-1180"');
|
||||
expect(html).toContain(">/issues/PAP-1179<");
|
||||
expect(html).toContain(">/PAP/issues/pap-1180<");
|
||||
expect(html).toContain("text-red-600");
|
||||
expect(html).toContain("text-green-600");
|
||||
});
|
||||
|
||||
it("rewrites issue scheme links to internal issue links", () => {
|
||||
const html = renderMarkdown("See issue://PAP-1310 and issue://:PAP-1311.", [
|
||||
{ identifier: "PAP-1310", status: "done" },
|
||||
@@ -198,6 +212,21 @@ describe("MarkdownBody", () => {
|
||||
expect(html).toContain("paperclip-markdown-issue-ref");
|
||||
});
|
||||
|
||||
it("keeps trailing punctuation outside auto-linked issue references", () => {
|
||||
const html = renderMarkdown("See PAP-1271: /issues/PAP-1272] and issue://PAP-1273.", [
|
||||
{ identifier: "PAP-1271", status: "done" },
|
||||
{ identifier: "PAP-1272", status: "blocked" },
|
||||
{ identifier: "PAP-1273", status: "todo" },
|
||||
]);
|
||||
|
||||
expect(html).toContain('<a href="/issues/PAP-1271"');
|
||||
expect(html).toContain('>PAP-1271</a>:');
|
||||
expect(html).toContain('<a href="/issues/PAP-1272"');
|
||||
expect(html).toContain('>/issues/PAP-1272</a>]');
|
||||
expect(html).toContain('<a href="/issues/PAP-1273"');
|
||||
expect(html).toContain('>issue://PAP-1273</a>.');
|
||||
});
|
||||
|
||||
it("can opt out of issue reference linkification for offline previews", () => {
|
||||
const html = renderToStaticMarkup(
|
||||
<QueryClientProvider client={new QueryClient()}>
|
||||
|
||||
@@ -4,6 +4,7 @@ import { parseIssuePathIdFromPath, parseIssueReferenceFromHref } from "./issue-r
|
||||
describe("issue-reference", () => {
|
||||
it("extracts issue ids from company-scoped issue paths", () => {
|
||||
expect(parseIssuePathIdFromPath("/PAP/issues/PAP-1271")).toBe("PAP-1271");
|
||||
expect(parseIssuePathIdFromPath("/PAP/issues/pap-1272")).toBe("PAP-1272");
|
||||
expect(parseIssuePathIdFromPath("/issues/PAP-1179")).toBe("PAP-1179");
|
||||
expect(parseIssuePathIdFromPath("/issues/:id")).toBeNull();
|
||||
});
|
||||
@@ -32,6 +33,10 @@ describe("issue-reference", () => {
|
||||
issuePathId: "PAP-1179",
|
||||
href: "/issues/PAP-1179",
|
||||
});
|
||||
expect(parseIssueReferenceFromHref("/PAP/issues/pap-1180")).toEqual({
|
||||
issuePathId: "PAP-1180",
|
||||
href: "/issues/PAP-1180",
|
||||
});
|
||||
expect(parseIssueReferenceFromHref("issue://PAP-1310")).toEqual({
|
||||
issuePathId: "PAP-1310",
|
||||
href: "/issues/PAP-1310",
|
||||
|
||||
@@ -7,7 +7,7 @@ type MarkdownNode = {
|
||||
|
||||
const BARE_ISSUE_IDENTIFIER_RE = /^[A-Z][A-Z0-9]+-\d+$/i;
|
||||
const ISSUE_SCHEME_RE = /^issue:\/\/:?([^?#\s]+)(?:[?#].*)?$/i;
|
||||
const ISSUE_REFERENCE_TOKEN_RE = /issue:\/\/:?[^\s<>()]+|https?:\/\/[^\s<>()]+|\b[A-Z][A-Z0-9]+-\d+\b/gi;
|
||||
const ISSUE_REFERENCE_TOKEN_RE = /issue:\/\/:?[^\s<>()]+|https?:\/\/[^\s<>()]+|\/[^\s<>()]+|\b[A-Z][A-Z0-9]+-\d+\b/gi;
|
||||
|
||||
export function parseIssuePathIdFromPath(pathOrUrl: string | null | undefined): string | null {
|
||||
if (!pathOrUrl) return null;
|
||||
@@ -29,7 +29,7 @@ export function parseIssuePathIdFromPath(pathOrUrl: string | null | undefined):
|
||||
if (issueIndex === -1 || issueIndex === segments.length - 1) return null;
|
||||
const issuePathId = decodeURIComponent(segments[issueIndex + 1] ?? "");
|
||||
if (!issuePathId || issuePathId.startsWith(":")) return null;
|
||||
return issuePathId;
|
||||
return BARE_ISSUE_IDENTIFIER_RE.test(issuePathId) ? issuePathId.toUpperCase() : issuePathId;
|
||||
}
|
||||
|
||||
export function parseIssueReferenceFromHref(href: string | null | undefined) {
|
||||
@@ -66,12 +66,17 @@ function splitTrailingPunctuation(token: string) {
|
||||
|
||||
while (core.length > 0) {
|
||||
const lastChar = core.at(-1);
|
||||
if (!lastChar || !/[),.;!?]/.test(lastChar)) break;
|
||||
if (!lastChar || !/[),.;!?:\]]/.test(lastChar)) break;
|
||||
if (lastChar === ")") {
|
||||
const openCount = (core.match(/\(/g) ?? []).length;
|
||||
const closeCount = (core.match(/\)/g) ?? []).length;
|
||||
if (closeCount <= openCount) break;
|
||||
}
|
||||
if (lastChar === "]") {
|
||||
const openCount = (core.match(/\[/g) ?? []).length;
|
||||
const closeCount = (core.match(/\]/g) ?? []).length;
|
||||
if (closeCount <= openCount) break;
|
||||
}
|
||||
trailing = `${lastChar}${trailing}`;
|
||||
core = core.slice(0, -1);
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user