parent
07c5854649
commit
506f2ffe09
|
@ -177,3 +177,7 @@ ul li > p {
|
||||||
.embed-content blockquote > a {
|
.embed-content blockquote > a {
|
||||||
@apply cursor-pointer text-blue hover:underline;
|
@apply cursor-pointer text-blue hover:underline;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.embed-content iframe {
|
||||||
|
@apply mb-6;
|
||||||
|
}
|
||||||
|
|
|
@ -1,8 +1,10 @@
|
||||||
import { IEmbed, INoembed, isNoembed } from '@/services/embed';
|
import { IEmbed, INoembed, isNoembed } from '@/services/embed';
|
||||||
import { ReactElement, useEffect, useRef } from 'react';
|
import { ReactElement, useEffect, useRef, useState } from 'react';
|
||||||
|
|
||||||
|
import { Button } from './Button';
|
||||||
import Image from 'next/image';
|
import Image from 'next/image';
|
||||||
import Link from 'next/link';
|
import Link from 'next/link';
|
||||||
|
import { TOS } from '@/constants';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
|
@ -13,15 +15,80 @@ interface Props {
|
||||||
export default function EmbedContent(props: Props): ReactElement {
|
export default function EmbedContent(props: Props): ReactElement {
|
||||||
const { content, classes } = props;
|
const { content, classes } = props;
|
||||||
const htmlRef = useRef<HTMLDivElement>(null);
|
const htmlRef = useRef<HTMLDivElement>(null);
|
||||||
|
const [allowExternalContent, setAllowExternalContent] = useState(false);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (isNoembed(content) && null !== htmlRef.current) {
|
if (isNoembed(content) && null !== htmlRef.current) {
|
||||||
htmlRef.current.innerHTML = content.html;
|
htmlRef.current.innerHTML = content.html;
|
||||||
}
|
}
|
||||||
}, [content]);
|
if (allowExternalContent) {
|
||||||
|
if (isNoembed(content) && null !== htmlRef.current) {
|
||||||
|
htmlRef.current.innerHTML = content.html;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, [allowExternalContent, content]);
|
||||||
|
|
||||||
if (isNoembed(content)) {
|
if (isNoembed(content)) {
|
||||||
return (
|
if (content.isExternalVideo) {
|
||||||
<div className={classNames('embed-content', classes)} ref={htmlRef}></div>
|
return allowExternalContent ? (
|
||||||
);
|
<div
|
||||||
|
className={classNames('embed-content', classes)}
|
||||||
|
ref={htmlRef}
|
||||||
|
></div>
|
||||||
|
) : (
|
||||||
|
<div
|
||||||
|
className={classNames(
|
||||||
|
'embed-content w-full border border-primary my-6 mx-auto max-w-sm p-6',
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<p
|
||||||
|
className={classNames(
|
||||||
|
'text-sm font-semibold mb-2 leading-relaxed',
|
||||||
|
'tablet:text-base',
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
This content is hosted by {content.site_name}.
|
||||||
|
</p>
|
||||||
|
<p
|
||||||
|
className={classNames(
|
||||||
|
'text-xs font-normal leading-relaxed mb-4',
|
||||||
|
'tablet:text-sm',
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
By showing the external content you accept their{' '}
|
||||||
|
{TOS[content.site_name] && TOS[content.site_name].length > 0 ? (
|
||||||
|
<a
|
||||||
|
href={TOS[content.site_name]}
|
||||||
|
target="_blank"
|
||||||
|
rel="noreferrer"
|
||||||
|
className={classNames('text-primary font-semibold')}
|
||||||
|
>
|
||||||
|
Terms and Conditions
|
||||||
|
</a>
|
||||||
|
) : (
|
||||||
|
'Terms and Conditions'
|
||||||
|
)}
|
||||||
|
.
|
||||||
|
</p>
|
||||||
|
<Button
|
||||||
|
size="large"
|
||||||
|
onClick={() => {
|
||||||
|
setAllowExternalContent(true);
|
||||||
|
}}
|
||||||
|
className={'block ml-auto'}
|
||||||
|
>
|
||||||
|
Show
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className={classNames('embed-content', classes)}
|
||||||
|
ref={htmlRef}
|
||||||
|
></div>
|
||||||
|
);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
return (
|
return (
|
||||||
<Link href={content.url}>
|
<Link href={content.url}>
|
||||||
|
|
|
@ -2,6 +2,7 @@ import CMS from '@/constants/cms';
|
||||||
import METADATA from '@/constants/metadata';
|
import METADATA from '@/constants/metadata';
|
||||||
import NAVIGATION from '@/constants/navigation';
|
import NAVIGATION from '@/constants/navigation';
|
||||||
import SEARCH from '@/constants/search';
|
import SEARCH from '@/constants/search';
|
||||||
|
import TOS from './tos';
|
||||||
import UI from '@/constants/ui';
|
import UI from '@/constants/ui';
|
||||||
|
|
||||||
export { UI, CMS, NAVIGATION, METADATA, SEARCH };
|
export { CMS, METADATA, NAVIGATION, SEARCH, TOS, UI };
|
||||||
|
|
|
@ -0,0 +1,6 @@
|
||||||
|
const TOS: Record<string, string> = {
|
||||||
|
Vimeo: 'https://vimeo.com/terms',
|
||||||
|
YouTube: 'https://www.youtube.com/static?template=terms',
|
||||||
|
};
|
||||||
|
|
||||||
|
export default TOS;
|
|
@ -7,10 +7,10 @@ const ContentSecurityPolicy = `
|
||||||
script-src 'self' ${
|
script-src 'self' ${
|
||||||
process.env.NODE_ENV == 'development' ? "'unsafe-eval' " : ''
|
process.env.NODE_ENV == 'development' ? "'unsafe-eval' " : ''
|
||||||
}'unsafe-inline' *.ctfassets.net *.youtube.com *.twitter.com;
|
}'unsafe-inline' *.ctfassets.net *.youtube.com *.twitter.com;
|
||||||
child-src 'self' *.ctfassets.net *.youtube.com *.twitter.com;
|
child-src 'self' *.ctfassets.net *.youtube.com player.vimeo.com *.twitter.com;
|
||||||
style-src 'self' 'unsafe-inline' *.googleapis.com;
|
style-src 'self' 'unsafe-inline' *.googleapis.com;
|
||||||
img-src 'self' blob: data: *.ctfassets.net *.youtube.com *.twitter.com;
|
img-src 'self' blob: data: *.ctfassets.net *.youtube.com *.twitter.com;
|
||||||
media-src 'self';
|
media-src 'self' *.youtube.com;
|
||||||
connect-src *;
|
connect-src *;
|
||||||
font-src 'self' blob: data: fonts.gstatic.com maxcdn.bootstrapcdn.com;
|
font-src 'self' blob: data: fonts.gstatic.com maxcdn.bootstrapcdn.com;
|
||||||
worker-src 'self' blob:;
|
worker-src 'self' blob:;
|
||||||
|
|
|
@ -7,10 +7,10 @@ export interface IEmbed {
|
||||||
description?: string;
|
description?: string;
|
||||||
site_name?: string;
|
site_name?: string;
|
||||||
image?: string;
|
image?: string;
|
||||||
|
isExternalVideo?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
function extractMetadata(html: string): IEmbed {
|
function extractMetadata(html: string): IEmbed {
|
||||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
|
||||||
const himalaya = require('himalaya');
|
const himalaya = require('himalaya');
|
||||||
html = html.trim();
|
html = html.trim();
|
||||||
const nodes = himalaya.parse(html);
|
const nodes = himalaya.parse(html);
|
||||||
|
@ -95,7 +95,9 @@ export function isNoembed(object: unknown): object is INoembed {
|
||||||
export async function fetchContent(
|
export async function fetchContent(
|
||||||
targetUrl: string,
|
targetUrl: string,
|
||||||
): Promise<IEmbed | INoembed> {
|
): Promise<IEmbed | INoembed> {
|
||||||
const fetchUrl = `https://noembed.com/embed?url=${targetUrl}`;
|
const fetchUrl = `https://noembed.com/embed?url=${encodeURIComponent(
|
||||||
|
targetUrl,
|
||||||
|
)}`;
|
||||||
const response = await fetch(fetchUrl);
|
const response = await fetch(fetchUrl);
|
||||||
let data = await response.json();
|
let data = await response.json();
|
||||||
|
|
||||||
|
@ -118,10 +120,43 @@ export async function fetchContent(
|
||||||
}
|
}
|
||||||
|
|
||||||
function convertToNoembed(rawData: any): INoembed {
|
function convertToNoembed(rawData: any): INoembed {
|
||||||
return {
|
const noembed: INoembed = {
|
||||||
title: sanitize(rawData.title),
|
title: sanitize(rawData.title),
|
||||||
url: sanitize(rawData.url),
|
url: sanitize(rawData.url),
|
||||||
site_name: sanitize(rawData.provider_name),
|
site_name: sanitize(rawData.provider_name),
|
||||||
html: sanitize(rawData.html),
|
html: '',
|
||||||
};
|
};
|
||||||
|
|
||||||
|
switch (noembed.site_name) {
|
||||||
|
case 'Vimeo':
|
||||||
|
case 'YouTube':
|
||||||
|
const himalaya = require('himalaya');
|
||||||
|
|
||||||
|
let nodes = himalaya.parse(rawData.html);
|
||||||
|
nodes[0].attributes = nodes[0].attributes.map((attr: any) => {
|
||||||
|
switch (attr.key) {
|
||||||
|
case 'width':
|
||||||
|
attr.value = '100%';
|
||||||
|
break;
|
||||||
|
case 'height':
|
||||||
|
// Vimeo player has a fixed height
|
||||||
|
if (noembed.site_name !== 'Vimeo') {
|
||||||
|
attr.value = '350px';
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return attr;
|
||||||
|
});
|
||||||
|
|
||||||
|
noembed.html = himalaya.stringify(nodes);
|
||||||
|
noembed.isExternalVideo = true;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
noembed.html = sanitize(rawData.html);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return noembed;
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue