oxen-website/services/render.tsx

148 lines
4.5 KiB
TypeScript

import { useContext } from 'react';
import Image from 'next/image';
import Link from 'next/link';
import classNames from 'classnames';
import { Block, Inline } from '@contentful/rich-text-types';
import sanitize from '../utils/sanitize';
import EmbedContent from '../components/EmbedContent';
import { ScreenContext } from '../contexts/screen';
import { ReactElement, CSSProperties } from 'react';
function Markup(node: any): ReactElement {
const frontTags: string[] = [];
const endTags: string[] = [];
const styles: any = {};
if (node.color) {
styles.color = sanitize(node.color);
}
frontTags.push(
node.strikethrough && '<s>',
node.subscript && '<sub>',
node.superscript && '<sup>',
);
endTags.push(
node.strikethrough && '</s>',
node.subscript && '</sub>',
node.superscript && '</sup>',
);
let htmlContent = frontTags.join('') + node.content + endTags.join('');
htmlContent = sanitize(htmlContent);
return (
<span dangerouslySetInnerHTML={{ __html: htmlContent }} style={styles} />
);
}
function EmbeddedLink(node: any, isInline = false): ReactElement {
const figureClasses = [
isInline && node.position === 'left' && 'tablet:float-left',
isInline && node.position === 'right' && 'tablet:float-right',
isInline && node.position && 'tablet:w-3/5 desktop:w-1/2',
];
const inlineClasses = [
isInline && !node.position && 'inline-block align-middle mx-1',
];
const captionClasses = [...inlineClasses, !isInline && 'pb-4'];
return (
<figure className={classNames(figureClasses)}>
<EmbedContent content={node.meta} classes={classNames(inlineClasses)} />
{node.caption && (
<figcaption className={classNames(captionClasses)}>
<em>{node.caption}</em>
</figcaption>
)}
</figure>
);
}
function EmbeddedMedia(node: any, isInline = false): ReactElement {
const { isMobile, isTablet, isDesktop } = useContext(ScreenContext);
// is either an asset or entry
const media = node.file.fields ?? node;
const url = media.file.url.replace('//', 'https://');
if (
media.file.contentType === 'image/jpeg' ||
media.file.contentType === 'image/png' ||
media.file.contentType === 'image/gif' ||
media.file.contentType === 'image/svg+xml'
) {
const imageWidth = node.width ?? media.file.details.image.width;
const imageHeight = node.height ?? media.file.details.image.height;
const figureClasses = [
isInline && node.position && 'text-center mx-auto mt-4 mb-5',
isInline && !node.position && 'inline-block align-middle mx-1',
isInline && node.position === 'left' && 'tablet:float-left tablet:mr-4',
isInline && node.position === 'right' && 'tablet:float-right tablet:ml-4',
!isInline && 'text-center mb-5',
];
const captionClasses = [
!node.position && 'mt-1',
isInline &&
!node.position &&
'text-center tablet:inline-block tablet:align-middle tablet:mx-1',
];
const figureStyles: CSSProperties = {};
if (!isMobile && node.position) {
figureStyles.width = imageWidth;
}
if (isDesktop) {
figureStyles.maxWidth = '800px';
}
return (
<figure className={classNames(figureClasses)} style={figureStyles}>
<Image
src={`${url}${isMobile ? '?w=300' : isTablet ? '?w=600' : ''}`}
alt={node.title}
width={imageWidth}
height={imageHeight}
priority={true}
/>
{node.caption && (
<figcaption className={classNames(captionClasses)}>
<em>
{node.sourceUrl ? (
<Link href={node.sourceUrl}>
<a
aria-label={node.caption}
className={classNames('font-extralight')}
target="_blank"
rel="noreferrer"
>
{node.caption}
</a>
</Link>
) : (
<>{node.caption}</>
)}
</em>
</figcaption>
)}
</figure>
);
} else {
return <></>;
}
}
interface IEmbedEntry {
node: Block | Inline;
isInline?: boolean;
}
export function renderEmbeddedEntry(props: IEmbedEntry) {
const { node, isInline = false } = props;
const target = node.data.target;
const asset = target.fields;
if (target.sys.contentType && target.sys.contentType.sys.id === 'markup') {
return Markup(asset);
}
if (!asset.file) {
return EmbeddedLink(asset, isInline);
} else {
return EmbeddedMedia(asset, isInline);
}
}