parent
07c5854649
commit
506f2ffe09
|
@ -177,3 +177,7 @@ ul li > p {
|
|||
.embed-content blockquote > a {
|
||||
@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 { ReactElement, useEffect, useRef } from 'react';
|
||||
import { ReactElement, useEffect, useRef, useState } from 'react';
|
||||
|
||||
import { Button } from './Button';
|
||||
import Image from 'next/image';
|
||||
import Link from 'next/link';
|
||||
import { TOS } from '@/constants';
|
||||
import classNames from 'classnames';
|
||||
|
||||
interface Props {
|
||||
|
@ -13,15 +15,80 @@ interface Props {
|
|||
export default function EmbedContent(props: Props): ReactElement {
|
||||
const { content, classes } = props;
|
||||
const htmlRef = useRef<HTMLDivElement>(null);
|
||||
const [allowExternalContent, setAllowExternalContent] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
if (isNoembed(content) && null !== htmlRef.current) {
|
||||
htmlRef.current.innerHTML = content.html;
|
||||
}
|
||||
}, [content]);
|
||||
if (allowExternalContent) {
|
||||
if (isNoembed(content) && null !== htmlRef.current) {
|
||||
htmlRef.current.innerHTML = content.html;
|
||||
}
|
||||
}
|
||||
}, [allowExternalContent, content]);
|
||||
|
||||
if (isNoembed(content)) {
|
||||
return (
|
||||
<div className={classNames('embed-content', classes)} ref={htmlRef}></div>
|
||||
);
|
||||
if (content.isExternalVideo) {
|
||||
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 {
|
||||
return (
|
||||
<Link href={content.url}>
|
||||
|
|
|
@ -2,6 +2,7 @@ import CMS from '@/constants/cms';
|
|||
import METADATA from '@/constants/metadata';
|
||||
import NAVIGATION from '@/constants/navigation';
|
||||
import SEARCH from '@/constants/search';
|
||||
import TOS from './tos';
|
||||
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' ${
|
||||
process.env.NODE_ENV == 'development' ? "'unsafe-eval' " : ''
|
||||
}'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;
|
||||
img-src 'self' blob: data: *.ctfassets.net *.youtube.com *.twitter.com;
|
||||
media-src 'self';
|
||||
media-src 'self' *.youtube.com;
|
||||
connect-src *;
|
||||
font-src 'self' blob: data: fonts.gstatic.com maxcdn.bootstrapcdn.com;
|
||||
worker-src 'self' blob:;
|
||||
|
|
|
@ -7,10 +7,10 @@ export interface IEmbed {
|
|||
description?: string;
|
||||
site_name?: string;
|
||||
image?: string;
|
||||
isExternalVideo?: boolean;
|
||||
}
|
||||
|
||||
function extractMetadata(html: string): IEmbed {
|
||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||
const himalaya = require('himalaya');
|
||||
html = html.trim();
|
||||
const nodes = himalaya.parse(html);
|
||||
|
@ -95,7 +95,9 @@ export function isNoembed(object: unknown): object is INoembed {
|
|||
export async function fetchContent(
|
||||
targetUrl: string,
|
||||
): 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);
|
||||
let data = await response.json();
|
||||
|
||||
|
@ -118,10 +120,43 @@ export async function fetchContent(
|
|||
}
|
||||
|
||||
function convertToNoembed(rawData: any): INoembed {
|
||||
return {
|
||||
const noembed: INoembed = {
|
||||
title: sanitize(rawData.title),
|
||||
url: sanitize(rawData.url),
|
||||
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