import React from 'react' import PropTypes from 'prop-types' import { connect } from 'react-redux' import ImmutablePropTypes from 'react-immutable-proptypes' import ImmutablePureComponent from 'react-immutable-pure-component' import debounce from 'lodash.debounce' import { me } from '../initial_state' import { CX, POPOVER_USER_INFO, } from '../constants' import { openPopover, closePopover } from '../actions/popover' import Icon from './icon' import Text from './text' class DisplayName extends ImmutablePureComponent { mouseOverTimeout = null componentWillUnmount () { document.removeEventListener('mousemove', this.handleMouseMove, true) clearTimeout(this.mouseOverTimeout) } handleMouseEnter = () => { if (this.mouseOverTimeout) return null this.mouseOverTimeout = setTimeout(() => { this.props.openUserInfoPopover({ targetRef: this.node, position: 'top', accountId: this.props.account.get('id'), }) document.addEventListener('mousemove', this.handleMouseMove, true) }, 1250) } handleMouseLeave = debounce((e) => { this.attemptToHidePopover(e) }, 250) handleMouseMove = debounce((e) => { this.attemptToHidePopover(e) }, 100) attemptToHidePopover = (e) => { const lastTarget = e.toElement || e.relatedTarget const isElement = (lastTarget instanceof Element || lastTarget instanceof HTMLDocument) const userInfoPopoverEl = document.getElementById('user-info-popover') if (this.mouseOverTimeout && ( !isElement && !userInfoPopoverEl || (userInfoPopoverEl && isElement && lastTarget && !userInfoPopoverEl.contains(lastTarget)) || (!userInfoPopoverEl && isElement && lastTarget && this.node && !this.node.contains(lastTarget)) )) { document.removeEventListener('mousemove', this.handleMouseMove, true) clearTimeout(this.mouseOverTimeout) this.mouseOverTimeout = null this.props.closeUserInfoPopover() } } setRef = (n) => { this.node = n } render() { const { account, isMultiline, isLarge, noHover, noUsername, noRelationship, isSmall, isComment, isCentered, isInline, } = this.props if (!account) return null const containerClassName = CX({ d: 1, maxW100PC: 1, aiCenter: !isMultiline, flexRow: !isMultiline, cursorPointer: !noHover, aiCenter: isCentered, displayInlineBlock: isInline, }) const displayNameClasses = CX({ text: 1, overflowWrapBreakWord: 1, whiteSpaceNoWrap: 1, fw600: 1, cPrimary: 1, mr2: 1, lineHeight125: !isSmall, fs14PX: isSmall, fs15PX: !isLarge, fs24PX: isLarge && !isSmall, }) const usernameClasses = CX({ text: 1, displayFlex: 1, flexShrink0: 1, overflowWrapBreakWord: 1, textOverflowEllipsis: 1, cSecondary: 1, fw400: 1, lineHeight15: isMultiline, lineHeight125: !isMultiline, ml5: !isMultiline, fs14PX: isSmall, fs15PX: !isLarge, fs16PX: isLarge && !isSmall, }) const iconSize = !!isLarge ? 19 : !!isComment ? 12 : !!isSmall ? 14 : 15 let relationshipLabel if (me && account) { const accountId = account.get('id') const isFollowedBy = (me !== accountId && account.getIn(['relationship', 'followed_by'])) if (isFollowedBy) { relationshipLabel = 'Follows you' } } return ( <div className={containerClassName} onMouseEnter={noHover ? undefined : this.handleMouseEnter} onMouseLeave={noHover ? undefined : this.handleMouseLeave} ref={this.setRef} > <span className={[_s.d, _s.flexRow, _s.aiCenter, _s.maxW100PC, _s.flexShrink1, _s.overflowHidden, _s.cPrimary].join(' ')}> <bdi className={[_s.text, _s.whiteSpaceNoWrap, _s.textOverflowEllipsis].join(' ')}> <strong className={displayNameClasses} dangerouslySetInnerHTML={{ __html: account.get('display_name_html') }} /> { account.get('locked') && <Icon id='lock-filled' size={`${iconSize - 3}px`} className={[_s.cPrimary, _s.ml5].join(' ')} /> } </bdi> { account.get('is_verified') && <Icon id='verified' size={`${iconSize}px`} className={[_s.ml5, _s.d].join(' ')} /> } </span> { !noUsername && <span className={usernameClasses}> @{account.get('acct')} { !noRelationship && !!relationshipLabel && <span className={[_s.d, _s.ml5, _s.jcCenter].join(' ')}> <Text size='extraSmall' isBadge color='tertiary' className={[_s.bgSecondary, _s.py2].join(' ')} > {relationshipLabel} </Text> </span> } </span> } </div> ) } } const mapDispatchToProps = (dispatch) => ({ openUserInfoPopover(props) { dispatch(openPopover(POPOVER_USER_INFO, props)) }, closeUserInfoPopover() { dispatch(closePopover(POPOVER_USER_INFO)) } }) DisplayName.propTypes = { account: ImmutablePropTypes.map, openUserInfoPopover: PropTypes.func.isRequired, closeUserInfoPopover: PropTypes.func.isRequired, isLarge: PropTypes.bool, isMultiline: PropTypes.bool, isSmall: PropTypes.bool, noHover: PropTypes.bool, noRelationship: PropTypes.bool, noUsername: PropTypes.bool, isComment: PropTypes.bool, isCentered: PropTypes.bool, isInline: PropTypes.bool, } export default (connect(null, mapDispatchToProps)(DisplayName))