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