Merge branch 'relative-timestamp-tsx' into 'develop'
RelativeTimestamp: convert to TSX See merge request soapbox-pub/soapbox-fe!1762
This commit is contained in:
commit
1876af9be7
11 changed files with 53 additions and 40 deletions
|
@ -8,7 +8,7 @@ import { useAppSelector, useOnScreen } from 'soapbox/hooks';
|
|||
import { getAcct } from 'soapbox/utils/accounts';
|
||||
import { displayFqn } from 'soapbox/utils/state';
|
||||
|
||||
import RelativeTimestamp from './relative_timestamp';
|
||||
import RelativeTimestamp from './relative-timestamp';
|
||||
import { Avatar, Emoji, HStack, Icon, IconButton, Stack, Text } from './ui';
|
||||
|
||||
import type { Account as AccountEntity } from 'soapbox/types/entities';
|
||||
|
@ -54,7 +54,7 @@ interface IAccount {
|
|||
id?: string,
|
||||
onActionClick?: (account: any) => void,
|
||||
showProfileHoverCard?: boolean,
|
||||
timestamp?: string | Date,
|
||||
timestamp?: string,
|
||||
timestampUrl?: string,
|
||||
futureTimestamp?: boolean,
|
||||
withAccountNote?: boolean,
|
||||
|
|
|
@ -6,7 +6,7 @@ import { useSoapboxConfig } from 'soapbox/hooks';
|
|||
import { getAcct } from '../utils/accounts';
|
||||
|
||||
import Icon from './icon';
|
||||
import RelativeTimestamp from './relative_timestamp';
|
||||
import RelativeTimestamp from './relative-timestamp';
|
||||
import VerificationBadge from './verification_badge';
|
||||
|
||||
import type { Account } from 'soapbox/types/entities';
|
||||
|
|
|
@ -4,7 +4,7 @@ import { defineMessages, FormattedMessage, useIntl } from 'react-intl';
|
|||
import { fetchPoll, vote } from 'soapbox/actions/polls';
|
||||
import { useAppDispatch } from 'soapbox/hooks';
|
||||
|
||||
import RelativeTimestamp from '../relative_timestamp';
|
||||
import RelativeTimestamp from '../relative-timestamp';
|
||||
import { Button, HStack, Stack, Text, Tooltip } from '../ui';
|
||||
|
||||
import type { Selected } from './poll';
|
||||
|
|
|
@ -1,8 +1,7 @@
|
|||
import PropTypes from 'prop-types';
|
||||
import React from 'react';
|
||||
import { injectIntl, defineMessages } from 'react-intl';
|
||||
import { injectIntl, defineMessages, IntlShape, FormatDateOptions } from 'react-intl';
|
||||
|
||||
import { Text } from './ui';
|
||||
import Text, { IText } from './ui/text/text';
|
||||
|
||||
const messages = defineMessages({
|
||||
just_now: { id: 'relative_time.just_now', defaultMessage: 'now' },
|
||||
|
@ -17,7 +16,7 @@ const messages = defineMessages({
|
|||
days_remaining: { id: 'time_remaining.days', defaultMessage: '{number, plural, one {# day} other {# days}} left' },
|
||||
});
|
||||
|
||||
const dateFormatOptions = {
|
||||
const dateFormatOptions: FormatDateOptions = {
|
||||
hour12: false,
|
||||
year: 'numeric',
|
||||
month: 'short',
|
||||
|
@ -26,7 +25,7 @@ const dateFormatOptions = {
|
|||
minute: '2-digit',
|
||||
};
|
||||
|
||||
const shortDateFormatOptions = {
|
||||
const shortDateFormatOptions: FormatDateOptions = {
|
||||
month: 'short',
|
||||
day: 'numeric',
|
||||
};
|
||||
|
@ -38,7 +37,7 @@ const DAY = 1000 * 60 * 60 * 24;
|
|||
|
||||
const MAX_DELAY = 2147483647;
|
||||
|
||||
const selectUnits = delta => {
|
||||
const selectUnits = (delta: number) => {
|
||||
const absDelta = Math.abs(delta);
|
||||
|
||||
if (absDelta < MINUTE) {
|
||||
|
@ -52,7 +51,7 @@ const selectUnits = delta => {
|
|||
return 'day';
|
||||
};
|
||||
|
||||
const getUnitDelay = units => {
|
||||
const getUnitDelay = (units: string) => {
|
||||
switch (units) {
|
||||
case 'second':
|
||||
return SECOND;
|
||||
|
@ -67,7 +66,7 @@ const getUnitDelay = units => {
|
|||
}
|
||||
};
|
||||
|
||||
export const timeAgoString = (intl, date, now, year) => {
|
||||
export const timeAgoString = (intl: IntlShape, date: Date, now: number, year: number) => {
|
||||
const delta = now - date.getTime();
|
||||
|
||||
let relativeTime;
|
||||
|
@ -93,7 +92,7 @@ export const timeAgoString = (intl, date, now, year) => {
|
|||
return relativeTime;
|
||||
};
|
||||
|
||||
const timeRemainingString = (intl, date, now) => {
|
||||
const timeRemainingString = (intl: IntlShape, date: Date, now: number) => {
|
||||
const delta = date.getTime() - now;
|
||||
|
||||
let relativeTime;
|
||||
|
@ -113,16 +112,21 @@ const timeRemainingString = (intl, date, now) => {
|
|||
return relativeTime;
|
||||
};
|
||||
|
||||
export default @injectIntl
|
||||
class RelativeTimestamp extends React.Component {
|
||||
interface RelativeTimestampProps extends IText {
|
||||
intl: IntlShape,
|
||||
timestamp: string,
|
||||
year?: number,
|
||||
futureDate?: boolean,
|
||||
}
|
||||
|
||||
static propTypes = {
|
||||
intl: PropTypes.object.isRequired,
|
||||
timestamp: PropTypes.string.isRequired,
|
||||
year: PropTypes.number.isRequired,
|
||||
theme: PropTypes.string,
|
||||
futureDate: PropTypes.bool,
|
||||
};
|
||||
interface RelativeTimestampState {
|
||||
now: number,
|
||||
}
|
||||
|
||||
/** Displays a timestamp compared to the current time, eg "1m" for one minute ago. */
|
||||
class RelativeTimestamp extends React.Component<RelativeTimestampProps, RelativeTimestampState> {
|
||||
|
||||
_timer: NodeJS.Timeout | undefined;
|
||||
|
||||
state = {
|
||||
now: Date.now(),
|
||||
|
@ -130,10 +134,10 @@ class RelativeTimestamp extends React.Component {
|
|||
|
||||
static defaultProps = {
|
||||
year: (new Date()).getFullYear(),
|
||||
theme: 'inherit',
|
||||
theme: 'inherit' as const,
|
||||
};
|
||||
|
||||
shouldComponentUpdate(nextProps, nextState) {
|
||||
shouldComponentUpdate(nextProps: RelativeTimestampProps, nextState: RelativeTimestampState) {
|
||||
// As of right now the locale doesn't change without a new page load,
|
||||
// but we might as well check in case that ever changes.
|
||||
return this.props.timestamp !== nextProps.timestamp ||
|
||||
|
@ -141,14 +145,14 @@ class RelativeTimestamp extends React.Component {
|
|||
this.state.now !== nextState.now;
|
||||
}
|
||||
|
||||
UNSAFE_componentWillReceiveProps(prevProps) {
|
||||
UNSAFE_componentWillReceiveProps(prevProps: RelativeTimestampProps) {
|
||||
if (this.props.timestamp !== prevProps.timestamp) {
|
||||
this.setState({ now: Date.now() });
|
||||
}
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this._scheduleNextUpdate(this.props, this.state);
|
||||
this._scheduleNextUpdate();
|
||||
}
|
||||
|
||||
UNSAFE_componentWillUpdate() {
|
||||
|
@ -156,11 +160,15 @@ class RelativeTimestamp extends React.Component {
|
|||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
clearTimeout(this._timer);
|
||||
if (this._timer) {
|
||||
clearTimeout(this._timer);
|
||||
}
|
||||
}
|
||||
|
||||
_scheduleNextUpdate() {
|
||||
clearTimeout(this._timer);
|
||||
if (this._timer) {
|
||||
clearTimeout(this._timer);
|
||||
}
|
||||
|
||||
const { timestamp } = this.props;
|
||||
const delta = (new Date(timestamp)).getTime() - this.state.now;
|
||||
|
@ -177,8 +185,8 @@ class RelativeTimestamp extends React.Component {
|
|||
render() {
|
||||
const { timestamp, intl, year, futureDate, theme, ...textProps } = this.props;
|
||||
|
||||
const date = new Date(timestamp);
|
||||
const relativeTime = futureDate ? timeRemainingString(intl, date, this.state.now) : timeAgoString(intl, date, this.state.now, year);
|
||||
const date = new Date(timestamp);
|
||||
const relativeTime = futureDate ? timeRemainingString(intl, date, this.state.now) : timeAgoString(intl, date, this.state.now, year!);
|
||||
|
||||
return (
|
||||
<Text {...textProps} theme={theme} tag='time' title={intl.formatDate(date, dateFormatOptions)}>
|
||||
|
@ -188,3 +196,5 @@ class RelativeTimestamp extends React.Component {
|
|||
}
|
||||
|
||||
}
|
||||
|
||||
export default injectIntl(RelativeTimestamp);
|
|
@ -84,7 +84,9 @@ interface IText extends Pick<React.HTMLAttributes<HTMLParagraphElement>, 'danger
|
|||
/** Whether to truncate the text if its container is too small. */
|
||||
truncate?: boolean,
|
||||
/** Font weight of the text. */
|
||||
weight?: Weights
|
||||
weight?: Weights,
|
||||
/** Tooltip title. */
|
||||
title?: string,
|
||||
}
|
||||
|
||||
/** UI-friendly text container with dark mode support. */
|
||||
|
@ -133,4 +135,7 @@ const Text: React.FC<IText> = React.forwardRef(
|
|||
},
|
||||
);
|
||||
|
||||
export default Text;
|
||||
export {
|
||||
Text as default,
|
||||
IText,
|
||||
};
|
||||
|
|
|
@ -6,7 +6,7 @@ import { getSettings } from 'soapbox/actions/settings';
|
|||
import Avatar from 'soapbox/components/avatar';
|
||||
import DisplayName from 'soapbox/components/display-name';
|
||||
import Permalink from 'soapbox/components/permalink';
|
||||
import RelativeTimestamp from 'soapbox/components/relative_timestamp';
|
||||
import RelativeTimestamp from 'soapbox/components/relative-timestamp';
|
||||
import { Text } from 'soapbox/components/ui';
|
||||
import ActionButton from 'soapbox/features/ui/components/action-button';
|
||||
import { useAppSelector } from 'soapbox/hooks';
|
||||
|
|
|
@ -21,7 +21,6 @@ describe('normalizePoll()', () => {
|
|||
expect(ImmutableRecord.isRecord(result)).toBe(true);
|
||||
expect(ImmutableRecord.isRecord(result.options.get(0))).toBe(true);
|
||||
expect(result.toJS()).toMatchObject(expected);
|
||||
expect(result.expires_at instanceof Date).toBe(true);
|
||||
});
|
||||
|
||||
it('normalizes a Pleroma logged-out poll', () => {
|
||||
|
|
|
@ -164,7 +164,6 @@ describe('normalizeStatus()', () => {
|
|||
expect(ImmutableRecord.isRecord(poll)).toBe(true);
|
||||
expect(ImmutableRecord.isRecord(poll.options.get(0))).toBe(true);
|
||||
expect(poll.toJS()).toMatchObject(expected);
|
||||
expect(poll.expires_at instanceof Date).toBe(true);
|
||||
});
|
||||
|
||||
it('normalizes a Pleroma logged-out poll', () => {
|
||||
|
|
|
@ -26,7 +26,7 @@ export const AccountRecord = ImmutableRecord({
|
|||
avatar_static: '',
|
||||
birthday: '',
|
||||
bot: false,
|
||||
created_at: new Date(),
|
||||
created_at: '',
|
||||
discoverable: false,
|
||||
display_name: '',
|
||||
emojis: ImmutableList<Emoji>(),
|
||||
|
@ -38,7 +38,7 @@ export const AccountRecord = ImmutableRecord({
|
|||
header: '',
|
||||
header_static: '',
|
||||
id: '',
|
||||
last_status_at: new Date(),
|
||||
last_status_at: '',
|
||||
location: '',
|
||||
locked: false,
|
||||
moved: null as EmbeddedEntity<any>,
|
||||
|
|
|
@ -21,7 +21,7 @@ import type { Emoji, PollOption } from 'soapbox/types/entities';
|
|||
export const PollRecord = ImmutableRecord({
|
||||
emojis: ImmutableList<Emoji>(),
|
||||
expired: false,
|
||||
expires_at: new Date(),
|
||||
expires_at: '',
|
||||
id: '',
|
||||
multiple: false,
|
||||
options: ImmutableList<PollOption>(),
|
||||
|
|
|
@ -28,8 +28,8 @@ export const StatusRecord = ImmutableRecord({
|
|||
bookmarked: false,
|
||||
card: null as Card | null,
|
||||
content: '',
|
||||
created_at: new Date(),
|
||||
edited_at: null as Date | null,
|
||||
created_at: '',
|
||||
edited_at: null as string | null,
|
||||
emojis: ImmutableList<Emoji>(),
|
||||
favourited: false,
|
||||
favourites_count: 0,
|
||||
|
|
Loading…
Reference in a new issue