diff --git a/src/middleware.ts b/src/middleware.ts index de792d71e2..2036a1e165 100644 --- a/src/middleware.ts +++ b/src/middleware.ts @@ -39,13 +39,11 @@ export async function onRequest( currentLocale = pathSegments[0]; } - let modifiedHtml = html; - - // If we have a currentLocale, modify the HTML to ensure all links - // have the correct locale prefix - if (currentLocale) { - modifiedHtml = ensureCorrectLocalePrefixesInHtmlLinks(html, currentLocale); - } + // Process HTML links to normalize external URLs and add locale prefixes when applicable + const modifiedHtml = ensureCorrectLocalePrefixesInHtmlLinks( + html, + currentLocale, + ); // Return a new Response with the modified HTML return new Response(modifiedHtml, { @@ -56,7 +54,7 @@ export async function onRequest( export const ensureCorrectLocalePrefixesInHtmlLinks = ( html: string, - currentLocale: string, + currentLocale?: string, ) => { const $ = load(html); @@ -65,13 +63,24 @@ export const ensureCorrectLocalePrefixesInHtmlLinks = ( $("a").each(function () { let href = $(this).attr("href"); // Skip if href is undefined, an external link, or written with a dot slash - if ( - !href || - href.startsWith("http") || - href.startsWith("./") || - href.startsWith("#") - ) + if (!href || href.startsWith("./") || href.startsWith("#")) return; + + // Remove trailing slashes from external/absolute URLs (like Wikipedia) + // to prevent breaking external links + if (href.startsWith("http")) { + try { + const urlObj = new URL(href); + // Only remove trailing slash if the path is not just "/" + if (href.endsWith("/") && urlObj.pathname !== "/") { + href = href.slice(0, -1); + $(this).attr("href", href); + } + } catch (e) { + // If URL parsing fails, log the error and leave the href unchanged + console.error(`Failed to parse URL in tag href: "${href}". Error:`, e); + } return; + } const startsWithLocale = nonDefaultSupportedLocales.some( (locale) => href && href.startsWith(`/${locale}/`), diff --git a/src/scripts/builders/reference.ts b/src/scripts/builders/reference.ts index 33391d42a4..11d85a8844 100644 --- a/src/scripts/builders/reference.ts +++ b/src/scripts/builders/reference.ts @@ -206,6 +206,8 @@ const correctRelativeLinksInDescription = (description: string | undefined) => { // Add a trailing / if the link isn't to a file and does not have query params or a hash reference if ( !href.startsWith('#') && + !href.startsWith('http://') && + !href.startsWith('https://') && !href.endsWith('/') && !/(\.\w+)$/.exec(href) && !href.includes('?') && @@ -469,6 +471,7 @@ export const testingExports = { memberMethodPreviews: classMethodAndPropertyPreviews, addDocToModulePathTree, addMemberMethodPreviewsToClassDocs: addMethodAndPropertyPreviewsToClassDocs, + correctRelativeLinksInDescription, }; buildReference(); diff --git a/test/scripts/build-reference.test.ts b/test/scripts/build-reference.test.ts index e9fd465ede..e38a3261e6 100644 --- a/test/scripts/build-reference.test.ts +++ b/test/scripts/build-reference.test.ts @@ -6,6 +6,7 @@ const { modulePathTree, addMemberMethodPreviewsToClassDocs, memberMethodPreviews, + correctRelativeLinksInDescription, } = testingExports; const defaultDoc = { @@ -106,3 +107,12 @@ describe("modulePathTree with method previews", () => { ).toBe(expectedPath); }); }); + +describe("correctRelativeLinksInDescription", () => { + test("should not add trailing slash to external URLs in descriptions", () => { + const input = + '

Animation

'; + + expect(correctRelativeLinksInDescription(input)).toEqual(input); + }); +});