Skip to content

Commit

Permalink
Make YouTube Embeds GDPR compliant (#3253)
Browse files Browse the repository at this point in the history
* Make all youtube embeds gdpr compliant with on hover message
* add custom youtube transformer to gatsby-remark-embedder
* add needed js and css code in doc/blog components
  • Loading branch information
julieg18 authored and iesahin committed Apr 11, 2022
1 parent 44409f0 commit c6c57be
Show file tree
Hide file tree
Showing 9 changed files with 328 additions and 15 deletions.
59 changes: 59 additions & 0 deletions config/gatsby-remark-embedder/custom-yt-embedder.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
const shouldTransform = url => {
const { host, pathname, searchParams } = new URL(url)

return (
host === 'youtu.be' ||
(['youtube.com', 'www.youtube.com'].includes(host) &&
pathname.includes('/watch') &&
Boolean(searchParams.get('v')))
)
}

const getTimeValueInSeconds = timeValue => {
if (Number(timeValue).toString() === timeValue) {
return timeValue
}

const {
1: hours = '0',
2: minutes = '0',
3: seconds = '0'
} = timeValue.match(/(?:(\d*)h)?(?:(\d*)m)?(?:(\d*)s)?/)

return String((Number(hours) * 60 + Number(minutes)) * 60 + Number(seconds))
}

const getYouTubeIFrameSrc = urlString => {
const url = new URL(urlString)
const id =
url.host === 'youtu.be' ? url.pathname.slice(1) : url.searchParams.get('v')

const embedUrl = new URL(
`https://www.youtube-nocookie.com/embed/${id}?rel=0&&showinfo=0;`
)

url.searchParams.forEach((value, name) => {
if (name === 'v') {
return
}

if (name === 't') {
embedUrl.searchParams.append('start', getTimeValueInSeconds(value))
} else {
embedUrl.searchParams.append(name, value)
}
})
return embedUrl.toString()
}

// all code above taken from gatsby-remark-embedder (https://github.com/MichaelDeBoey/gatsby-remark-embedder)

const name = 'YouTubeCustom'

const getHTML = url => {
const iframeSrc = getYouTubeIFrameSrc(url)

return `<div class="yt-embed-wrapper"><iframe width="100%" height="315" src="${iframeSrc}" frameBorder="0" allow="autoplay; encrypted-media; gyroscope; picture-in-picture" allowFullScreen></iframe><div className="yt-embed-wrapper__overlay"><span class="yt-embed-wrapper__tooltip">By clicking play, you agree to YouTube&apos;s <a href="https://policies.google.com/u/3/privacy?hl=en" target="_blank" rel="nofollow noopener noreferrer">Privacy Policy</a> and <a href="https://www.youtube.com/static?template=terms" target="_blank" rel="nofollow noopener noreferrer">Terms of Service</a></span></div></div>`
}

module.exports = { getHTML, name, shouldTransform }
8 changes: 7 additions & 1 deletion gatsby-config.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ require('./config/prismjs/dvc')
require('./config/prismjs/usage')
require('./config/prismjs/dvctable')

const customYoutubeTransformer = require('./config/gatsby-remark-embedder/custom-yt-embedder')
const apiMiddleware = require('./src/server/middleware/api')
const redirectsMiddleware = require('./src/server/middleware/redirects')
const makeFeedHtml = require('./plugins/utils/makeFeedHtml')
Expand Down Expand Up @@ -70,7 +71,12 @@ const plugins = [
resolve: 'gatsby-transformer-remark',
options: {
plugins: [
'gatsby-remark-embedder',
{
resolve: 'gatsby-remark-embedder',
options: {
customTransformers: [customYoutubeTransformer]
}
},
'gatsby-remark-dvc-linker',
{
resolve: 'gatsby-remark-args-linker',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { navigate } from '@reach/router'
import Link from '../../../Link'
import Tutorials from '../../TutorialsLinks'
import { getPathWithSource } from '../../../../utils/shared/sidebar'
import useCustomYtEmbeds from '../../../../utils/front/useCustomYtEmbeds'

import 'github-markdown-css/github-markdown-light.css'
import * as sharedStyles from '../../styles.module.css'
Expand Down Expand Up @@ -43,6 +44,7 @@ const Main: React.FC<IMainProps> = ({
const touchstartXRef = useRef(0)
const touchendXRef = useRef(0)
const isCodeBlockRef = useRef(false)
useCustomYtEmbeds()
const handleSwipeGesture = useCallback(() => {
if (isCodeBlockRef.current) return

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -273,6 +273,60 @@
margin-left: 20px;
margin-right: 10px;
}

.yt-embed-wrapper {
position: relative;
display: flex;

&:hover &__tooltip {
opacity: 1;
}

&__overlay {
position: absolute;
bottom: 0;
left: 0;
width: 100%;
height: 100%;
display: flex;
align-items: flex-end;

&:hover {
cursor: pointer;
}

&.hidden {
display: none;
}
}

&__tooltip {
padding: 10px;
box-sizing: border-box;
color: #fff;
opacity: 0;
width: 100%;
font-size: 16px;
background-color: rgb(23 23 23 / 59%);
text-shadow: 0 1px 0 rgb(33 45 69 / 25%);
transition: opacity 0.2s ease-in-out;

&:hover {
cursor: auto;
}

a {
@mixin focus;

color: #fff;
text-decoration: underline;

&::after {
display: none;
}
}
}
}
}

.content {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import { useEffect } from 'react'

const hideAllEmbedOverlays = (embeds: NodeListOf<Element>) => {
embeds.forEach(embed => {
const overlay = embed.querySelector('.yt-embed-wrapper__overlay')
overlay?.classList.add('hidden')
})
}

const setUpEmbedClickListeners = (embeds: NodeListOf<Element>) => {
const removeClickListeners: Array<() => void> = []

embeds.forEach(embed => {
const iframe = embed.querySelector('iframe')
const overlay = embed.querySelector('.yt-embed-wrapper__overlay')
const tooltip = embed.querySelector('.yt-embed-wrapper__tooltip')

const handleOverlayClick = (event: MouseEvent) => {
if (event.target === tooltip || tooltip?.contains(event.target as Node)) {
return
}

if (iframe && iframe.src) {
iframe.src = iframe?.src + `&autoplay=1`
}
hideAllEmbedOverlays(embeds)
localStorage.setItem('yt-embed-consent', 'true')
}
const removeListener = () => {
overlay?.removeEventListener('click', handleOverlayClick as EventListener)
}
overlay?.addEventListener('click', handleOverlayClick as EventListener)
removeClickListeners.push(removeListener)
})

return () => {
removeClickListeners.forEach(rmListener => rmListener())
}
}

const useCustomYtEmbeds = () => {
useEffect(() => {
const hasUserGivenConsent = Boolean(
localStorage.getItem('yt-embed-consent')
)
const embeds = document.querySelectorAll('.yt-embed-wrapper')

if (hasUserGivenConsent) {
hideAllEmbedOverlays(embeds)
return
}

const cleanUpEventListeners = setUpEmbedClickListeners(embeds)

return () => {
cleanUpEventListeners()
}
}, [])
}

export default useCustomYtEmbeds
64 changes: 64 additions & 0 deletions src/components/Blog/Post/Markdown/styles.module.css
Original file line number Diff line number Diff line change
Expand Up @@ -564,4 +564,68 @@
border-bottom: none;
}
}

:global {
.yt-embed-wrapper {
position: relative;
display: flex;

&:hover &__tooltip {
opacity: 1;
}

&__overlay {
position: absolute;
bottom: 0;
left: 0;
width: 100%;
height: 100%;
display: flex;
align-items: flex-end;

&:hover {
cursor: pointer;
}

&.hidden {
display: none;
}
}

&__tooltip {
padding: 10px;
box-sizing: border-box;
color: #fff;
opacity: 0;
width: 100%;
font-size: 16px;
background-color: rgb(23 23 23 / 59%);
text-shadow: 0 1px 0 rgb(33 45 69 / 25%);
transition: opacity 0.2s ease-in-out;

&:hover {
cursor: auto;
}

a {
@mixin focus;

color: #fff;
text-decoration: underline;

&:hover {
opacity: 1;
}

&:focus {
position: static;
}

&::after {
display: none;
}
}
}
}
}
}
3 changes: 3 additions & 0 deletions src/components/Blog/Post/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { IBlogPostData } from '../../../templates/blog-post'
import { useCommentsCount } from 'gatsby-theme-iterative-docs/src/utils/front/api'
import { pluralizeComments } from 'gatsby-theme-iterative-docs/src/utils/front/i18n'
import tagToSlug from 'gatsby-theme-iterative-docs/src/utils/shared/tagToSlug'
import useCustomYtEmbeds from 'gatsby-theme-iterative-docs/src/utils/front/useCustomYtEmbeds'

import Markdown from './Markdown'
import FeedMeta from '../FeedMeta'
Expand Down Expand Up @@ -38,6 +39,8 @@ const Post: React.FC<IBlogPostData> = ({
const { width, height } = useWindowSize()
const { y } = useWindowScroll()

useCustomYtEmbeds()

const isFixed = useMemo(() => {
if (!wrapperRef.current) {
return false
Expand Down
53 changes: 39 additions & 14 deletions src/components/Home/UseCases/Video/index.tsx
Original file line number Diff line number Diff line change
@@ -1,36 +1,61 @@
import React, { useState, useCallback } from 'react'
import React, { useState, useCallback, useEffect } from 'react'

import TwoRowsButton from '../../../TwoRowsButton'
import { logEvent } from 'gatsby-theme-iterative-docs/src/utils/front/plausible'

import Link from 'gatsby-theme-iterative-docs/src/components/Link'

import * as styles from './styles.module.css'

const Video: React.FC<{ id: string }> = ({ id }) => {
const [isWatching, setWatching] = useState(false)
const [hasUserGivenConsent, setHasUserGivenConsent] = useState(false)

useEffect(() => {
const givenConsent = Boolean(localStorage.getItem('yt-embed-consent'))

setHasUserGivenConsent(givenConsent)
}, [])

const watchVideo = useCallback(() => {
logEvent('Button', { Item: 'video' })
setWatching(true)
localStorage.setItem('yt-embed-consent', 'true')
}, [])

return (
<div className={styles.container}>
<div className={styles.handler}>
{!isWatching && (
<div className={styles.overlay}>
<TwoRowsButton
mode="azure"
title="Watch video"
description="How it works"
icon={
<img
className={styles.buttonIcon}
src="/img/watch_white.svg"
alt="Watch video"
/>
}
onClick={watchVideo}
/>
<div className={styles.content}>
<TwoRowsButton
mode="azure"
title="Watch video"
description="How it works"
className={styles.button}
icon={
<img
className={styles.buttonIcon}
src="/img/watch_white.svg"
alt="Watch video"
/>
}
onClick={watchVideo}
/>
{!hasUserGivenConsent && (
<div className={styles.tooltip}>
By clicking play, you agree to YouTube&apos;s{' '}
<Link href="https://policies.google.com/u/3/privacy?hl=en">
Privacy Policy
</Link>{' '}
and{' '}
<Link href="https://www.youtube.com/static?template=terms">
Terms of Service
</Link>
</div>
)}
</div>
</div>
)}
<iframe
Expand Down
Loading

0 comments on commit c6c57be

Please sign in to comment.