diff --git a/src/components/Markdown.svelte b/src/components/Markdown.svelte
index ee0fb293a..2d3d5f241 100644
--- a/src/components/Markdown.svelte
+++ b/src/components/Markdown.svelte
@@ -98,8 +98,14 @@
}
afterUpdate(async () => {
- // Don't underline tags that contain images.
for (const e of container.querySelectorAll("a")) {
+ if (URL.canParse(e.href)) {
+ const url = new URL(e.href);
+ if (url.origin !== window.origin) {
+ e.target = "_blank";
+ }
+ }
+ // Don't underline tags that contain images.
if (e.firstElementChild instanceof HTMLImageElement) {
e.classList.add("no-underline");
}
diff --git a/src/lib/markdown.ts b/src/lib/markdown.ts
index ba9c2ae71..2bae17a50 100644
--- a/src/lib/markdown.ts
+++ b/src/lib/markdown.ts
@@ -140,11 +140,10 @@ export class Renderer extends BaseRenderer {
// By lowercasing we avoid casing mismatches, between headings and links.
return `${text}`;
} else {
- try {
- href = new URL(href, this.#baseUrl).href;
- } catch {
- // Use original href value
- }
+ href = URL.canParse(href, this.#baseUrl)
+ ? new URL(href, this.#baseUrl).href
+ : href;
+
return `${text}`;
}
}
diff --git a/tests/e2e/project.spec.ts b/tests/e2e/project.spec.ts
index 1b885aa60..c4356fc46 100644
--- a/tests/e2e/project.spec.ts
+++ b/tests/e2e/project.spec.ts
@@ -449,13 +449,15 @@ test.describe("browser error handling", () => {
});
});
-test("external markdown link", async ({ page }) => {
+test("external markdown link", async ({ context, page }) => {
await page.route("https://example.com/**", route => {
return route.fulfill({ body: "hello", contentType: "text/plain" });
});
await page.goto(`${markdownUrl}/tree/main/footnotes.md`);
+ const pagePromise = context.waitForEvent("page");
await page.getByRole("link", { name: "https://example.com" }).click();
- await expect(page).toHaveURL("https://example.com");
+ const newPage = await pagePromise;
+ await expect(newPage).toHaveURL("https://example.com");
});
test("internal file markdown link", async ({ page }) => {