diff --git a/.changeset/lucky-doors-applaud.md b/.changeset/lucky-doors-applaud.md new file mode 100644 index 000000000..1ca8845f8 --- /dev/null +++ b/.changeset/lucky-doors-applaud.md @@ -0,0 +1,6 @@ +--- +"@headstartwp/core": patch +"@headstartwp/next": patch +--- + +fix: make convertUrl and removeSourceUrl respect the original link's trailingslash diff --git a/packages/core/src/utils/__tests__/removeSourceUrl.ts b/packages/core/src/utils/__tests__/removeSourceUrl.ts index 5fc1051da..233898ee3 100644 --- a/packages/core/src/utils/__tests__/removeSourceUrl.ts +++ b/packages/core/src/utils/__tests__/removeSourceUrl.ts @@ -1,7 +1,30 @@ import { removeSourceUrl } from '..'; describe('removeSourceUrl', () => { + it('returns empty string without nonEmptyLink', () => { + expect( + removeSourceUrl({ + link: 'https://test.com/test', + backendUrl: 'https://test.com/test', + nonEmptyLink: false, + }), + ).toBe(''); + }); it('removes source url', () => { + expect( + removeSourceUrl({ + link: 'https://test.com/test', + backendUrl: 'https://test.com/test', + }), + ).toBe('/'); + + expect( + removeSourceUrl({ + link: 'http://backendurl.com', + backendUrl: 'https://backendurl.com', + }), + ).toBe('/'); + expect( removeSourceUrl({ link: 'http://backendurl.com/', @@ -44,6 +67,13 @@ describe('removeSourceUrl', () => { }), ).toBe('/post-name-1?a=1&b=3&d=3'); + expect( + removeSourceUrl({ + link: 'http://backendurl.com/post-name-1?a=1&b=3&d=3/', + backendUrl: 'https://backendurl.com/', + }), + ).toBe('/post-name-1?a=1&b=3&d=3/'); + expect( removeSourceUrl({ link: 'http://backendurl.com/post-name-1#id', diff --git a/packages/core/src/utils/removeSourceUrl.ts b/packages/core/src/utils/removeSourceUrl.ts index 31dbc7acb..15f4c579b 100644 --- a/packages/core/src/utils/removeSourceUrl.ts +++ b/packages/core/src/utils/removeSourceUrl.ts @@ -15,6 +15,12 @@ export type removeSourceUrlType = { * The public url. Defaults to '/'. */ publicUrl?: string; + + /** + * If the removal of source url from link leads to a empty string, + * this setting control whether a '/' should be returned or the empty string + */ + nonEmptyLink?: boolean; }; /** @@ -25,17 +31,28 @@ export type removeSourceUrlType = { * * @see https://github.com/frontity/frontity/blob/dev/packages/components/link/utils.ts * + * @param props.link The link that might contain the sourceUrl + * @param props.backendUrl The source url + * @param props.publicUrl The public url + * @param props.nonEmptyLinks If the removal of source url from link leads to a empty string, + * this setting control whether a '/' should be returned or the empty string + * * @returns The URL without the Source URL. */ -export function removeSourceUrl({ link, backendUrl, publicUrl = '/' }: removeSourceUrlType) { - if (typeof link === 'undefined') { +export function removeSourceUrl({ + link: originalLink, + backendUrl, + publicUrl = '/', + nonEmptyLink = true, +}: removeSourceUrlType) { + if (typeof originalLink === 'undefined') { warn('link is undefined, double check if you are passing a valid value'); return ''; } if (typeof backendUrl === 'undefined') { warn('backendUrl is undefined, double check if you are passing a valid value'); - return link; + return originalLink; } // Ensure `sourceUrl` and `publicUrl` always include a trailing slash. All @@ -43,21 +60,33 @@ export function removeSourceUrl({ link, backendUrl, publicUrl = '/' }: removeSou const sourceUrl = backendUrl.replace(/\/?$/, '/'); const appUrl = publicUrl.replace(/\/?$/, '/'); - if (sourceUrl === '/' || link.startsWith('#')) { - return link; + if (sourceUrl === '/' || originalLink.startsWith('#')) { + return originalLink; } const { host: sourceHost, pathname: sourcePath } = new URL(sourceUrl); const { pathname: appPath } = new URL(appUrl, sourceUrl); + // we need to know if the original link has trailing slash or not + const hasTrailingSlash = /\/$/.test(originalLink); + const link = !hasTrailingSlash ? `${originalLink}/` : originalLink; const linkUrl = new URL(link, sourceUrl); // Compare just the host and the pathname. This way we ignore the protocol if // it doesn't match. if (linkUrl.host === sourceHost && linkUrl.pathname.startsWith(sourcePath)) { - return linkUrl.pathname.replace(sourcePath, appPath) + linkUrl.search + linkUrl.hash; + let transformedLink = + linkUrl.pathname.replace(sourcePath, appPath) + linkUrl.search + linkUrl.hash; + + transformedLink = hasTrailingSlash ? transformedLink : transformedLink.replace(/\/?$/, ''); + + if (nonEmptyLink && transformedLink === '') { + return '/'; + } + + return transformedLink; } // Do not change the link for other cases. - return link; + return originalLink; } diff --git a/packages/next/src/components/Yoast.tsx b/packages/next/src/components/Yoast.tsx index 8c7462d38..6cc0641a3 100644 --- a/packages/next/src/components/Yoast.tsx +++ b/packages/next/src/components/Yoast.tsx @@ -18,8 +18,10 @@ export function convertUrl(url: string, hostUrl: string, sourceUrl: string) { } return `${hostUrl}${removeSourceUrl({ - link: url.replace(/\/?$/, '/'), + link: url, + publicUrl: hostUrl, backendUrl: sourceUrl, + nonEmptyLink: false, })}`; } diff --git a/packages/next/src/components/__tests__/Yoast.tsx b/packages/next/src/components/__tests__/Yoast.tsx index 2405e164e..1a6c45312 100644 --- a/packages/next/src/components/__tests__/Yoast.tsx +++ b/packages/next/src/components/__tests__/Yoast.tsx @@ -1,16 +1,60 @@ import { convertUrl } from '../Yoast'; describe('convertUrl', () => { - it('root works without trailing slash', () => { + it('works without trainling slash', () => { expect( - convertUrl('https://test.com/test', 'https://test.test.com', 'https://test.com/test'), - ).toBe('https://test.test.com/'); + convertUrl( + 'https://backendurl.com/test', + 'https://publicurl.com', + 'https://backendurl.com/test', + ), + ).toBe('https://publicurl.com'); + + // a subsite on test.com/site1 + expect( + convertUrl( + 'https://backendurl.com/site1/post-name-1', + 'https://publicurl.com', + 'https://backendurl.com/site1', + ), + ).toBe('https://publicurl.com/post-name-1'); + + // front-end with subdomain + expect( + convertUrl( + 'https://backendurl.com/site1/post-name-1', + 'https://site1.publicurl.com', + 'https://backendurl.com/site1', + ), + ).toBe('https://site1.publicurl.com/post-name-1'); }); it('root works with trailing slash', () => { expect( - convertUrl('https://test.com/test/', 'https://test.test.com', 'https://test.com/test'), - ).toBe('https://test.test.com/'); + convertUrl( + 'https://backendurl.com/test/', + 'https://publicurl.com', + 'https://backendurl.com/test', + ), + ).toBe('https://publicurl.com/'); + + // a subsite on test.com/site1 + expect( + convertUrl( + 'https://backendurl.com/site1/post-name-1/', + 'https://publicurl.com', + 'https://backendurl.com/site1', + ), + ).toBe('https://publicurl.com/post-name-1/'); + + // front-end with subdomain + expect( + convertUrl( + 'https://backendurl.com/site1/post-name-1/', + 'https://site1.publicurl.com', + 'https://backendurl.com/site1', + ), + ).toBe('https://site1.publicurl.com/post-name-1/'); }); it('external url returns external url', () => {