From 6798836713e8652d729c342a20325dbe28dae45f Mon Sep 17 00:00:00 2001 From: xyOz Date: Wed, 18 Jun 2025 15:57:53 +0100 Subject: [PATCH 1/4] Update MarkdownBlock.tsx --- webview-ui/src/components/common/MarkdownBlock.tsx | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/webview-ui/src/components/common/MarkdownBlock.tsx b/webview-ui/src/components/common/MarkdownBlock.tsx index a9c5ada19a9..08db3c00b45 100644 --- a/webview-ui/src/components/common/MarkdownBlock.tsx +++ b/webview-ui/src/components/common/MarkdownBlock.tsx @@ -34,14 +34,19 @@ const remarkUrlToLink = () => { const parts = node.value.split(urlRegex) const children: any[] = [] + const cleanedMatches = matches.map((url: string) => url.replace(/[.,;:!?'"]+$/, "")) parts.forEach((part: string, i: number) => { if (part) { children.push({ type: "text", value: part }) } - if (matches[i]) { - children.push({ type: "link", url: matches[i], children: [{ type: "text", value: matches[i] }] }) + if (cleanedMatches[i]) { + children.push({ + type: "link", + url: cleanedMatches[i], + children: [{ type: "text", value: cleanedMatches[i] }], + }) } }) From 7b87dc5c61bfa4e8a51216c8317da351f10e1803 Mon Sep 17 00:00:00 2001 From: Daniel Riccio Date: Wed, 18 Jun 2025 12:42:39 -0500 Subject: [PATCH 2/4] fix: correct URL handling in MarkdownBlock and add tests for trailing punctuation --- .../src/components/common/MarkdownBlock.tsx | 2 +- .../common/__tests__/MarkdownBlock.spec.tsx | 29 +++++++++++++++++++ 2 files changed, 30 insertions(+), 1 deletion(-) create mode 100644 webview-ui/src/components/common/__tests__/MarkdownBlock.spec.tsx diff --git a/webview-ui/src/components/common/MarkdownBlock.tsx b/webview-ui/src/components/common/MarkdownBlock.tsx index 08db3c00b45..34ed2edf621 100644 --- a/webview-ui/src/components/common/MarkdownBlock.tsx +++ b/webview-ui/src/components/common/MarkdownBlock.tsx @@ -45,7 +45,7 @@ const remarkUrlToLink = () => { children.push({ type: "link", url: cleanedMatches[i], - children: [{ type: "text", value: cleanedMatches[i] }], + children: [{ type: "text", value: matches[i] }], }) } }) diff --git a/webview-ui/src/components/common/__tests__/MarkdownBlock.spec.tsx b/webview-ui/src/components/common/__tests__/MarkdownBlock.spec.tsx new file mode 100644 index 00000000000..09389b01fc9 --- /dev/null +++ b/webview-ui/src/components/common/__tests__/MarkdownBlock.spec.tsx @@ -0,0 +1,29 @@ +import React from "react" +import { render, screen } from "@testing-library/react" +import MarkdownBlock from "../MarkdownBlock" +import { vi } from "vitest" + +vi.mock("@src/utils/vscode", () => ({ + vscode: { + postMessage: vi.fn(), + }, +})) + +vi.mock("@src/context/ExtensionStateContext", () => ({ + useExtensionState: () => ({ + theme: "dark", + }), +})) + +describe("MarkdownBlock", () => { + it("should correctly handle URLs with trailing punctuation", async () => { + const markdown = "Check out this link: https://example.com." + render() + + await (async () => { + const linkElement = screen.getByRole("link") + expect(linkElement).toHaveAttribute("href", "https://example.com") + expect(linkElement.textContent).toBe("https://example.com.") + }) + }) +}) From bd2eb062c62e118905cce5f1067c732babfb9bc1 Mon Sep 17 00:00:00 2001 From: xyOz Date: Thu, 19 Jun 2025 05:13:48 +0100 Subject: [PATCH 3/4] Fix URL punctuation rendering --- webview-ui/src/components/common/MarkdownBlock.tsx | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/webview-ui/src/components/common/MarkdownBlock.tsx b/webview-ui/src/components/common/MarkdownBlock.tsx index 34ed2edf621..6a857d5526d 100644 --- a/webview-ui/src/components/common/MarkdownBlock.tsx +++ b/webview-ui/src/components/common/MarkdownBlock.tsx @@ -42,11 +42,19 @@ const remarkUrlToLink = () => { } if (cleanedMatches[i]) { + const originalUrl = matches[i] + const cleanedUrl = cleanedMatches[i] + const removedPunctuation = originalUrl.substring(cleanedUrl.length) + children.push({ type: "link", - url: cleanedMatches[i], - children: [{ type: "text", value: matches[i] }], + url: cleanedUrl, + children: [{ type: "text", value: cleanedUrl }], }) + + if (removedPunctuation) { + children.push({ type: "text", value: removedPunctuation }) + } } }) From 78d0dbd7aadab5e3e6a10a7702336188cb86b14c Mon Sep 17 00:00:00 2001 From: Daniel Riccio Date: Fri, 20 Jun 2025 16:35:09 -0500 Subject: [PATCH 4/4] fix: improve URL handling in MarkdownBlock and update tests for trailing punctuation --- .../src/components/common/MarkdownBlock.tsx | 95 ++++++++++--------- .../common/__tests__/MarkdownBlock.spec.tsx | 22 +++-- 2 files changed, 66 insertions(+), 51 deletions(-) diff --git a/webview-ui/src/components/common/MarkdownBlock.tsx b/webview-ui/src/components/common/MarkdownBlock.tsx index 6a857d5526d..2883bd4c678 100644 --- a/webview-ui/src/components/common/MarkdownBlock.tsx +++ b/webview-ui/src/components/common/MarkdownBlock.tsx @@ -28,7 +28,7 @@ const remarkUrlToLink = () => { const urlRegex = /https?:\/\/[^\s<>)"]+/g const matches = node.value.match(urlRegex) - if (!matches) { + if (!matches || !parent) { return } @@ -45,25 +45,32 @@ const remarkUrlToLink = () => { const originalUrl = matches[i] const cleanedUrl = cleanedMatches[i] const removedPunctuation = originalUrl.substring(cleanedUrl.length) - + + // Create a proper link node with all required properties children.push({ type: "link", url: cleanedUrl, + title: null, children: [{ type: "text", value: cleanedUrl }], + data: { + hProperties: { + href: cleanedUrl, + }, + }, }) - + if (removedPunctuation) { children.push({ type: "text", value: removedPunctuation }) } } }) - // Fix: Instead of converting the node to a paragraph (which broke things), - // we replace the original text node with our new nodes in the parent's children array. + // Replace the original text node with our new nodes in the parent's children array. // This preserves the document structure while adding our links. - if (parent) { - parent.children.splice(index, 1, ...children) - } + parent.children.splice(index!, 1, ...children) + + // Return SKIP to prevent visiting the newly created nodes + return ["skip", index! + children.length] }) } } @@ -154,44 +161,42 @@ const MarkdownBlock = memo(({ markdown }: MarkdownBlockProps) => { rehypePlugins: [], rehypeReactOptions: { components: { - a: ({ href, children }: any) => { + a: ({ href, children, ...props }: any) => { + const handleClick = (e: React.MouseEvent) => { + // Only process file:// protocol or local file paths + const isLocalPath = href.startsWith("file://") || href.startsWith("/") || !href.includes("://") + + if (!isLocalPath) { + return + } + + e.preventDefault() + + // Handle absolute vs project-relative paths + let filePath = href.replace("file://", "") + + // Extract line number if present + const match = filePath.match(/(.*):(\d+)(-\d+)?$/) + let values = undefined + if (match) { + filePath = match[1] + values = { line: parseInt(match[2]) } + } + + // Add ./ prefix if needed + if (!filePath.startsWith("/") && !filePath.startsWith("./")) { + filePath = "./" + filePath + } + + vscode.postMessage({ + type: "openFile", + text: filePath, + values, + }) + } + return ( - { - // Only process file:// protocol or local file paths - const isLocalPath = - href.startsWith("file://") || href.startsWith("/") || !href.includes("://") - - if (!isLocalPath) { - return - } - - e.preventDefault() - - // Handle absolute vs project-relative paths - let filePath = href.replace("file://", "") - - // Extract line number if present - const match = filePath.match(/(.*):(\d+)(-\d+)?$/) - let values = undefined - if (match) { - filePath = match[1] - values = { line: parseInt(match[2]) } - } - - // Add ./ prefix if needed - if (!filePath.startsWith("/") && !filePath.startsWith("./")) { - filePath = "./" + filePath - } - - vscode.postMessage({ - type: "openFile", - text: filePath, - values, - }) - }}> + {children} ) diff --git a/webview-ui/src/components/common/__tests__/MarkdownBlock.spec.tsx b/webview-ui/src/components/common/__tests__/MarkdownBlock.spec.tsx index 09389b01fc9..5190f7fe82f 100644 --- a/webview-ui/src/components/common/__tests__/MarkdownBlock.spec.tsx +++ b/webview-ui/src/components/common/__tests__/MarkdownBlock.spec.tsx @@ -18,12 +18,22 @@ vi.mock("@src/context/ExtensionStateContext", () => ({ describe("MarkdownBlock", () => { it("should correctly handle URLs with trailing punctuation", async () => { const markdown = "Check out this link: https://example.com." - render() + const { container } = render() - await (async () => { - const linkElement = screen.getByRole("link") - expect(linkElement).toHaveAttribute("href", "https://example.com") - expect(linkElement.textContent).toBe("https://example.com.") - }) + // Wait for the content to be processed + await screen.findByText(/Check out this link/, { exact: false }) + + // Check for nested links - this should not happen + const nestedLinks = container.querySelectorAll("a a") + expect(nestedLinks.length).toBe(0) + + // Should have exactly one link + const linkElement = screen.getByRole("link") + expect(linkElement).toHaveAttribute("href", "https://example.com") + expect(linkElement.textContent).toBe("https://example.com") + + // Check that the period is outside the link + const paragraph = container.querySelector("p") + expect(paragraph?.textContent).toBe("Check out this link: https://example.com.") }) })