Converted hashtag.jsx to TypeScript (#27872)
Co-authored-by: Claire <claire.github-309c@sitedethib.com> Co-authored-by: Renaud Chaput <renchap@gmail.com>th-new
parent
1142f4c79e
commit
3a7f10c3f1
|
@ -284,7 +284,6 @@ module.exports = defineConfig({
|
||||||
'formatjs/no-id': 'off', // IDs are used for translation keys
|
'formatjs/no-id': 'off', // IDs are used for translation keys
|
||||||
'formatjs/no-invalid-icu': 'error',
|
'formatjs/no-invalid-icu': 'error',
|
||||||
'formatjs/no-literal-string-in-jsx': 'off', // Should be looked at, but mainly flagging punctuation outside of strings
|
'formatjs/no-literal-string-in-jsx': 'off', // Should be looked at, but mainly flagging punctuation outside of strings
|
||||||
'formatjs/no-multiple-plurals': 'off', // Only used by hashtag.jsx
|
|
||||||
'formatjs/no-multiple-whitespaces': 'error',
|
'formatjs/no-multiple-whitespaces': 'error',
|
||||||
'formatjs/no-offset': 'error',
|
'formatjs/no-offset': 'error',
|
||||||
'formatjs/no-useless-message': 'error',
|
'formatjs/no-useless-message': 'error',
|
||||||
|
|
|
@ -6,7 +6,7 @@ import { FormattedMessage } from 'react-intl';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
|
|
||||||
import api from 'mastodon/api';
|
import api from 'mastodon/api';
|
||||||
import Hashtag from 'mastodon/components/hashtag';
|
import { Hashtag } from 'mastodon/components/hashtag';
|
||||||
|
|
||||||
export default class Trends extends PureComponent {
|
export default class Trends extends PureComponent {
|
||||||
|
|
||||||
|
|
|
@ -1,120 +0,0 @@
|
||||||
// @ts-check
|
|
||||||
import PropTypes from 'prop-types';
|
|
||||||
import { Component } from 'react';
|
|
||||||
|
|
||||||
import { FormattedMessage } from 'react-intl';
|
|
||||||
|
|
||||||
import classNames from 'classnames';
|
|
||||||
import { Link } from 'react-router-dom';
|
|
||||||
|
|
||||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
|
||||||
|
|
||||||
import { Sparklines, SparklinesCurve } from 'react-sparklines';
|
|
||||||
|
|
||||||
import { ShortNumber } from 'mastodon/components/short_number';
|
|
||||||
import { Skeleton } from 'mastodon/components/skeleton';
|
|
||||||
|
|
||||||
class SilentErrorBoundary extends Component {
|
|
||||||
|
|
||||||
static propTypes = {
|
|
||||||
children: PropTypes.node,
|
|
||||||
};
|
|
||||||
|
|
||||||
state = {
|
|
||||||
error: false,
|
|
||||||
};
|
|
||||||
|
|
||||||
componentDidCatch() {
|
|
||||||
this.setState({ error: true });
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
|
||||||
if (this.state.error) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return this.props.children;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Used to render counter of how much people are talking about hashtag
|
|
||||||
* @type {(displayNumber: JSX.Element, pluralReady: number) => JSX.Element}
|
|
||||||
*/
|
|
||||||
export const accountsCountRenderer = (displayNumber, pluralReady) => (
|
|
||||||
<FormattedMessage
|
|
||||||
id='trends.counter_by_accounts'
|
|
||||||
defaultMessage='{count, plural, one {{counter} person} other {{counter} people}} in the past {days, plural, one {day} other {# days}}'
|
|
||||||
values={{
|
|
||||||
count: pluralReady,
|
|
||||||
counter: <strong>{displayNumber}</strong>,
|
|
||||||
days: 2,
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
|
|
||||||
// @ts-expect-error
|
|
||||||
export const ImmutableHashtag = ({ hashtag }) => (
|
|
||||||
<Hashtag
|
|
||||||
name={hashtag.get('name')}
|
|
||||||
to={`/tags/${hashtag.get('name')}`}
|
|
||||||
people={hashtag.getIn(['history', 0, 'accounts']) * 1 + hashtag.getIn(['history', 1, 'accounts']) * 1}
|
|
||||||
// @ts-expect-error
|
|
||||||
history={hashtag.get('history').reverse().map((day) => day.get('uses')).toArray()}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
|
|
||||||
ImmutableHashtag.propTypes = {
|
|
||||||
hashtag: ImmutablePropTypes.map.isRequired,
|
|
||||||
};
|
|
||||||
|
|
||||||
// @ts-expect-error
|
|
||||||
const Hashtag = ({ name, to, people, uses, history, className, description, withGraph }) => (
|
|
||||||
<div className={classNames('trends__item', className)}>
|
|
||||||
<div className='trends__item__name'>
|
|
||||||
<Link to={to}>
|
|
||||||
{name ? <>#<span>{name}</span></> : <Skeleton width={50} />}
|
|
||||||
</Link>
|
|
||||||
|
|
||||||
{description ? (
|
|
||||||
<span>{description}</span>
|
|
||||||
) : (
|
|
||||||
typeof people !== 'undefined' ? <ShortNumber value={people} renderer={accountsCountRenderer} /> : <Skeleton width={100} />
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{typeof uses !== 'undefined' && (
|
|
||||||
<div className='trends__item__current'>
|
|
||||||
<ShortNumber value={uses} />
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{withGraph && (
|
|
||||||
<div className='trends__item__sparkline'>
|
|
||||||
<SilentErrorBoundary>
|
|
||||||
<Sparklines width={50} height={28} data={history ? history : Array.from(Array(7)).map(() => 0)}>
|
|
||||||
<SparklinesCurve style={{ fill: 'none' }} />
|
|
||||||
</Sparklines>
|
|
||||||
</SilentErrorBoundary>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
|
|
||||||
Hashtag.propTypes = {
|
|
||||||
name: PropTypes.string,
|
|
||||||
to: PropTypes.string,
|
|
||||||
people: PropTypes.number,
|
|
||||||
description: PropTypes.node,
|
|
||||||
uses: PropTypes.number,
|
|
||||||
history: PropTypes.arrayOf(PropTypes.number),
|
|
||||||
className: PropTypes.string,
|
|
||||||
withGraph: PropTypes.bool,
|
|
||||||
};
|
|
||||||
|
|
||||||
Hashtag.defaultProps = {
|
|
||||||
withGraph: true,
|
|
||||||
};
|
|
||||||
|
|
||||||
export default Hashtag;
|
|
|
@ -0,0 +1,145 @@
|
||||||
|
import type { JSX } from 'react';
|
||||||
|
import { Component } from 'react';
|
||||||
|
|
||||||
|
import { FormattedMessage } from 'react-intl';
|
||||||
|
|
||||||
|
import classNames from 'classnames';
|
||||||
|
import { Link } from 'react-router-dom';
|
||||||
|
|
||||||
|
import type Immutable from 'immutable';
|
||||||
|
|
||||||
|
import { Sparklines, SparklinesCurve } from 'react-sparklines';
|
||||||
|
|
||||||
|
import { ShortNumber } from 'mastodon/components/short_number';
|
||||||
|
import { Skeleton } from 'mastodon/components/skeleton';
|
||||||
|
|
||||||
|
interface SilentErrorBoundaryProps {
|
||||||
|
children: React.ReactNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
class SilentErrorBoundary extends Component<SilentErrorBoundaryProps> {
|
||||||
|
state = {
|
||||||
|
error: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
componentDidCatch() {
|
||||||
|
this.setState({ error: true });
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
if (this.state.error) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.props.children;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Used to render counter of how much people are talking about hashtag
|
||||||
|
* @param displayNumber Counter number to display
|
||||||
|
* @param pluralReady Whether the count is plural
|
||||||
|
* @returns Formatted counter of how much people are talking about hashtag
|
||||||
|
*/
|
||||||
|
export const accountsCountRenderer = (
|
||||||
|
displayNumber: JSX.Element,
|
||||||
|
pluralReady: number,
|
||||||
|
) => (
|
||||||
|
<FormattedMessage
|
||||||
|
id='trends.counter_by_accounts'
|
||||||
|
defaultMessage='{count, plural, one {{counter} person} other {{counter} people}} in the past {days, plural, one {day} other {# days}}'
|
||||||
|
values={{
|
||||||
|
count: pluralReady,
|
||||||
|
counter: <strong>{displayNumber}</strong>,
|
||||||
|
days: 2,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
|
interface ImmutableHashtagProps {
|
||||||
|
hashtag: Immutable.Map<string, unknown>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const ImmutableHashtag = ({ hashtag }: ImmutableHashtagProps) => (
|
||||||
|
<Hashtag
|
||||||
|
name={hashtag.get('name') as string}
|
||||||
|
to={`/tags/${hashtag.get('name') as string}`}
|
||||||
|
people={
|
||||||
|
(hashtag.getIn(['history', 0, 'accounts']) as number) * 1 +
|
||||||
|
(hashtag.getIn(['history', 1, 'accounts']) as number) * 1
|
||||||
|
}
|
||||||
|
history={(
|
||||||
|
hashtag.get('history') as Immutable.Collection.Indexed<
|
||||||
|
Immutable.Map<string, number>
|
||||||
|
>
|
||||||
|
)
|
||||||
|
.reverse()
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||||
|
.map((day) => day.get('uses')!)
|
||||||
|
.toArray()}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
|
export interface HashtagProps {
|
||||||
|
className?: string;
|
||||||
|
description?: React.ReactNode;
|
||||||
|
history?: number[];
|
||||||
|
name: string;
|
||||||
|
people: number;
|
||||||
|
to: string;
|
||||||
|
uses?: number;
|
||||||
|
withGraph?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const Hashtag: React.FC<HashtagProps> = ({
|
||||||
|
name,
|
||||||
|
to,
|
||||||
|
people,
|
||||||
|
uses,
|
||||||
|
history,
|
||||||
|
className,
|
||||||
|
description,
|
||||||
|
withGraph = true,
|
||||||
|
}) => (
|
||||||
|
<div className={classNames('trends__item', className)}>
|
||||||
|
<div className='trends__item__name'>
|
||||||
|
<Link to={to}>
|
||||||
|
{name ? (
|
||||||
|
<>
|
||||||
|
#<span>{name}</span>
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<Skeleton width={50} />
|
||||||
|
)}
|
||||||
|
</Link>
|
||||||
|
|
||||||
|
{description ? (
|
||||||
|
<span>{description}</span>
|
||||||
|
) : typeof people !== 'undefined' ? (
|
||||||
|
<ShortNumber value={people} renderer={accountsCountRenderer} />
|
||||||
|
) : (
|
||||||
|
<Skeleton width={100} />
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{typeof uses !== 'undefined' && (
|
||||||
|
<div className='trends__item__current'>
|
||||||
|
<ShortNumber value={uses} />
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{withGraph && (
|
||||||
|
<div className='trends__item__sparkline'>
|
||||||
|
<SilentErrorBoundary>
|
||||||
|
<Sparklines
|
||||||
|
width={50}
|
||||||
|
height={28}
|
||||||
|
data={history ? history : Array.from(Array(7)).map(() => 0)}
|
||||||
|
>
|
||||||
|
<SparklinesCurve style={{ fill: 'none' }} />
|
||||||
|
</Sparklines>
|
||||||
|
</SilentErrorBoundary>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
|
@ -5,7 +5,7 @@ import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
|
||||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||||
import ImmutablePureComponent from 'react-immutable-pure-component';
|
import ImmutablePureComponent from 'react-immutable-pure-component';
|
||||||
|
|
||||||
import Hashtag from 'mastodon/components/hashtag';
|
import { Hashtag } from 'mastodon/components/hashtag';
|
||||||
|
|
||||||
const messages = defineMessages({
|
const messages = defineMessages({
|
||||||
lastStatusAt: { id: 'account.featured_tags.last_status_at', defaultMessage: 'Last post on {date}' },
|
lastStatusAt: { id: 'account.featured_tags.last_status_at', defaultMessage: 'Last post on {date}' },
|
||||||
|
|
|
@ -13,7 +13,7 @@ import { debounce } from 'lodash';
|
||||||
|
|
||||||
import { expandFollowedHashtags, fetchFollowedHashtags } from 'mastodon/actions/tags';
|
import { expandFollowedHashtags, fetchFollowedHashtags } from 'mastodon/actions/tags';
|
||||||
import ColumnHeader from 'mastodon/components/column_header';
|
import ColumnHeader from 'mastodon/components/column_header';
|
||||||
import Hashtag from 'mastodon/components/hashtag';
|
import { Hashtag } from 'mastodon/components/hashtag';
|
||||||
import ScrollableList from 'mastodon/components/scrollable_list';
|
import ScrollableList from 'mastodon/components/scrollable_list';
|
||||||
import Column from 'mastodon/features/ui/components/column';
|
import Column from 'mastodon/features/ui/components/column';
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue