import React, { useCallback, useEffect, useRef, useState } from 'react'; import { IPreviewUrlResponse } from 'matrix-js-sdk'; import { Box, Icon, IconButton, Icons, Scroll, Spinner, Text, as, color, config } from 'folds'; import { AsyncStatus, useAsyncCallback } from '../../../hooks/useAsyncCallback'; import { useMatrixClient } from '../../../hooks/useMatrixClient'; import { UrlPreview, UrlPreviewContent, UrlPreviewDescription, UrlPreviewImg, } from '../../../components/url-preview'; import { getIntersectionObserverEntry, useIntersectionObserver, } from '../../../hooks/useIntersectionObserver'; import * as css from './styles.css'; const linkStyles = { color: color.Success.Main }; export const UrlPreviewCard = as<'div', { url: string; ts: number }>( ({ url, ts, ...props }, ref) => { const mx = useMatrixClient(); const [previewStatus, loadPreview] = useAsyncCallback( useCallback(() => mx.getUrlPreview(url, ts), [url, ts, mx]) ); useEffect(() => { loadPreview(); }, [loadPreview]); if (previewStatus.status === AsyncStatus.Error) return null; const renderContent = (prev: IPreviewUrlResponse) => { const imgUrl = mx.mxcUrlToHttp(prev['og:image'] || '', 256, 256, 'scale', false); return ( <> {imgUrl && } {typeof prev['og:site_name'] === 'string' && `${prev['og:site_name']} | `} {decodeURIComponent(url)} {prev['og:title']} {prev['og:description']} ); }; return ( {previewStatus.status === AsyncStatus.Success ? ( renderContent(previewStatus.data) ) : ( )} ); } ); export const UrlPreviewHolder = as<'div'>(({ children, ...props }, ref) => { const scrollRef = useRef(null); const backAnchorRef = useRef(null); const frontAnchorRef = useRef(null); const [backVisible, setBackVisible] = useState(true); const [frontVisible, setFrontVisible] = useState(true); const intersectionObserver = useIntersectionObserver( useCallback((entries) => { const backAnchor = backAnchorRef.current; const frontAnchor = frontAnchorRef.current; const backEntry = backAnchor && getIntersectionObserverEntry(backAnchor, entries); const frontEntry = frontAnchor && getIntersectionObserverEntry(frontAnchor, entries); if (backEntry) { setBackVisible(backEntry.isIntersecting); } if (frontEntry) { setFrontVisible(frontEntry.isIntersecting); } }, []), useCallback( () => ({ root: scrollRef.current, rootMargin: '10px', }), [] ) ); useEffect(() => { const backAnchor = backAnchorRef.current; const frontAnchor = frontAnchorRef.current; if (backAnchor) intersectionObserver?.observe(backAnchor); if (frontAnchor) intersectionObserver?.observe(frontAnchor); return () => { if (backAnchor) intersectionObserver?.unobserve(backAnchor); if (frontAnchor) intersectionObserver?.unobserve(frontAnchor); }; }, [intersectionObserver]); const handleScrollBack = () => { const scroll = scrollRef.current; if (!scroll) return; const { offsetWidth, scrollLeft } = scroll; scroll.scrollTo({ left: scrollLeft - offsetWidth / 1.3, behavior: 'smooth', }); }; const handleScrollFront = () => { const scroll = scrollRef.current; if (!scroll) return; const { offsetWidth, scrollLeft } = scroll; scroll.scrollTo({ left: scrollLeft + offsetWidth / 1.3, behavior: 'smooth', }); }; return (
{!backVisible && ( <>
)} {children} {!frontVisible && ( <>
)}
); });