2022-02-25 10:34:33 +11:00
|
|
|
import PropTypes from 'prop-types';
|
2024-06-13 23:04:16 +10:00
|
|
|
import { useState, useCallback } from 'react';
|
2023-05-24 01:15:17 +10:00
|
|
|
|
2023-07-25 08:57:15 +10:00
|
|
|
import { FormattedMessage } from 'react-intl';
|
|
|
|
|
2023-05-24 01:15:17 +10:00
|
|
|
import classNames from 'classnames';
|
|
|
|
|
2024-06-13 23:04:16 +10:00
|
|
|
|
2023-05-09 11:11:56 +10:00
|
|
|
import { Blurhash } from 'mastodon/components/blurhash';
|
2023-07-25 08:57:15 +10:00
|
|
|
import { RelativeTimestamp } from 'mastodon/components/relative_timestamp';
|
2023-07-08 19:11:58 +10:00
|
|
|
import { ShortNumber } from 'mastodon/components/short_number';
|
2023-05-23 16:58:08 +10:00
|
|
|
import { Skeleton } from 'mastodon/components/skeleton';
|
2022-02-25 10:34:33 +11:00
|
|
|
|
2024-06-13 23:04:16 +10:00
|
|
|
import { AuthorLink } from './author_link';
|
|
|
|
|
|
|
|
const sharesCountRenderer = (displayNumber, pluralReady) => (
|
|
|
|
<FormattedMessage
|
|
|
|
id='link_preview.shares'
|
|
|
|
defaultMessage='{count, plural, one {{counter} post} other {{counter} posts}}'
|
|
|
|
values={{
|
|
|
|
count: pluralReady,
|
|
|
|
counter: <strong>{displayNumber}</strong>,
|
|
|
|
}}
|
|
|
|
/>
|
|
|
|
);
|
|
|
|
|
|
|
|
export const Story = ({
|
|
|
|
url,
|
|
|
|
title,
|
|
|
|
lang,
|
|
|
|
publisher,
|
|
|
|
publishedAt,
|
|
|
|
author,
|
|
|
|
authorAccount,
|
|
|
|
sharedTimes,
|
|
|
|
thumbnail,
|
|
|
|
thumbnailDescription,
|
|
|
|
blurhash,
|
|
|
|
expanded
|
|
|
|
}) => {
|
|
|
|
const [thumbnailLoaded, setThumbnailLoaded] = useState(false);
|
|
|
|
|
|
|
|
const handleImageLoad = useCallback(() => {
|
|
|
|
setThumbnailLoaded(true);
|
|
|
|
}, [setThumbnailLoaded]);
|
|
|
|
|
|
|
|
return (
|
|
|
|
<div className={classNames('story', { expanded })}>
|
|
|
|
<div className='story__details'>
|
|
|
|
<div className='story__details__publisher'>
|
|
|
|
{publisher ? <span lang={lang}>{publisher}</span> : <Skeleton width={50} />}{publishedAt && <> · <RelativeTimestamp timestamp={publishedAt} /></>}
|
2022-02-25 10:34:33 +11:00
|
|
|
</div>
|
|
|
|
|
2024-06-13 23:04:16 +10:00
|
|
|
<a className='story__details__title' lang={lang} href={url} target='blank' rel='noopener'>
|
|
|
|
{title ? title : <Skeleton />}
|
|
|
|
</a>
|
|
|
|
|
|
|
|
<div className='story__details__shared'>
|
|
|
|
{author ? <FormattedMessage id='link_preview.author' className='story__details__shared__author' defaultMessage='By {name}' values={{ name: authorAccount ? <AuthorLink accountId={authorAccount} /> : <strong>{author}</strong> }} /> : <span />}
|
|
|
|
{typeof sharedTimes === 'number' ? <span className='story__details__shared__pill'><ShortNumber value={sharedTimes} renderer={sharesCountRenderer} /></span> : <Skeleton width='10ch' />}
|
2022-02-25 10:34:33 +11:00
|
|
|
</div>
|
2024-06-13 23:04:16 +10:00
|
|
|
</div>
|
|
|
|
|
|
|
|
<a className='story__thumbnail' href={url} target='blank' rel='noopener'>
|
|
|
|
{thumbnail ? (
|
|
|
|
<>
|
|
|
|
<div className={classNames('story__thumbnail__preview', { 'story__thumbnail__preview--hidden': thumbnailLoaded })}><Blurhash hash={blurhash} /></div>
|
|
|
|
<img src={thumbnail} onLoad={handleImageLoad} alt={thumbnailDescription} title={thumbnailDescription} lang={lang} />
|
|
|
|
</>
|
|
|
|
) : <Skeleton />}
|
2022-02-25 10:34:33 +11:00
|
|
|
</a>
|
2024-06-13 23:04:16 +10:00
|
|
|
</div>
|
|
|
|
);
|
|
|
|
};
|
2022-02-25 10:34:33 +11:00
|
|
|
|
2024-06-13 23:04:16 +10:00
|
|
|
Story.propTypes = {
|
|
|
|
url: PropTypes.string,
|
|
|
|
title: PropTypes.string,
|
|
|
|
lang: PropTypes.string,
|
|
|
|
publisher: PropTypes.string,
|
|
|
|
publishedAt: PropTypes.string,
|
|
|
|
author: PropTypes.string,
|
|
|
|
authorAccount: PropTypes.string,
|
|
|
|
sharedTimes: PropTypes.number,
|
|
|
|
thumbnail: PropTypes.string,
|
|
|
|
thumbnailDescription: PropTypes.string,
|
|
|
|
blurhash: PropTypes.string,
|
|
|
|
expanded: PropTypes.bool,
|
|
|
|
};
|