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 './UrlPreview'; import { getIntersectionObserverEntry, useIntersectionObserver, } from '../../hooks/useIntersectionObserver'; import * as css from './UrlPreviewCard.css'; import { tryDecodeURIComponent } from '../../utils/dom'; import { mxcUrlToHttp } from '../../utils/matrix'; import { useSpecVersions } from '../../hooks/useSpecVersions'; const linkStyles = { color: color.Success.Main }; export const UrlPreviewCard = as<'div', { url: string; ts: number }>( ({ url, ts, ...props }, ref) => { const mx = useMatrixClient(); const { versions } = useSpecVersions(); const useAuthentication = versions.includes('v1.11'); 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 = mxcUrlToHttp(mx, prev['og:image'] || '', useAuthentication, 256, 256, 'scale', false); return ( <> {imgUrl && } {typeof prev['og:site_name'] === 'string' && `${prev['og:site_name']} | `} {tryDecodeURIComponent(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 && ( <>
)}
); });