import { useEffect, useState } from 'react'; import styled from 'styled-components'; import { motion } from 'framer-motion'; import { Box, Flex, skeletonStyle, Text } from '../../../general'; import { Bookmark } from '../../os/Bookmark/Bookmark'; import { MediaBlock } from '../MediaBlock/MediaBlock'; import { ImageBlock } from '../ImageBlock/ImageBlock'; import { BlockProps, Block } from '../Block/Block'; import { parseMediaType } from '../../util/links'; import { TweetBlock } from './TweetBlock'; import { fetchOGData, RAW_LINK_HEIGHT, LINK_PREVIEW_HEIGHT, extractOGData, LinkPreviewType, } from './util'; const LinkTitle = styled(Text.Anchor)` overflow: hidden; line-height: 1.7rem; height: 1.6rem; `; const LinkDescription = styled(Text.Custom)` overflow: hidden; height: 1rem; `; const LinkImage = styled(motion.img)<{ isSkeleton?: boolean }>` width: 100%; height: 170px; object-fit: cover; border-radius: 4px; background: rgba(var(--rlm-window-rgba)); ${({ isSkeleton }) => isSkeleton && skeletonStyle} `; type LinkBlockProps = { link: string; by: string; metadata?: any; containerWidth?: number; onLinkLoaded: () => void; } & BlockProps; export type LinkType = 'opengraph' | 'url'; type MediaType = 'twitter' | 'media' | 'image'; export type LinkBlockType = LinkType | MediaType; export const LinkBlock = ({ link, by, containerWidth, metadata = {}, onLinkLoaded, ...rest }: LinkBlockProps) => { const [openGraph, setOpenGraph] = useState( metadata.ogData ? JSON.parse(metadata.ogData) : null ); const [linkBlockType, setLinkBlockType] = useState('url'); useEffect(() => { if (!metadata.ogData && !metadata.height) { const { linkType } = parseMediaType(link); if (linkType !== 'link') { return setLinkBlockType(linkType); } if (!openGraph && linkBlockType === 'opengraph') { fetchOGData(link).then(({ linkType: detectedLinkType, data }) => { setLinkBlockType(detectedLinkType); if (data) { setOpenGraph(extractOGData(data)); } }); } } }, []); useEffect(() => { onLinkLoaded(); }, [openGraph]); let description = openGraph?.ogDescription || ''; // TODO make twitter height dynamic if (metadata.linkType === 'twitter' || linkBlockType === 'twitter') { let height = 300; let width = (typeof rest.width === 'number' && rest.width) || 320; if (metadata.width) { width = parseInt(metadata.width); height = parseInt(metadata.height); } // if (width < 400) { // width = 320; // } return ( ); } if (linkBlockType === 'media') { if (typeof rest.height === 'string') { // strip out non-numeric characters if (rest.height.includes('px')) { rest.height = parseInt(rest.height.replace('px', '')); } // convert rem to px else rest.height = parseInt(rest.height.replace('rem', '')) * 16; } const width = containerWidth ? containerWidth * 0.9 : 320; return ( ); } if (linkBlockType === 'image') { return ( ); } if ( metadata.linkType === 'url' || !metadata.ogData || (metadata.ogData && !openGraph?.ogTitle) ) { const width = containerWidth ? containerWidth - 12 : 320; return ( { window.open(url, '_blank'); }} /> ); } const ogHasURL = openGraph && openGraph.ogUrl; // 254px in rem is 15rem if (!ogHasURL) { const width = containerWidth ? containerWidth - 12 : 320; return ( ); } return ( { // todo: if the image fails to load, set the image to error image }} /> ) => { evt.stopPropagation(); window.open(openGraph?.ogUrl, '_blank'); }} > {openGraph?.ogTitle} {description} ) => { evt.stopPropagation(); if (ogHasURL) { const origin = new URL(openGraph.ogUrl).origin; window.open(origin, '_blank'); } }} > {openGraph?.ogSiteName || (ogHasURL && new URL(openGraph.ogUrl).hostname)} {by} ); };