Skip to content

Commit

Permalink
Fix external link badge display for tools hosted under the same domain (
Browse files Browse the repository at this point in the history
#1192)

**Related Ticket:** #1187

### Description of Changes

After discussing with @hanbyul-here during pod week, we identified two
potential solutions:

1. Introduce an isExternalLink prop in the .mdx configuration files for
each story, or,
2. Add logic in the card component to dynamically check the domain of
the link and decide whether it is external

Option 2 requires less configuration for developers/users of VEDA
instances, but it's not easy to test locally especially if we want more
fine-grained control over future integration tests. So the
implementation here goes for Option 1.

More context:
https://developmentseed.slack.com/archives/C063GD0NYP8/p1728407361439379

- Add a new `isLinkExternal` prop that can be passed to the story as a
configuration
- The existing check for `http/https (/^https?:\/\//)` remains in place
as a fallback if the new prop is not present

### Notes & Questions About Changes

### Validation / Testing
- Use the new `isLinkExternal` prop in some of the stories and validate
that it shows/hides the "External Link" badge in the UI
- Remove the `isLinkExternal` prop that you added and see if the UI
still behaves as expected (e.g if the link you added in the story .mdx
file has `https`, it will be marked as `External Link` in the UI cards)
- Click around the UI cards and verify that the links work as expected
  • Loading branch information
dzole0311 authored Oct 16, 2024
2 parents 9018639 + ed27b16 commit 46e1c5d
Show file tree
Hide file tree
Showing 11 changed files with 53 additions and 29 deletions.
12 changes: 8 additions & 4 deletions app/scripts/components/common/card/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,14 @@ 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,
multiply,
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';
Expand Down Expand Up @@ -227,6 +227,7 @@ export interface LinkProperties {

export interface LinkWithPathProperties extends LinkProperties {
linkTo: string;
isLinkExternal?: boolean;
}

export interface CardComponentBaseProps {
Expand All @@ -250,6 +251,7 @@ export interface CardComponentBaseProps {
export interface CardComponentPropsDeprecated extends CardComponentBaseProps {
linkTo: string;
onLinkClick?: MouseEventHandler;
isLinkExternal?: boolean;
}
export interface CardComponentProps extends CardComponentBaseProps {
linkProperties: LinkWithPathProperties;
Expand Down Expand Up @@ -287,23 +289,25 @@ 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 = /^https?:\/\//.test(linkProperties.linkTo);
const isExternalLink = linkProperties.isLinkExternal ?? /^https?:\/\//.test(linkProperties.linkTo);

return (
<ElementInteractive
linkProps={{
as: linkProperties.LinkElement,
[linkProperties.pathAttributeKeyName]: linkProperties.linkTo,
onLinkClick: linkProperties.onLinkClick,
isLinkExternal: isExternalLink
}}
as={CardItem}
cardType={cardType}
Expand Down
3 changes: 2 additions & 1 deletion app/scripts/components/common/featured-slider-section.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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={
Expand Down
4 changes: 3 additions & 1 deletion app/scripts/components/common/related-content.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ interface FormatBlock {
date: string;
link: string;
asLink?: LinkContentData;
isLinkExternal?: boolean;
parentLink: string;
media: Media;
parent: RelatedContentData['type'];
Expand Down Expand Up @@ -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={
Expand Down
16 changes: 9 additions & 7 deletions app/scripts/components/common/smart-link.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,18 @@ import { getLinkProps } from '$utils/url';

interface SmartLinkProps {
to: string;
isLinkExternal?: boolean;
onLinkClick?: ()=> void;
children?: ReactNode;
}

/**
* 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, onLinkClick, children, ...rest } = props;
const isExternalLink = /^https?:\/\//.test(to);
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 ? (
<a {...linkProps} {...rest}> {children} </a>
Expand All @@ -27,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 ? (
<a {...linkProps} {...rest} />
Expand Down
3 changes: 2 additions & 1 deletion app/scripts/components/home/featured-stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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]}
Expand Down
2 changes: 2 additions & 0 deletions app/scripts/components/sandbox/cards/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ function SandboxCards() {
<Card
linkLabel='View more'
linkTo='/'
isLinkExternal={false}
title='Cities Experiencing Clearer Air During Lockdowns'
description='Lorem ipsum dolor sit amet, consectetur adipiscing elit. Fusce varius erat in vulputate.'
date={new Date('2021-10-26')}
Expand All @@ -28,6 +29,7 @@ function SandboxCards() {
cardType='cover'
linkLabel='View more'
linkTo='/'
isLinkExternal={false}
title='Nitrogen Dioxide (NO₂)'
description='Lorem ipsum dolor sit amet, consectetur adipiscing elit. Fusce varius erat in vulputate.'
tagLabels={['Dataset']}
Expand Down
3 changes: 2 additions & 1 deletion app/scripts/components/stories/hub/hub-content.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -168,7 +168,8 @@ export default function HubContent(props:HubContentProps) {
linkProperties={{
linkTo: `${d.asLink?.url ?? d.path}`,
LinkElement,
pathAttributeKeyName
pathAttributeKeyName,
isLinkExternal: d.isLinkExternal
}}
title={
<TextHighlight
Expand Down
1 change: 1 addition & 0 deletions app/scripts/types/veda.ts
Original file line number Diff line number Diff line change
Expand Up @@ -206,6 +206,7 @@ export interface StoryData {
taxonomy: Taxonomy[];
related?: RelatedContentData[];
asLink?: LinkContentData;
isLinkExternal?: boolean;
isHidden?: boolean;
}

Expand Down
30 changes: 18 additions & 12 deletions app/scripts/utils/url.ts
Original file line number Diff line number Diff line change
@@ -1,24 +1,30 @@
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<LinkProps & React.RefAttributes<HTMLAnchorElement>>,
onClick?: (() => void) | MouseEventHandler
) => {
// Open the link in a new tab when link is external
const isExternalLink = /^https?:\/\//.test(linkTo);
return isExternalLink
linkTo: string,
isLinkExternal?: boolean,
as?: React.ForwardRefExoticComponent<
LinkProps & React.RefAttributes<HTMLAnchorElement>
>,
onClick?: (() => void) | MouseEventHandler
) => {
// Open the link in a new tab when link is external
const isExternalLink = isLinkExternal ?? /^https?:\/\//.test(linkTo);
return isExternalLink
? {
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 } : {})
};
};
};
3 changes: 2 additions & 1 deletion mock/stories/external-link-example.stories.mdx
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -24,4 +24,5 @@ related:
id: air-quality-and-covid-19
asLink:
url: 'https://developmentseed.org'
isLinkExternal: true
---
5 changes: 4 additions & 1 deletion parcel-resolver-veda/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -207,6 +207,7 @@ declare module 'veda' {
taxonomy: Taxonomy[];
related?: RelatedContentData[];
asLink?: LinkContentData;
isLinkExternal?: boolean;
isHidden?: boolean;
}

Expand Down Expand Up @@ -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: () =>
| {
Expand Down

0 comments on commit 46e1c5d

Please sign in to comment.