Merge branch 'chats' into alex-chats
This commit is contained in:
@ -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';
|
||||
@ -54,7 +54,7 @@ const PollFooter: React.FC<IPollFooter> = ({ poll, showResults, selected }): JSX
|
||||
</Button>
|
||||
)}
|
||||
|
||||
<HStack space={1.5} alignItems='center'>
|
||||
<HStack space={1.5} alignItems='center' wrap>
|
||||
{poll.pleroma.get('non_anonymous') && (
|
||||
<>
|
||||
<Tooltip text={intl.formatMessage(messages.nonAnonymous)}>
|
||||
|
||||
@ -18,7 +18,7 @@ interface IPoll {
|
||||
}
|
||||
|
||||
const messages = defineMessages({
|
||||
multiple: { id: 'poll.chooseMultiple', defaultMessage: 'Choose as many as you\'d like.' },
|
||||
multiple: { id: 'poll.choose_multiple', defaultMessage: 'Choose as many as you\'d like.' },
|
||||
});
|
||||
|
||||
const Poll: React.FC<IPoll> = ({ id, status }): JSX.Element | null => {
|
||||
|
||||
@ -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);
|
||||
@ -6,7 +6,7 @@ import { Virtuoso, Components, VirtuosoProps, VirtuosoHandle, ListRange, IndexLo
|
||||
import { useSettings } from 'soapbox/hooks';
|
||||
|
||||
import LoadMore from './load_more';
|
||||
import { Card, Spinner, Text } from './ui';
|
||||
import { Card, Spinner } from './ui';
|
||||
|
||||
/** Custom Viruoso component context. */
|
||||
type Context = {
|
||||
@ -162,7 +162,7 @@ const ScrollableList = React.forwardRef<VirtuosoHandle, IScrollableList>(({
|
||||
{isLoading ? (
|
||||
<Spinner />
|
||||
) : (
|
||||
<Text>{emptyMessage}</Text>
|
||||
emptyMessage
|
||||
)}
|
||||
</Card>
|
||||
</div>
|
||||
|
||||
@ -301,12 +301,12 @@ const StatusActionBar: React.FC<IStatusActionBar> = ({
|
||||
};
|
||||
|
||||
const handleCopy: React.EventHandler<React.MouseEvent> = (e) => {
|
||||
const { url } = status;
|
||||
const { uri } = status;
|
||||
const textarea = document.createElement('textarea');
|
||||
|
||||
e.stopPropagation();
|
||||
|
||||
textarea.textContent = url;
|
||||
textarea.textContent = uri;
|
||||
textarea.style.position = 'fixed';
|
||||
|
||||
document.body.appendChild(textarea);
|
||||
|
||||
@ -42,11 +42,13 @@ interface IHStack {
|
||||
grow?: boolean,
|
||||
/** Extra CSS styles for the <div> */
|
||||
style?: React.CSSProperties
|
||||
/** Whether to let the flexbox wrap onto multiple lines. */
|
||||
wrap?: boolean,
|
||||
}
|
||||
|
||||
/** Horizontal row of child elements. */
|
||||
const HStack = forwardRef<HTMLDivElement, IHStack>((props, ref) => {
|
||||
const { space, alignItems, grow, justifyContent, className, ...filteredProps } = props;
|
||||
const { space, alignItems, grow, justifyContent, wrap, className, ...filteredProps } = props;
|
||||
|
||||
return (
|
||||
<div
|
||||
@ -60,6 +62,7 @@ const HStack = forwardRef<HTMLDivElement, IHStack>((props, ref) => {
|
||||
// @ts-ignore
|
||||
[spaces[space]]: typeof space !== 'undefined',
|
||||
'flex-grow': grow,
|
||||
'flex-wrap': wrap,
|
||||
}, className)}
|
||||
/>
|
||||
);
|
||||
|
||||
@ -39,12 +39,13 @@ interface IStack extends React.HTMLAttributes<HTMLDivElement> {
|
||||
}
|
||||
|
||||
/** Vertical stack of child elements. */
|
||||
const Stack: React.FC<IStack> = (props) => {
|
||||
const Stack: React.FC<IStack> = React.forwardRef((props, ref: React.LegacyRef<HTMLDivElement> | undefined) => {
|
||||
const { space, alignItems, justifyContent, className, grow, ...filteredProps } = props;
|
||||
|
||||
return (
|
||||
<div
|
||||
{...filteredProps}
|
||||
ref={ref}
|
||||
className={classNames('flex flex-col', {
|
||||
// @ts-ignore
|
||||
[spaces[space]]: typeof space !== 'undefined',
|
||||
@ -56,6 +57,6 @@ const Stack: React.FC<IStack> = (props) => {
|
||||
}, className)}
|
||||
/>
|
||||
);
|
||||
};
|
||||
});
|
||||
|
||||
export default Stack;
|
||||
|
||||
@ -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,
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user