Merge branch 'fc' into 'develop'

TypeScript conversions

See merge request soapbox-pub/soapbox-fe!1285
edit-profile-fix
Alex Gleason 2022-05-01 18:34:55 +00:00
commit 1ebd66f6c1
20 zmienionych plików z 151 dodań i 178 usunięć

Wyświetl plik

@ -20,7 +20,7 @@ interface IHoverRefWrapper {
/** Makes a profile hover card appear when the wrapped element is hovered. */ /** Makes a profile hover card appear when the wrapped element is hovered. */
export const HoverRefWrapper: React.FC<IHoverRefWrapper> = ({ accountId, children, inline = false }) => { export const HoverRefWrapper: React.FC<IHoverRefWrapper> = ({ accountId, children, inline = false }) => {
const dispatch = useDispatch(); const dispatch = useDispatch();
const ref = useRef<HTMLElement>(); const ref = useRef<HTMLDivElement>(null);
const Elem: keyof JSX.IntrinsicElements = inline ? 'span' : 'div'; const Elem: keyof JSX.IntrinsicElements = inline ? 'span' : 'div';
const handleMouseEnter = () => { const handleMouseEnter = () => {
@ -41,7 +41,6 @@ export const HoverRefWrapper: React.FC<IHoverRefWrapper> = ({ accountId, childre
return ( return (
<Elem <Elem
// @ts-ignore: not sure how to fix :\
ref={ref} ref={ref}
className='hover-ref-wrapper' className='hover-ref-wrapper'
onMouseEnter={handleMouseEnter} onMouseEnter={handleMouseEnter}

Wyświetl plik

@ -1,10 +1,7 @@
import classNames from 'classnames'; import classNames from 'classnames';
import PropTypes from 'prop-types';
import React, { useEffect, useState } from 'react'; import React, { useEffect, useState } from 'react';
import ImmutablePropTypes from 'react-immutable-proptypes';
import { useIntl } from 'react-intl'; import { useIntl } from 'react-intl';
import { usePopper } from 'react-popper'; import { usePopper } from 'react-popper';
import { useSelector, useDispatch } from 'react-redux';
import { useHistory } from 'react-router-dom'; import { useHistory } from 'react-router-dom';
import { fetchRelationships } from 'soapbox/actions/accounts'; import { fetchRelationships } from 'soapbox/actions/accounts';
@ -16,14 +13,18 @@ import Badge from 'soapbox/components/badge';
import ActionButton from 'soapbox/features/ui/components/action_button'; import ActionButton from 'soapbox/features/ui/components/action_button';
import BundleContainer from 'soapbox/features/ui/containers/bundle_container'; import BundleContainer from 'soapbox/features/ui/containers/bundle_container';
import { UserPanel } from 'soapbox/features/ui/util/async-components'; import { UserPanel } from 'soapbox/features/ui/util/async-components';
import { useAppSelector, useAppDispatch } from 'soapbox/hooks';
import { makeGetAccount } from 'soapbox/selectors'; import { makeGetAccount } from 'soapbox/selectors';
import { showProfileHoverCard } from './hover_ref_wrapper'; import { showProfileHoverCard } from './hover_ref_wrapper';
import { Card, CardBody, Stack, Text } from './ui'; import { Card, CardBody, Stack, Text } from './ui';
import type { AppDispatch } from 'soapbox/store';
import type { Account } from 'soapbox/types/entities';
const getAccount = makeGetAccount(); const getAccount = makeGetAccount();
const getBadges = (account) => { const getBadges = (account: Account): JSX.Element[] => {
const badges = []; const badges = [];
if (account.admin) { if (account.admin) {
@ -43,29 +44,34 @@ const getBadges = (account) => {
return badges; return badges;
}; };
const handleMouseEnter = (dispatch) => { const handleMouseEnter = (dispatch: AppDispatch): React.MouseEventHandler => {
return e => { return () => {
dispatch(updateProfileHoverCard()); dispatch(updateProfileHoverCard());
}; };
}; };
const handleMouseLeave = (dispatch) => { const handleMouseLeave = (dispatch: AppDispatch): React.MouseEventHandler => {
return e => { return () => {
dispatch(closeProfileHoverCard(true)); dispatch(closeProfileHoverCard(true));
}; };
}; };
export const ProfileHoverCard = ({ visible }) => { interface IProfileHoverCard {
const dispatch = useDispatch(); visible: boolean,
}
/** Popup profile preview that appears when hovering avatars and display names. */
export const ProfileHoverCard: React.FC<IProfileHoverCard> = ({ visible = true }) => {
const dispatch = useAppDispatch();
const history = useHistory(); const history = useHistory();
const intl = useIntl(); const intl = useIntl();
const [popperElement, setPopperElement] = useState(null); const [popperElement, setPopperElement] = useState<HTMLElement | null>(null);
const me = useSelector(state => state.get('me')); const me = useAppSelector(state => state.me);
const accountId = useSelector(state => state.getIn(['profile_hover_card', 'accountId'])); const accountId: string | undefined = useAppSelector(state => state.profile_hover_card.get<string | undefined>('accountId', undefined));
const account = useSelector(state => accountId && getAccount(state, accountId)); const account = useAppSelector(state => accountId && getAccount(state, accountId));
const targetRef = useSelector(state => state.getIn(['profile_hover_card', 'ref', 'current'])); const targetRef = useAppSelector(state => state.profile_hover_card.getIn(['ref', 'current']) as Element | null);
const badges = account ? getBadges(account) : []; const badges = account ? getBadges(account) : [];
useEffect(() => { useEffect(() => {
@ -86,8 +92,8 @@ export const ProfileHoverCard = ({ visible }) => {
const { styles, attributes } = usePopper(targetRef, popperElement); const { styles, attributes } = usePopper(targetRef, popperElement);
if (!account) return null; if (!account) return null;
const accountBio = { __html: account.get('note_emojified') }; const accountBio = { __html: account.note_emojified };
const followedBy = me !== account.get('id') && account.getIn(['relationship', 'followed_by']); const followedBy = me !== account.id && account.relationship.get('followed_by') === true;
return ( return (
<div <div
@ -115,7 +121,7 @@ export const ProfileHoverCard = ({ visible }) => {
)} )}
</BundleContainer> </BundleContainer>
{account.getIn(['source', 'note'], '').length > 0 && ( {account.source.get('note', '').length > 0 && (
<Text size='sm' dangerouslySetInnerHTML={accountBio} /> <Text size='sm' dangerouslySetInnerHTML={accountBio} />
)} )}
</Stack> </Stack>
@ -134,14 +140,4 @@ export const ProfileHoverCard = ({ visible }) => {
); );
}; };
ProfileHoverCard.propTypes = {
visible: PropTypes.bool,
accountId: PropTypes.string,
account: ImmutablePropTypes.record,
};
ProfileHoverCard.defaultProps = {
visible: true,
};
export default ProfileHoverCard; export default ProfileHoverCard;

Wyświetl plik

@ -1,4 +1,3 @@
import PropTypes from 'prop-types';
import * as React from 'react'; import * as React from 'react';
import { defineMessages, useIntl } from 'react-intl'; import { defineMessages, useIntl } from 'react-intl';
@ -75,9 +74,4 @@ const DeleteAccount = () => {
); );
}; };
DeleteAccount.propTypes = {
intl: PropTypes.object,
dispatch: PropTypes.func,
};
export default DeleteAccount; export default DeleteAccount;

Wyświetl plik

@ -1,23 +0,0 @@
import React from 'react';
import PlaceholderAvatar from './placeholder_avatar';
import PlaceholderDisplayName from './placeholder_display_name';
export default class PlaceholderAccount extends React.Component {
render() {
return (
<div className='account'>
<div className='account__wrapper'>
<span className='account__display-name'>
<div className='account__avatar-wrapper'>
<PlaceholderAvatar size={36} />
</div>
<PlaceholderDisplayName minLength={3} maxLength={25} />
</span>
</div>
</div>
);
}
}

Wyświetl plik

@ -0,0 +1,22 @@
import React from 'react';
import PlaceholderAvatar from './placeholder_avatar';
import PlaceholderDisplayName from './placeholder_display_name';
/** Fake account to display while data is loading. */
const PlaceholderAccount: React.FC = () => {
return (
<div className='account'>
<div className='account__wrapper'>
<span className='account__display-name'>
<div className='account__avatar-wrapper'>
<PlaceholderAvatar size={36} />
</div>
<PlaceholderDisplayName minLength={3} maxLength={25} />
</span>
</div>
</div>
);
};
export default PlaceholderAccount;

Wyświetl plik

@ -1,7 +1,11 @@
import PropTypes from 'prop-types';
import * as React from 'react'; import * as React from 'react';
const PlaceholderAvatar = ({ size }) => { interface IPlaceholderAvatar {
size: number,
}
/** Fake avatar to display while data is loading. */
const PlaceholderAvatar: React.FC<IPlaceholderAvatar> = ({ size }) => {
const style = React.useMemo(() => { const style = React.useMemo(() => {
if (!size) { if (!size) {
return {}; return {};
@ -17,13 +21,8 @@ const PlaceholderAvatar = ({ size }) => {
<div <div
className='rounded-full bg-slate-200 dark:bg-slate-700' className='rounded-full bg-slate-200 dark:bg-slate-700'
style={style} style={style}
alt=''
/> />
); );
}; };
PlaceholderAvatar.propTypes = {
size: PropTypes.number.isRequired,
};
export default PlaceholderAvatar; export default PlaceholderAvatar;

Wyświetl plik

@ -3,7 +3,8 @@ import * as React from 'react';
import { randomIntFromInterval, generateText } from '../utils'; import { randomIntFromInterval, generateText } from '../utils';
const PlaceholderCard = () => ( /** Fake link preview to display while data is loading. */
const PlaceholderCard: React.FC = () => (
<div className={classNames('status-card', { <div className={classNames('status-card', {
'animate-pulse': true, 'animate-pulse': true,
})} })}

Wyświetl plik

@ -1,32 +0,0 @@
import React from 'react';
import { randomIntFromInterval, generateText } from '../utils';
import PlaceholderAvatar from './placeholder_avatar';
import PlaceholderDisplayName from './placeholder_display_name';
export default class PlaceholderAccount extends React.Component {
render() {
const messageLength = randomIntFromInterval(5, 75);
return (
<div className='chat-list-item chat-list-item--placeholder'>
<div className='account'>
<div className='account__wrapper'>
<div className='account__display-name'>
<div className='account__avatar-wrapper'>
<PlaceholderAvatar size={36} />
</div>
<PlaceholderDisplayName minLength={3} maxLength={25} />
<span className='chat__last-message'>
{generateText(messageLength)}
</span>
</div>
</div>
</div>
</div>
);
}
}

Wyświetl plik

@ -0,0 +1,31 @@
import React from 'react';
import { randomIntFromInterval, generateText } from '../utils';
import PlaceholderAvatar from './placeholder_avatar';
import PlaceholderDisplayName from './placeholder_display_name';
/** Fake chat to display while data is loading. */
const PlaceholderChat: React.FC = () => {
const messageLength = randomIntFromInterval(5, 75);
return (
<div className='chat-list-item chat-list-item--placeholder'>
<div className='account'>
<div className='account__wrapper'>
<div className='account__display-name'>
<div className='account__avatar-wrapper'>
<PlaceholderAvatar size={36} />
</div>
<PlaceholderDisplayName minLength={3} maxLength={25} />
<span className='chat__last-message'>
{generateText(messageLength)}
</span>
</div>
</div>
</div>
</div>
);
};
export default PlaceholderChat;

Wyświetl plik

@ -1,9 +1,14 @@
import PropTypes from 'prop-types';
import React from 'react'; import React from 'react';
import { randomIntFromInterval, generateText } from '../utils'; import { randomIntFromInterval, generateText } from '../utils';
const PlaceholderDisplayName = ({ minLength, maxLength }) => { interface IPlaceholderDisplayName {
maxLength: number,
minLength: number,
}
/** Fake display name to show when data is loading. */
const PlaceholderDisplayName: React.FC<IPlaceholderDisplayName> = ({ minLength, maxLength }) => {
const length = randomIntFromInterval(maxLength, minLength); const length = randomIntFromInterval(maxLength, minLength);
const acctLength = randomIntFromInterval(maxLength, minLength); const acctLength = randomIntFromInterval(maxLength, minLength);
@ -15,9 +20,4 @@ const PlaceholderDisplayName = ({ minLength, maxLength }) => {
); );
}; };
PlaceholderDisplayName.propTypes = {
maxLength: PropTypes.number.isRequired,
minLength: PropTypes.number.isRequired,
};
export default PlaceholderDisplayName; export default PlaceholderDisplayName;

Wyświetl plik

@ -1,21 +0,0 @@
import React from 'react';
import { generateText, randomIntFromInterval } from '../utils';
export default class PlaceholderHashtag extends React.Component {
render() {
const length = randomIntFromInterval(15, 30);
return (
<div className='placeholder-hashtag'>
<div className='trends__item'>
<div className='trends__item__name'>
{generateText(length)}
</div>
</div>
</div>
);
}
}

Wyświetl plik

@ -0,0 +1,20 @@
import React from 'react';
import { generateText, randomIntFromInterval } from '../utils';
/** Fake hashtag to display while data is loading. */
const PlaceholderHashtag: React.FC = () => {
const length = randomIntFromInterval(15, 30);
return (
<div className='placeholder-hashtag'>
<div className='trends__item'>
<div className='trends__item__name'>
{generateText(length)}
</div>
</div>
</div>
);
};
export default PlaceholderHashtag;

Wyświetl plik

@ -1,17 +0,0 @@
import React from 'react';
import PlaceholderStatus from './placeholder_status';
export default class PlaceholderMaterialStatus extends React.Component {
render() {
return (
<div className='material-status' tabIndex={-1} aria-hidden>
<div className='material-status__status' tabIndex={0}>
<PlaceholderStatus {...this.props} focusable={false} />
</div>
</div>
);
}
}

Wyświetl plik

@ -0,0 +1,16 @@
import React from 'react';
import PlaceholderStatus from './placeholder_status';
/** Fake material status to display while data is loading. */
const PlaceholderMaterialStatus: React.FC = () => {
return (
<div className='material-status' tabIndex={-1} aria-hidden>
<div className='material-status__status' tabIndex={0}>
<PlaceholderStatus />
</div>
</div>
);
};
export default PlaceholderMaterialStatus;

Wyświetl plik

@ -4,6 +4,7 @@ import PlaceholderAvatar from './placeholder_avatar';
import PlaceholderDisplayName from './placeholder_display_name'; import PlaceholderDisplayName from './placeholder_display_name';
import PlaceholderStatusContent from './placeholder_status_content'; import PlaceholderStatusContent from './placeholder_status_content';
/** Fake notification to display while data is loading. */
const PlaceholderNotification = () => ( const PlaceholderNotification = () => (
<div className='bg-white dark:bg-slate-800 px-4 py-6 sm:p-6'> <div className='bg-white dark:bg-slate-800 px-4 py-6 sm:p-6'>
<div className='w-full animate-pulse'> <div className='w-full animate-pulse'>

Wyświetl plik

@ -9,7 +9,8 @@ interface IPlaceholderStatus {
thread?: boolean thread?: boolean
} }
const PlaceholderStatus = ({ thread = false }: IPlaceholderStatus) => ( /** Fake status to display while data is loading. */
const PlaceholderStatus: React.FC<IPlaceholderStatus> = ({ thread = false }) => (
<div <div
className={classNames({ className={classNames({
'status-placeholder bg-white dark:bg-slate-800': true, 'status-placeholder bg-white dark:bg-slate-800': true,

Wyświetl plik

@ -1,9 +1,14 @@
import PropTypes from 'prop-types';
import React from 'react'; import React from 'react';
import { randomIntFromInterval, generateText } from '../utils'; import { randomIntFromInterval, generateText } from '../utils';
const PlaceholderStatusContent = ({ minLength, maxLength }) => { interface IPlaceholderStatusContent {
maxLength: number,
minLength: number,
}
/** Fake status content while data is loading. */
const PlaceholderStatusContent: React.FC<IPlaceholderStatusContent> = ({ minLength, maxLength }) => {
const length = randomIntFromInterval(maxLength, minLength); const length = randomIntFromInterval(maxLength, minLength);
return ( return (
@ -13,9 +18,4 @@ const PlaceholderStatusContent = ({ minLength, maxLength }) => {
); );
}; };
PlaceholderStatusContent.propTypes = {
maxLength: PropTypes.number.isRequired,
minLength: PropTypes.number.isRequired,
};
export default PlaceholderStatusContent; export default PlaceholderStatusContent;

Wyświetl plik

@ -1,16 +0,0 @@
import PropTypes from 'prop-types';
import React from 'react';
const ColumnSubheading = ({ text }) => {
return (
<div className='column-subheading'>
{text}
</div>
);
};
ColumnSubheading.propTypes = {
text: PropTypes.string.isRequired,
};
export default ColumnSubheading;

Wyświetl plik

@ -1,15 +1,22 @@
import classNames from 'classnames'; import classNames from 'classnames';
import PropTypes from 'prop-types';
import * as React from 'react'; import * as React from 'react';
import { FormattedMessage } from 'react-intl'; import { FormattedMessage } from 'react-intl';
import spring from 'react-motion/lib/spring'; import { spring } from 'react-motion';
import { Icon, Stack, Text } from 'soapbox/components/ui'; import { Icon, Stack, Text } from 'soapbox/components/ui';
import Motion from '../../ui/util/optional_motion'; import Motion from '../../ui/util/optional_motion';
const UploadArea = ({ active, onClose }) => { interface IUploadArea {
const handleKeyUp = (e) => { /** Whether the upload area is active. */
active: boolean,
/** Callback when the upload area is closed. */
onClose: () => void,
}
/** Component to display when a file is dragged over the UI. */
const UploadArea: React.FC<IUploadArea> = ({ active, onClose }) => {
const handleKeyUp = (e: KeyboardEvent) => {
const keyCode = e.keyCode; const keyCode = e.keyCode;
if (active) { if (active) {
@ -63,9 +70,4 @@ const UploadArea = ({ active, onClose }) => {
); );
}; };
UploadArea.propTypes = {
active: PropTypes.bool,
onClose: PropTypes.func,
};
export default UploadArea; export default UploadArea;

Wyświetl plik

@ -61,7 +61,7 @@ export const AccountRecord = ImmutableRecord({
note_emojified: '', note_emojified: '',
note_plain: '', note_plain: '',
patron: null as PatronAccount | null, patron: null as PatronAccount | null,
relationship: ImmutableList<ImmutableMap<string, any>>(), relationship: ImmutableMap<string, any>(),
should_refetch: false, should_refetch: false,
staff: false, staff: false,
}); });