diff --git a/src/components/content/rich-text/rich-text.tsx b/src/components/content/rich-text/rich-text.tsx index bf43d7407..e6eccfe35 100644 --- a/src/components/content/rich-text/rich-text.tsx +++ b/src/components/content/rich-text/rich-text.tsx @@ -76,13 +76,19 @@ export const ContentfulRichText = ({ data }: ContentfulRichTextProps) => { [MARKS.CODE]: (text) => {text}, }, renderNode: { - [INLINES.HYPERLINK]: (props, children) => - customComponents.a({ + [INLINES.HYPERLINK]: (props, children) => { + const uri = props.data.uri; + if (uri.startsWith('youtube://')) { + const videoId = uri.split('//')[1]; + return customComponents.youtube({ videoId }); + } + return customComponents.a({ children, - href: props.data.uri, + href: uri, target: '_blank', rel: 'nofollow noopener noreferrer', - }), + }); + }, [INLINES.EMBEDDED_ENTRY]: (node) => { const { __typename } = node.data.target; switch (__typename) { diff --git a/src/components/legacy/markdown/custom-components.tsx b/src/components/legacy/markdown/custom-components.tsx index a0d33b79b..e4f6a2d5e 100644 --- a/src/components/legacy/markdown/custom-components.tsx +++ b/src/components/legacy/markdown/custom-components.tsx @@ -8,6 +8,7 @@ import { TextStyles } from '../../typography'; import { Quote } from '../../ui/quote/quote'; import { Link } from '../links/links'; import { WithAnchorHOC } from '../../layout/with-anchor-hoc'; +import { YoutubeEmbed } from './youtube-embed'; /** * Override markdown generated html content with custom React components (for us mostly to pass in custom styling) @@ -235,6 +236,9 @@ const customSatellytesComponents = { const { children, ...rest } = props; return
{children}
; }, + youtube(props) { + return ; + }, }; export default customSatellytesComponents; diff --git a/src/components/legacy/markdown/youtube-embed.tsx b/src/components/legacy/markdown/youtube-embed.tsx new file mode 100644 index 000000000..c69bdfcef --- /dev/null +++ b/src/components/legacy/markdown/youtube-embed.tsx @@ -0,0 +1,96 @@ +import React, { useContext } from 'react'; +import styled from 'styled-components'; +import { theme } from '../../layout/theme'; +import { Button } from '../../ui/buttons/button'; +import { TextStyles } from '../../typography'; +import YouTubeConsentContext from '../../../context/youtube-consent-context'; +import { Link } from '../links/links'; + +interface YoutubeEmbedProps { + videoId: string; +} + +// how to make iframe responsive: https://stackoverflow.com/questions/17838607/making-an-iframe-responsive +const StyledIframe = styled.iframe` + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; +`; + +const YoutubeEmbedWrapper = styled.div` + position: relative; + padding-bottom: 56.25%; /* 16:9 */ + padding-top: 25px; + height: 0; +`; + +const YouTubePrivacyBannerWrapper = styled.div` + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + padding: 16px; + gap: 16px; + background-color: ${theme.palette.background.card}; + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; +`; + +const LegalText = styled.p` + ${TextStyles.label} + text-align: center; +`; + +const StyledButton = styled(Button)` + width: fit-content; +`; + +const SyledLink = styled(Link)` + color: ${theme.palette.text.link.default}; + + &:hover { + border-bottom: 1px solid ${theme.palette.text.link.default}; + } +`; + +export const YoutubeEmbed = ({ videoId }: YoutubeEmbedProps) => { + const { consentGiven, giveConsent } = useContext(YouTubeConsentContext); + + return ( + + {consentGiven ? ( + + ) : ( + + )} + + ); +}; + +const YouTubePrivacyBanner = ({ giveConsent }) => { + return ( + + + By clicking on "Show YouTube Content" you accept{' '} + + YouTube's privacy policy + + . + + Show YouTube Content + + ); +}; diff --git a/src/components/pages/blog-post/blog-post.tsx b/src/components/pages/blog-post/blog-post.tsx index 8e913020e..41a44a654 100644 --- a/src/components/pages/blog-post/blog-post.tsx +++ b/src/components/pages/blog-post/blog-post.tsx @@ -12,6 +12,7 @@ import FollowPanel from './follow-panel'; import SharePanel from './share-panel'; import { ContentfulRichText } from '../../content/rich-text/rich-text'; import { TextStyles } from '../../typography'; +import { YouTubeConsentProvider } from '../../../context/youtube-consent-context'; interface BlogPostPageProps { blogPost: BlogArticleQueryData; @@ -63,41 +64,43 @@ export const BlogPostPage = ({ const heroByLine = `${formattedDate} • ${readingTime} • ${byLine}`; return ( - - } - leadbox={leadbox} - showLanguageSwitch={false} - breadcrumb={breadcrumb} - > - - {blogPost.introRichText && ( - - - - )} - + + + } + leadbox={leadbox} + showLanguageSwitch={false} + breadcrumb={breadcrumb} + > + + {blogPost.introRichText && ( + + + + )} + - + - - - - - + + + + + + ); }; diff --git a/src/context/youtube-consent-context.tsx b/src/context/youtube-consent-context.tsx new file mode 100644 index 000000000..10ecb4d37 --- /dev/null +++ b/src/context/youtube-consent-context.tsx @@ -0,0 +1,41 @@ +import React, { createContext, useState, useEffect, ReactNode } from 'react'; + +interface YouTubeConsentContextType { + consentGiven: boolean; + giveConsent: () => void; +} + +const YouTubeConsentContext = createContext({ + consentGiven: false, + giveConsent: () => {}, +}); + +interface YouTubeConsentProviderProps { + children: ReactNode; +} + +export const YouTubeConsentProvider = ({ + children, +}: YouTubeConsentProviderProps) => { + const [consentGiven, setConsentGiven] = useState(false); + + useEffect(() => { + const consent = localStorage.getItem('youtube-consent'); + if (consent === 'true') { + setConsentGiven(true); + } + }, []); + + const giveConsent = () => { + setConsentGiven(true); + localStorage.setItem('youtube-consent', 'true'); + }; + + return ( + + {children} + + ); +}; + +export default YouTubeConsentContext;