From 38b6f87a8310237bfa46a083c09b72c4e44366ad Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Wed, 31 Aug 2022 17:01:19 -0500 Subject: [PATCH 1/2] RelativeTimestamp: convert to TSX --- app/soapbox/components/account.tsx | 2 +- ...ve_timestamp.js => relative_timestamp.tsx} | 62 +++++++++++-------- app/soapbox/components/ui/text/text.tsx | 9 ++- .../normalizers/__tests__/poll.test.ts | 1 - .../normalizers/__tests__/status.test.ts | 1 - app/soapbox/normalizers/account.ts | 4 +- app/soapbox/normalizers/poll.ts | 2 +- app/soapbox/normalizers/status.ts | 4 +- 8 files changed, 49 insertions(+), 36 deletions(-) rename app/soapbox/components/{relative_timestamp.js => relative_timestamp.tsx} (78%) diff --git a/app/soapbox/components/account.tsx b/app/soapbox/components/account.tsx index 281e1aee2..eeb970518 100644 --- a/app/soapbox/components/account.tsx +++ b/app/soapbox/components/account.tsx @@ -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, diff --git a/app/soapbox/components/relative_timestamp.js b/app/soapbox/components/relative_timestamp.tsx similarity index 78% rename from app/soapbox/components/relative_timestamp.js rename to app/soapbox/components/relative_timestamp.tsx index 1e64f9807..d530051d8 100644 --- a/app/soapbox/components/relative_timestamp.js +++ b/app/soapbox/components/relative_timestamp.tsx @@ -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 { + + _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 ( @@ -188,3 +196,5 @@ class RelativeTimestamp extends React.Component { } } + +export default injectIntl(RelativeTimestamp); diff --git a/app/soapbox/components/ui/text/text.tsx b/app/soapbox/components/ui/text/text.tsx index 933ac7a76..d25554972 100644 --- a/app/soapbox/components/ui/text/text.tsx +++ b/app/soapbox/components/ui/text/text.tsx @@ -84,7 +84,9 @@ interface IText extends Pick, '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 = React.forwardRef( }, ); -export default Text; +export { + Text as default, + IText, +}; diff --git a/app/soapbox/normalizers/__tests__/poll.test.ts b/app/soapbox/normalizers/__tests__/poll.test.ts index 8acf2ece4..b7ba0a46f 100644 --- a/app/soapbox/normalizers/__tests__/poll.test.ts +++ b/app/soapbox/normalizers/__tests__/poll.test.ts @@ -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', () => { diff --git a/app/soapbox/normalizers/__tests__/status.test.ts b/app/soapbox/normalizers/__tests__/status.test.ts index 43336d00f..b60373975 100644 --- a/app/soapbox/normalizers/__tests__/status.test.ts +++ b/app/soapbox/normalizers/__tests__/status.test.ts @@ -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', () => { diff --git a/app/soapbox/normalizers/account.ts b/app/soapbox/normalizers/account.ts index 1a519b8a8..cfaf92a1c 100644 --- a/app/soapbox/normalizers/account.ts +++ b/app/soapbox/normalizers/account.ts @@ -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(), @@ -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, diff --git a/app/soapbox/normalizers/poll.ts b/app/soapbox/normalizers/poll.ts index fb0f786ed..efae796c3 100644 --- a/app/soapbox/normalizers/poll.ts +++ b/app/soapbox/normalizers/poll.ts @@ -21,7 +21,7 @@ import type { Emoji, PollOption } from 'soapbox/types/entities'; export const PollRecord = ImmutableRecord({ emojis: ImmutableList(), expired: false, - expires_at: new Date(), + expires_at: '', id: '', multiple: false, options: ImmutableList(), diff --git a/app/soapbox/normalizers/status.ts b/app/soapbox/normalizers/status.ts index 6f35a3900..4788758d3 100644 --- a/app/soapbox/normalizers/status.ts +++ b/app/soapbox/normalizers/status.ts @@ -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(), favourited: false, favourites_count: 0, From 5f1d9ac56a4bf39acb58c23dfcec9dd0fd2cf096 Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Wed, 31 Aug 2022 17:01:58 -0500 Subject: [PATCH 2/2] relative_timestamp --> relative-timestamp --- app/soapbox/components/account.tsx | 2 +- app/soapbox/components/display-name.tsx | 2 +- app/soapbox/components/polls/poll-footer.tsx | 2 +- .../{relative_timestamp.tsx => relative-timestamp.tsx} | 0 app/soapbox/features/directory/components/account_card.tsx | 2 +- 5 files changed, 4 insertions(+), 4 deletions(-) rename app/soapbox/components/{relative_timestamp.tsx => relative-timestamp.tsx} (100%) diff --git a/app/soapbox/components/account.tsx b/app/soapbox/components/account.tsx index eeb970518..438d2f0f4 100644 --- a/app/soapbox/components/account.tsx +++ b/app/soapbox/components/account.tsx @@ -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'; diff --git a/app/soapbox/components/display-name.tsx b/app/soapbox/components/display-name.tsx index 1bb72a319..63028ccfe 100644 --- a/app/soapbox/components/display-name.tsx +++ b/app/soapbox/components/display-name.tsx @@ -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'; diff --git a/app/soapbox/components/polls/poll-footer.tsx b/app/soapbox/components/polls/poll-footer.tsx index 366f34d1c..ef4ca2276 100644 --- a/app/soapbox/components/polls/poll-footer.tsx +++ b/app/soapbox/components/polls/poll-footer.tsx @@ -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'; diff --git a/app/soapbox/components/relative_timestamp.tsx b/app/soapbox/components/relative-timestamp.tsx similarity index 100% rename from app/soapbox/components/relative_timestamp.tsx rename to app/soapbox/components/relative-timestamp.tsx diff --git a/app/soapbox/features/directory/components/account_card.tsx b/app/soapbox/features/directory/components/account_card.tsx index ca3e852f1..12ac5e7a7 100644 --- a/app/soapbox/features/directory/components/account_card.tsx +++ b/app/soapbox/features/directory/components/account_card.tsx @@ -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';