commit
57b82f7778
@ -0,0 +1,24 @@
|
|||||||
|
export const PROFILE_HOVER_CARD_OPEN = 'PROFILE_HOVER_CARD_OPEN';
|
||||||
|
export const PROFILE_HOVER_CARD_UPDATE = 'PROFILE_HOVER_CARD_UPDATE';
|
||||||
|
export const PROFILE_HOVER_CARD_CLOSE = 'PROFILE_HOVER_CARD_CLOSE';
|
||||||
|
|
||||||
|
export function openProfileHoverCard(ref, accountId) {
|
||||||
|
return {
|
||||||
|
type: PROFILE_HOVER_CARD_OPEN,
|
||||||
|
ref,
|
||||||
|
accountId,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function updateProfileHoverCard() {
|
||||||
|
return {
|
||||||
|
type: PROFILE_HOVER_CARD_UPDATE,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function closeProfileHoverCard(force = false) {
|
||||||
|
return {
|
||||||
|
type: PROFILE_HOVER_CARD_CLOSE,
|
||||||
|
force,
|
||||||
|
};
|
||||||
|
}
|
@ -0,0 +1,64 @@
|
|||||||
|
import React, { useRef } from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import {
|
||||||
|
openProfileHoverCard,
|
||||||
|
closeProfileHoverCard,
|
||||||
|
} from 'soapbox/actions/profile_hover_card';
|
||||||
|
import { useDispatch } from 'react-redux';
|
||||||
|
import { debounce } from 'lodash';
|
||||||
|
import { isMobile } from 'soapbox/is_mobile';
|
||||||
|
|
||||||
|
const showProfileHoverCard = debounce((dispatch, ref, accountId) => {
|
||||||
|
dispatch(openProfileHoverCard(ref, accountId));
|
||||||
|
}, 1200);
|
||||||
|
|
||||||
|
const handleMouseEnter = (dispatch, ref, accountId) => {
|
||||||
|
return e => {
|
||||||
|
if (!isMobile(window.innerWidth))
|
||||||
|
showProfileHoverCard(dispatch, ref, accountId);
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleMouseLeave = (dispatch) => {
|
||||||
|
return e => {
|
||||||
|
showProfileHoverCard.cancel();
|
||||||
|
setTimeout(() => dispatch(closeProfileHoverCard()), 300);
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleClick = (dispatch) => {
|
||||||
|
return e => {
|
||||||
|
showProfileHoverCard.cancel();
|
||||||
|
dispatch(closeProfileHoverCard(true));
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export const HoverRefWrapper = ({ accountId, children, inline }) => {
|
||||||
|
const dispatch = useDispatch();
|
||||||
|
const ref = useRef();
|
||||||
|
const Elem = inline ? 'span' : 'div';
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Elem
|
||||||
|
ref={ref}
|
||||||
|
className='hover-ref-wrapper'
|
||||||
|
onMouseEnter={handleMouseEnter(dispatch, ref, accountId)}
|
||||||
|
onMouseLeave={handleMouseLeave(dispatch)}
|
||||||
|
onClick={handleClick(dispatch)}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</Elem>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
HoverRefWrapper.propTypes = {
|
||||||
|
accountId: PropTypes.string,
|
||||||
|
children: PropTypes.node,
|
||||||
|
inline: PropTypes.bool,
|
||||||
|
};
|
||||||
|
|
||||||
|
HoverRefWrapper.defaultProps = {
|
||||||
|
inline: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
export default HoverRefWrapper;
|
@ -0,0 +1,92 @@
|
|||||||
|
import React, { useEffect, useState } from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import { useSelector, useDispatch } from 'react-redux';
|
||||||
|
import { makeGetAccount } from 'soapbox/selectors';
|
||||||
|
import { injectIntl, FormattedMessage } from 'react-intl';
|
||||||
|
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||||
|
import UserPanel from 'soapbox/features/ui/components/user_panel';
|
||||||
|
import ActionButton from 'soapbox/features/ui/components/action_button';
|
||||||
|
import { isAdmin, isModerator } from 'soapbox/utils/accounts';
|
||||||
|
import Badge from 'soapbox/components/badge';
|
||||||
|
import classNames from 'classnames';
|
||||||
|
import { fetchRelationships } from 'soapbox/actions/accounts';
|
||||||
|
import { usePopper } from 'react-popper';
|
||||||
|
import {
|
||||||
|
closeProfileHoverCard,
|
||||||
|
updateProfileHoverCard,
|
||||||
|
} from 'soapbox/actions/profile_hover_card';
|
||||||
|
|
||||||
|
const getAccount = makeGetAccount();
|
||||||
|
|
||||||
|
const getBadges = (account) => {
|
||||||
|
let badges = [];
|
||||||
|
if (isAdmin(account)) badges.push(<Badge key='admin' slug='admin' title='Admin' />);
|
||||||
|
if (isModerator(account)) badges.push(<Badge key='moderator' slug='moderator' title='Moderator' />);
|
||||||
|
if (account.getIn(['patron', 'is_patron'])) badges.push(<Badge key='patron' slug='patron' title='Patron' />);
|
||||||
|
return badges;
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleMouseEnter = (dispatch) => {
|
||||||
|
return e => {
|
||||||
|
dispatch(updateProfileHoverCard());
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleMouseLeave = (dispatch) => {
|
||||||
|
return e => {
|
||||||
|
dispatch(closeProfileHoverCard(true));
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export const ProfileHoverCard = ({ visible }) => {
|
||||||
|
const dispatch = useDispatch();
|
||||||
|
|
||||||
|
const [popperElement, setPopperElement] = useState(null);
|
||||||
|
|
||||||
|
const accountId = useSelector(state => state.getIn(['profile_hover_card', 'accountId']));
|
||||||
|
const account = useSelector(state => accountId && getAccount(state, accountId));
|
||||||
|
const targetRef = useSelector(state => state.getIn(['profile_hover_card', 'ref', 'current']));
|
||||||
|
const badges = account ? getBadges(account) : [];
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (accountId) dispatch(fetchRelationships([accountId]));
|
||||||
|
}, [dispatch, accountId]);
|
||||||
|
|
||||||
|
const { styles, attributes } = usePopper(targetRef, popperElement);
|
||||||
|
|
||||||
|
if (!account) return null;
|
||||||
|
const accountBio = { __html: account.get('note_emojified') };
|
||||||
|
const followedBy = account.getIn(['relationship', 'followed_by']);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={classNames('profile-hover-card', { 'profile-hover-card--visible': visible })} ref={setPopperElement} style={styles.popper} {...attributes.popper} onMouseEnter={handleMouseEnter(dispatch)} onMouseLeave={handleMouseLeave(dispatch)}>
|
||||||
|
<div className='profile-hover-card__container'>
|
||||||
|
{followedBy &&
|
||||||
|
<span className='relationship-tag'>
|
||||||
|
<FormattedMessage id='account.follows_you' defaultMessage='Follows you' />
|
||||||
|
</span>}
|
||||||
|
<div className='profile-hover-card__action-button'><ActionButton account={account} small /></div>
|
||||||
|
<UserPanel className='profile-hover-card__user' accountId={account.get('id')} />
|
||||||
|
{badges.length > 0 &&
|
||||||
|
<div className='profile-hover-card__badges'>
|
||||||
|
{badges}
|
||||||
|
</div>}
|
||||||
|
{account.getIn(['source', 'note'], '').length > 0 &&
|
||||||
|
<div className='profile-hover-card__bio' dangerouslySetInnerHTML={accountBio} />}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
ProfileHoverCard.propTypes = {
|
||||||
|
visible: PropTypes.bool,
|
||||||
|
accountId: PropTypes.string,
|
||||||
|
account: ImmutablePropTypes.map,
|
||||||
|
intl: PropTypes.object.isRequired,
|
||||||
|
};
|
||||||
|
|
||||||
|
ProfileHoverCard.defaultProps = {
|
||||||
|
visible: true,
|
||||||
|
};
|
||||||
|
|
||||||
|
export default injectIntl(ProfileHoverCard);
|
@ -1,79 +0,0 @@
|
|||||||
import React from 'react';
|
|
||||||
import PropTypes from 'prop-types';
|
|
||||||
import { connect } from 'react-redux';
|
|
||||||
import { makeGetAccount } from '../../selectors';
|
|
||||||
import { injectIntl, FormattedMessage } from 'react-intl';
|
|
||||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
|
||||||
import ImmutablePureComponent from 'react-immutable-pure-component';
|
|
||||||
import UserPanel from '../ui/components/user_panel';
|
|
||||||
import ActionButton from '../ui/components/action_button';
|
|
||||||
import { isAdmin, isModerator } from 'soapbox/utils/accounts';
|
|
||||||
import Badge from 'soapbox/components/badge';
|
|
||||||
import classNames from 'classnames';
|
|
||||||
import { fetchRelationships } from 'soapbox/actions/accounts';
|
|
||||||
|
|
||||||
const getAccount = makeGetAccount();
|
|
||||||
|
|
||||||
const mapStateToProps = (state, { accountId }) => {
|
|
||||||
return {
|
|
||||||
account: getAccount(state, accountId),
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
export default @connect(mapStateToProps)
|
|
||||||
@injectIntl
|
|
||||||
class ProfileHoverCardContainer extends ImmutablePureComponent {
|
|
||||||
|
|
||||||
static propTypes = {
|
|
||||||
visible: PropTypes.bool,
|
|
||||||
accountId: PropTypes.string,
|
|
||||||
account: ImmutablePropTypes.map,
|
|
||||||
intl: PropTypes.object.isRequired,
|
|
||||||
dispatch: PropTypes.func.isRequired,
|
|
||||||
}
|
|
||||||
|
|
||||||
static defaultProps = {
|
|
||||||
visible: true,
|
|
||||||
}
|
|
||||||
|
|
||||||
getBadges = () => {
|
|
||||||
const { account } = this.props;
|
|
||||||
let badges = [];
|
|
||||||
if (isAdmin(account)) badges.push(<Badge key='admin' slug='admin' title='Admin' />);
|
|
||||||
if (isModerator(account)) badges.push(<Badge key='moderator' slug='moderator' title='Moderator' />);
|
|
||||||
if (account.getIn(['patron', 'is_patron'])) badges.push(<Badge key='patron' slug='patron' title='Patron' />);
|
|
||||||
return badges;
|
|
||||||
}
|
|
||||||
|
|
||||||
componentDidMount() {
|
|
||||||
this.props.dispatch(fetchRelationships([this.props.accountId]));
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
|
||||||
const { visible, accountId, account } = this.props;
|
|
||||||
if (!accountId) return null;
|
|
||||||
const accountBio = { __html: account.get('note_emojified') };
|
|
||||||
const followedBy = account.getIn(['relationship', 'followed_by']);
|
|
||||||
const badges = this.getBadges();
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className={classNames('profile-hover-card', { 'profile-hover-card--visible': visible })}>
|
|
||||||
<div className='profile-hover-card__container'>
|
|
||||||
{followedBy &&
|
|
||||||
<span className='relationship-tag'>
|
|
||||||
<FormattedMessage id='account.follows_you' defaultMessage='Follows you' />
|
|
||||||
</span>}
|
|
||||||
<div className='profile-hover-card__action-button'><ActionButton account={account} small /></div>
|
|
||||||
<UserPanel className='profile-hover-card__user' accountId={accountId} />
|
|
||||||
{badges.length > 0 &&
|
|
||||||
<div className='profile-hover-card__badges'>
|
|
||||||
{badges}
|
|
||||||
</div>}
|
|
||||||
{account.getIn(['source', 'note'], '').length > 0 &&
|
|
||||||
<div className='profile-hover-card__bio' dangerouslySetInnerHTML={accountBio} />}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
};
|
|
@ -0,0 +1,27 @@
|
|||||||
|
import {
|
||||||
|
PROFILE_HOVER_CARD_OPEN,
|
||||||
|
PROFILE_HOVER_CARD_CLOSE,
|
||||||
|
PROFILE_HOVER_CARD_UPDATE,
|
||||||
|
} from 'soapbox/actions/profile_hover_card';
|
||||||
|
import { Map as ImmutableMap } from 'immutable';
|
||||||
|
|
||||||
|
const initialState = ImmutableMap();
|
||||||
|
|
||||||
|
export default function profileHoverCard(state = initialState, action) {
|
||||||
|
switch(action.type) {
|
||||||
|
case PROFILE_HOVER_CARD_OPEN:
|
||||||
|
return ImmutableMap({
|
||||||
|
ref: action.ref,
|
||||||
|
accountId: action.accountId,
|
||||||
|
});
|
||||||
|
case PROFILE_HOVER_CARD_UPDATE:
|
||||||
|
return state.set('hovered', true);
|
||||||
|
case PROFILE_HOVER_CARD_CLOSE:
|
||||||
|
if (state.get('hovered') === true && !action.force)
|
||||||
|
return state;
|
||||||
|
else
|
||||||
|
return ImmutableMap();
|
||||||
|
default:
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in new issue