fix(citations): keep copied markdown references valid (#7743)

* fix(citations): keep copied markdown references valid

formatCitationMarkdownReference pasted the source title and URL into
"[title](url)" verbatim. A title with a stray "[" or "]" ends the link text
early, and a URL with an unbalanced parenthesis ends the destination, so a
copied reference no longer rendered as a link. Escape brackets in the title and
wrap a parenthesized URL in the angle-bracket form.

* fix(citations): only angle-bracket a parenthesized URL

normalizeUrl already percent-encodes "<", ">" and whitespace, so including
them in the trigger could wrap such a URL in an angle-bracket destination that
CommonMark forbids, which is worse than the bare form. Parentheses are the only
reachable delimiter that breaks the bare destination.
This commit is contained in:
一片一片 2026-10-31 23:00:39 +08:00 committed by GitHub
parent c6a3993695
commit a9bd03ef57
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 37 additions and 2 deletions

View File

@ -1708,7 +1708,10 @@ provider, document title, and retrieved excerpt in chat, including after reload
and when a subagent performed the retrieval. Missing page numbers and public
URLs remain absent; sources without a URL are readable as local evidence.
Inline citations remain readable after Markdown sanitization, and supplied page
numbers use the selected interface language. New providers use the shared
numbers use the selected interface language.
Copying a source reference produces a Markdown link that escapes brackets in the
visible title and wraps a URL containing parentheses in angle brackets, so the
copied reference stays a valid link. New providers use the shared
envelope; existing RAGFlow citation histories remain supported.
Expand a completed subtask card to inspect its report's forwarded knowledge
sources, including when reopening a saved conversation.

View File

@ -206,7 +206,13 @@ export function extractCitationSources(
export function formatCitationMarkdownReference(
source: CitationSource,
): string {
return `[${source.title}](${source.url})`;
// Link text may contain balanced brackets, but a stray "[" or "]" would end
// it early; escape both so a copied reference keeps the visible title.
const title = source.title.replace(/([\\[\]])/g, "\\$1");
// A bare link destination stops at an unbalanced parenthesis. The
// angle-bracket form keeps URLs with parentheses valid.
const url = /[()]/.test(source.url) ? `<${source.url}>` : source.url;
return `[${title}](${url})`;
}
function normalizeTitle(title: string, domain: string): string {

View File

@ -1042,4 +1042,30 @@ describe("formatCitationMarkdownReference", () => {
"[Paper A](https://example.com/a)",
);
});
it("escapes brackets in the title so the copied link keeps its label", () => {
const [source] = extractCitationSources(
"Evidence [citation:Paper A](https://example.com/a).",
);
expect(
formatCitationMarkdownReference({
...source!,
title: "Why ] matters [draft",
}),
).toBe("[Why \\] matters \\[draft](https://example.com/a)");
});
it("wraps a URL with parentheses so the destination stays intact", () => {
const [source] = extractCitationSources(
"Evidence [citation:Paper A](https://example.com/a).",
);
expect(
formatCitationMarkdownReference({
...source!,
url: "https://example.com/a)b(c",
}),
).toBe("[Paper A](<https://example.com/a)b(c>)");
});
});