This commit is contained in:
mgabdev
2020-02-28 10:20:47 -05:00
parent 0bd1eb2c77
commit 3ca4ffcc6b
77 changed files with 6110 additions and 1427 deletions

View File

@@ -1,6 +1,6 @@
import { Fragment } from 'react'
import ImmutablePropTypes from 'react-immutable-proptypes';
import classNames from 'classnames';
import classNames from 'classnames/bind'
import ImmutablePureComponent from 'react-immutable-pure-component';
import Textarea from 'react-textarea-autosize';
import { isRtl } from '../../utils/rtl';
@@ -8,6 +8,8 @@ import { textAtCursorMatchesToken } from '../../utils/cursor_token_match';
import AutosuggestAccount from '../autosuggest_account';
import AutosuggestEmoji from '../autosuggest_emoji';
const cx = classNames.bind(_s)
export default class AutosuggestTextbox extends ImmutablePureComponent {
static propTypes = {
@@ -30,6 +32,7 @@ export default class AutosuggestTextbox extends ImmutablePureComponent {
onFocus: PropTypes.func,
onBlur: PropTypes.func,
textarea: PropTypes.bool,
small: PropTypes.bool,
};
static defaultProps = {
@@ -188,7 +191,7 @@ export default class AutosuggestTextbox extends ImmutablePureComponent {
}
render () {
const { value, suggestions, disabled, placeholder, onKeyUp, autoFocus, children, className, id, maxLength, textarea } = this.props;
const { value, small, suggestions, disabled, placeholder, onKeyUp, autoFocus, children, className, id, maxLength, textarea } = this.props;
const { suggestionsHidden } = this.state;
const style = { direction: 'ltr' };
@@ -196,14 +199,30 @@ export default class AutosuggestTextbox extends ImmutablePureComponent {
style.direction = 'rtl';
}
const textClasses = cx({
default: 1,
lineHeight125: 1,
resizeNone: 1,
text: 1,
displayBlock: 1,
outlineNone: 1,
backgroundColorPrimary: !small,
backgroundSubtle: small,
paddingVertical15PX: !small,
paddingVertical10PX: small,
fontSize16PX: !small,
fontSize14PX: small,
marginRight5PX: small,
})
if (textarea) {
return (
<Fragment>
<div className={[_s.default].join(' ')}>
<div className={[_s.default, _s.flexGrow1].join(' ')}>
<div className={[_s.default, _s.marginLeft5PX].join(' ')}>
<Textarea
inputRef={this.setTextbox}
className={[_s.default, _s.backgroundColorPrimary, _s.lineHeight125, _s.resizeNone, _s.paddingVertical15PX, _s.outlineFocusBrand, _s.fontSize16PX, _s.text, _s.displayBlock].join(' ')}
className={textClasses}
disabled={disabled}
placeholder={placeholder}
autoFocus={autoFocus}

View File

@@ -4,7 +4,8 @@ import { Map as ImmutableMap } from 'immutable'
import { autoPlayGif } from '../initial_state'
import Image from './image'
export default class Avatar extends ImmutablePureComponent {
export default
class Avatar extends ImmutablePureComponent {
static propTypes = {
account: ImmutablePropTypes.map,
@@ -24,6 +25,7 @@ export default class Avatar extends ImmutablePureComponent {
}
handleMouseEnter = () => {
// : todo : user popover
this.setState({ hovering: true })
}

View File

@@ -140,7 +140,6 @@ export default class Button extends PureComponent {
}
if (tagName === 'NavLink' && !!to) {
console.log("to: ", to)
return (
<NavLink {...options}>
{theChildren}

View File

@@ -1,44 +1,120 @@
import ImmutablePropTypes from 'react-immutable-proptypes'
import ImmutablePureComponent from 'react-immutable-pure-component'
import { debounce } from 'lodash'
import classNames from 'classnames/bind'
import { openPopover, closePopover } from '../actions/popover'
import Badge from './badge'
import Icon from './icon'
export default class DisplayName extends ImmutablePureComponent {
const cx = classNames.bind(_s)
const mapDispatchToProps = dispatch => ({
openUserInfoPopover() {
dispatch(openPopover('USER_INFO'))
},
closeUserInfoPopover() {
dispatch(closePopover())
}
})
export default
@connect(null, mapDispatchToProps)
class DisplayName extends ImmutablePureComponent {
static propTypes = {
account: ImmutablePropTypes.map.isRequired,
openUserInfoPopover: PropTypes.func.isRequired,
closeUserInfoPopover: PropTypes.func.isRequired,
multiline: PropTypes.bool,
large: PropTypes.bool,
noHover: PropTypes.bool,
}
render () {
const { account, multiline } = this.props
handleMouseEnter = debounce(() => {
console.log("SHOW - USER POPOVER")
this.props.openUserInfoPopover()
}, 50, { leading: true })
handleMouseLeave = () => {
console.log("HIDE - USER POPOVER")
// this.props.closeUserInfoPopover()
}
render() {
const { account, multiline, large, noHover } = this.props
if (!account) return null
const containerOptions = {
className: cx({
default: 1,
maxWidth100PC: 1,
alignItemsCenter: !multiline,
flexRow: !multiline,
cursorPointer: !noHover,
}),
onMouseEnter: noHover ? undefined : this.handleMouseEnter,
onMouseLeave: noHover ? undefined : this.handleMouseLeave,
}
const displayNameClasses = cx({
text: 1,
overflowWrapBreakWord: 1,
whiteSpaceNoWrap: 1,
fontWeightBold: 1,
colorPrimary: 1,
lineHeight125: 1,
marginRight2PX: 1,
fontSize15PX: !large,
fontSize24PX: large,
})
const usernameClasses = cx({
text: 1,
displayFlex: 1,
flexNormal: 1,
flexShrink1: 1,
overflowWrapBreakWord: 1,
textOverflowEllipsis: 1,
colorSecondary: 1,
fontWeightNormal: 1,
lineHeight15: multiline,
lineHeight125: !multiline,
marginLeft5PX: !multiline,
fontSize15PX: !large,
fontSize16PX: large
})
const iconSize = !!large ? '19px' : '16px'
// : todo :
return (
<span className={[_s.default, _s.flexRow, _s.maxWidth100PC, _s.alignItemsCenter].join(' ')}>
<bdi className={[_s.text, _s.whiteSpaceNoWrap, _s.textOverflowEllipsis].join(' ')}>
<strong
className={[_s.text, _s.overflowWrapBreakWord, _s.whiteSpaceNoWrap, _s.fontSize15PX, _s.fontWeightBold, _s.colorPrimary, _s.lineHeight125, _s.marginRight2PX].join(' ')}
dangerouslySetInnerHTML={{ __html: account.get('display_name_html') }}
/>
</bdi>
{
account.get('is_verified') &&
<Icon id='verified' width='16px' height='16px' className={_s.default} title='Verified Account' />
}
{ /*
account.get('is_pro') &&
<Icon id='verified' width='16px' height='16px' className={_s.default} title='Gab PRO' />
*/ }
{ /*
account.get('is_donor') &&
<Icon id='verified' width='16px' height='16px' className={_s.default} title='Gab Donor' />
*/ }
{ /*
account.get('is_investor') &&
<Icon id='verified' width='16px' height='16px' className={_s.default} title='Gab Investor' />
*/ }
<span className={[_s.text, _s.displayFlex, _s.flexNormal, _s.flexShrink1, _s.fontSize15PX, _s.overflowWrapBreakWord, _s.textOverflowEllipsis, _s.marginLeft5PX, _s.colorSecondary, _s.fontWeightNormal, _s.lineHeight125].join(' ')}>
<span {...containerOptions}>
<div className={[_s.default, _s.flexRow, _s.alignItemsCenter].join(' ')}>
<bdi className={[_s.text, _s.whiteSpaceNoWrap, _s.textOverflowEllipsis].join(' ')}>
<strong
className={displayNameClasses}
dangerouslySetInnerHTML={{ __html: account.get('display_name_html') }}
/>
</bdi>
{
account.get('is_verified') &&
<Icon id='verified' width={iconSize} height={iconSize} className={_s.default} title='Verified Account' />
}
{ /*
account.get('is_pro') &&
<Icon id='verified' width='16px' height='16px' className={_s.default} title='Gab PRO' />
*/ }
{ /*
account.get('is_donor') &&
<Icon id='verified' width='16px' height='16px' className={_s.default} title='Gab Donor' />
*/ }
{ /*
account.get('is_investor') &&
<Icon id='verified' width='16px' height='16px' className={_s.default} title='Gab Investor' />
*/ }
</div>
<span className={usernameClasses}>
@{account.get('acct')}
</span>
</span>

View File

@@ -1,297 +0,0 @@
import ImmutablePropTypes from 'react-immutable-proptypes';
import ImmutablePureComponent from 'react-immutable-pure-component';
import detectPassiveEvents from 'detect-passive-events';
import Overlay from 'react-overlays/lib/Overlay';
import spring from 'react-motion/lib/spring';
import { openDropdownMenu, closeDropdownMenu } from '../../actions/dropdown_menu';
import { openModal, closeModal } from '../../actions/modal';
import { isUserTouching } from '../../utils/is_mobile';
import Motion from '../../features/ui/util/optional_motion'
import IconButton from '../icon_button';
const listenerOptions = detectPassiveEvents.hasSupport ? { passive: true } : false;
let id = 0;
const mapStateToProps = state => ({
isModalOpen: state.get('modal').modalType === 'ACTIONS',
dropdownPlacement: state.getIn(['dropdown_menu', 'placement']),
openDropdownId: state.getIn(['dropdown_menu', 'openId']),
openedViaKeyboard: state.getIn(['dropdown_menu', 'keyboard']),
});
const mapDispatchToProps = (dispatch, { status, items }) => ({
onOpen(id, onItemClick, dropdownPlacement, keyboard) {
dispatch(isUserTouching() ? openModal('ACTIONS', {
status,
actions: items,
onClick: onItemClick,
}) : openDropdownMenu(id, dropdownPlacement, keyboard));
},
onClose(id) {
dispatch(closeModal());
dispatch(closeDropdownMenu(id));
},
});
class DropdownMenu extends PureComponent {
static contextTypes = {
router: PropTypes.object,
};
static propTypes = {
items: PropTypes.array.isRequired,
onClose: PropTypes.func.isRequired,
style: PropTypes.object,
placement: PropTypes.string,
arrowOffsetLeft: PropTypes.string,
arrowOffsetTop: PropTypes.string,
openedViaKeyboard: PropTypes.bool,
};
static defaultProps = {
style: {},
placement: 'bottom',
};
state = {
mounted: false,
};
handleDocumentClick = e => {
if (this.node && !this.node.contains(e.target)) {
this.props.onClose();
}
}
componentDidMount () {
document.addEventListener('click', this.handleDocumentClick, false);
document.addEventListener('keydown', this.handleKeyDown, false);
document.addEventListener('touchend', this.handleDocumentClick, listenerOptions);
if (this.focusedItem && this.props.openedViaKeyboard) this.focusedItem.focus();
this.setState({ mounted: true });
}
componentWillUnmount () {
document.removeEventListener('click', this.handleDocumentClick, false);
document.removeEventListener('keydown', this.handleKeyDown, false);
document.removeEventListener('touchend', this.handleDocumentClick, listenerOptions);
}
setRef = c => {
this.node = c;
}
setFocusRef = c => {
this.focusedItem = c;
}
handleKeyDown = e => {
const items = Array.from(this.node.getElementsByTagName('a'));
const index = items.indexOf(document.activeElement);
let element;
switch(e.key) {
case 'ArrowDown':
element = items[index+1];
if (element) element.focus();
break;
case 'ArrowUp':
element = items[index-1];
if (element) element.focus();
break;
case 'Home':
element = items[0];
if (element) element.focus();
break;
case 'End':
element = items[items.length-1];
if (element) element.focus();
break;
}
}
handleItemKeyDown = e => {
if (e.key === 'Enter') {
this.handleClick(e);
}
}
handleClick = e => {
const i = Number(e.currentTarget.getAttribute('data-index'));
const { action, to } = this.props.items[i];
this.props.onClose();
if (typeof action === 'function') {
e.preventDefault();
action(e);
} else if (to) {
e.preventDefault();
this.context.router.history.push(to);
}
}
renderItem (option, i) {
if (option === null) {
return <li key={`sep-${i}`} className='dropdown-menu__separator' />;
}
const { text, href = '#', newTab, isLogout } = option;
return (
<li className='dropdown-menu__item' key={`${text}-${i}`}>
<a
href={href}
role='button'
tabIndex='0'
ref={i === 0 ? this.setFocusRef : null}
onClick={this.handleClick}
onKeyDown={this.handleItemKeyDown}
data-index={i}
target={newTab ? '_blank' : null}
data-method={isLogout ? 'delete' : null}
>
{text}
</a>
</li>
);
}
render () {
const { items, style, placement, arrowOffsetLeft, arrowOffsetTop } = this.props;
const { mounted } = this.state;
return (
<Motion defaultStyle={{ opacity: 0, scaleX: 1, scaleY: 1 }} style={{ opacity: spring(1, { damping: 35, stiffness: 400 }), scaleX: spring(1, { damping: 35, stiffness: 400 }), scaleY: spring(1, { damping: 35, stiffness: 400 }) }}>
{({ opacity, scaleX, scaleY }) => (
// It should not be transformed when mounting because the resulting
// size will be used to determine the coordinate of the menu by
// react-overlays
<div className={`dropdown-menu ${placement}`} style={{ ...style, opacity: opacity, transform: mounted ? `scale(${scaleX}, ${scaleY})` : null }} ref={this.setRef}>
<div className={`dropdown-menu__arrow ${placement}`} style={{ left: arrowOffsetLeft, top: arrowOffsetTop }} />
<ul>
{items.map((option, i) => this.renderItem(option, i))}
</ul>
</div>
)}
</Motion>
);
}
}
export default
@connect(mapStateToProps, mapDispatchToProps)
class Dropdown extends ImmutablePureComponent {
static contextTypes = {
router: PropTypes.object,
};
static propTypes = {
icon: PropTypes.string.isRequired,
items: PropTypes.array.isRequired,
size: PropTypes.number.isRequired,
title: PropTypes.string,
disabled: PropTypes.bool,
status: ImmutablePropTypes.map,
isUserTouching: PropTypes.func,
isModalOpen: PropTypes.bool.isRequired,
onOpen: PropTypes.func.isRequired,
onClose: PropTypes.func.isRequired,
dropdownPlacement: PropTypes.string,
openDropdownId: PropTypes.number,
openedViaKeyboard: PropTypes.bool,
};
static defaultProps = {
title: 'Menu',
};
state = {
id: id++,
};
handleClick = ({ target, type }) => {
if (this.state.id === this.props.openDropdownId) {
this.handleClose();
} else {
const { top } = target.getBoundingClientRect();
const placement = top * 2 < innerHeight ? 'bottom' : 'top';
this.props.onOpen(this.state.id, this.handleItemClick, placement, type !== 'click');
}
}
handleClose = () => {
this.props.onClose(this.state.id);
}
handleKeyDown = e => {
switch(e.key) {
case ' ':
case 'Enter':
this.handleClick(e);
e.preventDefault();
break;
case 'Escape':
this.handleClose();
break;
}
}
handleItemClick = e => {
const i = Number(e.currentTarget.getAttribute('data-index'));
const { action, to } = this.props.items[i];
this.handleClose();
if (typeof action === 'function') {
e.preventDefault();
action();
} else if (to) {
e.preventDefault();
this.context.router.history.push(to);
}
}
setTargetRef = c => {
this.target = c;
}
findTarget = () => {
return this.target;
}
componentWillUnmount = () => {
if (this.state.id === this.props.openDropdownId) {
this.handleClose();
}
}
render () {
const { icon, items, size, title, disabled, dropdownPlacement, openDropdownId, openedViaKeyboard } = this.props;
const open = this.state.id === openDropdownId;
return (
<div onKeyDown={this.handleKeyDown}>
<IconButton
icon={icon}
title={title}
active={open}
disabled={disabled}
size={size}
ref={this.setTargetRef}
onClick={this.handleClick}
/>
<Overlay show={open} placement={dropdownPlacement} target={this.findTarget}>
<DropdownMenu items={items} onClose={this.handleClose} openedViaKeyboard={openedViaKeyboard} />
</Overlay>
</div>
);
}
}

View File

@@ -1,93 +0,0 @@
.dropdown-menu {
z-index: 9999;
position: absolute;
background: $gab-background-container;
padding: 4px 0;
box-shadow: 0 0 6px 0 rgba(0, 0, 0, 0.5);
@include border-design($gab-placeholder-accent, 1px, 4px);
&.left {
transform-origin: 100% 50%;
}
&.top {
transform-origin: 50% 100%;
}
&.bottom {
transform-origin: 50% 0;
}
&.right {
transform-origin: 0 50%;
}
&__arrow {
position: absolute;
border: 0 solid transparent;
@include size(0);
&.left {
right: -5px;
margin-top: -5px;
border-width: 5px 0 5px 5px;
border-left-color: $gab-placeholder-accent;
}
&.top {
bottom: -5px;
margin-left: -5px;
border-width: 5px 5px 0;
border-top-color: $gab-placeholder-accent;
}
&.bottom {
top: -5px;
margin-left: -5px;
border-width: 0 5px 5px;
border-bottom-color: $gab-placeholder-accent;
}
&.right {
left: -5px;
margin-top: -5px;
border-width: 5px 5px 5px 0;
border-right-color: $gab-placeholder-accent;
}
}
ul {
overflow: hidden;
padding: 6px 0;
}
&__item a {
display: block;
box-sizing: border-box;
padding: 3px 10px 1px;
text-decoration: none;
text-transform: capitalize;
color: $gab-secondary-text;
@include text-overflow(nowrap);
@include text-sizing(13px, 400, 26px);
&:focus,
&:hover,
&:active {
outline: 0;
color: $gab-text-highlight;
background: $gab-background-base !important;
box-shadow: 0 0 6px 0 rgba(0, 0, 0, 0.5);
}
}
&__separator {
display: block;
margin: 10px !important;
height: 1px;
background: $gab-background-base;
}
}

View File

@@ -1 +0,0 @@
export { default } from './dropdown_menu';

View File

@@ -9,6 +9,7 @@ export default class ListItem extends PureComponent {
isLast: PropTypes.bool,
to: PropTypes.string,
title: PropTypes.string,
onClick: PropTypes.func,
}
render() {

View File

@@ -1,9 +1,19 @@
import ModalBase from './modal_base'
import { closeModal } from '../../actions/modal'
import { cancelReplyCompose } from '../../actions/compose'
import Bundle from '../../features/ui/util/bundle'
import {
MuteModal,
ReportModal,
EmbedModal,
// ListEditor,
// ListAdder,
StatusRevisionModal,
} from '../../features/ui/util/async-components'
import ModalBase from './modal_base'
import BundleModalError from '../bundle_modal_error'
import ActionsModal from './actions_modal'
import MediaModal from './media_modal'
// import VideoModal from './video_modal'
import VideoModal from './video_modal'
import BoostModal from './boost_modal'
import ConfirmationModal from './confirmation_modal'
import FocalPointModal from './focal_point_modal'
@@ -12,35 +22,46 @@ import ComposeModal from './compose_modal'
import UnauthorizedModal from './unauthorized_modal'
import ProUpgradeModal from './pro_upgrade_modal'
import ModalLoading from './modal_loading'
import {
MuteModal,
ReportModal,
EmbedModal,
ListEditor,
ListAdder,
StatusRevisionModal,
} from '../../features/ui/util/async-components'
const MODAL_COMPONENTS = {
'MEDIA': () => Promise.resolve({ default: MediaModal }),
// 'VIDEO': () => Promise.resolve({ default: VideoModal }),
'BOOST': () => Promise.resolve({ default: BoostModal }),
'CONFIRM': () => Promise.resolve({ default: ConfirmationModal }),
'MUTE': MuteModal,
'REPORT': ReportModal,
'ACTIONS': () => Promise.resolve({ default: ActionsModal }),
'EMBED': EmbedModal,
'LIST_EDITOR': ListEditor,
'FOCAL_POINT': () => Promise.resolve({ default: FocalPointModal }),
'LIST_ADDER': ListAdder,
'HOTKEYS': () => Promise.resolve({ default: HotkeysModal }),
'STATUS_REVISION': StatusRevisionModal,
'BOOST': () => Promise.resolve({ default: BoostModal }),
'COMPOSE': () => Promise.resolve({ default: ComposeModal }),
'UNAUTHORIZED': () => Promise.resolve({ default: UnauthorizedModal }),
'CONFIRM': () => Promise.resolve({ default: ConfirmationModal }),
'EMBED': EmbedModal,
'FOCAL_POINT': () => Promise.resolve({ default: FocalPointModal }),
'HOTKEYS': () => Promise.resolve({ default: HotkeysModal }),
'MEDIA': () => Promise.resolve({ default: MediaModal }),
'MUTE': MuteModal,
'PRO_UPGRADE': () => Promise.resolve({ default: ProUpgradeModal }),
'REPORT': ReportModal,
'STATUS_REVISION': StatusRevisionModal,
'UNAUTHORIZED': () => Promise.resolve({ default: UnauthorizedModal }),
'VIDEO': () => Promise.resolve({ default: VideoModal }),
// 'LIST_EDITOR': ListEditor,
// 'LIST_ADDER': ListAdder,
// group create
// group members
}
export default class ModalRoot extends PureComponent {
const mapStateToProps = state => ({
type: state.get('modal').modalType,
props: state.get('modal').modalProps,
})
const mapDispatchToProps = (dispatch) => ({
onClose (optionalType) {
if (optionalType === 'COMPOSE') {
dispatch(cancelReplyCompose())
}
dispatch(closeModal())
},
})
export default
@connect(mapStateToProps, mapDispatchToProps)
class ModalRoot extends PureComponent {
static propTypes = {
type: PropTypes.string,

View File

@@ -0,0 +1,31 @@
import { defineMessages, injectIntl } from 'react-intl'
import ImmutablePureComponent from 'react-immutable-pure-component'
import ImmutablePropTypes from 'react-immutable-proptypes'
import { me } from '../../initial_state'
import { shortNumberFormat } from '../../utils/numbers'
import PanelLayout from './panel_layout'
import UserStat from '../user_stat'
const messages = defineMessages({
title: { id: 'about', defaultMessage: 'About' },
})
export default
@injectIntl
class GroupInfoPanel extends ImmutablePureComponent {
static propTypes = {
group: ImmutablePropTypes.list.isRequired,
intl: PropTypes.object.isRequired,
}
render() {
const { intl, group } = this.props
return (
<PanelLayout title={intl.formatMessage(messages.title)}>
</PanelLayout>
)
}
}

View File

@@ -1,71 +1,53 @@
import { defineMessages, injectIntl } from 'react-intl';
import { fetchSuggestions, dismissSuggestion } from '../../actions/suggestions';
import ImmutablePureComponent from 'react-immutable-pure-component';
import ImmutablePropTypes from 'react-immutable-proptypes';
import AccountContainer from '../../containers/account_container';
import PanelLayout from './panel_layout';
import { defineMessages, injectIntl } from 'react-intl'
import { fetchSuggestions, dismissSuggestion } from '../../actions/suggestions'
import ImmutablePureComponent from 'react-immutable-pure-component'
import ImmutablePropTypes from 'react-immutable-proptypes'
import AccountContainer from '../../containers/account_container'
import PanelLayout from './panel_layout'
const messages = defineMessages({
dismissSuggestion: { id: 'suggestions.dismiss', defaultMessage: 'Dismiss suggestion' },
title: { id: 'who_to_follow.title', defaultMessage: 'Who to Follow' },
show_more: { id: 'who_to_follow.more', defaultMessage: 'Show more' },
});
title: { id: 'media_gallery_panel.title', defaultMessage: 'Media' },
show_all: { id: 'media_gallery_panel.all', defaultMessage: 'Show all' },
})
const mapStateToProps = state => ({
suggestions: state.getIn(['suggestions', 'items']),
});
})
const mapDispatchToProps = dispatch => {
return {
fetchSuggestions: () => dispatch(fetchSuggestions()),
dismissSuggestion: account => dispatch(dismissSuggestion(account.get('id'))),
}
};
}
export default
@connect(mapStateToProps, mapDispatchToProps)
@injectIntl
class WhoToFollowPanel extends ImmutablePureComponent {
class MediaGalleryPanel extends ImmutablePureComponent {
static propTypes = {
suggestions: ImmutablePropTypes.list.isRequired,
fetchSuggestions: PropTypes.func.isRequired,
dismissSuggestion: PropTypes.func.isRequired,
account: ImmutablePropTypes.map.isRequired,
intl: PropTypes.object.isRequired,
};
}
componentDidMount () {
this.props.fetchSuggestions();
componentDidMount() {
// this.props.fetchSuggestions()
}
render() {
const { intl, /* suggestions, */ dismissSuggestion } = this.props;
// : testing!!! :
const suggestions = [
"1",
]
// if (suggestions.isEmpty()) {
// return null;
// }
const { intl, account } = this.props
console.log("account:", account)
return (
<PanelLayout
title={intl.formatMessage(messages.title)}
footerButtonTitle={intl.formatMessage(messages.show_more)}
footerButtonTo='/explore'
headerButtonTitle={intl.formatMessage(messages.show_all)}
headerButtonTo='/explore'
>
<div className={_s.default}>
{suggestions && suggestions.map(accountId => (
<AccountContainer
key={accountId}
id={accountId}
actionIcon='times'
actionTitle={intl.formatMessage(messages.dismissSuggestion)}
onActionClick={dismissSuggestion}
/>
))}
</div>
</PanelLayout>
);
};
};
)
}
}

View File

@@ -1,71 +1,157 @@
import { defineMessages, injectIntl } from 'react-intl';
import { fetchSuggestions, dismissSuggestion } from '../../actions/suggestions';
import ImmutablePureComponent from 'react-immutable-pure-component';
import ImmutablePropTypes from 'react-immutable-proptypes';
import AccountContainer from '../../containers/account_container';
import PanelLayout from './panel_layout';
import { defineMessages, injectIntl } from 'react-intl'
import { fetchSuggestions, dismissSuggestion } from '../../actions/suggestions'
import ImmutablePureComponent from 'react-immutable-pure-component'
import ImmutablePropTypes from 'react-immutable-proptypes'
import { List as ImmutableList } from 'immutable'
import classNames from 'classnames/bind'
import PanelLayout from './panel_layout'
import Icon from '../icon'
import Text from '../text'
const cx = classNames.bind(_s)
const messages = defineMessages({
dismissSuggestion: { id: 'suggestions.dismiss', defaultMessage: 'Dismiss suggestion' },
title: { id: 'who_to_follow.title', defaultMessage: 'Who to Follow' },
show_more: { id: 'who_to_follow.more', defaultMessage: 'Show more' },
});
title: { id: 'about', defaultMessage: 'About' },
linkVerifiedOn: { id: 'account.link_verified_on', defaultMessage: 'Ownership of this link was checked on {date}' },
account_locked: { id: 'account.locked_info', defaultMessage: 'This account privacy status is set to locked. The owner manually reviews who can follow them.' },
bot: { id: 'account.badges.bot', defaultMessage: 'Bot' },
memberSince: { id: 'account.member_since', defaultMessage: 'Member since {date}' },
})
const mapStateToProps = (state, { account }) => {
const identityProofs = !!account ? state.getIn(['identity_proofs', account.get('id')], ImmutableList()) : ImmutableList();
return {
identityProofs,
domain: state.getIn(['meta', 'domain']),
};
};
const mapStateToProps = state => ({
suggestions: state.getIn(['suggestions', 'items']),
});
const mapDispatchToProps = dispatch => {
return {
fetchSuggestions: () => dispatch(fetchSuggestions()),
dismissSuggestion: account => dispatch(dismissSuggestion(account.get('id'))),
}
};
}
export default
@connect(mapStateToProps, mapDispatchToProps)
@injectIntl
class WhoToFollowPanel extends ImmutablePureComponent {
class ProfileInfoPanel extends ImmutablePureComponent {
static propTypes = {
suggestions: ImmutablePropTypes.list.isRequired,
fetchSuggestions: PropTypes.func.isRequired,
dismissSuggestion: PropTypes.func.isRequired,
identityProofs: ImmutablePropTypes.list,
account: ImmutablePropTypes.map.isRequired,
intl: PropTypes.object.isRequired,
};
}
componentDidMount () {
this.props.fetchSuggestions();
componentDidMount() {
this.props.fetchSuggestions()
}
render() {
const { intl, /* suggestions, */ dismissSuggestion } = this.props;
// : testing!!! :
const suggestions = [
"1",
]
// if (suggestions.isEmpty()) {
// return null;
// }
const { intl, account, identityProofs } = this.props
const fields = !account ? null : account.get('fields')
const content = !account ? null : { __html: account.get('note_emojified') }
const memberSinceDate = !account ? null : intl.formatDate(account.get('created_at'), { month: 'long', year: 'numeric' });
const hasNote = !!content ? (account.get('note').length > 0 && account.get('note') !== '<p></p>') : false
const lineClasses = cx({
default: 1,
flexRow: 1,
alignItemsCenter: 1,
borderTop1PX: 1,
borderColorSecondary: 1,
marginTop10PX: 1,
paddingTop10PX: 1,
})
const memberLineClasses = cx({
default: 1,
flexRow: 1,
alignItemsCenter: 1,
borderTop1PX: hasNote,
borderColorSecondary: hasNote,
marginTop10PX: hasNote,
paddingTop10PX: hasNote,
})
return (
<PanelLayout
title={intl.formatMessage(messages.title)}
footerButtonTitle={intl.formatMessage(messages.show_more)}
footerButtonTo='/explore'
>
<div className={_s.default}>
{suggestions && suggestions.map(accountId => (
<AccountContainer
key={accountId}
id={accountId}
actionIcon='times'
actionTitle={intl.formatMessage(messages.dismissSuggestion)}
onActionClick={dismissSuggestion}
/>
))}
</div>
<PanelLayout title={intl.formatMessage(messages.title)}>
{
!!account &&
<div className={[_s.default].join(' ')}>
{
hasNote &&
<div className={_s.dangerousContent} dangerouslySetInnerHTML={content} />
}
<div className={memberLineClasses}>
<Icon id='calendar' width='12px' height='12px' className={_s.fillcolorSecondary} />
<Text
size='small'
color='secondary'
className={_s.marginLeft5PX}
>
{
intl.formatMessage(messages.memberSince, {
date: memberSinceDate
})
}
</Text>
</div>
{(fields.size > 0 || identityProofs.size > 0) && (
<div className={[_s.default]}>
{identityProofs.map((proof, i) => (
<dl className={lineClasses} key={`profile-identity-proof-${i}`}>
<dt
className={_s.dangerousContent}
dangerouslySetInnerHTML={{ __html: proof.get('provider') }}
/>
{ /* : todo : */ }
<dd className='verified'>
<a href={proof.get('proof_url')} target='_blank' rel='noopener'>
<span title={intl.formatMessage(messages.linkVerifiedOn, { date: intl.formatDate(proof.get('updated_at'), dateFormatOptions) })}>
<Icon id='check' className='verified__mark' />
</span>
</a>
<a href={proof.get('profile_url')} target='_blank' rel='noopener'>
<span
className={_s.dangerousContent}
dangerouslySetInnerHTML={{ __html: ' ' + proof.get('provider_username') }}
/>
</a>
</dd>
</dl>
))}
{
fields.map((pair, i) => (
<dl className={lineClasses} key={`profile-field-${i}`}>
<dt
className={[_s.text, _s.dangerousContent].join('')}
dangerouslySetInnerHTML={{ __html: pair.get('name_emojified') }}
title={pair.get('name')}
/>
<dd
className={[_s.dangerousContent, _s.marginLeftAuto].join(' ')}
title={pair.get('value_plain')}
dangerouslySetInnerHTML={{ __html: pair.get('value_emojified') }}
/>
</dl>
))
}
</div>
)}
</div>
}
</PanelLayout>
);
};
};
)
}
}

View File

@@ -0,0 +1,63 @@
import { defineMessages, injectIntl } from 'react-intl'
import ImmutablePureComponent from 'react-immutable-pure-component'
import ImmutablePropTypes from 'react-immutable-proptypes'
import { me } from '../../initial_state'
import { shortNumberFormat } from '../../utils/numbers'
import PanelLayout from './panel_layout'
import UserStat from '../user_stat'
const messages = defineMessages({
gabs: { id: 'account.gabs', defaultMessage: 'Gabs' },
followers: { id: 'account.followers', defaultMessage: 'Followers' },
follows: { id: 'account.follows', defaultMessage: 'Follows' },
favorites: { id: 'navigation_bar.favorites', defaultMessage: 'Favorites' },
})
export default
@injectIntl
class ProfileStatsPanel extends ImmutablePureComponent {
static propTypes = {
account: ImmutablePropTypes.list.isRequired,
intl: PropTypes.object.isRequired,
}
render() {
const { intl, account } = this.props
if (!account) return null
return (
<PanelLayout>
{
!!account &&
<div className={[_s.default, _s.flexRow].join(' ')}>
<UserStat
title={intl.formatMessage(messages.gabs)}
value={shortNumberFormat(account.get('statuses_count'))}
to={`/${account.get('acct')}`}
/>
<UserStat
title={intl.formatMessage(messages.follows)}
value={shortNumberFormat(account.get('following_count'))}
to={`/${account.get('acct')}/following`}
/>
<UserStat
title={intl.formatMessage(messages.followers)}
value={shortNumberFormat(account.get('followers_count'))}
to={`/${account.get('acct')}/followers`}
/>
{
account.get('id') === me &&
<UserStat
title={intl.formatMessage(messages.favorites)}
value={shortNumberFormat(account.get('favourite_count'))}
to={`/${account.get('acct')}/favorites`}
/>
}
</div>
}
</PanelLayout>
)
}
}

View File

@@ -2,7 +2,7 @@ import { NavLink } from 'react-router-dom'
import { injectIntl, defineMessages } from 'react-intl'
import ImmutablePropTypes from 'react-immutable-proptypes'
import ImmutablePureComponent from 'react-immutable-pure-component'
import { autoPlayGif, me } from '../../initial_state'
import { me } from '../../initial_state'
import { makeGetAccount } from '../../selectors'
import { shortNumberFormat } from '../../utils/numbers'
import DisplayName from '../display_name'
@@ -18,10 +18,8 @@ const messages = defineMessages({
})
const mapStateToProps = state => {
const getAccount = makeGetAccount()
return {
account: getAccount(state, me),
account: makeGetAccount()(state, me),
}
}
@@ -36,25 +34,6 @@ class UserPanel extends ImmutablePureComponent {
render() {
const { account, intl } = this.props
const displayNameHtml = { __html: account.get('display_name_html') }
const statItems = [
{
to: `/${account.get('acct')}`,
title: intl.formatMessage(messages.gabs),
value: shortNumberFormat(account.get('statuses_count')),
},
{
to: `/${account.get('acct')}/followers`,
title: intl.formatMessage(messages.followers),
value: shortNumberFormat(account.get('followers_count')),
},
{
to: `/${account.get('acct')}/following`,
title: intl.formatMessage(messages.follows),
value: shortNumberFormat(account.get('following_count')),
},
]
return (
<PanelLayout noPadding>
@@ -76,11 +55,21 @@ class UserPanel extends ImmutablePureComponent {
</NavLink>
<div className={[_s.default, _s.marginBottom15PX, _s.marginTop5PX, _s.flexRow, _s.paddingHorizontal15PX].join(' ')}>
{
statItems.map((statItem, i) => (
<UserStat {...statItem} key={`user-stat-panel-item-${i}`} />
))
}
<UserStat
to={`/${account.get('acct')}`}
title={intl.formatMessage(messages.gabs)}
value={shortNumberFormat(account.get('statuses_count'))}
/>
<UserStat
to={`/${account.get('acct')}/followers`}
title={intl.formatMessage(messages.followers)}
value={shortNumberFormat(account.get('followers_count'))}
/>
<UserStat
to={`/${account.get('acct')}/following`}
title={intl.formatMessage(messages.follows)}
value={shortNumberFormat(account.get('following_count'))}
/>
</div>
</PanelLayout>
)

View File

@@ -1,4 +1,4 @@
export default class Popover extends PureComponent {
export default class ContentWarningPopover extends PureComponent {
render() {
return (
<div>

View File

@@ -1,4 +1,4 @@
export default class Popover extends PureComponent {
export default class DatePickerPopover extends PureComponent {
render() {
return (
<div>

View File

@@ -0,0 +1,12 @@
import PopoverLayout from './popover_layout'
import Text from '../text'
export default class UserInfoPopover extends PureComponent {
render() {
return (
<PopoverLayout>
<Text>testing</Text>
</PopoverLayout>
)
}
}

View File

@@ -1,9 +0,0 @@
export default class Popover extends PureComponent {
render() {
return (
<div>
{ /* */ }
</div>
)
}
}

View File

@@ -0,0 +1,146 @@
import ImmutablePropTypes from 'react-immutable-proptypes'
import ImmutablePureComponent from 'react-immutable-pure-component'
import classnames from 'classnames/bind'
import Overlay from 'react-overlays/lib/Overlay'
import spring from 'react-motion/lib/spring'
import Motion from '../../features/ui/util/optional_motion'
import { openPopover, closePopover } from '../../actions/popover'
import { openModal, closeModal } from '../../actions/modal'
import { isUserTouching } from '../../utils/is_mobile'
const cx = classnames.bind(_s)
let id = 0
const mapStateToProps = state => ({
isModalOpen: state.get('modal').modalType === 'ACTIONS',
popoverPlacement: state.getIn(['popover', 'placement']),
openPopoverType: state.getIn(['popover', 'popoverType']),
openedViaKeyboard: state.getIn(['popover', 'keyboard']),
})
const mapDispatchToProps = (dispatch, { status, items }) => ({
onOpen(id, onItemClick, popoverPlacement, keyboard) {
dispatch(isUserTouching() ? openModal('ACTIONS', {
status,
actions: items,
onClick: onItemClick,
}) : openPopover(id, popoverPlacement, keyboard))
},
onClose(id) {
dispatch(closeModal())
dispatch(closePopover(id))
},
})
export default
@connect(mapStateToProps, mapDispatchToProps)
class PopoverBase extends ImmutablePureComponent {
static contextTypes = {
router: PropTypes.object,
}
static propTypes = {
icon: PropTypes.string.isRequired,
items: PropTypes.array.isRequired,
size: PropTypes.number.isRequired,
title: PropTypes.string,
disabled: PropTypes.bool,
status: ImmutablePropTypes.map,
isUserTouching: PropTypes.func,
isModalOpen: PropTypes.bool.isRequired,
onOpen: PropTypes.func.isRequired,
onClose: PropTypes.func.isRequired,
popoverPlacement: PropTypes.string,
openPopoverType: PropTypes.number,
openedViaKeyboard: PropTypes.bool,
visible: PropTypes.bool,
}
static defaultProps = {
title: 'Menu',
}
state = {
id: id++,
}
handleClick = ({ target, type }) => {
if (this.state.id === this.props.openPopoverType) {
this.handleClose()
} else {
const { top } = target.getBoundingClientRect()
const placement = top * 2 < innerHeight ? 'bottom' : 'top'
this.props.onOpen(this.state.id, this.handleItemClick, placement, type !== 'click')
}
}
handleClose = () => {
this.props.onClose(this.state.id)
}
handleKeyDown = e => {
switch (e.key) {
case ' ':
case 'Enter':
this.handleClick(e)
e.preventDefault()
break
case 'Escape':
this.handleClose()
break
}
}
handleItemClick = e => {
const i = Number(e.currentTarget.getAttribute('data-index'))
const { action, to } = this.props.items[i]
this.handleClose()
if (typeof action === 'function') {
e.preventDefault()
action()
} else if (to) {
e.preventDefault()
this.context.router.history.push(to)
}
}
setTargetRef = c => {
this.target = c
}
findTarget = () => {
return this.target
}
componentWillUnmount = () => {
if (this.state.id === this.props.openPopoverType) {
this.handleClose()
}
}
render() {
const { icon, children, visible, items, size, title, disabled, popoverPlacement, openPopoverType, openedViaKeyboard } = this.props
const open = this.state.id === openPopoverType
const containerClasses = cx({
default: 1,
z4: 1,
displayNone: !visible,
})
return (
<div onKeyDown={this.handleKeyDown} className={containerClasses}>
<div show={open} placement={popoverPlacement} target={this.findTarget}>
{ /* <PopoverMenu items={items} onClose={this.handleClose} openedViaKeyboard={openedViaKeyboard} /> */}
{children}
</div>
</div>
)
}
}

View File

@@ -1,8 +1,14 @@
export default class Popover extends PureComponent {
import Block from '../block'
export default class PopoverLayout extends PureComponent {
render() {
const { children } = this.props
return (
<div>
{ /* */ }
<Block>
{children}
</Block>
</div>
)
}

View File

@@ -0,0 +1,171 @@
import detectPassiveEvents from 'detect-passive-events'
import { closePopover } from '../../actions/popover'
import Bundle from '../../features/ui/util/bundle'
import BundleModalError from '../bundle_modal_error'
import PopoverBase from './popover_base'
import ContentWarningPopover from './content_warning_popover'
import DatePickerPopover from './date_picker_popover'
import GroupInfoPopover from './group_info_popover'
import ProfileOptionsPopover from './profile_options_popover'
import SearchPopover from './search_popover'
import SidebarMorePopover from './sidebar_more_popover'
import StatusOptionsPopover from './status_options_popover'
import UserInfoPopover from './user_info_popover'
import StatusVisibilityPopover from './status_visibility_popover'
const listenerOptions = detectPassiveEvents.hasSupport ? { passive: true } : false
const POPOVER_COMPONENTS = {
CONTENT_WARNING: () => Promise.resolve({ default: ContentWarningPopover }),
DATE_PICKER: () => Promise.resolve({ default: DatePickerPopover }),
GROUP_INFO: () => GroupInfoPopover,
PROFILE_OPTIONS: () => ProfileOptionsPopover,
SEARCH: () => Promise.resolve({ default: SearchPopover }),
SIDEBAR_MORE: () => Promise.resolve({ default: SidebarMorePopover }),
STATUS_OPTIONS: () => Promise.resolve({ default: StatusOptionsPopover }),
STATUS_VISIBILITY: () => Promise.resolve({ default: StatusVisibilityPopover }),
USER_INFO: () => UserInfoPopover,
}
const mapStateToProps = state => ({
type: state.get('popover').popoverType,
props: state.get('popover').popoverProps,
})
const mapDispatchToProps = (dispatch) => ({
onClose(optionalType) {
//
dispatch(closePopover())
},
})
export default
@connect(mapStateToProps, mapDispatchToProps)
class PopoverRoot extends PureComponent {
static propTypes = {
type: PropTypes.string,
props: PropTypes.object,
onClose: PropTypes.func.isRequired,
}
getSnapshotBeforeUpdate() {
return { visible: !!this.props.type }
}
static contextTypes = {
router: PropTypes.object,
}
static propTypes = {
onClose: PropTypes.func.isRequired,
style: PropTypes.object,
placement: PropTypes.string,
openedViaKeyboard: PropTypes.bool,
}
static defaultProps = {
style: {},
placement: 'bottom',
}
state = {
mounted: false,
}
handleDocumentClick = e => {
if (this.node && !this.node.contains(e.target)) {
this.props.onClose()
}
}
componentDidMount() {
document.addEventListener('click', this.handleDocumentClick, false)
document.addEventListener('keydown', this.handleKeyDown, false)
document.addEventListener('touchend', this.handleDocumentClick, listenerOptions)
if (this.focusedItem && this.props.openedViaKeyboard) this.focusedItem.focus()
this.setState({ mounted: true })
}
componentWillUnmount() {
document.removeEventListener('click', this.handleDocumentClick, false)
document.removeEventListener('keydown', this.handleKeyDown, false)
document.removeEventListener('touchend', this.handleDocumentClick, listenerOptions)
}
setRef = c => {
this.node = c
}
setFocusRef = c => {
this.focusedItem = c
}
handleKeyDown = e => {
const items = Array.from(this.node.getElementsByTagName('a'))
const index = items.indexOf(document.activeElement)
let element
switch (e.key) {
case 'ArrowDown':
element = items[index + 1]
if (element) element.focus()
break
case 'ArrowUp':
element = items[index - 1]
if (element) element.focus()
break
case 'Home':
element = items[0]
if (element) element.focus()
break
case 'End':
element = items[items.length - 1]
if (element) element.focus()
break
}
}
handleItemKeyDown = e => {
if (e.key === 'Enter') {
this.handleClick(e)
}
}
handleClick = e => {
const i = Number(e.currentTarget.getAttribute('data-index'))
const { action, to } = this.props.items[i]
this.props.onClose()
if (typeof action === 'function') {
e.preventDefault()
action(e)
} else if (to) {
e.preventDefault()
this.context.router.history.push(to)
}
}
renderError = (props) => {
return <BundleModalError />
}
render() {
const { type, style, placement } = this.props
const { mounted } = this.state
const visible = !!type
console.log("popover root - type, visible:", type, visible)
return (
<PopoverBase className={`popover-menu ${placement}`} visible={visible} ref={this.setRef}>
{
visible &&
<UserInfoPopover />
}
</PopoverBase>
)
}
}

View File

@@ -0,0 +1,12 @@
import PopoverLayout from './popover_layout'
import Text from '../text'
export default class UserInfoPopover extends PureComponent {
render() {
return (
<PopoverLayout>
<Text>testing</Text>
</PopoverLayout>
)
}
}

View File

@@ -1,4 +1,4 @@
export default class Popover extends PureComponent {
export default class SearchPopover extends PureComponent {
render() {
return (
<div>

View File

@@ -1,4 +1,4 @@
export default class Popover extends PureComponent {
export default class SidebarMorePopover extends PureComponent {
render() {
return (
<div>

View File

@@ -1,4 +1,4 @@
export default class Popover extends PureComponent {
export default class StatusOptionsPopover extends PureComponent {
render() {
return (
<div>

View File

@@ -1,4 +1,4 @@
export default class Popover extends PureComponent {
export default class StatusVisibilityPopover extends PureComponent {
render() {
return (
<div>

View File

@@ -0,0 +1,12 @@
import PopoverLayout from './popover_layout'
import Text from '../text'
export default class UserInfoPopover extends PureComponent {
render() {
return (
<PopoverLayout>
<Text>testing</Text>
</PopoverLayout>
)
}
}

View File

@@ -1,9 +0,0 @@
export default class Popover extends PureComponent {
render() {
return (
<div>
{ /* */ }
</div>
)
}
}

View File

@@ -45,7 +45,7 @@ const mapDispatchToProps = (dispatch) => ({
dispatch(closeSidebar())
},
onOpenComposeModal() {
dispatch(openModal('PRO_UPGRADE'))
dispatch(openModal('COMPOSE'))
},
})
@@ -98,6 +98,7 @@ class Sidebar extends ImmutablePureComponent {
const { sidebarOpen, intl, account } = this.props
const { moreOpen } = this.state
// : todo :
if (!me || !account) return null
const acct = account.get('acct')
@@ -124,11 +125,6 @@ class Sidebar extends ImmutablePureComponent {
to: '/notifications',
count: 40,
},
{
title: 'Bookmarks',
icon: 'bookmarks',
to: '/bookmarks',
},
{
title: 'Groups',
icon: 'group',
@@ -142,7 +138,8 @@ class Sidebar extends ImmutablePureComponent {
{
title: 'Chat',
icon: 'chat',
to: '/',
to: '',
// href: 'https://chat.gab.com',
},
{
title: 'More',
@@ -175,22 +172,26 @@ class Sidebar extends ImmutablePureComponent {
{
title: 'Apps',
icon: 'apps',
to: '/',
to: '',
// href: 'https://apps.gab.com',
},
{
title: 'Shop',
icon: 'shop',
to: '/',
to: '',
// href: 'https://shop.dissenter.com',
},
{
title: 'Trends',
icon: 'trends',
to: '/',
to: '',
// href: 'https://trends.gab.com',
},
{
title: 'Dissenter',
icon: 'dissenter',
to: '/',
to: '',
// href: 'https://dissenter.com',
},
]

View File

@@ -15,7 +15,8 @@ const mapStateToProps = state => {
}
}
export default @connect(mapStateToProps)
export default
@connect(mapStateToProps)
class SidebarHeader extends ImmutablePureComponent {
static propTypes = {

View File

@@ -1,10 +1,10 @@
import { Fragment } from 'react'
import { NavLink } from 'react-router-dom';
import ImmutablePropTypes from 'react-immutable-proptypes';
import { injectIntl, FormattedMessage } from 'react-intl';
import { injectIntl, defineMessages } from 'react-intl';
import ImmutablePureComponent from 'react-immutable-pure-component';
import { HotKeys } from 'react-hotkeys';
import classNames from 'classnames';
import classNames from 'classnames/bind'
import { displayMedia } from '../../initial_state';
import Card from '../../features/status/components/card';
import { MediaGallery, Video } from '../../features/ui/util/async-components';
@@ -18,11 +18,14 @@ import Block from '../block';
import Icon from '../icon';
import Poll from '../poll';
import StatusHeader from '../status_header'
import Text from '../text'
// We use the component (and not the container) since we do not want
// to use the progress bar to show download progress
import Bundle from '../../features/ui/util/bundle';
const cx = classNames.bind(_s)
export const textForScreenReader = (intl, status, rebloggedByText = false) => {
const displayName = status.getIn(['account', 'display_name']);
@@ -52,6 +55,12 @@ export const defaultMediaVisibility = status => {
return (displayMedia !== 'hide_all' && !status.get('sensitive')) || displayMedia === 'show_all';
};
const messages = defineMessages({
filtered: { id: 'status.filtered', defaultMessage: 'Filtered' },
promoted: { id:'status.promoted', defaultMessage: 'Promoted gab' },
pinned: { id: 'status.pinned', defaultMessage: 'Pinned gab' },
})
export default
@injectIntl
class Status extends ImmutablePureComponent {
@@ -293,7 +302,7 @@ class Status extends ImmutablePureComponent {
return (
<HotKeys handlers={minHandlers}>
<div className='status__wrapper status__wrapper--filtered focusable' tabIndex='0' ref={this.handleRef}>
<FormattedMessage id='status.filtered' defaultMessage='Filtered' />
<Text>{intl.formatMessage(messages.filtered)}</Text>
</div>
</HotKeys>
);
@@ -302,17 +311,24 @@ class Status extends ImmutablePureComponent {
if (promoted) {
prepend = (
<button className='status__prepend status__prepend--promoted' onClick={this.handleOpenProUpgradeModal}>
<div className='status__prepend-icon-wrapper'><Icon id='star' className='status__prepend-icon' fixedWidth /></div>
<FormattedMessage id='status.promoted' defaultMessage='Promoted gab' />
<div className='status__prepend-icon-wrapper'>
<Icon id='star' className='status__prepend-icon' fixedWidth />
</div>
<Text>{intl.formatMessage(messages.promoted)}</Text>
</button>
);
} else if (featured) {
prepend = (
<div className='status__prepend'>
<div className='status__prepend-icon-wrapper'>
<Icon id='thumb-tack' className='status__prepend-icon' fixedWidth />
</div>
<FormattedMessage id='status.pinned' defaultMessage='Pinned gab' />
<div className={[_s.default, _s.flexRow, _s.alignItemsCenter, _s.borderBottom1PX, _s.borderColorSecondary, _s.paddingVertical5PX, _s.paddingHorizontal15PX].join(' ')}>
<Icon
id='thumb-tack'
width='12px'
height='12px'
className={_s.fillcolorSecondary}
/>
<Text size='small' color='secondary' className={_s.marginLeft5PX}>
{intl.formatMessage(messages.pinned)}
</Text>
</div>
);
} else if (status.get('reblog', null) !== null && typeof status.get('reblog') === 'object') {
@@ -323,7 +339,7 @@ class Status extends ImmutablePureComponent {
<div className='status__prepend-icon-wrapper'>
<Icon id='retweet' className='status__prepend-icon' fixedWidth />
</div>
<FormattedMessage
{/*<FormattedMessage
id='status.reblogged_by'
defaultMessage='{name} reposted'
values={{
@@ -335,7 +351,7 @@ class Status extends ImmutablePureComponent {
</NavLink>
),
}}
/>
/> */ }
</div>
);
@@ -423,10 +439,18 @@ class Status extends ImmutablePureComponent {
const statusUrl = `/${status.getIn(['account', 'acct'])}/posts/${status.get('id')}`;
const containerClasses = cx({
default: 1,
marginBottom15PX: 1,
paddingBottom15PX: featured,
borderBottom1PX: featured,
borderColorSecondary: featured,
})
return (
<HotKeys handlers={handlers}>
<div
className={[_s.default, _s.marginBottom15PX].join(' ')}
className={containerClasses}
tabIndex={this.props.muted ? null : 0}
data-featured={featured ? 'true' : null}
aria-label={textForScreenReader(intl, status, rebloggedByText)}

View File

@@ -1,13 +1,12 @@
import ImmutablePropTypes from 'react-immutable-proptypes';
import ImmutablePureComponent from 'react-immutable-pure-component';
import { defineMessages, injectIntl } from 'react-intl';
import ImmutablePropTypes from 'react-immutable-proptypes'
import ImmutablePureComponent from 'react-immutable-pure-component'
import { defineMessages, injectIntl } from 'react-intl'
import classNames from 'classnames/bind'
import { Link } from 'react-router-dom';
import { openModal } from '../../actions/modal';
import { me, isStaff } from '../../initial_state';
import Dropdown from '../dropdown_menu'
import ComposeFormContainer from '../../features/compose/containers/compose_form_container';
import Icon from '../icon';
import { openModal } from '../../actions/modal'
import { me, isStaff } from '../../initial_state'
import ComposeFormContainer from '../../features/compose/containers/compose_form_container'
import Icon from '../icon'
import StatusActionBarItem from '../status_action_bar_item'
const messages = defineMessages({
delete: { id: 'status.delete', defaultMessage: 'Delete' },
@@ -40,64 +39,16 @@ const messages = defineMessages({
copy: { id: 'status.copy', defaultMessage: 'Copy link to status' },
group_remove_account: { id: 'status.remove_account_from_group', defaultMessage: 'Remove account from group' },
group_remove_post: { id: 'status.remove_post_from_group', defaultMessage: 'Remove status from group' },
});
})
const mapDispatchToProps = (dispatch) => ({
onOpenUnauthorizedModal() {
dispatch(openModal('UNAUTHORIZED'));
dispatch(openModal('UNAUTHORIZED'))
},
});
})
const cx = classNames.bind(_s)
class StatusActionBarItem extends PureComponent {
static propTypes = {
title: PropTypes.string.isRequired,
onClick: PropTypes.func.isRequired,
icon: PropTypes.string.isRequired,
active: PropTypes.bool,
disabled: PropTypes.bool,
}
render() {
const { title, onClick, icon, active, disabled } = this.props
const btnClasses = cx({
default: 1,
text: 1,
fontSize13PX: 1,
fontWeightMedium: 1,
cursorPointer: 1,
displayFlex: 1,
justifyContentCenter: 1,
flexRow: 1,
alignItemsCenter: 1,
paddingVertical10PX: 1,
paddingHorizontal10PX: 1,
width100PC: 1,
radiusSmall: 1,
outlineFocusBrand: 1,
backgroundTransparent: 1,
backgroundSubtle_onHover: 1,
colorSecondary: 1,
})
return (
<div className={[_s.default, _s.flexGrow1, _s.paddingHorizontal10PX].join(' ')}>
<button
className={btnClasses}
onClick={onClick}
active={active}
disabled={disabled}
>
<Icon width='16px' height='16px' id={icon} className={[_s.default, _s.marginRight10PX, _s.fillcolorSecondary].join(' ')} />
{title}
</button>
</div>
)
}
}
export default
@connect(null, mapDispatchToProps)
@injectIntl
@@ -105,7 +56,7 @@ class StatusActionBar extends ImmutablePureComponent {
static contextTypes = {
router: PropTypes.object,
};
}
static propTypes = {
status: ImmutablePropTypes.map.isRequired,
@@ -125,7 +76,7 @@ class StatusActionBar extends ImmutablePureComponent {
withDismiss: PropTypes.bool,
withGroupAdmin: PropTypes.bool,
intl: PropTypes.object.isRequired,
};
}
// Avoid checking props that are functions (and whose equality will always
// evaluate to false. See react-immutable-pure-component for usage.
@@ -136,53 +87,53 @@ class StatusActionBar extends ImmutablePureComponent {
handleReplyClick = () => {
if (me) {
this.props.onReply(this.props.status, this.context.router.history);
this.props.onReply(this.props.status, this.context.router.history)
} else {
this.props.onOpenUnauthorizedModal();
this.props.onOpenUnauthorizedModal()
}
}
handleQuoteClick = () => {
if (me) {
this.props.onQuote(this.props.status, this.context.router.history);
this.props.onQuote(this.props.status, this.context.router.history)
} else {
this.props.onOpenUnauthorizedModal();
this.props.onOpenUnauthorizedModal()
}
}
handleFavouriteClick = () => {
if (me) {
this.props.onFavourite(this.props.status);
this.props.onFavourite(this.props.status)
} else {
this.props.onOpenUnauthorizedModal();
this.props.onOpenUnauthorizedModal()
}
}
handleReblogClick = e => {
if (me) {
this.props.onReblog(this.props.status, e);
this.props.onReblog(this.props.status, e)
} else {
this.props.onOpenUnauthorizedModal();
this.props.onOpenUnauthorizedModal()
}
}
render () {
const { status, intl: { formatMessage } } = this.props;
render() {
const { status, intl: { formatMessage } } = this.props
const publicStatus = ['public', 'unlisted'].includes(status.get('visibility'));
const publicStatus = ['public', 'unlisted'].includes(status.get('visibility'))
const replyCount = status.get('replies_count');
const replyIcon = (status.get('in_reply_to_id', null) === null) ? 'reply' : 'reply-all';
const replyTitle = (status.get('in_reply_to_id', null) === null) ? formatMessage(messages.reply) : formatMessage(messages.replyAll);
const replyCount = status.get('replies_count')
const replyIcon = (status.get('in_reply_to_id', null) === null) ? 'reply' : 'reply-all'
const replyTitle = (status.get('in_reply_to_id', null) === null) ? formatMessage(messages.reply) : formatMessage(messages.replyAll)
const reblogCount = status.get('reblogs_count');
const reblogTitle = !publicStatus ? formatMessage(messages.cannot_reblog) : formatMessage(messages.reblog);
const reblogCount = status.get('reblogs_count')
const reblogTitle = !publicStatus ? formatMessage(messages.cannot_reblog) : formatMessage(messages.reblog)
const favoriteCount = status.get('favourites_count');
const favoriteCount = status.get('favourites_count')
const shareButton = ('share' in navigator) && status.get('visibility') === 'public' && (
<IconButton className='status-action-bar-button' title={formatMessage(messages.share)} icon='share-alt' onClick={this.handleShareClick} />
);
)
const items = [
{
@@ -248,19 +199,19 @@ class StatusActionBar extends ImmutablePureComponent {
{
hasInteractions &&
<div className={[_s.default, _s.flexRow, _s.paddingHorizontal5PX].join(' ')}>
{ favoriteCount > 0 &&
{favoriteCount > 0 &&
<button className={interactionBtnClasses}>
{favoriteCount}
&nbsp;Likes
</button>
}
{ replyCount > 0 &&
{replyCount > 0 &&
<button className={interactionBtnClasses}>
{replyCount}
&nbsp;Comments
</button>
}
{ reblogCount > 0 &&
{reblogCount > 0 &&
<button className={interactionBtnClasses}>
{reblogCount}
&nbsp;Reposts
@@ -277,11 +228,11 @@ class StatusActionBar extends ImmutablePureComponent {
}
</div>
</div>
<div className='status-action-bar__comment'>
{/*<ComposeFormContainer shouldCondense statusId={status.get('id')} />*/}
<div className={[_s.default, _s.borderTop1PX, _s.borderColorSecondary, _s.paddingTop10PX, _s.marginBottom10PX].join(' ')}>
{ /* <ComposeFormContainer statusId={status.get('id')} shouldCondense /> */ }
</div>
</div>
);
)
}
}

View File

@@ -0,0 +1,52 @@
import classNames from 'classnames/bind'
import Icon from './icon'
const cx = classNames.bind(_s)
export default class StatusActionBarItem extends PureComponent {
static propTypes = {
title: PropTypes.string.isRequired,
onClick: PropTypes.func.isRequired,
icon: PropTypes.string.isRequired,
active: PropTypes.bool,
disabled: PropTypes.bool,
}
render() {
const { title, onClick, icon, active, disabled } = this.props
const btnClasses = cx({
default: 1,
text: 1,
fontSize13PX: 1,
fontWeightMedium: 1,
cursorPointer: 1,
displayFlex: 1,
justifyContentCenter: 1,
flexRow: 1,
alignItemsCenter: 1,
paddingVertical10PX: 1,
paddingHorizontal10PX: 1,
width100PC: 1,
radiusSmall: 1,
outlineFocusBrand: 1,
backgroundTransparent: 1,
backgroundSubtle_onHover: 1,
colorSecondary: 1,
})
return (
<div className={[_s.default, _s.flexGrow1, _s.paddingHorizontal10PX].join(' ')}>
<button
className={btnClasses}
onClick={onClick}
active={active}
disabled={disabled}
>
<Icon width='16px' height='16px' id={icon} className={[_s.default, _s.marginRight10PX, _s.fillcolorSecondary].join(' ')} />
{title}
</button>
</div>
)
}
}

View File

@@ -3,16 +3,17 @@ import TabBarItem from './tab_bar_item'
export default class TabBar extends PureComponent {
static propTypes = {
tabs: PropTypes.array,
large: PropTypes.bool,
}
render() {
const { tabs } = this.props
const { tabs, large } = this.props
return (
<div className={[_s.default, _s.height53PX, _s.paddingHorizontal5PX, _s.flexRow].join(' ')}>
{
{ !!tabs &&
tabs.map((tab, i) => (
<TabBarItem key={`tab-bar-item-${i}`} {...tab} />
<TabBarItem key={`tab-bar-item-${i}`} {...tab} large={large} />
))
}
</div>

View File

@@ -11,6 +11,7 @@ class TabBarItem extends PureComponent {
location: PropTypes.object.isRequired,
title: PropTypes.string,
to: PropTypes.string,
large: PropTypes.bool,
}
state = {
@@ -30,7 +31,7 @@ class TabBarItem extends PureComponent {
}
render() {
const { title, to, location } = this.props
const { title, to, location, large } = this.props
const { active } = this.state
const isCurrent = active === -1 ? to === location.pathname : active
@@ -43,23 +44,40 @@ class TabBarItem extends PureComponent {
displayFlex: 1,
alignItemsCenter: 1,
justifyContentCenter: 1,
paddingHorizontal10PX: 1,
borderBottom2PX: 1,
paddingVertical5PX: 1,
borderColorTransparent: !isCurrent,
borderColorBrand: isCurrent,
marginRight5PX: large,
})
const textParentClasses = cx({
default: 1,
height100PC: 1,
alignItemsCenter: 1,
justifyContentCenter: 1,
radiusSmall: 1,
paddingHorizontal10PX: !large,
paddingHorizontal15PX: large,
backgroundSubtle2Dark_onHover: !isCurrent,
})
const textOptions = {
size: 'small',
color: isCurrent ? 'brand' : 'primary',
weight: isCurrent ? 'bold' : 'normal',
size: !!large ? 'normal' : 'small',
color: isCurrent ? 'brand' : large ? 'secondary' : 'primary',
weight: isCurrent ? 'bold' : large ? 'medium' : 'normal',
className: cx({
paddingHorizontal5PX: large,
}),
}
return (
<NavLink to={to} className={containerClasses}>
<Text {...textOptions}>
{title}
</Text>
<span className={textParentClasses}>
<Text {...textOptions}>
{title}
</Text>
</span>
</NavLink>
)
}

View File

@@ -44,9 +44,6 @@ class TimelineComposeBlock extends ImmutablePureComponent {
</Heading>
</div>
<div className={[_s.default, _s.flexRow, _s.paddingVertical15PX, _s.paddingHorizontal15PX].join(' ')}>
<div className={[_s.default, _s.marginRight10PX].join(' ')}>
<Avatar account={account} size={46} />
</div>
<ComposeFormContainer {...rest} />
</div>
</Block>