From 8819981982ce957b6b188b2bf0bc5dc84a45cc29 Mon Sep 17 00:00:00 2001 From: Gjore Milevski Date: Mon, 14 Oct 2024 13:17:13 +0200 Subject: [PATCH 1/3] Fix external link badge display for tools hosted under the same domain --- app/scripts/components/common/card/index.tsx | 9 +++---- app/scripts/components/common/smart-link.tsx | 8 +++--- app/scripts/utils/url.ts | 28 +++++++++++--------- 3 files changed, 23 insertions(+), 22 deletions(-) diff --git a/app/scripts/components/common/card/index.tsx b/app/scripts/components/common/card/index.tsx index c9b34a2fe..3cad69412 100644 --- a/app/scripts/components/common/card/index.tsx +++ b/app/scripts/components/common/card/index.tsx @@ -2,7 +2,6 @@ import React, { lazy, MouseEventHandler, ComponentType } from 'react'; import styled, { css } from 'styled-components'; import { format } from 'date-fns'; import { CollecticonExpandTopRight } from '@devseed-ui/collecticons'; -const SmartLink = lazy(() => import('../smart-link')); import { glsp, media, @@ -10,12 +9,14 @@ import { themeVal, listReset, } from '@devseed-ui/theme-provider'; +const SmartLink = lazy(() => import('../smart-link')); import { CardBody, CardBlank, CardHeader, CardHeadline, CardTitle, CardOverline } from './styles'; import HorizontalInfoCard, { HorizontalCardStyles } from './horizontal-info-card'; import { variableBaseType, variableGlsp } from '$styles/variable-utils'; import { ElementInteractive } from '$components/common/element-interactive'; import { Figure } from '$components/common/figure'; +import { isExternalLink } from '$utils/url'; type CardType = 'classic' | 'cover' | 'featured' | 'horizontal-info'; @@ -296,8 +297,6 @@ function CardComponent(props: CardComponentPropsType) { }; } - const isExternalLink = /^https?:\/\//.test(linkProperties.linkTo); - return ( {title} - {isExternalLink && } - {!isExternalLink && tagLabels && parentTo && ( + {isExternalLink(linkProperties.linkTo) && } + {!isExternalLink(linkProperties.linkTo) && tagLabels && parentTo && ( tagLabels.map((label) => ( {label} diff --git a/app/scripts/components/common/smart-link.tsx b/app/scripts/components/common/smart-link.tsx index 62c2d61f8..0927b1464 100644 --- a/app/scripts/components/common/smart-link.tsx +++ b/app/scripts/components/common/smart-link.tsx @@ -1,7 +1,7 @@ import React, { ReactNode } from 'react'; import { Link } from 'react-router-dom'; -import { getLinkProps } from '$utils/url'; +import { getLinkProps, isExternalLink } from '$utils/url'; interface SmartLinkProps { to: string; @@ -14,10 +14,9 @@ interface SmartLinkProps { */ export default function SmartLink(props: SmartLinkProps) { const { to, onLinkClick, children, ...rest } = props; - const isExternalLink = /^https?:\/\//.test(to); const linkProps = getLinkProps(to, undefined, onLinkClick); - return isExternalLink ? ( + return isExternalLink(to) ? ( {children} ) : ( {children} @@ -34,9 +33,8 @@ interface CustomLinkProps { */ export function CustomLink(props: CustomLinkProps) { const { href, ...rest } = props; - const isExternalLink = /^https?:\/\//.test(href); const linkProps = getLinkProps(href); - return isExternalLink ? ( + return isExternalLink(href) ? ( ) : ( diff --git a/app/scripts/utils/url.ts b/app/scripts/utils/url.ts index 0d869889e..bae6705f4 100644 --- a/app/scripts/utils/url.ts +++ b/app/scripts/utils/url.ts @@ -1,24 +1,28 @@ import React, { MouseEventHandler } from 'react'; import { LinkProps } from 'react-router-dom'; +export function isExternalLink(link: string): boolean { + return /^https?:\/\//.test(link) && !link.includes(window.location.hostname); +} export const getLinkProps = ( - linkTo: string, - as?: React.ForwardRefExoticComponent>, - onClick?: (() => void) | MouseEventHandler - ) => { - // Open the link in a new tab when link is external - const isExternalLink = /^https?:\/\//.test(linkTo); - return isExternalLink + linkTo: string, + as?: React.ForwardRefExoticComponent< + LinkProps & React.RefAttributes + >, + onClick?: (() => void) | MouseEventHandler +) => { + // Open the link in a new tab when link is external + return isExternalLink(linkTo) ? { href: linkTo, to: linkTo, - ...{target: '_blank', rel: 'noopener noreferrer'}, - ...(onClick ? {onClick: onClick} : {}) + ...{ target: '_blank', rel: 'noopener noreferrer' }, + ...(onClick ? { onClick: onClick } : {}) } : { - ...(as ? {as: as} : {}), + ...(as ? { as: as } : {}), to: linkTo, - ...(onClick ? {onClick: onClick} : {}) + ...(onClick ? { onClick: onClick } : {}) }; - }; +}; From 218f651dc893c6410cf56ba707296a173fed1530 Mon Sep 17 00:00:00 2001 From: Gjore Milevski Date: Mon, 14 Oct 2024 14:19:31 +0200 Subject: [PATCH 2/3] Actually, add a isLinkExternal prop instead of only relying on url checking --- app/scripts/components/common/card/index.tsx | 14 +++++++++----- .../components/common/featured-slider-section.tsx | 3 ++- app/scripts/components/common/related-content.tsx | 4 +++- app/scripts/components/common/smart-link.tsx | 13 ++++++++----- app/scripts/components/home/featured-stories.tsx | 3 ++- app/scripts/components/sandbox/cards/index.js | 2 ++ app/scripts/components/stories/hub/hub-content.tsx | 3 ++- app/scripts/types/veda.ts | 1 + app/scripts/utils/url.ts | 4 +++- mock/stories/external-link-example.stories.mdx | 3 ++- parcel-resolver-veda/index.d.ts | 5 ++++- 11 files changed, 38 insertions(+), 17 deletions(-) diff --git a/app/scripts/components/common/card/index.tsx b/app/scripts/components/common/card/index.tsx index 3cad69412..f4bcdd3c1 100644 --- a/app/scripts/components/common/card/index.tsx +++ b/app/scripts/components/common/card/index.tsx @@ -16,7 +16,6 @@ import HorizontalInfoCard, { HorizontalCardStyles } from './horizontal-info-card import { variableBaseType, variableGlsp } from '$styles/variable-utils'; import { ElementInteractive } from '$components/common/element-interactive'; import { Figure } from '$components/common/figure'; -import { isExternalLink } from '$utils/url'; type CardType = 'classic' | 'cover' | 'featured' | 'horizontal-info'; @@ -228,6 +227,7 @@ export interface LinkProperties { export interface LinkWithPathProperties extends LinkProperties { linkTo: string; + isLinkExternal?: boolean; } export interface CardComponentBaseProps { @@ -251,6 +251,7 @@ export interface CardComponentBaseProps { export interface CardComponentPropsDeprecated extends CardComponentBaseProps { linkTo: string; onLinkClick?: MouseEventHandler; + isLinkExternal?: boolean; } export interface CardComponentProps extends CardComponentBaseProps { linkProperties: LinkWithPathProperties; @@ -288,15 +289,18 @@ function CardComponent(props: CardComponentPropsType) { const { linkProperties: linkPropertiesProps } = props; linkProperties = linkPropertiesProps; } else { - const { linkTo, onLinkClick, } = props; + const { linkTo, onLinkClick, isLinkExternal } = props; linkProperties = { linkTo, onLinkClick, pathAttributeKeyName: 'to', - LinkElement: SmartLink + LinkElement: SmartLink, + isLinkExternal }; } + const isExternalLink = linkProperties.isLinkExternal ?? /^https?:\/\//.test(linkProperties.linkTo); + return ( {title} - {isExternalLink(linkProperties.linkTo) && } - {!isExternalLink(linkProperties.linkTo) && tagLabels && parentTo && ( + {isExternalLink && } + {!isExternalLink && tagLabels && parentTo && ( tagLabels.map((label) => ( {label} diff --git a/app/scripts/components/common/featured-slider-section.tsx b/app/scripts/components/common/featured-slider-section.tsx index 824d1e2ce..e70a00b15 100644 --- a/app/scripts/components/common/featured-slider-section.tsx +++ b/app/scripts/components/common/featured-slider-section.tsx @@ -110,7 +110,8 @@ function FeaturedSliderSection(props: FeaturedSliderSectionProps) { linkProperties={{ linkTo: `${d.asLink?.url ?? getItemPath(d)}`, pathAttributeKeyName: 'to', - LinkElement: SmartLink + LinkElement: SmartLink, + isLinkExternal: d.isLinkExternal }} title={d.name} overline={ diff --git a/app/scripts/components/common/related-content.tsx b/app/scripts/components/common/related-content.tsx index 79fcfbc99..b78e0a9ca 100644 --- a/app/scripts/components/common/related-content.tsx +++ b/app/scripts/components/common/related-content.tsx @@ -52,6 +52,7 @@ interface FormatBlock { date: string; link: string; asLink?: LinkContentData; + isLinkExternal?: boolean; parentLink: string; media: Media; parent: RelatedContentData['type']; @@ -151,7 +152,8 @@ export default function RelatedContent(props: RelatedContentProps) { linkProperties={{ linkTo: `${t.asLink?.url ?? t.link}`, LinkElement: SmartLink, - pathAttributeKeyName: 'to' + pathAttributeKeyName: 'to', + isLinkExternal: t.isLinkExternal }} title={t.name} date={ diff --git a/app/scripts/components/common/smart-link.tsx b/app/scripts/components/common/smart-link.tsx index 0927b1464..04423658e 100644 --- a/app/scripts/components/common/smart-link.tsx +++ b/app/scripts/components/common/smart-link.tsx @@ -1,10 +1,11 @@ import React, { ReactNode } from 'react'; import { Link } from 'react-router-dom'; -import { getLinkProps, isExternalLink } from '$utils/url'; +import { getLinkProps } from '$utils/url'; interface SmartLinkProps { to: string; + isLinkExternal?: boolean; onLinkClick?: ()=> void; children?: ReactNode; } @@ -13,10 +14,11 @@ interface SmartLinkProps { * Switches between a `a` and a `Link` depending on the url. */ export default function SmartLink(props: SmartLinkProps) { - const { to, onLinkClick, children, ...rest } = props; - const linkProps = getLinkProps(to, undefined, onLinkClick); + const { to, isLinkExternal, onLinkClick, children, ...rest } = props; + const isExternalLink = isLinkExternal ?? /^https?:\/\//.test(to); + const linkProps = getLinkProps(to, isLinkExternal, undefined, onLinkClick); - return isExternalLink(to) ? ( + return isExternalLink ? ( {children} ) : ( {children} @@ -33,8 +35,9 @@ interface CustomLinkProps { */ export function CustomLink(props: CustomLinkProps) { const { href, ...rest } = props; + const isExternalLink = /^https?:\/\//.test(href); const linkProps = getLinkProps(href); - return isExternalLink(href) ? ( + return isExternalLink ? ( ) : ( diff --git a/app/scripts/components/home/featured-stories.tsx b/app/scripts/components/home/featured-stories.tsx index a8bb557a7..23d1c1f23 100644 --- a/app/scripts/components/home/featured-stories.tsx +++ b/app/scripts/components/home/featured-stories.tsx @@ -82,7 +82,8 @@ function FeaturedStories() { linkProperties={{ linkTo: `${d.asLink?.url ?? getStoryPath(d)}`, LinkElement: SmartLink, - pathAttributeKeyName: 'to' + pathAttributeKeyName: 'to', + isLinkExternal: d.isLinkExternal }} title={d.name} tagLabels={[getString('stories').one]} diff --git a/app/scripts/components/sandbox/cards/index.js b/app/scripts/components/sandbox/cards/index.js index 834473e9f..dbd59add9 100644 --- a/app/scripts/components/sandbox/cards/index.js +++ b/app/scripts/components/sandbox/cards/index.js @@ -13,6 +13,7 @@ function SandboxCards() { >, onClick?: (() => void) | MouseEventHandler ) => { // Open the link in a new tab when link is external - return isExternalLink(linkTo) + const isExternalLink = isLinkExternal ?? /^https?:\/\//.test(linkTo); + return isExternalLink ? { href: linkTo, to: linkTo, diff --git a/mock/stories/external-link-example.stories.mdx b/mock/stories/external-link-example.stories.mdx index 184932fd7..51a5b85d9 100644 --- a/mock/stories/external-link-example.stories.mdx +++ b/mock/stories/external-link-example.stories.mdx @@ -1,7 +1,7 @@ --- featured: true id: 'external-link-test' -name: External Link Test +name: External Link Test description: Story to test external link media: src: ::file ./img-placeholder-6.jpg @@ -24,4 +24,5 @@ related: id: air-quality-and-covid-19 asLink: url: 'https://developmentseed.org' +isLinkExternal: true --- diff --git a/parcel-resolver-veda/index.d.ts b/parcel-resolver-veda/index.d.ts index 072384819..52083c4dd 100644 --- a/parcel-resolver-veda/index.d.ts +++ b/parcel-resolver-veda/index.d.ts @@ -207,6 +207,7 @@ declare module 'veda' { taxonomy: Taxonomy[]; related?: RelatedContentData[]; asLink?: LinkContentData; + isLinkExternal?: boolean; isHidden?: boolean; } @@ -339,7 +340,9 @@ declare module 'veda' { export const getBoolean: (variable: string) => boolean; export const getBannerFromVedaConfig: () => BannerData | undefined; - export const getCookieConsentFromVedaConfig: () => CookieConsentData| undefined; + export const getCookieConsentFromVedaConfig: () => + | CookieConsentData + | undefined; export const getNavItemsFromVedaConfig: () => | { From ed27b168707a1293be7dff7f147f0f0dc4cc8e23 Mon Sep 17 00:00:00 2001 From: Gjore Milevski Date: Wed, 16 Oct 2024 15:28:28 +0200 Subject: [PATCH 3/3] Update prop and comments --- app/scripts/components/common/card/index.tsx | 1 + app/scripts/components/common/smart-link.tsx | 9 +++++---- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/app/scripts/components/common/card/index.tsx b/app/scripts/components/common/card/index.tsx index f4bcdd3c1..3de3b603d 100644 --- a/app/scripts/components/common/card/index.tsx +++ b/app/scripts/components/common/card/index.tsx @@ -307,6 +307,7 @@ function CardComponent(props: CardComponentPropsType) { as: linkProperties.LinkElement, [linkProperties.pathAttributeKeyName]: linkProperties.linkTo, onLinkClick: linkProperties.onLinkClick, + isLinkExternal: isExternalLink }} as={CardItem} cardType={cardType} diff --git a/app/scripts/components/common/smart-link.tsx b/app/scripts/components/common/smart-link.tsx index 04423658e..09a5a1f88 100644 --- a/app/scripts/components/common/smart-link.tsx +++ b/app/scripts/components/common/smart-link.tsx @@ -11,7 +11,7 @@ interface SmartLinkProps { } /** - * Switches between a `a` and a `Link` depending on the url. + * Switches between a `a` and a `Link` depending on the optional `isLinkExternal` prop or the url. */ export default function SmartLink(props: SmartLinkProps) { const { to, isLinkExternal, onLinkClick, children, ...rest } = props; @@ -28,14 +28,15 @@ export default function SmartLink(props: SmartLinkProps) { interface CustomLinkProps { href: string; + isLinkExternal?: boolean; } /** - * For links defined as markdown, this component will open the external link in a new tab. + * For links defined as markdown, this component will open the external link in a new tab depending on the optional `isLinkExternal` prop or the url. */ export function CustomLink(props: CustomLinkProps) { - const { href, ...rest } = props; - const isExternalLink = /^https?:\/\//.test(href); + const { href, isLinkExternal, ...rest } = props; + const isExternalLink = isLinkExternal ?? /^https?:\/\//.test(href); const linkProps = getLinkProps(href); return isExternalLink ? (