Progress
This commit is contained in:
@@ -107,7 +107,7 @@ class Avatar extends ImmutablePureComponent {
|
||||
return (
|
||||
<Image
|
||||
alt={alt}
|
||||
ref={this.setRef}
|
||||
imageRef={this.setRef}
|
||||
className={classes.join(' ')}
|
||||
{...options}
|
||||
/>
|
||||
|
||||
@@ -195,10 +195,13 @@ class Composer extends PureComponent {
|
||||
return (
|
||||
<div className={_s.default}>
|
||||
|
||||
<RichTextEditorBar
|
||||
editorState={editorState}
|
||||
onChange={this.onChange}
|
||||
/>
|
||||
{ /** : todo : */
|
||||
!small &&
|
||||
<RichTextEditorBar
|
||||
editorState={editorState}
|
||||
onChange={this.onChange}
|
||||
/>
|
||||
}
|
||||
|
||||
<div
|
||||
onClick={this.focus}
|
||||
|
||||
@@ -1,4 +1,17 @@
|
||||
import Layout from '../layouts/layout'
|
||||
import { FormattedMessage } from 'react-intl'
|
||||
import {
|
||||
source_url,
|
||||
version,
|
||||
} from '../initial_state'
|
||||
import {
|
||||
APP_NAME,
|
||||
DEFAULT_REL,
|
||||
} from '../constants'
|
||||
import Button from './button'
|
||||
import DotTextSeperator from './dot_text_seperator'
|
||||
import Divider from './divider'
|
||||
import Icon from './icon'
|
||||
import Text from './text'
|
||||
|
||||
export default class ErrorBoundary extends PureComponent {
|
||||
|
||||
@@ -10,6 +23,7 @@ export default class ErrorBoundary extends PureComponent {
|
||||
hasError: false,
|
||||
stackTrace: undefined,
|
||||
componentStack: undefined,
|
||||
copied: false,
|
||||
}
|
||||
|
||||
componentDidCatch(error, info) {
|
||||
@@ -17,20 +31,129 @@ export default class ErrorBoundary extends PureComponent {
|
||||
hasError: true,
|
||||
stackTrace: error.stack,
|
||||
componentStack: info && info.componentStack,
|
||||
copied: false,
|
||||
})
|
||||
}
|
||||
|
||||
handleCopyStackTrace = () => {
|
||||
const { stackTrace } = this.state;
|
||||
const textarea = document.createElement('textarea');
|
||||
|
||||
textarea.textContent = stackTrace;
|
||||
textarea.style.position = 'fixed';
|
||||
|
||||
document.body.appendChild(textarea);
|
||||
|
||||
try {
|
||||
textarea.select();
|
||||
document.execCommand('copy');
|
||||
} catch (e) {
|
||||
//
|
||||
}
|
||||
|
||||
document.body.removeChild(textarea);
|
||||
|
||||
this.setState({ copied: true });
|
||||
setTimeout(() => this.setState({ copied: false }), 700);
|
||||
}
|
||||
|
||||
render() {
|
||||
const { hasError } = this.state
|
||||
const { hasError, copied } = this.state
|
||||
|
||||
if (!hasError) return this.props.children
|
||||
|
||||
// : todo : custom error page
|
||||
|
||||
return (
|
||||
<div className='error-boundary'>
|
||||
<div className='error-boundary__container'>
|
||||
<a className='error-boundary__link' href='/home'>Return Home</a>
|
||||
<div className={[_s.default, _s.heightMin100VH, _s.width100PC, _s.alignItemsCenter, _s.justifyContentCenter].join(' ')}>
|
||||
<div className={[_s.default, _s.height53PX, _s.bgBrand, _s.alignItemsCenter, _s.z3, _s.top0, _s.right0, _s.left0, _s.posFixed].join(' ')} >
|
||||
<div className={[_s.default, _s.flexRow, _s.width1255PX].join(' ')}>
|
||||
|
||||
<div className={[_s.default, _s.flexRow].join(' ')}>
|
||||
|
||||
<h1 className={[_s.default, _s.mr15].join(' ')}>
|
||||
<Button href='/' isText aria-label='Gab' className={[_s.default, _s.justifyContentCenter, _s.noSelect, _s.noUnderline, _s.height53PX, _s.cursorPointer, _s.px10, _s.mr15].join(' ')}>
|
||||
<Icon id='gab-logo' className={_s.fillWhite} />
|
||||
</Button>
|
||||
</h1>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className={[_s.default, _s.maxWidth640PX, _s.px15, _s.py10].join(' ')}>
|
||||
|
||||
<Icon id='warning' size='28px' className={[_s.default, _s.fillSecondary, _s.mb15].join(' ')} />
|
||||
|
||||
<Text size='medium' className={_s.pt15}>
|
||||
<FormattedMessage
|
||||
id='error.unexpected_crash.explanation'
|
||||
defaultMessage='Due to a bug in our code or a browser compatibility issue, this page or feature could not be displayed correctly.'
|
||||
/>
|
||||
</Text>
|
||||
|
||||
<Text size='medium' className={_s.mt10}>
|
||||
<FormattedMessage
|
||||
id='error.unexpected_crash.next_steps'
|
||||
defaultMessage='Try refreshing the page or trying the action again. If that does not help, you may still be able to use Gab Social through a different browser or native app.'
|
||||
/>
|
||||
</Text>
|
||||
|
||||
<div className={[_s.default, _s.py10, _s.my10].join(' ')}>
|
||||
<Text>
|
||||
{APP_NAME} ({version})
|
||||
</Text>
|
||||
|
||||
<div className={[_s.default, _s.flexRow, _s.mt10, _s.alignItemsCenter].join(' ')}>
|
||||
<Button
|
||||
isText
|
||||
href={source_url}
|
||||
rel={DEFAULT_REL}
|
||||
target='_blank'
|
||||
backgroundColor='tertiary'
|
||||
color='primary'
|
||||
radiusSmall
|
||||
className={[_s.py2, _s.px10].join(' ')}
|
||||
>
|
||||
<Text color='inherit'>
|
||||
<FormattedMessage
|
||||
id='errors.unexpected_crash.report_issue'
|
||||
defaultMessage='Report issue'
|
||||
/>
|
||||
</Text>
|
||||
</Button>
|
||||
|
||||
<DotTextSeperator />
|
||||
|
||||
<Button
|
||||
isText
|
||||
backgroundColor='tertiary'
|
||||
color='primary'
|
||||
onClick={this.handleCopyStackTrace}
|
||||
radiusSmall
|
||||
className={[_s.ml5, _s.py2, _s.px10].join(' ')}
|
||||
>
|
||||
<Text color='inherit'>
|
||||
<FormattedMessage
|
||||
id='errors.unexpected_crash.copy_stacktrace'
|
||||
defaultMessage='Copy stacktrace to clipboard'
|
||||
/>
|
||||
</Text>
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Divider />
|
||||
|
||||
<div className={[_s.default, _s.flexRow].join(' ')}>
|
||||
<Button href='/home'>
|
||||
<Text align='center' color='inherit'>
|
||||
<FormattedMessage
|
||||
id='return_home'
|
||||
defaultMessage='Return Home'
|
||||
/>
|
||||
</Text>
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
|
||||
@@ -14,7 +14,6 @@ import Text from './text'
|
||||
const messages = defineMessages({
|
||||
join: { id: 'groups.join', defaultMessage: 'Join group' },
|
||||
leave: { id: 'groups.leave', defaultMessage: 'Leave group' },
|
||||
share: { id: 'status.share', defaultMessage: 'Share' },
|
||||
removed_accounts: { id: 'groups.removed_accounts', defaultMessage: 'Removed Accounts' },
|
||||
group_archived: { id: 'group.detail.archived_group', defaultMessage: 'Archived group' },
|
||||
group_admin: { id: 'groups.detail.role_admin', defaultMessage: 'You\'re an admin' }
|
||||
@@ -30,10 +29,6 @@ const mapDispatchToProps = (dispatch, { intl }) => ({
|
||||
}
|
||||
},
|
||||
|
||||
onShare() {
|
||||
|
||||
},
|
||||
|
||||
onOpenGroupOptions() {
|
||||
|
||||
},
|
||||
@@ -53,7 +48,6 @@ class GroupHeader extends ImmutablePureComponent {
|
||||
group: ImmutablePropTypes.map,
|
||||
intl: PropTypes.object.isRequired,
|
||||
onToggleMembership: PropTypes.func.isRequired,
|
||||
onShare: PropTypes.func.isRequired,
|
||||
onOpenGroupOptions: PropTypes.func.isRequired,
|
||||
relationships: ImmutablePropTypes.map,
|
||||
}
|
||||
@@ -67,7 +61,6 @@ class GroupHeader extends ImmutablePureComponent {
|
||||
const {
|
||||
group,
|
||||
intl,
|
||||
onShare,
|
||||
onOpenGroupOptions,
|
||||
relationships,
|
||||
} = this.props
|
||||
@@ -130,17 +123,7 @@ class GroupHeader extends ImmutablePureComponent {
|
||||
</Text>
|
||||
</Button>
|
||||
}
|
||||
<Button
|
||||
color='primary'
|
||||
backgroundColor='tertiary'
|
||||
radiusSmall
|
||||
className={_s.mr5}
|
||||
onClick={onShare}
|
||||
>
|
||||
<Text color='inherit' size='small'>
|
||||
{intl.formatMessage(messages.share)}
|
||||
</Text>
|
||||
</Button>
|
||||
|
||||
<Button
|
||||
radiusSmall
|
||||
color='primary'
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import AddIcon from '../assets/add_icon'
|
||||
import AngleRightIcon from '../assets/angle_right_icon'
|
||||
import AppsIcon from '../assets/apps_icon'
|
||||
import ArrowLeftIcon from '../assets/arrow_left_icon'
|
||||
import ArrowRightIcon from '../assets/arrow_right_icon'
|
||||
import AudioIcon from '../assets/audio_icon'
|
||||
import AudioMuteIcon from '../assets/audio_mute_icon'
|
||||
import BackIcon from '../assets/back_icon'
|
||||
@@ -73,6 +75,8 @@ const ICONS = {
|
||||
'add': AddIcon,
|
||||
'angle-right': AngleRightIcon,
|
||||
'apps': AppsIcon,
|
||||
'arrow-left': ArrowLeftIcon,
|
||||
'arrow-right': ArrowRightIcon,
|
||||
'audio': AudioIcon,
|
||||
'audio-mute': AudioMuteIcon,
|
||||
'back': BackIcon,
|
||||
|
||||
@@ -19,7 +19,7 @@ export default class Image extends PureComponent {
|
||||
fit: PropTypes.oneOf(['contain', 'cover', 'tile', 'none']),
|
||||
nullable: PropTypes.bool,
|
||||
lazy: PropTypes.bool,
|
||||
ref: PropTypes.func,
|
||||
imageRef: PropTypes.func,
|
||||
}
|
||||
|
||||
static defaultProps = {
|
||||
@@ -43,6 +43,7 @@ export default class Image extends PureComponent {
|
||||
className,
|
||||
nullable,
|
||||
lazy,
|
||||
imageRef,
|
||||
...otherProps
|
||||
} = this.props
|
||||
const { error } = this.state
|
||||
@@ -69,6 +70,7 @@ export default class Image extends PureComponent {
|
||||
alt={alt}
|
||||
className={classes}
|
||||
{...otherProps}
|
||||
ref={imageRef}
|
||||
src={src}
|
||||
onError={this.handleOnError}
|
||||
/>
|
||||
|
||||
@@ -9,6 +9,7 @@ import {
|
||||
source_url,
|
||||
me,
|
||||
} from '../initial_state'
|
||||
import { DEFAULT_REL } from '../constants'
|
||||
import Text from './text'
|
||||
import Button from './button'
|
||||
|
||||
@@ -133,7 +134,7 @@ class LinkFooter extends PureComponent {
|
||||
defaultMessage='Gab Social is open source software. You can contribute or report issues on our self-hosted GitLab at {gitlab}.'
|
||||
values={{
|
||||
gitlab: (
|
||||
<a href={source_url} className={_s.inherit} rel='noopener noreferrer' target='_blank'>
|
||||
<a href={source_url} className={_s.inherit} rel={DEFAULT_REL} target='_blank'>
|
||||
{repository}
|
||||
</a>
|
||||
)
|
||||
|
||||
@@ -12,7 +12,7 @@ import SensitiveMediaItem from './sensitive_media_item'
|
||||
import Text from './text'
|
||||
|
||||
const messages = defineMessages({
|
||||
toggle_visible: { id: 'media_gallery.toggle_visible', defaultMessage: 'Toggle visibility' },
|
||||
toggle_visible: { id: 'media_gallery.toggle_visible', defaultMessage: 'Hide media' },
|
||||
warning: { id: 'status.sensitive_warning', defaultMessage: 'Sensitive content' },
|
||||
hidden: { id: 'status.media_hidden', defaultMessage: 'Media hidden' },
|
||||
});
|
||||
|
||||
@@ -4,15 +4,13 @@ import ConfirmationModal from './confirmation_modal'
|
||||
|
||||
const messages = defineMessages({
|
||||
blockDomain: { id: 'block_domain', defaultMessage: 'Block {domain}' },
|
||||
blockDomainConfirm: { id: 'confirmations.domain_block.confirm', defaultMessage: 'Hide entire domain' },
|
||||
blockDomainConfirm: { id: 'confirmations.domain_block.confirm', defaultMessage: 'Block entire domain' },
|
||||
blockDomainMessage: { id: 'confirmations.domain_block.message', defaultMessage: 'Are you really, really sure you want to block the entire {domain}? In most cases a few targeted blocks or mutes are sufficient and preferable. You will not see content from that domain in any public timelines or your notifications. Your followers from that domain will be removed.' },
|
||||
cancel: { id: 'confirmation_modal.cancel', defaultMessage: 'Cancel' },
|
||||
})
|
||||
|
||||
const mapDispatchToProps = (dispatch) => ({
|
||||
onConfirm(domain) {
|
||||
dispatch(blockDomain(domain))
|
||||
},
|
||||
onConfirm: (domain) => dispatch(blockDomain(domain)),
|
||||
})
|
||||
|
||||
export default
|
||||
@@ -22,9 +20,9 @@ class BlockDomainModal extends PureComponent {
|
||||
|
||||
static propTypes = {
|
||||
domain: PropTypes.string.isRequired,
|
||||
intl: PropTypes.object.isRequired,
|
||||
onConfirm: PropTypes.func.isRequired,
|
||||
onClose: PropTypes.func.isRequired,
|
||||
intl: PropTypes.object.isRequired,
|
||||
}
|
||||
|
||||
handleClick = () => {
|
||||
@@ -34,8 +32,6 @@ class BlockDomainModal extends PureComponent {
|
||||
render() {
|
||||
const { onClose, domain, intl } = this.props
|
||||
|
||||
console.log("this.props: ", this.props)
|
||||
|
||||
return (
|
||||
<ConfirmationModal
|
||||
title={intl.formatMessage(messages.blockDomain, { domain })}
|
||||
|
||||
@@ -1,20 +1,16 @@
|
||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||
import ImmutablePureComponent from 'react-immutable-pure-component';
|
||||
import { defineMessages, injectIntl } from 'react-intl';
|
||||
import Button from '../button';
|
||||
import StatusContent from '../status_content';
|
||||
import Avatar from '../avatar';
|
||||
import RelativeTimestamp from '../relative_timestamp';
|
||||
import DisplayName from '../display_name';
|
||||
import Icon from '../icon';
|
||||
import ImmutablePropTypes from 'react-immutable-proptypes'
|
||||
import ImmutablePureComponent from 'react-immutable-pure-component'
|
||||
import { defineMessages, injectIntl } from 'react-intl'
|
||||
import Button from '../button'
|
||||
import StatusContainer from '../../containers/status_container'
|
||||
import Text from '../text'
|
||||
import ModalLayout from './modal_layout'
|
||||
|
||||
const messages = defineMessages({
|
||||
cancel_repost: { id: 'status.cancel_repost_private', defaultMessage: 'Un-repost' },
|
||||
removeRepost: { id: 'status.cancel_repost_private', defaultMessage: 'Remove Repost' },
|
||||
repost: { id: 'status.repost', defaultMessage: 'Repost' },
|
||||
combo: { id: 'boost_modal.combo', defaultMessage: 'You can press {combo} to skip this next time' },
|
||||
});
|
||||
|
||||
// : todo :
|
||||
combo: { id: 'boost_modal.combo', defaultMessage: 'You can press Shift + Repost to skip this next time' },
|
||||
})
|
||||
|
||||
export default
|
||||
@injectIntl
|
||||
@@ -22,86 +18,62 @@ class BoostModal extends ImmutablePureComponent {
|
||||
|
||||
static contextTypes = {
|
||||
router: PropTypes.object,
|
||||
};
|
||||
}
|
||||
|
||||
static propTypes = {
|
||||
status: ImmutablePropTypes.map.isRequired,
|
||||
onRepost: PropTypes.func.isRequired,
|
||||
onClose: PropTypes.func.isRequired,
|
||||
intl: PropTypes.object.isRequired,
|
||||
};
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.button.focus();
|
||||
this.button.focus()
|
||||
}
|
||||
|
||||
handleRepost = () => {
|
||||
this.props.onRepost(this.props.status);
|
||||
this.props.onClose();
|
||||
}
|
||||
|
||||
handleAccountClick = (e) => {
|
||||
if (e.button === 0 && !(e.ctrlKey || e.metaKey)) {
|
||||
e.preventDefault();
|
||||
this.props.onClose();
|
||||
this.context.router.history.push(`/${this.props.status.getIn(['account', 'acct'])}`);
|
||||
}
|
||||
}
|
||||
|
||||
handleStatusClick = (e) => {
|
||||
if (e.button === 0 && !(e.ctrlKey || e.metaKey)) {
|
||||
e.preventDefault();
|
||||
this.props.onClose();
|
||||
this.context.router.history.push(`/${this.props.status.getIn(['account', 'acct'])}/posts/${this.props.status.get('url')}`);
|
||||
}
|
||||
this.props.onRepost(this.props.status)
|
||||
this.props.onClose()
|
||||
}
|
||||
|
||||
setRef = (c) => {
|
||||
this.button = c;
|
||||
this.button = c
|
||||
}
|
||||
|
||||
render () {
|
||||
const { status, intl } = this.props;
|
||||
const buttonText = status.get('reblogged') ? messages.cancel_repost : messages.repost;
|
||||
|
||||
const statusUrl = `/${status.getIn(['account', 'acct'])}/posts/${status.get('url')}`;
|
||||
const { status, onClose, intl } = this.props
|
||||
|
||||
const buttonText = status.get('reblogged') ? messages.removeRepost : messages.repost
|
||||
|
||||
return (
|
||||
<div className='modal-root__modal boost-modal'>
|
||||
<div className='boost-modal__container'>
|
||||
<div className='status light'>
|
||||
<div className='boost-modal__status-header'>
|
||||
<div className='boost-modal__status-time'>
|
||||
<a onClick={this.handleStatusClick} href={statusUrl} className='status__relative-time'>
|
||||
<RelativeTimestamp timestamp={status.get('created_at')} />
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<a onClick={this.handleAccountClick} href={`/${status.getIn(['account', 'acct'])}`} className='status__display-name'>
|
||||
<div className='status__avatar'>
|
||||
<Avatar account={status.get('account')} size={48} />
|
||||
</div>
|
||||
|
||||
<DisplayName account={status.get('account')} />
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<StatusContent status={status} />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className='boost-modal__action-bar'>
|
||||
<div>
|
||||
{intl.formatMessage(messages.combo, {
|
||||
values: {
|
||||
combo: <span>Shift + <Icon id='retweet' /></span>
|
||||
}
|
||||
})}
|
||||
</div>
|
||||
<Button text={intl.formatMessage(buttonText)} onClick={this.handleRepost} ref={this.setRef} />
|
||||
</div>
|
||||
<ModalLayout
|
||||
title={intl.formatMessage(messages.repost)}
|
||||
noPadding
|
||||
width={480}
|
||||
onClose={onClose}
|
||||
>
|
||||
|
||||
<div className={[_s.default, _s.px15, _s.py10, _s.mt5].join(' ')}>
|
||||
<StatusContainer
|
||||
id={status.get('id')}
|
||||
isChild
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
||||
<div className={[_s.default, _s.justifyContentCenter, _s.px15, _s.mt5, _s.mb15].join(' ')}>
|
||||
<Text align='center'>
|
||||
{intl.formatMessage(messages.combo)}
|
||||
</Text>
|
||||
<div className={[_s.default, _s.flexRow, _s.justifyContentCenter, _s.my10, _s.pt15, _s.pb5].join(' ')}>
|
||||
<Button onClick={this.handleRepost} buttonRef={this.setRef}>
|
||||
<Text color='inherit' className={_s.px15}>
|
||||
{intl.formatMessage(buttonText)}
|
||||
</Text>
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</ModalLayout>
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -86,8 +86,8 @@ class GifPickerModal extends PureComponent {
|
||||
this.props.handleFetchCategories()
|
||||
}
|
||||
|
||||
onChange = (e) => {
|
||||
this.props.handleOnChange(e.target.value)
|
||||
onChange = (value) => {
|
||||
this.props.handleOnChange(value)
|
||||
}
|
||||
|
||||
onHandleCloseModal = () => {
|
||||
|
||||
@@ -1,13 +1,12 @@
|
||||
import classNames from 'classnames'
|
||||
import { defineMessages, injectIntl } from 'react-intl'
|
||||
import ReactSwipeableViews from 'react-swipeable-views'
|
||||
import ImmutablePropTypes from 'react-immutable-proptypes'
|
||||
import ImmutablePureComponent from 'react-immutable-pure-component'
|
||||
import { CX } from '../../constants'
|
||||
import Video from '../video'
|
||||
import ExtendedVideoPlayer from '../extended_video_player'
|
||||
import Button from '../button'
|
||||
import ImageLoader from '../image_loader'
|
||||
import Icon from '../icon'
|
||||
|
||||
const messages = defineMessages({
|
||||
close: { id: 'lightbox.close', defaultMessage: 'Close' },
|
||||
@@ -16,9 +15,7 @@ const messages = defineMessages({
|
||||
viewContext: { id: 'lightbox.view_context', defaultMessage: 'View context' },
|
||||
})
|
||||
|
||||
export const previewState = 'previewMediaModal';
|
||||
|
||||
// : todo :
|
||||
export const previewState = 'previewMediaModal'
|
||||
|
||||
export default
|
||||
@injectIntl
|
||||
@@ -30,115 +27,158 @@ class MediaModal extends ImmutablePureComponent {
|
||||
index: PropTypes.number.isRequired,
|
||||
onClose: PropTypes.func.isRequired,
|
||||
intl: PropTypes.object.isRequired,
|
||||
};
|
||||
}
|
||||
|
||||
static contextTypes = {
|
||||
router: PropTypes.object,
|
||||
};
|
||||
}
|
||||
|
||||
state = {
|
||||
index: null,
|
||||
navigationHidden: false,
|
||||
};
|
||||
}
|
||||
|
||||
updateOnProps = [
|
||||
'media',
|
||||
'status',
|
||||
'index',
|
||||
]
|
||||
|
||||
handleSwipe = (index) => {
|
||||
this.setState({ index: index % this.props.media.size });
|
||||
this.setState({ index: index % this.props.media.size })
|
||||
}
|
||||
|
||||
handleNextClick = () => {
|
||||
this.setState({ index: (this.getIndex() + 1) % this.props.media.size });
|
||||
this.setState({ index: (this.getIndex() + 1) % this.props.media.size })
|
||||
}
|
||||
|
||||
handlePrevClick = () => {
|
||||
this.setState({ index: (this.props.media.size + this.getIndex() - 1) % this.props.media.size });
|
||||
this.setState({ index: (this.props.media.size + this.getIndex() - 1) % this.props.media.size })
|
||||
}
|
||||
|
||||
handleChangeIndex = (e) => {
|
||||
const index = Number(e.currentTarget.getAttribute('data-index'));
|
||||
this.setState({ index: index % this.props.media.size });
|
||||
const index = Number(e.currentTarget.getAttribute('data-index'))
|
||||
this.setState({ index: index % this.props.media.size })
|
||||
}
|
||||
|
||||
handleKeyDown = (e) => {
|
||||
switch(e.key) {
|
||||
case 'ArrowLeft':
|
||||
this.handlePrevClick();
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
break;
|
||||
case 'ArrowRight':
|
||||
this.handleNextClick();
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
break;
|
||||
switch (e.key) {
|
||||
case 'ArrowLeft':
|
||||
this.handlePrevClick()
|
||||
e.preventDefault()
|
||||
e.stopPropagation()
|
||||
break
|
||||
case 'ArrowRight':
|
||||
this.handleNextClick()
|
||||
e.preventDefault()
|
||||
e.stopPropagation()
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
componentDidMount () {
|
||||
window.addEventListener('keydown', this.handleKeyDown, false);
|
||||
componentDidMount() {
|
||||
window.addEventListener('keydown', this.handleKeyDown, false)
|
||||
|
||||
if (this.context.router) {
|
||||
const history = this.context.router.history;
|
||||
const history = this.context.router.history
|
||||
|
||||
history.push(history.location.pathname, previewState);
|
||||
history.push(history.location.pathname, previewState)
|
||||
|
||||
this.unlistenHistory = history.listen(() => {
|
||||
this.props.onClose();
|
||||
});
|
||||
this.props.onClose()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
componentWillUnmount () {
|
||||
window.removeEventListener('keydown', this.handleKeyDown);
|
||||
componentWillUnmount() {
|
||||
window.removeEventListener('keydown', this.handleKeyDown)
|
||||
|
||||
if (this.context.router) {
|
||||
this.unlistenHistory();
|
||||
this.unlistenHistory()
|
||||
|
||||
if (this.context.router.history.location.state === previewState) {
|
||||
this.context.router.history.goBack();
|
||||
this.context.router.history.goBack()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
getIndex () {
|
||||
return this.state.index !== null ? this.state.index : this.props.index;
|
||||
getIndex() {
|
||||
return this.state.index !== null ? this.state.index : this.props.index
|
||||
}
|
||||
|
||||
toggleNavigation = () => {
|
||||
this.setState(prevState => ({
|
||||
navigationHidden: !prevState.navigationHidden,
|
||||
}));
|
||||
};
|
||||
}))
|
||||
}
|
||||
|
||||
handleStatusClick = e => {
|
||||
if (e.button === 0 && !(e.ctrlKey || e.metaKey)) {
|
||||
e.preventDefault();
|
||||
this.context.router.history.push(`/${this.props.status.getIn(['account', 'acct'])}/posts/${this.props.status.get('id')}`);
|
||||
e.preventDefault()
|
||||
this.context.router.history.push(`/${this.props.status.getIn(['account', 'acct'])}/posts/${this.props.status.get('id')}`)
|
||||
}
|
||||
}
|
||||
|
||||
render () {
|
||||
const { media, status, intl, onClose } = this.props;
|
||||
const { navigationHidden } = this.state;
|
||||
render() {
|
||||
const {
|
||||
media,
|
||||
status,
|
||||
intl,
|
||||
onClose,
|
||||
} = this.props
|
||||
const { navigationHidden } = this.state
|
||||
|
||||
const index = this.getIndex();
|
||||
let pagination = [];
|
||||
const index = this.getIndex()
|
||||
let pagination = []
|
||||
|
||||
const leftNav = media.size > 1 && <button tabIndex='0' className='media-modal__nav media-modal__nav--left' onClick={this.handlePrevClick} aria-label={intl.formatMessage(messages.previous)}><Icon id='chevron-left' fixedWidth /></button>;
|
||||
const rightNav = media.size > 1 && <button tabIndex='0' className='media-modal__nav media-modal__nav--right' onClick={this.handleNextClick} aria-label={intl.formatMessage(messages.next)}><Icon id='chevron-right' fixedWidth /></button>;
|
||||
const leftNav = media.size > 1 && (
|
||||
<Button
|
||||
tabIndex='0'
|
||||
backgroundColor='black'
|
||||
className={[_s.py15, _s.posFixed, _s.top50PC, _s.left0, _s.mt10, _s.ml10].join(' ')}
|
||||
onClick={this.handlePrevClick}
|
||||
aria-label={intl.formatMessage(messages.previous)}
|
||||
icon='arrow-left'
|
||||
iconSize='18px'
|
||||
/>
|
||||
)
|
||||
const rightNav = media.size > 1 && (
|
||||
<Button
|
||||
tabIndex='0'
|
||||
backgroundColor='black'
|
||||
className={[_s.py15, _s.posFixed, _s.top50PC, _s.right0, _s.mt10, _s.mr10].join(' ')}
|
||||
onClick={this.handleNextClick}
|
||||
aria-label={intl.formatMessage(messages.next)}
|
||||
icon='arrow-right'
|
||||
iconSize='18px'
|
||||
/>
|
||||
)
|
||||
|
||||
if (media.size > 1) {
|
||||
pagination = media.map((item, i) => {
|
||||
const classes = ['media-modal__button'];
|
||||
if (i === index) {
|
||||
classes.push('media-modal__button--active');
|
||||
}
|
||||
return (<li className='media-modal__page-dot' key={i}><button tabIndex='0' className={classes.join(' ')} onClick={this.handleChangeIndex} data-index={i}>{i + 1}</button></li>);
|
||||
});
|
||||
const btnClasses = CX({
|
||||
default: 1,
|
||||
px5: 1,
|
||||
py5: 1,
|
||||
outlineNone: 1,
|
||||
colorPrimary: 1,
|
||||
circle: 1,
|
||||
cursorPointer: 1,
|
||||
bgPrimaryOpaque: i !== index,
|
||||
bgPrimary: i === index,
|
||||
})
|
||||
|
||||
return (
|
||||
<li className={[_s.default, _s.px5].join(' ')} key={`media-pagination-${i}`}>
|
||||
<button tabIndex='0' className={btnClasses} onClick={this.handleChangeIndex} data-index={i} />
|
||||
</li>
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
const content = media.map((image) => {
|
||||
const width = image.getIn(['meta', 'original', 'width']) || null;
|
||||
const height = image.getIn(['meta', 'original', 'height']) || null;
|
||||
const width = image.getIn(['meta', 'original', 'width']) || null
|
||||
const height = image.getIn(['meta', 'original', 'height']) || null
|
||||
|
||||
if (image.get('type') === 'image') {
|
||||
return (
|
||||
@@ -151,9 +191,9 @@ class MediaModal extends ImmutablePureComponent {
|
||||
key={image.get('url')}
|
||||
onClick={this.toggleNavigation}
|
||||
/>
|
||||
);
|
||||
)
|
||||
} else if (image.get('type') === 'video') {
|
||||
const { time } = this.props;
|
||||
const { time } = this.props
|
||||
|
||||
return (
|
||||
<Video
|
||||
@@ -168,7 +208,7 @@ class MediaModal extends ImmutablePureComponent {
|
||||
alt={image.get('description')}
|
||||
key={image.get('url')}
|
||||
/>
|
||||
);
|
||||
)
|
||||
} else if (image.get('type') === 'gifv') {
|
||||
return (
|
||||
<ExtendedVideoPlayer
|
||||
@@ -181,11 +221,11 @@ class MediaModal extends ImmutablePureComponent {
|
||||
alt={image.get('description')}
|
||||
onClick={this.toggleNavigation}
|
||||
/>
|
||||
);
|
||||
)
|
||||
}
|
||||
|
||||
return null;
|
||||
}).toArray();
|
||||
return null
|
||||
}).toArray()
|
||||
|
||||
// you can't use 100vh, because the viewport height is taller
|
||||
// than the visible part of the document in some mobile
|
||||
@@ -194,26 +234,25 @@ class MediaModal extends ImmutablePureComponent {
|
||||
const swipeableViewsStyle = {
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
};
|
||||
}
|
||||
|
||||
const containerStyle = {
|
||||
alignItems: 'center', // center vertically
|
||||
};
|
||||
|
||||
const navigationClassName = classNames('media-modal__navigation', {
|
||||
'media-modal__navigation--hidden': navigationHidden,
|
||||
});
|
||||
const navigationClasses = CX({
|
||||
default: 1,
|
||||
displayNone: navigationHidden,
|
||||
})
|
||||
|
||||
return (
|
||||
<div className='modal-root__modal media-modal'>
|
||||
<div className={[_s.default, _s.width100PC, _s.height100PC, _s.alignItemsCenter, _s.justifyContentCenter].join(' ')}>
|
||||
<div
|
||||
className='media-modal__closer'
|
||||
className={[_s.default, _s.top0, _s.right0, _s.bottom0, _s.left0].join(' ')}
|
||||
role='presentation'
|
||||
onClick={onClose}
|
||||
>
|
||||
<ReactSwipeableViews
|
||||
style={swipeableViewsStyle}
|
||||
containerStyle={containerStyle}
|
||||
containerStyle={{
|
||||
alignItems: 'center',
|
||||
}}
|
||||
onChangeIndex={this.handleSwipe}
|
||||
onSwitching={this.handleSwitching}
|
||||
index={index}
|
||||
@@ -222,26 +261,36 @@ class MediaModal extends ImmutablePureComponent {
|
||||
</ReactSwipeableViews>
|
||||
</div>
|
||||
|
||||
<div className={navigationClassName}>
|
||||
<Button className='media-modal__close' title={intl.formatMessage(messages.close)} icon='times' onClick={onClose} size={40} />
|
||||
<div className={navigationClasses}>
|
||||
<Button
|
||||
title={intl.formatMessage(messages.close)}
|
||||
icon='close'
|
||||
backgroundColor='black'
|
||||
onClick={onClose}
|
||||
iconSize='14px'
|
||||
className={[_s.py15, _s.posFixed, _s.top0, _s.right0, _s.mt10, _s.mr10].join(' ')}
|
||||
/>
|
||||
|
||||
{leftNav}
|
||||
{rightNav}
|
||||
|
||||
{status && (
|
||||
{ /** : todo :
|
||||
status &&
|
||||
<div className={classNames('media-modal__meta', { 'media-modal__meta--shifted': media.size > 1 })}>
|
||||
<a href={status.get('url')} onClick={this.handleStatusClick}>
|
||||
{intl.formatMessage(messages.viewContext)}
|
||||
</a>
|
||||
</div>
|
||||
)}
|
||||
*/
|
||||
}
|
||||
|
||||
<ul className='media-modal__pagination'>
|
||||
{pagination}
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<ul className={[_s.default, _s.posAbsolute, _s.bottom0, _s.mb15, _s.flexRow, _s.bgBlackOpaque, _s.circle, _s.py10, _s.px15, _s.listStyleNone].join(' ')}>
|
||||
{pagination}
|
||||
</ul>
|
||||
</div>
|
||||
);
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import { closeModal } from '../../actions/modal'
|
||||
import { cancelReplyCompose } from '../../actions/compose'
|
||||
import Bundle from '../../features/ui/util/bundle'
|
||||
import ModalBase from './modal_base'
|
||||
import BundleModalError from '../bundle_modal_error'
|
||||
import {
|
||||
MODAL_ACTIONS,
|
||||
MODAL_BLOCK_ACCOUNT,
|
||||
@@ -64,9 +66,6 @@ import {
|
||||
VideoModal,
|
||||
} from '../../features/ui/util/async_components'
|
||||
|
||||
import ModalBase from './modal_base'
|
||||
import BundleModalError from '../bundle_modal_error'
|
||||
|
||||
const MODAL_COMPONENTS = {}
|
||||
MODAL_COMPONENTS[MODAL_ACTIONS] = ActionsModal
|
||||
MODAL_COMPONENTS[MODAL_BLOCK_ACCOUNT] = BlockAccountModal
|
||||
@@ -135,12 +134,12 @@ class ModalRoot extends PureComponent {
|
||||
}
|
||||
}
|
||||
|
||||
renderLoading = modalId => () => {
|
||||
return ['MEDIA', 'VIDEO', 'BOOST', 'CONFIRM', 'ACTIONS'].indexOf(modalId) === -1 ? <ModalLoading /> : null
|
||||
renderLoading = () => {
|
||||
return <ModalLoading />
|
||||
}
|
||||
|
||||
renderError = (props) => {
|
||||
return <BundleModalError {...props} onClose={this.onClickClose} />
|
||||
renderError = () => {
|
||||
return <BundleModalError {...this.props} onClose={this.onClickClose} />
|
||||
}
|
||||
|
||||
onClickClose = () => {
|
||||
@@ -157,12 +156,12 @@ class ModalRoot extends PureComponent {
|
||||
visible &&
|
||||
<Bundle
|
||||
fetchComponent={MODAL_COMPONENTS[type]}
|
||||
loading={this.renderLoading(type)}
|
||||
loading={this.renderLoading}
|
||||
error={this.renderError}
|
||||
renderDelay={200}
|
||||
>
|
||||
{
|
||||
(SpecificComponent) => <SpecificComponent {...props} onClose={this.onClickClose} />
|
||||
(Component) => <Component {...props} onClose={this.onClickClose} />
|
||||
}
|
||||
</Bundle>
|
||||
}
|
||||
|
||||
@@ -49,7 +49,7 @@ class NavigationBar extends ImmutablePureComponent {
|
||||
<div className={[_s.default, _s.flexRow].join(' ')}>
|
||||
|
||||
<h1 className={[_s.default, _s.mr15].join(' ')}>
|
||||
<Button to='/' aria-label='Gab' className={[_s.default, _s.justifyContentCenter, _s.noSelect, _s.noUnderline, _s.height53PX, _s.cursorPointer, _s.px10, _s.mr15].join(' ')}>
|
||||
<Button to='/' isText title='Gab' aria-label='Gab' className={[_s.default, _s.justifyContentCenter, _s.noSelect, _s.noUnderline, _s.height53PX, _s.cursorPointer, _s.px10, _s.mr15].join(' ')}>
|
||||
<Icon id='gab-logo' className={_s.fillWhite} />
|
||||
</Button>
|
||||
</h1>
|
||||
@@ -68,14 +68,21 @@ class NavigationBar extends ImmutablePureComponent {
|
||||
|
||||
<NavigationBarButtonDivider />
|
||||
|
||||
<NavigationBarButton icon='notifications' to='/notifications' />
|
||||
<NavigationBarButton icon='cog' href='/settings/preferences' />
|
||||
<NavigationBarButton attrTitle='Notifications' icon='notifications' to='/notifications' />
|
||||
<NavigationBarButton attrTitle='Settings' icon='cog' href='/settings/preferences' />
|
||||
|
||||
<NavigationBarButtonDivider />
|
||||
|
||||
<button onClick={this.handleProfileClick} className={[_s.height53PX, _s.bgTransparent, _s.outlineNone, _s.cursorPointer, _s.default, _s.justifyContentCenter, _s.ml15].join(' ')}>
|
||||
<Avatar account={account} size={32} noHover />
|
||||
</button>
|
||||
|
||||
{
|
||||
!!account &&
|
||||
<button
|
||||
title={account.get('display_name')}
|
||||
onClick={this.handleProfileClick}
|
||||
className={[_s.height53PX, _s.bgTransparent, _s.outlineNone, _s.cursorPointer, _s.default, _s.justifyContentCenter, _s.ml15].join(' ')}
|
||||
>
|
||||
<Avatar account={account} size={32} noHover />
|
||||
</button>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -105,6 +112,7 @@ class NavigationBarButton extends PureComponent {
|
||||
icon: PropTypes.string,
|
||||
to: PropTypes.string,
|
||||
href: PropTypes.string,
|
||||
attrTitle: PropTypes.string,
|
||||
}
|
||||
|
||||
render() {
|
||||
@@ -113,6 +121,7 @@ class NavigationBarButton extends PureComponent {
|
||||
icon,
|
||||
to,
|
||||
href,
|
||||
attrTitle,
|
||||
} = this.props
|
||||
|
||||
const active = false
|
||||
@@ -124,15 +133,16 @@ class NavigationBarButton extends PureComponent {
|
||||
alignItemsCenter: 1,
|
||||
justifyContentCenter: 1,
|
||||
outlineNone: 1,
|
||||
px10: !!title,
|
||||
px5: !title,
|
||||
cursorPointer: 1,
|
||||
bgTransparent: 1,
|
||||
noUnderline: 1,
|
||||
px10: !!title,
|
||||
px5: !title,
|
||||
colorWhite: !!title,
|
||||
fs13PX: !!title,
|
||||
fontWeightNormal: !!title,
|
||||
textUppercase: !!title,
|
||||
noUnderline: 1,
|
||||
bgBrandDark_onHover: !!title,
|
||||
})
|
||||
|
||||
const iconClasses = CX({
|
||||
@@ -141,12 +151,13 @@ class NavigationBarButton extends PureComponent {
|
||||
mr10: !!title,
|
||||
})
|
||||
|
||||
const iconSize = !!title ? 16 : 18
|
||||
const iconSize = !!title ? '16px' : '18px'
|
||||
|
||||
return (
|
||||
<Button
|
||||
to={to}
|
||||
href={href}
|
||||
attrTitle={attrTitle}
|
||||
color='white'
|
||||
className={classes}
|
||||
noClasses
|
||||
|
||||
@@ -3,6 +3,7 @@ import { injectIntl, defineMessages } from 'react-intl'
|
||||
import ImmutablePureComponent from 'react-immutable-pure-component'
|
||||
import { HotKeys } from 'react-hotkeys'
|
||||
import ImmutablePropTypes from 'react-immutable-proptypes'
|
||||
import { me } from '../initial_state'
|
||||
import StatusContainer from '../containers/status_container'
|
||||
import Avatar from './avatar'
|
||||
import Icon from './icon'
|
||||
@@ -11,6 +12,7 @@ import DisplayName from './display_name'
|
||||
|
||||
const messages = defineMessages({
|
||||
poll: { id: 'notification.poll', defaultMessage: 'A poll you have voted in has ended' },
|
||||
ownPoll: { id: 'notification.own_poll', defaultMessage: 'Your poll has ended' },
|
||||
mentionedInPost: { id: 'mentioned_in_post', defaultMessage: 'mentioned you in their post' },
|
||||
mentionedInComment: { id: 'mentioned_in_comment', defaultMessage: 'mentioned you in their comment' },
|
||||
followedYouOne: { id: 'followed_you_one', defaultMessage: 'followed you' },
|
||||
@@ -21,6 +23,7 @@ const messages = defineMessages({
|
||||
repostedStatusMultiple: { id: 'reposted_status_multiple', defaultMessage: 'and {count} others reposted your status' },
|
||||
})
|
||||
|
||||
// : todo :
|
||||
const notificationForScreenReader = (intl, message, timestamp) => {
|
||||
const output = [message]
|
||||
|
||||
@@ -84,8 +87,14 @@ class Notification extends ImmutablePureComponent {
|
||||
})
|
||||
break
|
||||
case 'poll':
|
||||
let msg = messages.poll
|
||||
if (accounts.size === 1) {
|
||||
if (accounts.first().get('id') === me) {
|
||||
msg = messages.ownPoll
|
||||
}
|
||||
}
|
||||
icon = 'poll'
|
||||
message = intl.formatMessage(messages.poll)
|
||||
message = intl.formatMessage(msg)
|
||||
break
|
||||
default:
|
||||
return null
|
||||
|
||||
@@ -4,6 +4,7 @@ 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 { DEFAULT_REL } from '../../constants'
|
||||
import PanelLayout from './panel_layout'
|
||||
import Divider from '../divider'
|
||||
import Icon from '../icon'
|
||||
@@ -110,12 +111,12 @@ class ProfileInfoPanel extends ImmutablePureComponent {
|
||||
|
||||
{ /* : todo : */}
|
||||
<dd className='verified'>
|
||||
<a href={proof.get('proof_url')} target='_blank' rel='noopener noreferrer'>
|
||||
<a href={proof.get('proof_url')} target='_blank' rel={DEFAULT_REL}>
|
||||
<span title={intl.formatMessage(messages.linkVerifiedOn, { date: intl.formatDate(proof.get('updated_at'), dateFormatOptions) })}>
|
||||
<Icon id='check' size='12px' className='verified__mark' />
|
||||
</span>
|
||||
</a>
|
||||
<a href={proof.get('profile_url')} target='_blank' rel='noopener noreferrer'>
|
||||
<a href={proof.get('profile_url')} target='_blank' rel={DEFAULT_REL}>
|
||||
<span
|
||||
className={_s.dangerousContent}
|
||||
dangerouslySetInnerHTML={{ __html: ' ' + proof.get('provider_username') }}
|
||||
|
||||
@@ -25,6 +25,8 @@ class ProgressPanel extends PureComponent {
|
||||
|
||||
const value = Math.min(parseFloat(monthlyExpensesComplete), 100)
|
||||
|
||||
console.log("monthlyExpensesComplete:", monthlyExpensesComplete)
|
||||
|
||||
return (
|
||||
<PanelLayout
|
||||
title={intl.formatMessage(messages.operationsTitle)}
|
||||
|
||||
@@ -16,7 +16,7 @@ class PillItem extends PureComponent {
|
||||
}
|
||||
|
||||
state = {
|
||||
isCurrent: -1,
|
||||
isCurrent: false,
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps) {
|
||||
@@ -43,8 +43,7 @@ class PillItem extends PureComponent {
|
||||
|
||||
// Combine state, props, location to make absolutely
|
||||
// sure of active status.
|
||||
const active = isActive ||
|
||||
(isCurrent === -1 ? to === location.pathname : false)
|
||||
const active = isActive || to === location.pathname || isCurrent
|
||||
|
||||
const containerClasses = CX({
|
||||
default: 1,
|
||||
|
||||
@@ -16,7 +16,7 @@ import '!style-loader!css-loader!emoji-mart/css/emoji-mart.css'
|
||||
|
||||
const messages = defineMessages({
|
||||
emoji: { id: 'emoji_button.label', defaultMessage: 'Insert emoji' },
|
||||
emoji_search: { id: 'emoji_button.search', defaultMessage: 'Search...' },
|
||||
emoji_search: { id: 'emoji_button.search', defaultMessage: 'Search for emoji' },
|
||||
emoji_not_found: { id: 'emoji_button.not_found', defaultMessage: 'No emojos!! (╯°□°)╯︵ ┻━┻' },
|
||||
custom: { id: 'emoji_button.custom', defaultMessage: 'Custom' },
|
||||
recent: { id: 'emoji_button.recent', defaultMessage: 'Frequently used' },
|
||||
|
||||
@@ -1,12 +1,97 @@
|
||||
import ImmutablePureComponent from 'react-immutable-pure-component'
|
||||
import ImmutablePropTypes from 'react-immutable-proptypes'
|
||||
import { defineMessages, injectIntl } from 'react-intl'
|
||||
import { quoteCompose } from '../../actions/compose'
|
||||
import { repost, unrepost } from '../../actions/interactions'
|
||||
import { openModal } from '../../actions/modal'
|
||||
import { boostModal, me } from '../../initial_state'
|
||||
import PopoverLayout from './popover_layout'
|
||||
import Text from '../text'
|
||||
import List from '../list'
|
||||
|
||||
const messages = defineMessages({
|
||||
repost: { id: 'repost', defaultMessage: 'Repost' },
|
||||
repostWithComment: { id: 'repost_with_comment', defaultMessage: 'Repost with comment' },
|
||||
});
|
||||
|
||||
const mapDispatchToProps = (dispatch, { intl }) => ({
|
||||
onQuote (status, router) {
|
||||
if (!me) return dispatch(openModal('UNAUTHORIZED'))
|
||||
|
||||
dispatch((_, getState) => {
|
||||
const state = getState();
|
||||
if (state.getIn(['compose', 'text']).trim().length !== 0) {
|
||||
dispatch(openModal('CONFIRM', {
|
||||
message: intl.formatMessage(messages.quoteMessage),
|
||||
confirm: intl.formatMessage(messages.quoteConfirm),
|
||||
onConfirm: () => dispatch(quoteCompose(status, router)),
|
||||
}));
|
||||
} else {
|
||||
dispatch(quoteCompose(status, router));
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
onRepost (status) {
|
||||
if (!me) return dispatch(openModal('UNAUTHORIZED'))
|
||||
|
||||
if (status.get('reblogged')) {
|
||||
dispatch(unrepost(status));
|
||||
} else {
|
||||
dispatch(repost(status));
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
export default
|
||||
@injectIntl
|
||||
@connect(null, mapDispatchToProps)
|
||||
class GroupOptionsPopover extends ImmutablePureComponent {
|
||||
|
||||
static defaultProps = {
|
||||
intl: PropTypes.object.isRequired,
|
||||
status: ImmutablePropTypes.map.isRequired,
|
||||
onQuote: PropTypes.func.isRequired,
|
||||
onRepost: PropTypes.func.isRequired,
|
||||
}
|
||||
|
||||
updateOnProps = ['status']
|
||||
|
||||
handleOnRepost = () => {
|
||||
|
||||
}
|
||||
|
||||
handleOnQuote = () => {
|
||||
|
||||
}
|
||||
|
||||
export default class UserInfoPopover extends PureComponent {
|
||||
render() {
|
||||
const { intl } = this.props
|
||||
|
||||
const listItems = [
|
||||
{
|
||||
hideArrow: true,
|
||||
icon: 'repost',
|
||||
title: intl.formatMessage(messages.repost),
|
||||
onClick: this.handleOnRepost,
|
||||
},
|
||||
{
|
||||
hideArrow: true,
|
||||
icon: 'pencil',
|
||||
title: intl.formatMessage(messages.repostWithComment),
|
||||
onClick: this.handleBlockDomain,
|
||||
}
|
||||
]
|
||||
|
||||
return (
|
||||
<PopoverLayout>
|
||||
<Text>testing</Text>
|
||||
<PopoverLayout width={220}>
|
||||
<List
|
||||
scrollKey='repost_options'
|
||||
items={listItems}
|
||||
size='large'
|
||||
/>
|
||||
</PopoverLayout>
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,33 +1,20 @@
|
||||
import detectPassiveEvents from 'detect-passive-events'
|
||||
import ImmutablePropTypes from 'react-immutable-proptypes'
|
||||
import ImmutablePureComponent from 'react-immutable-pure-component'
|
||||
import { Manager, Reference, Popper } from 'react-popper'
|
||||
import classnames from 'classnames/bind'
|
||||
import { openPopover, closePopover } from '../../actions/popover'
|
||||
import { openModal, closeModal } from '../../actions/modal'
|
||||
import { closePopover } from '../../actions/popover'
|
||||
import { CX } from '../../constants'
|
||||
import { isUserTouching } from '../../utils/is_mobile'
|
||||
|
||||
const cx = classnames.bind(_s)
|
||||
|
||||
let id = 0
|
||||
const listenerOptions = detectPassiveEvents.hasSupport ? { passive: true } : false
|
||||
|
||||
const mapStateToProps = (state) => ({
|
||||
isModalOpen: state.getIn(['modal', 'modalType']) === 'ACTIONS',
|
||||
isModalOpen: !!state.getIn(['modal', 'modalType']),
|
||||
popoverPlacement: state.getIn(['popover', 'placement']),
|
||||
openPopoverType: state.getIn(['popover', 'popoverType']),
|
||||
})
|
||||
|
||||
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))
|
||||
},
|
||||
const mapDispatchToProps = (dispatch) => ({
|
||||
onClose: (type) => dispatch(closePopover(type)),
|
||||
})
|
||||
|
||||
export default
|
||||
@@ -44,10 +31,8 @@ class PopoverBase extends ImmutablePureComponent {
|
||||
status: ImmutablePropTypes.map,
|
||||
isUserTouching: PropTypes.func,
|
||||
isModalOpen: PropTypes.bool.isRequired,
|
||||
onOpen: PropTypes.func.isRequired,
|
||||
onClose: PropTypes.func.isRequired,
|
||||
position: PropTypes.string,
|
||||
openPopoverType: PropTypes.string,
|
||||
visible: PropTypes.bool,
|
||||
targetRef: PropTypes.node,
|
||||
innerRef: PropTypes.oneOfType([
|
||||
@@ -61,23 +46,57 @@ class PopoverBase extends ImmutablePureComponent {
|
||||
position: 'bottom',
|
||||
}
|
||||
|
||||
state = {
|
||||
id: id++,
|
||||
componentDidMount() {
|
||||
document.addEventListener('click', this.handleDocumentClick, false)
|
||||
document.addEventListener('keydown', this.handleKeyDown, false)
|
||||
document.addEventListener('touchend', this.handleDocumentClick, listenerOptions)
|
||||
}
|
||||
|
||||
handleClose = () => {
|
||||
this.props.onClose(this.state.id)
|
||||
componentWillUnmount() {
|
||||
document.removeEventListener('click', this.handleDocumentClick, false)
|
||||
document.removeEventListener('keydown', this.handleKeyDown, false)
|
||||
document.removeEventListener('touchend', this.handleDocumentClick, listenerOptions)
|
||||
}
|
||||
|
||||
handleKeyDown = e => {
|
||||
handleDocumentClick = (e) => {
|
||||
const { targetRef, visible, onClose } = this.props
|
||||
|
||||
const containsTargetRef = !targetRef ? false : targetRef.contains(e.target)
|
||||
|
||||
if (this.node && !this.node.contains(e.target) && !containsTargetRef && visible) {
|
||||
onClose()
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
case 'Escape':
|
||||
this.handleClose()
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
handleItemClick = e => {
|
||||
handleItemClick = (e) => {
|
||||
const i = Number(e.currentTarget.getAttribute('data-index'))
|
||||
const { action, to } = this.props.items[i]
|
||||
|
||||
@@ -92,17 +111,16 @@ class PopoverBase extends ImmutablePureComponent {
|
||||
}
|
||||
}
|
||||
|
||||
setTargetRef = c => {
|
||||
this.target = c
|
||||
handleClose = () => {
|
||||
this.props.onClose()
|
||||
}
|
||||
|
||||
findTarget = () => {
|
||||
return this.target
|
||||
}
|
||||
|
||||
componentWillUnmount = () => {
|
||||
if (this.state.id === this.props.openPopoverType) {
|
||||
this.handleClose()
|
||||
setRef = (n) => {
|
||||
try {
|
||||
this.node = n
|
||||
this.props.innerRef = n
|
||||
} catch (error) {
|
||||
//
|
||||
}
|
||||
}
|
||||
|
||||
@@ -111,12 +129,10 @@ class PopoverBase extends ImmutablePureComponent {
|
||||
children,
|
||||
visible,
|
||||
position,
|
||||
openPopoverType,
|
||||
targetRef,
|
||||
innerRef,
|
||||
} = this.props
|
||||
|
||||
const containerClasses = cx({
|
||||
const containerClasses = CX({
|
||||
default: 1,
|
||||
z4: 1,
|
||||
displayNone: !visible,
|
||||
@@ -131,7 +147,7 @@ class PopoverBase extends ImmutablePureComponent {
|
||||
{({ ref, style, placement, arrowProps }) => (
|
||||
<div ref={ref} style={style} data-placement={placement} className={[_s.mt5, _s.mb5, _s.boxShadowPopover].join(' ')}>
|
||||
<div ref={arrowProps.ref} style={arrowProps.style} />
|
||||
<div ref={innerRef} data-popover='true' onKeyDown={this.handleKeyDown} className={containerClasses}>
|
||||
<div ref={this.setRef} data-popover='true' onKeyDown={this.handleKeyDown} className={containerClasses}>
|
||||
{children}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
import detectPassiveEvents from 'detect-passive-events'
|
||||
import { closePopover } from '../../actions/popover'
|
||||
import {
|
||||
POPOVER_CONTENT_WARNING,
|
||||
POPOVER_DATE_PICKER,
|
||||
@@ -31,8 +29,6 @@ import {
|
||||
import Bundle from '../../features/ui/util/bundle'
|
||||
import PopoverBase from './popover_base'
|
||||
|
||||
const listenerOptions = detectPassiveEvents.hasSupport ? { passive: true } : false
|
||||
|
||||
const POPOVER_COMPONENTS = {}
|
||||
POPOVER_COMPONENTS[POPOVER_CONTENT_WARNING] = ContentWarningPopover
|
||||
POPOVER_COMPONENTS[POPOVER_DATE_PICKER] = DatePickerPopover
|
||||
@@ -52,108 +48,23 @@ const mapStateToProps = (state) => ({
|
||||
props: state.getIn(['popover', 'popoverProps'], {}),
|
||||
})
|
||||
|
||||
const mapDispatchToProps = (dispatch) => ({
|
||||
onClose(optionalType) {
|
||||
dispatch(closePopover(optionalType))
|
||||
},
|
||||
})
|
||||
|
||||
export default
|
||||
@connect(mapStateToProps, mapDispatchToProps)
|
||||
@connect(mapStateToProps)
|
||||
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,
|
||||
}
|
||||
|
||||
handleDocumentClick = e => {
|
||||
if (this.node && !this.node.contains(e.target)) {
|
||||
this.props.onClose()
|
||||
getSnapshotBeforeUpdate() {
|
||||
return {
|
||||
visible: !!this.props.type
|
||||
}
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
document.addEventListener('click', this.handleDocumentClick, false)
|
||||
document.addEventListener('keydown', this.handleKeyDown, false)
|
||||
document.addEventListener('touchend', this.handleDocumentClick, listenerOptions)
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
document.removeEventListener('click', this.handleDocumentClick, false)
|
||||
document.removeEventListener('keydown', this.handleKeyDown, false)
|
||||
document.removeEventListener('touchend', this.handleDocumentClick, listenerOptions)
|
||||
}
|
||||
|
||||
setRef = (c) => {
|
||||
this.node = 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 = () => {
|
||||
return null
|
||||
}
|
||||
|
||||
renderLoading = () => {
|
||||
return null
|
||||
renderEmpty = () => {
|
||||
return <div />
|
||||
}
|
||||
|
||||
render() {
|
||||
@@ -170,8 +81,8 @@ class PopoverRoot extends PureComponent {
|
||||
visible &&
|
||||
<Bundle
|
||||
fetchComponent={POPOVER_COMPONENTS[type]}
|
||||
loading={this.renderLoading()}
|
||||
error={this.renderError}
|
||||
loading={this.renderEmpty}
|
||||
error={this.renderEmpty}
|
||||
renderDelay={200}
|
||||
>
|
||||
{
|
||||
|
||||
@@ -23,7 +23,7 @@ import List from '../list'
|
||||
|
||||
const messages = defineMessages({
|
||||
unfollowConfirm: { id: 'confirmations.unfollow.confirm', defaultMessage: 'Unfollow' },
|
||||
blockDomainConfirm: { id: 'confirmations.domain_block.confirm', defaultMessage: 'Hide entire domain' },
|
||||
blockDomainConfirm: { id: 'confirmations.domain_block.confirm', defaultMessage: 'Block entire domain' },
|
||||
blockAndReport: { id: 'confirmations.block.block_and_report', defaultMessage: 'Block & Report' },
|
||||
unfollow: { id: 'account.unfollow', defaultMessage: 'Unfollow' },
|
||||
follow: { id: 'account.follow', defaultMessage: 'Follow' },
|
||||
@@ -39,13 +39,13 @@ const messages = defineMessages({
|
||||
report: { id: 'account.report', defaultMessage: 'Report @{name}' },
|
||||
share: { id: 'account.share', defaultMessage: 'Share @{name}\'s profile' },
|
||||
media: { id: 'account.media', defaultMessage: 'Media' },
|
||||
blockDomain: { id: 'account.block_domain', defaultMessage: 'Hide everything from {domain}' },
|
||||
unblockDomain: { id: 'account.unblock_domain', defaultMessage: 'Unhide {domain}' },
|
||||
blockDomain: { id: 'account.block_domain', defaultMessage: 'Block domain {domain}' },
|
||||
unblockDomain: { id: 'account.unblock_domain', defaultMessage: 'Unblock domain {domain}' },
|
||||
hideReposts: { id: 'account.hide_reblogs', defaultMessage: 'Hide reposts from @{name}' },
|
||||
showReposts: { id: 'account.show_reblogs', defaultMessage: 'Show reposts from @{name}' },
|
||||
preferences: { id: 'navigation_bar.preferences', defaultMessage: 'Preferences' },
|
||||
blocks: { id: 'navigation_bar.blocks', defaultMessage: 'Blocked users' },
|
||||
domain_blocks: { id: 'navigation_bar.domain_blocks', defaultMessage: 'Hidden domains' },
|
||||
domain_blocks: { id: 'navigation_bar.domain_blocks', defaultMessage: 'Blocked domains' },
|
||||
mutes: { id: 'navigation_bar.mutes', defaultMessage: 'Muted users' },
|
||||
admin_account: { id: 'admin_account', defaultMessage: 'Open moderation interface' },
|
||||
add_or_remove_from_list: { id: 'account.add_or_remove_from_list', defaultMessage: 'Add or Remove from lists' },
|
||||
|
||||
@@ -1,94 +1,121 @@
|
||||
import ImmutablePureComponent from 'react-immutable-pure-component'
|
||||
import ImmutablePropTypes from 'react-immutable-proptypes'
|
||||
import { defineMessages, injectIntl } from 'react-intl'
|
||||
import {
|
||||
MODAL_BOOST,
|
||||
MODAL_CONFIRM,
|
||||
MODAL_UNAUTHORIZED,
|
||||
} from '../../constants'
|
||||
import { boostModal, me } from '../../initial_state'
|
||||
import { quoteCompose } from '../../actions/compose'
|
||||
import { repost, unrepost } from '../../actions/interactions'
|
||||
import { closePopover } from '../../actions/popover'
|
||||
import { openModal } from '../../actions/modal'
|
||||
import { boostModal, me } from '../../initial_state'
|
||||
import PopoverLayout from './popover_layout'
|
||||
import List from '../list'
|
||||
|
||||
const messages = defineMessages({
|
||||
repost: { id: 'repost', defaultMessage: 'Repost' },
|
||||
removeRepost: { id: 'status.cancel_repost_private', defaultMessage: 'Remove Repost' },
|
||||
repostWithComment: { id: 'repost_with_comment', defaultMessage: 'Repost with comment' },
|
||||
});
|
||||
quoteMessage: { id: 'confirmations.quote.message', defaultMessage: 'Quoting now will overwrite the message you are currently composing. Are you sure you want to proceed?' },
|
||||
quoteConfirm: { id: 'confirmations.quote.confirm', defaultMessage: 'Quote' },
|
||||
})
|
||||
|
||||
const mapDispatchToProps = (dispatch, { intl }) => ({
|
||||
onQuote (status, router) {
|
||||
if (!me) return dispatch(openModal('UNAUTHORIZED'))
|
||||
|
||||
if (!me) return dispatch(openModal(MODAL_UNAUTHORIZED))
|
||||
|
||||
dispatch(closePopover())
|
||||
|
||||
dispatch((_, getState) => {
|
||||
const state = getState();
|
||||
const state = getState()
|
||||
if (state.getIn(['compose', 'text']).trim().length !== 0) {
|
||||
dispatch(openModal('CONFIRM', {
|
||||
dispatch(openModal(MODAL_CONFIRM, {
|
||||
message: intl.formatMessage(messages.quoteMessage),
|
||||
confirm: intl.formatMessage(messages.quoteConfirm),
|
||||
onConfirm: () => dispatch(quoteCompose(status, router)),
|
||||
}));
|
||||
}))
|
||||
} else {
|
||||
dispatch(quoteCompose(status, router));
|
||||
dispatch(quoteCompose(status, router))
|
||||
}
|
||||
});
|
||||
})
|
||||
},
|
||||
|
||||
onRepost (status) {
|
||||
if (!me) return dispatch(openModal('UNAUTHORIZED'))
|
||||
if (!me) return dispatch(openModal(MODAL_UNAUTHORIZED))
|
||||
|
||||
if (status.get('reblogged')) {
|
||||
dispatch(unrepost(status));
|
||||
dispatch(closePopover())
|
||||
|
||||
const alreadyReposted = status.get('reblogged')
|
||||
|
||||
if (boostModal && !alreadyReposted) {
|
||||
dispatch(openModal(MODAL_BOOST, {
|
||||
status,
|
||||
onRepost: () => dispatch(repost(status)),
|
||||
}))
|
||||
} else {
|
||||
dispatch(repost(status));
|
||||
if (alreadyReposted) {
|
||||
dispatch(unrepost(status))
|
||||
} else {
|
||||
dispatch(repost(status))
|
||||
}
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
})
|
||||
|
||||
export default
|
||||
@injectIntl
|
||||
@connect(null, mapDispatchToProps)
|
||||
class RepostOptionsPopover extends ImmutablePureComponent {
|
||||
|
||||
static contextTypes = {
|
||||
router: PropTypes.object,
|
||||
}
|
||||
|
||||
static defaultProps = {
|
||||
intl: PropTypes.object.isRequired,
|
||||
status: ImmutablePropTypes.map.isRequired,
|
||||
onQuote: PropTypes.func.isRequired,
|
||||
onRepost: PropTypes.func.isRequired,
|
||||
status: ImmutablePropTypes.map.isRequired,
|
||||
}
|
||||
|
||||
updateOnProps = ['status']
|
||||
|
||||
handleOnRepost = () => {
|
||||
|
||||
}
|
||||
updateOnProps = [
|
||||
'status',
|
||||
]
|
||||
|
||||
handleOnQuote = () => {
|
||||
|
||||
this.props.onQuote(this.props.status, this.context.router.history)
|
||||
}
|
||||
|
||||
handleOnRepost = () => {
|
||||
this.props.onRepost(this.props.status)
|
||||
}
|
||||
|
||||
render() {
|
||||
const { intl } = this.props
|
||||
const { intl, status } = this.props
|
||||
|
||||
const listItems = [
|
||||
{
|
||||
hideArrow: true,
|
||||
icon: 'repost',
|
||||
title: intl.formatMessage(messages.repost),
|
||||
onClick: this.handleOnRepost,
|
||||
},
|
||||
{
|
||||
hideArrow: true,
|
||||
icon: 'pencil',
|
||||
title: intl.formatMessage(messages.repostWithComment),
|
||||
onClick: this.handleBlockDomain,
|
||||
}
|
||||
]
|
||||
const alreadyReposted = status.get('reblogged')
|
||||
|
||||
return (
|
||||
<PopoverLayout width={220}>
|
||||
<List
|
||||
scrollKey='repost_options'
|
||||
items={listItems}
|
||||
size='large'
|
||||
items={[
|
||||
{
|
||||
hideArrow: true,
|
||||
icon: 'repost',
|
||||
title: intl.formatMessage(!alreadyReposted ? messages.repost : messages.removeRepost),
|
||||
onClick: this.handleOnRepost,
|
||||
},
|
||||
{
|
||||
hideArrow: true,
|
||||
icon: 'pencil',
|
||||
title: intl.formatMessage(messages.repostWithComment),
|
||||
onClick: this.handleOnQuote,
|
||||
}
|
||||
]}
|
||||
/>
|
||||
</PopoverLayout>
|
||||
)
|
||||
|
||||
@@ -8,18 +8,16 @@ import List from '../list'
|
||||
const messages = defineMessages({
|
||||
delete: { id: 'status.delete', defaultMessage: 'Delete' },
|
||||
edit: { id: 'status.edit', defaultMessage: 'Edit' },
|
||||
mention: { id: 'status.mention', defaultMessage: 'Mention @{name}' },
|
||||
mute: { id: 'account.mute', defaultMessage: 'Mute @{name}' },
|
||||
block: { id: 'account.block', defaultMessage: 'Block @{name}' },
|
||||
reply: { id: 'status.reply', defaultMessage: 'Reply' },
|
||||
comment: { id: 'status.comment', defaultMessage: 'Comment' },
|
||||
more: { id: 'status.more', defaultMessage: 'More' },
|
||||
share: { id: 'status.share', defaultMessage: 'Share' },
|
||||
replyAll: { id: 'status.replyAll', defaultMessage: 'Reply to thread' },
|
||||
repost: { id: 'repost', defaultMessage: 'Repost' },
|
||||
quote: { id: 'status.quote', defaultMessage: 'Quote' },
|
||||
repost_private: { id: 'status.repost_private', defaultMessage: 'Repost to original audience' },
|
||||
cancel_repost_private: { id: 'status.cancel_repost_private', defaultMessage: 'Un-repost' },
|
||||
cancel_repost_private: { id: 'status.cancel_repost_private', defaultMessage: 'Remove Repost' },
|
||||
cannot_repost: { id: 'status.cannot_repost', defaultMessage: 'This post cannot be reposted' },
|
||||
cannot_quote: { id: 'status.cannot_quote', defaultMessage: 'This post cannot be quoted' },
|
||||
like: { id: 'status.like', defaultMessage: 'Like' },
|
||||
@@ -34,9 +32,19 @@ const messages = defineMessages({
|
||||
group_remove_post: { id: 'status.remove_post_from_group', defaultMessage: 'Remove status from group' },
|
||||
})
|
||||
|
||||
const mapStateToProps = (state) => ({
|
||||
|
||||
})
|
||||
|
||||
const mapDispatchToProps = (dispatch) => ({
|
||||
|
||||
})
|
||||
|
||||
export default
|
||||
@injectIntl
|
||||
@connect(mapStateToProps, mapDispatchToProps)
|
||||
class StatusOptionsPopover extends ImmutablePureComponent {
|
||||
|
||||
static propTypes = {
|
||||
status: ImmutablePropTypes.map.isRequired,
|
||||
account: ImmutablePropTypes.map.isRequired,
|
||||
@@ -56,7 +64,10 @@ class StatusOptionsPopover extends ImmutablePureComponent {
|
||||
intl: PropTypes.object.isRequired,
|
||||
}
|
||||
|
||||
updateOnProps = ['status', 'account']
|
||||
updateOnProps = [
|
||||
'status',
|
||||
'account',
|
||||
]
|
||||
|
||||
handleConversationMuteClick = () => {
|
||||
this.props.onMuteConversation(this.props.status);
|
||||
@@ -111,7 +122,7 @@ class StatusOptionsPopover extends ImmutablePureComponent {
|
||||
}
|
||||
}
|
||||
|
||||
getItems = () => {
|
||||
render() {
|
||||
const {
|
||||
status,
|
||||
intl,
|
||||
@@ -123,114 +134,104 @@ class StatusOptionsPopover extends ImmutablePureComponent {
|
||||
|
||||
let menu = [];
|
||||
|
||||
if (!me) return menu
|
||||
if (me) {
|
||||
// if (status.getIn(['account', 'id']) === me) {
|
||||
menu.push({
|
||||
icon: 'audio-mute',
|
||||
hideArrow: true,
|
||||
title: intl.formatMessage(mutingConversation ? messages.unmuteConversation : messages.muteConversation),
|
||||
onClick: this.handleConversationMuteClick,
|
||||
})
|
||||
// }
|
||||
|
||||
if (status.getIn(['account', 'id']) === me) {
|
||||
menu.push({
|
||||
icon: 'mute',
|
||||
hideArrow: true,
|
||||
title: intl.formatMessage(mutingConversation ? messages.unmuteConversation : messages.muteConversation),
|
||||
onClick: this.handleConversationMuteClick,
|
||||
})
|
||||
}
|
||||
|
||||
if (status.getIn(['account', 'id']) === me) {
|
||||
if (publicStatus) {
|
||||
// if (status.getIn(['account', 'id']) === me) {
|
||||
// if (publicStatus) {
|
||||
menu.push({
|
||||
icon: 'pin',
|
||||
hideArrow: true,
|
||||
title: intl.formatMessage(status.get('pinned') ? messages.unpin : messages.pin),
|
||||
onClick: this.handlePinClick,
|
||||
})
|
||||
// } else {
|
||||
// if (status.get('visibility') === 'private') {
|
||||
menu.push({
|
||||
icon: 'repost',
|
||||
hideArrow: true,
|
||||
title: intl.formatMessage(status.get('reblogged') ? messages.cancel_repost_private : messages.repost_private),
|
||||
onClick: this.handleRepostClick
|
||||
})
|
||||
// }
|
||||
// }
|
||||
menu.push({
|
||||
icon: 'trash',
|
||||
hideArrow: true,
|
||||
title: intl.formatMessage(messages.delete),
|
||||
action: this.handleDeleteClick
|
||||
});
|
||||
menu.push({
|
||||
icon: 'pencil',
|
||||
hideArrow: true,
|
||||
title: intl.formatMessage(messages.edit), action:
|
||||
this.handleEditClick
|
||||
});
|
||||
// } else {
|
||||
menu.push({
|
||||
icon: 'audio-mute',
|
||||
hideArrow: true,
|
||||
title: intl.formatMessage(messages.mute, { name: status.getIn(['account', 'username']) }),
|
||||
action: this.handleMuteClick
|
||||
});
|
||||
menu.push({
|
||||
icon: 'circle',
|
||||
hideArrow: true,
|
||||
title: intl.formatMessage(status.get('pinned') ? messages.unpin : messages.pin),
|
||||
onClick: this.handlePinClick,
|
||||
})
|
||||
} else {
|
||||
if (status.get('visibility') === 'private') {
|
||||
title: intl.formatMessage(messages.block, { name: status.getIn(['account', 'username']) }),
|
||||
action: this.handleBlockClick
|
||||
});
|
||||
menu.push({
|
||||
icon: 'circle',
|
||||
hideArrow: true,
|
||||
title: intl.formatMessage(messages.report, { name: status.getIn(['account', 'username']) }),
|
||||
action: this.handleReport
|
||||
});
|
||||
|
||||
// : todo :
|
||||
// if (withGroupAdmin) {
|
||||
menu.push({
|
||||
icon: 'circle',
|
||||
hideArrow: true,
|
||||
title: intl.formatMessage(status.get('reblogged') ? messages.cancel_repost_private : messages.repost_private),
|
||||
onClick: this.handleRepostClick
|
||||
})
|
||||
}
|
||||
}
|
||||
menu.push({
|
||||
icon: 'circle',
|
||||
hideArrow: true,
|
||||
title: intl.formatMessage(messages.delete),
|
||||
action: this.handleDeleteClick
|
||||
});
|
||||
menu.push({
|
||||
icon: 'circle',
|
||||
hideArrow: true,
|
||||
title: intl.formatMessage(messages.edit), action:
|
||||
this.handleEditClick
|
||||
});
|
||||
} else {
|
||||
menu.push({
|
||||
icon: 'comment',
|
||||
hideArrow: true,
|
||||
title: intl.formatMessage(messages.mention, { name: status.getIn(['account', 'username']) }),
|
||||
action: this.handleMentionClick
|
||||
});
|
||||
menu.push({
|
||||
icon: 'mute',
|
||||
hideArrow: true,
|
||||
title: intl.formatMessage(messages.mute, { name: status.getIn(['account', 'username']) }),
|
||||
action: this.handleMuteClick
|
||||
});
|
||||
menu.push({
|
||||
icon: 'circle',
|
||||
hideArrow: true,
|
||||
title: intl.formatMessage(messages.block, { name: status.getIn(['account', 'username']) }),
|
||||
action: this.handleBlockClick
|
||||
});
|
||||
menu.push({
|
||||
icon: 'circle',
|
||||
hideArrow: true,
|
||||
title: intl.formatMessage(messages.report, { name: status.getIn(['account', 'username']) }),
|
||||
action: this.handleReport
|
||||
});
|
||||
title: intl.formatMessage(messages.group_remove_account),
|
||||
action: this.handleGroupRemoveAccount
|
||||
});
|
||||
menu.push({
|
||||
icon: 'circle',
|
||||
hideArrow: true,
|
||||
title: intl.formatMessage(messages.group_remove_post),
|
||||
action: this.handleGroupRemovePost
|
||||
});
|
||||
// }
|
||||
|
||||
// if (withGroupAdmin) {
|
||||
// menu.push({
|
||||
// icon: 'circle',
|
||||
// hideArrow: true,
|
||||
// title: intl.formatMessage(messages.group_remove_account),
|
||||
// action: this.handleGroupRemoveAccount
|
||||
// });
|
||||
// menu.push({
|
||||
// icon: 'circle',
|
||||
// hideArrow: true,
|
||||
// title: intl.formatMessage(messages.group_remove_post),
|
||||
// action: this.handleGroupRemovePost
|
||||
// });
|
||||
// if (isStaff) {
|
||||
menu.push({
|
||||
title: intl.formatMessage(messages.admin_account, { name: status.getIn(['account', 'username']) }),
|
||||
href: `/admin/accounts/${status.getIn(['account', 'id'])}`
|
||||
});
|
||||
menu.push({
|
||||
title: intl.formatMessage(messages.admin_status),
|
||||
href: `/admin/accounts/${status.getIn(['account', 'id'])}/statuses/${status.get('id')}`
|
||||
});
|
||||
// }
|
||||
// }
|
||||
|
||||
if (isStaff) {
|
||||
menu.push({
|
||||
title: intl.formatMessage(messages.admin_account, { name: status.getIn(['account', 'username']) }),
|
||||
href: `/admin/accounts/${status.getIn(['account', 'id'])}`
|
||||
});
|
||||
menu.push({
|
||||
title: intl.formatMessage(messages.admin_status),
|
||||
href: `/admin/accounts/${status.getIn(['account', 'id'])}/statuses/${status.get('id')}`
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return menu;
|
||||
}
|
||||
|
||||
render() {
|
||||
const items = this.getItems()
|
||||
|
||||
return (
|
||||
<PopoverLayout className={_s.width240PX}>
|
||||
<List
|
||||
size='large'
|
||||
scrollKey='profile_options'
|
||||
items={items}
|
||||
items={menu}
|
||||
/>
|
||||
</PopoverLayout>
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,65 +1,71 @@
|
||||
import ImmutablePropTypes from 'react-immutable-proptypes'
|
||||
import ImmutablePureComponent from 'react-immutable-pure-component'
|
||||
import { defineMessages, injectIntl } from 'react-intl'
|
||||
import { closePopover } from '../../actions/popover'
|
||||
import { openModal } from '../../actions/modal'
|
||||
import {
|
||||
MODAL_EMBED,
|
||||
POPOVER_STATUS_SHARE,
|
||||
} from '../../constants'
|
||||
import PopoverLayout from './popover_layout'
|
||||
import List from '../list'
|
||||
|
||||
const messages = defineMessages({
|
||||
embed: { id: 'status.embed', defaultMessage: 'Embed gab' },
|
||||
email: { id: 'status.email', defaultMessage: 'Email gab' },
|
||||
copy: { id: 'status.copy', defaultMessage: 'Copy link to gab' },
|
||||
embed: { id: 'status.embed', defaultMessage: 'Embed' },
|
||||
email: { id: 'status.email', defaultMessage: 'Email this gab' },
|
||||
copy: { id: 'status.copy', defaultMessage: 'Copy link to status' },
|
||||
});
|
||||
|
||||
// const makeMapStateToProps = () => {
|
||||
// const getAccount = makeGetAccount();
|
||||
|
||||
// const mapStateToProps = (state, { account }) => ({
|
||||
|
||||
// });
|
||||
|
||||
// return mapStateToProps;
|
||||
// };
|
||||
|
||||
// const mapDispatchToProps = (dispatch, { intl }) => ({
|
||||
|
||||
// });
|
||||
|
||||
// : todo :
|
||||
const mapDispatchToProps = (dispatch) => ({
|
||||
onClosePopover: () => dispatch(closePopover(POPOVER_STATUS_SHARE)),
|
||||
onOpenEmbedModal(url) {
|
||||
dispatch(openModal(MODAL_EMBED, {
|
||||
url,
|
||||
}))
|
||||
},
|
||||
});
|
||||
|
||||
export default
|
||||
@injectIntl
|
||||
// @connect(makeMapStateToProps, mapDispatchToProps)
|
||||
@connect(null, mapDispatchToProps)
|
||||
class StatusSharePopover extends ImmutablePureComponent {
|
||||
|
||||
static propTypes = {
|
||||
status: ImmutablePropTypes.map,
|
||||
intl: PropTypes.object.isRequired,
|
||||
onClosePopover: PropTypes.func.isRequired,
|
||||
onOpenEmbedModal: PropTypes.func.isRequired,
|
||||
}
|
||||
|
||||
handleEmbed = () => {
|
||||
// this.props.onEmbed(this.props.status);
|
||||
handleOnOpenEmbedModal = () => {
|
||||
this.props.onOpenEmbedModal(this.props.status.get('url'))
|
||||
this.props.onClosePopover()
|
||||
}
|
||||
|
||||
handleCopy = () => {
|
||||
// const url = this.props.status.get('url');
|
||||
// const textarea = document.createElement('textarea');
|
||||
const url = this.props.status.get('url');
|
||||
const textarea = document.createElement('textarea');
|
||||
|
||||
// textarea.textContent = url;
|
||||
// textarea.style.position = 'fixed';
|
||||
textarea.textContent = url;
|
||||
textarea.style.position = 'fixed';
|
||||
|
||||
// document.body.appendChild(textarea);
|
||||
document.body.appendChild(textarea);
|
||||
|
||||
// try {
|
||||
// textarea.select();
|
||||
// document.execCommand('copy');
|
||||
// } catch (e) {
|
||||
// //
|
||||
// } finally {
|
||||
// document.body.removeChild(textarea);
|
||||
// }
|
||||
try {
|
||||
textarea.select();
|
||||
document.execCommand('copy');
|
||||
} catch (e) {
|
||||
//
|
||||
}
|
||||
|
||||
document.body.removeChild(textarea);
|
||||
this.props.onClosePopover()
|
||||
}
|
||||
|
||||
render() {
|
||||
const { intl } = this.props
|
||||
const { intl, status } = this.props
|
||||
|
||||
const mailToHref = !status ? undefined : `mailto:?subject=&body=${status.get('url')}`
|
||||
|
||||
return (
|
||||
<PopoverLayout width={220}>
|
||||
@@ -77,13 +83,13 @@ class StatusSharePopover extends ImmutablePureComponent {
|
||||
icon: 'email',
|
||||
hideArrow: true,
|
||||
title: intl.formatMessage(messages.email),
|
||||
href: 'mailto:',
|
||||
href: mailToHref,
|
||||
},
|
||||
{
|
||||
icon: 'code',
|
||||
hideArrow: true,
|
||||
title: intl.formatMessage(messages.embed),
|
||||
onClick: this.handleEmbed,
|
||||
onClick: this.handleOnOpenEmbedModal,
|
||||
},
|
||||
]}
|
||||
small
|
||||
|
||||
@@ -10,11 +10,11 @@ const cx = classNames.bind(_s)
|
||||
|
||||
const messages = defineMessages({
|
||||
public_short: { id: 'privacy.public.short', defaultMessage: 'Public' },
|
||||
public_long: { id: 'privacy.public.long', defaultMessage: 'Post to public timelines' },
|
||||
public_long: { id: 'privacy.public.long', defaultMessage: 'Visible for anyone on or off Gab' },
|
||||
unlisted_short: { id: 'privacy.unlisted.short', defaultMessage: 'Unlisted' },
|
||||
unlisted_long: { id: 'privacy.unlisted.long', defaultMessage: 'Do not show in public timelines' },
|
||||
private_short: { id: 'privacy.private.short', defaultMessage: 'Followers-only' },
|
||||
private_long: { id: 'privacy.private.long', defaultMessage: 'Post to followers only' },
|
||||
private_long: { id: 'privacy.private.long', defaultMessage: 'Visible for your followers only' },
|
||||
change_privacy: { id: 'privacy.change', defaultMessage: 'Adjust status privacy' },
|
||||
visibility: { id: 'privacy.visibility', defaultMessage: 'Visibility' },
|
||||
})
|
||||
@@ -43,14 +43,14 @@ class StatusVisibilityDropdown extends PureComponent {
|
||||
intl: PropTypes.object.isRequired,
|
||||
}
|
||||
|
||||
handleChange = value => {
|
||||
handleChange = (value) => {
|
||||
this.props.onChange(value)
|
||||
}
|
||||
|
||||
componentDidMount () {
|
||||
const { intl } = this.props
|
||||
render () {
|
||||
const { intl, value } = this.props
|
||||
|
||||
this.options = [
|
||||
const options = [
|
||||
{
|
||||
icon: 'globe',
|
||||
value: 'public',
|
||||
@@ -70,18 +70,14 @@ class StatusVisibilityDropdown extends PureComponent {
|
||||
subtitle: intl.formatMessage(messages.private_long)
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
render () {
|
||||
const { value } = this.props
|
||||
|
||||
return (
|
||||
<PopoverLayout className={_s.width240PX}>
|
||||
<PopoverLayout width={300}>
|
||||
<div className={[_s.default].join(' ')}>
|
||||
{
|
||||
this.options.map((option, i) => {
|
||||
options.map((option, i) => {
|
||||
const isActive = option.value === value
|
||||
const isLast = i === this.options.length - 1
|
||||
const isLast = i === options.length - 1
|
||||
|
||||
const containerClasses = cx({
|
||||
default: 1,
|
||||
|
||||
@@ -5,7 +5,10 @@ import Text from './text'
|
||||
export default class ProgressBar extends PureComponent {
|
||||
|
||||
static propTypes = {
|
||||
progress: PropTypes.number,
|
||||
progress: PropTypes.oneOfType([
|
||||
PropTypes.number,
|
||||
PropTypes.string,
|
||||
]).isRequired,
|
||||
small: PropTypes.bool,
|
||||
title: PropTypes.string,
|
||||
href: PropTypes.string,
|
||||
|
||||
@@ -75,10 +75,10 @@ class Search extends PureComponent {
|
||||
const { value } = this.props
|
||||
|
||||
if (e.key === 'Enter') {
|
||||
e.preventDefault();
|
||||
e.preventDefault()
|
||||
|
||||
this.props.onSubmit();
|
||||
this.context.router.history.push(`/search?q=${value}`);
|
||||
this.props.onSubmit()
|
||||
this.context.router.history.push(`/search?q=${value}`)
|
||||
|
||||
} else if (e.key === 'Escape') {
|
||||
this.textbox.blur()
|
||||
|
||||
@@ -22,7 +22,7 @@ const messages = defineMessages({
|
||||
preferences: { id: 'navigation_bar.preferences', defaultMessage: 'Preferences' },
|
||||
follow_requests: { id: 'navigation_bar.follow_requests', defaultMessage: 'Follow requests' },
|
||||
blocks: { id: 'navigation_bar.blocks', defaultMessage: 'Blocked users' },
|
||||
domain_blocks: { id: 'navigation_bar.domain_blocks', defaultMessage: 'Hidden domains' },
|
||||
domain_blocks: { id: 'navigation_bar.domain_blocks', defaultMessage: 'Blocked domains' },
|
||||
mutes: { id: 'navigation_bar.mutes', defaultMessage: 'Muted users' },
|
||||
filters: { id: 'navigation_bar.filters', defaultMessage: 'Muted words' },
|
||||
logout: { id: 'navigation_bar.logout', defaultMessage: 'Logout' },
|
||||
@@ -211,11 +211,14 @@ class Sidebar extends ImmutablePureComponent {
|
||||
<div className={[_s.default, _s.posFixed, _s.heightCalc53PX, _s.bottom0].join(' ')}>
|
||||
<div className={[_s.default, _s.height100PC, _s.alignItemsStart, _s.width240PX, _s.pr15, _s.py10, _s.overflowYScroll].join(' ')}>
|
||||
<div className={_s.default}>
|
||||
<div className={[_s.default, _s.px5, _s.py10].join(' ')}>
|
||||
<Heading size='h1'>
|
||||
{title}
|
||||
</Heading>
|
||||
</div>
|
||||
{
|
||||
!!title &&
|
||||
<div className={[_s.default, _s.px5, _s.py10].join(' ')}>
|
||||
<Heading size='h1'>
|
||||
{title}
|
||||
</Heading>
|
||||
</div>
|
||||
}
|
||||
{
|
||||
!!tabs &&
|
||||
<div className={[_s.default, _s.mt10, _s.pb15, _s.borderBottom1PX, _s.borderColorSecondary].join(' ')}>
|
||||
|
||||
@@ -51,8 +51,9 @@ export default class SidebarSectionItem extends PureComponent {
|
||||
} = this.props
|
||||
const { hovering } = this.state
|
||||
|
||||
const noRouter = !this.context.router
|
||||
const iconSize = '16px'
|
||||
const currentPathname = this.context.router ? this.context.router.route.location.pathname : undefined
|
||||
const currentPathname = noRouter ? '' : this.context.router.route.location.pathname
|
||||
const shouldShowActive = hovering || active || currentPathname === to || currentPathname === href
|
||||
const isNotifications = to === '/notifications'
|
||||
|
||||
@@ -103,8 +104,8 @@ export default class SidebarSectionItem extends PureComponent {
|
||||
|
||||
return (
|
||||
<Button
|
||||
to={to}
|
||||
href={href}
|
||||
to={noRouter ? undefined : to}
|
||||
href={noRouter ? (to || href) : href}
|
||||
onClick={onClick}
|
||||
noClasses
|
||||
buttonRef={buttonRef}
|
||||
|
||||
@@ -3,7 +3,7 @@ import { injectIntl } from 'react-intl'
|
||||
import ImmutablePureComponent from 'react-immutable-pure-component'
|
||||
import { HotKeys } from 'react-hotkeys'
|
||||
import classNames from 'classnames/bind'
|
||||
import { me, displayMedia } from '../initial_state'
|
||||
import { me, displayMedia, compactMode } from '../initial_state'
|
||||
import StatusCard from './status_card'
|
||||
import { MediaGallery, Video } from '../features/ui/util/async_components'
|
||||
import ComposeFormContainer from '../features/compose/containers/compose_form_container'
|
||||
@@ -44,8 +44,6 @@ export const textForScreenReader = (intl, status, rebloggedByText = false) => {
|
||||
export const defaultMediaVisibility = (status) => {
|
||||
if (!status) return undefined
|
||||
|
||||
// console.log("status:", status)
|
||||
|
||||
if (status.get('reblog', null) !== null && typeof status.get('reblog') === 'object') {
|
||||
status = status.get('reblog')
|
||||
}
|
||||
@@ -337,7 +335,6 @@ class Status extends ImmutablePureComponent {
|
||||
render() {
|
||||
const {
|
||||
intl,
|
||||
status,
|
||||
isFeatured,
|
||||
isPromoted,
|
||||
isChild,
|
||||
@@ -345,6 +342,8 @@ class Status extends ImmutablePureComponent {
|
||||
descendantsIds,
|
||||
commentsLimited,
|
||||
} = this.props
|
||||
|
||||
let { status } = this.props
|
||||
|
||||
if (!status) return null
|
||||
|
||||
@@ -356,6 +355,7 @@ class Status extends ImmutablePureComponent {
|
||||
{ name: status.getIn(['account', 'acct']) }
|
||||
)
|
||||
reblogContent = status.get('contentHtml')
|
||||
status = status.get('reblog')
|
||||
}
|
||||
|
||||
const handlers = (this.props.isMuted || isChild) ? {} : {
|
||||
@@ -445,14 +445,15 @@ class Status extends ImmutablePureComponent {
|
||||
const containerClasses = cx({
|
||||
default: 1,
|
||||
pb15: isFeatured,
|
||||
radiusSmall: !isChild,
|
||||
radiusSmall: !isChild && !compactMode,
|
||||
bgPrimary: !isChild,
|
||||
boxShadowBlock: !isChild,
|
||||
boxShadowBlock: !isChild && !compactMode,
|
||||
outlineNone: 1,
|
||||
mb15: !isChild,
|
||||
// border1PX: !isChild,
|
||||
// borderBottom1PX: isFeatured && !isChild,
|
||||
// borderColorSecondary: !isChild,
|
||||
mb15: !isChild && !compactMode,
|
||||
borderRight1PX: !isChild && compactMode,
|
||||
borderLeft1PX: !isChild && compactMode,
|
||||
borderBottom1PX: !isChild && compactMode,
|
||||
borderColorSecondary: !isChild && compactMode,
|
||||
})
|
||||
|
||||
const innerContainerClasses = cx({
|
||||
@@ -481,7 +482,7 @@ class Status extends ImmutablePureComponent {
|
||||
|
||||
<div data-id={status.get('id')}>
|
||||
|
||||
<StatusPrepend status={status} isPromoted={isPromoted} isFeatured={isFeatured} />
|
||||
<StatusPrepend status={this.props.status} isPromoted={isPromoted} isFeatured={isFeatured} />
|
||||
|
||||
<StatusHeader status={status} reduced={isChild} />
|
||||
|
||||
@@ -517,19 +518,19 @@ class Status extends ImmutablePureComponent {
|
||||
}
|
||||
|
||||
{
|
||||
!isChild && !!me &&
|
||||
!isChild && !compactMode && !!me &&
|
||||
<div className={[_s.default, _s.borderTop1PX, _s.borderColorSecondary, _s.pt10, _s.px15, _s.mb10].join(' ')}>
|
||||
<ComposeFormContainer replyToId={status.get('id')} shouldCondense />
|
||||
</div>
|
||||
}
|
||||
|
||||
{
|
||||
descendantsIds && !isChild && descendantsIds.size > 0 &&
|
||||
descendantsIds && !compactMode && !isChild && descendantsIds.size > 0 &&
|
||||
<div className={[_s.default, _s.mr10, _s.ml10, _s.mb10, _s.borderColorSecondary, _s.borderBottom1PX].join(' ')}/>
|
||||
}
|
||||
|
||||
{
|
||||
descendantsIds && !isChild && descendantsIds.size > 0 &&
|
||||
descendantsIds && !compactMode && !isChild && descendantsIds.size > 0 &&
|
||||
<CommentList
|
||||
commentsLimited={commentsLimited}
|
||||
descendants={descendantsIds}
|
||||
|
||||
@@ -2,6 +2,7 @@ import ImmutablePropTypes from 'react-immutable-proptypes'
|
||||
import ImmutablePureComponent from 'react-immutable-pure-component'
|
||||
import { defineMessages, injectIntl } from 'react-intl'
|
||||
import { NavLink } from 'react-router-dom'
|
||||
import { compactMode } from '../initial_state'
|
||||
import Text from './text'
|
||||
import StatusActionBarItem from './status_action_bar_item'
|
||||
import {
|
||||
@@ -21,6 +22,10 @@ const messages = defineMessages({
|
||||
commentsLabel: { id: 'comments.label', defaultMessage: '{number, plural, one {# comment} other {# comments}}' },
|
||||
})
|
||||
|
||||
const NOU = (num) => {
|
||||
return num <= 0 ? undefined : num
|
||||
}
|
||||
|
||||
export default
|
||||
@injectIntl
|
||||
class StatusActionBar extends ImmutablePureComponent {
|
||||
@@ -102,9 +107,9 @@ class StatusActionBar extends ImmutablePureComponent {
|
||||
py2: 1,
|
||||
flexRow: 1,
|
||||
width100PC: 1,
|
||||
borderTop1PX: !shouldCondense,
|
||||
borderColorSecondary: !shouldCondense,
|
||||
mt5: hasInteractions,
|
||||
borderTop1PX: !shouldCondense && !compactMode,
|
||||
borderColorSecondary: !shouldCondense && !compactMode,
|
||||
mt5: hasInteractions && !compactMode,
|
||||
})
|
||||
|
||||
const interactionBtnClasses = CX({
|
||||
@@ -122,7 +127,7 @@ class StatusActionBar extends ImmutablePureComponent {
|
||||
return (
|
||||
<div className={containerClasses}>
|
||||
{
|
||||
hasInteractions &&
|
||||
hasInteractions && !compactMode &&
|
||||
<div className={[_s.default, _s.flexRow, _s.alignItemsEnd, _s.px5].join(' ')}>
|
||||
{
|
||||
favoriteCount > 0 &&
|
||||
@@ -162,18 +167,18 @@ class StatusActionBar extends ImmutablePureComponent {
|
||||
<div className={innerContainerClasses}>
|
||||
<div className={[_s.default, _s.flexRow, _s.py2, _s.width100PC].join(' ')}>
|
||||
<StatusActionBarItem
|
||||
title={intl.formatMessage(messages.like)}
|
||||
title={compactMode ? NOU(favoriteCount) : intl.formatMessage(messages.like)}
|
||||
icon={!!status.get('favourited') ? 'liked' : 'like'}
|
||||
active={!!status.get('favourited')}
|
||||
onClick={this.handleFavoriteClick}
|
||||
/>
|
||||
<StatusActionBarItem
|
||||
title={intl.formatMessage(messages.comment)}
|
||||
title={compactMode ? NOU(replyCount) : intl.formatMessage(messages.comment)}
|
||||
icon='comment'
|
||||
onClick={this.handleReplyClick}
|
||||
/>
|
||||
<StatusActionBarItem
|
||||
title={intl.formatMessage(messages.repost)}
|
||||
title={compactMode ? NOU(repostCount) : intl.formatMessage(messages.repost)}
|
||||
altTitle={!publicStatus ? intl.formatMessage(messages.cannot_repost) : ''}
|
||||
icon={!publicStatus ? 'lock' : 'repost'}
|
||||
disabled={!publicStatus}
|
||||
@@ -184,7 +189,7 @@ class StatusActionBar extends ImmutablePureComponent {
|
||||
<Responsive min={BREAKPOINT_EXTRA_SMALL}>
|
||||
<StatusActionBarItem
|
||||
buttonRef={this.setShareButton}
|
||||
title={intl.formatMessage(messages.share)}
|
||||
title={compactMode ? '' : intl.formatMessage(messages.share)}
|
||||
icon='share'
|
||||
onClick={this.handleShareClick}
|
||||
/>
|
||||
|
||||
@@ -1,10 +1,8 @@
|
||||
import classNames from 'classnames/bind'
|
||||
import { compactMode } from '../initial_state'
|
||||
import { CX } from '../constants'
|
||||
import Button from './button'
|
||||
import Icon from './icon'
|
||||
import Text from './text'
|
||||
|
||||
const cx = classNames.bind(_s)
|
||||
|
||||
export default class StatusActionBarItem extends PureComponent {
|
||||
static propTypes = {
|
||||
title: PropTypes.string.isRequired,
|
||||
@@ -30,18 +28,34 @@ export default class StatusActionBarItem extends PureComponent {
|
||||
altTitle
|
||||
} = this.props
|
||||
|
||||
const btnClasses = cx({
|
||||
const containerClasses = CX({
|
||||
default: 1,
|
||||
px5: !compactMode,
|
||||
px10: compactMode,
|
||||
flexNormal: !compactMode,
|
||||
})
|
||||
|
||||
const btnClasses = CX({
|
||||
justifyContentCenter: 1,
|
||||
alignItemsCenter: 1,
|
||||
px10: 1,
|
||||
px10: !compactMode,
|
||||
px15: compactMode,
|
||||
pt10: compactMode,
|
||||
bgSubtle_onHover: !disabled,
|
||||
})
|
||||
|
||||
const iconClasses = CX({
|
||||
default: 1,
|
||||
inheritFill: 1,
|
||||
mr10: !!title,
|
||||
})
|
||||
|
||||
const color = active ? 'brand' : 'secondary'
|
||||
const weight = active ? 'bold' : 'medium'
|
||||
const iconSize = compactMode ? '14px' : '16px'
|
||||
|
||||
return (
|
||||
<div className={[_s.default, _s.flexNormal, _s.px5].join(' ')}>
|
||||
<div className={containerClasses}>
|
||||
<Button
|
||||
isBlock
|
||||
radiusSmall
|
||||
@@ -53,12 +67,15 @@ export default class StatusActionBarItem extends PureComponent {
|
||||
onClick={onClick}
|
||||
isDisabled={disabled}
|
||||
icon={icon}
|
||||
iconSize='16px'
|
||||
iconClassName={[_s.default, _s.mr10, _s.inheritFill].join(' ')}
|
||||
>
|
||||
<Text color='inherit' size='small' weight={weight}>
|
||||
{title}
|
||||
</Text>
|
||||
iconSize={iconSize}
|
||||
iconClassName={iconClasses}
|
||||
>
|
||||
{
|
||||
!!title &&
|
||||
<Text color='inherit' size='small' weight={weight}>
|
||||
{title}
|
||||
</Text>
|
||||
}
|
||||
</Button>
|
||||
</div>
|
||||
)
|
||||
|
||||
@@ -2,6 +2,7 @@ import Immutable from 'immutable'
|
||||
import ImmutablePropTypes from 'react-immutable-proptypes'
|
||||
import ImmutablePureComponent from 'react-immutable-pure-component'
|
||||
import punycode from 'punycode'
|
||||
import { DEFAULT_REL } from '../constants'
|
||||
import Icon from './icon'
|
||||
|
||||
const IDNA_PREFIX = 'xn--'
|
||||
@@ -144,7 +145,7 @@ export default class Card extends ImmutablePureComponent {
|
||||
className={[_s.default, _s.displayFlex, _s.text, _s.noUnderline, _s.overflowWrapBreakWord, _s.colorPrimary, _s.fs15PX, _s.fontWeightMedium].join(' ')}
|
||||
href={card.get('url')}
|
||||
title={card.get('title')}
|
||||
rel='noopener noreferrer'
|
||||
rel={DEFAULT_REL}
|
||||
target='_blank'
|
||||
>
|
||||
{card.get('title')}
|
||||
@@ -229,7 +230,7 @@ export default class Card extends ImmutablePureComponent {
|
||||
<a
|
||||
href={card.get('url')}
|
||||
className={[_s.default, _s.cursorPointer, _s.flexRow, _s.overflowHidden, _s.noUnderline, _s.width100PC, _s.bgSubtle_onHover, _s.borderColorSecondary, _s.border1PX, _s.radiusSmall].join(' ')}
|
||||
rel='noopener noreferrer'
|
||||
rel={DEFAULT_REL}
|
||||
ref={this.setRef}
|
||||
>
|
||||
{embed}
|
||||
|
||||
@@ -2,6 +2,8 @@ import { NavLink } from 'react-router-dom'
|
||||
import { injectIntl, defineMessages, FormattedMessage } from 'react-intl'
|
||||
import ImmutablePropTypes from 'react-immutable-proptypes'
|
||||
import ImmutablePureComponent from 'react-immutable-pure-component'
|
||||
import { compactMode } from '../initial_state'
|
||||
import { CX } from '../constants'
|
||||
import Icon from './icon'
|
||||
import Text from './text'
|
||||
|
||||
@@ -39,8 +41,16 @@ class StatusPrepend extends ImmutablePureComponent {
|
||||
|
||||
const iconId = isFeatured ? 'pin' : isPromoted ? 'star' : 'repost'
|
||||
|
||||
const containerClasses = CX({
|
||||
default: 1,
|
||||
width100PC: 1,
|
||||
alignItemsStart: 1,
|
||||
borderBottom1PX: !compactMode,
|
||||
borderColorSecondary: !compactMode,
|
||||
})
|
||||
|
||||
return (
|
||||
<div className={[_s.default, _s.width100PC, _s.alignItemsStart, _s.borderBottom1PX, _s.borderColorSecondary].join(' ')}>
|
||||
<div className={containerClasses}>
|
||||
<div className={[_s.default, _s.width100PC, _s.flexRow, _s.alignItemsCenter, _s.py5, _s.px15].join(' ')}>
|
||||
<Icon id={iconId} size='12px' className={[_s.fillSecondary, _s.mr5].join(' ')} />
|
||||
{
|
||||
|
||||
@@ -1,11 +1,8 @@
|
||||
import { withRouter } from 'react-router-dom'
|
||||
import classNames from 'classnames/bind'
|
||||
import { CX } from '../constants'
|
||||
import Button from './button'
|
||||
import Text from './text'
|
||||
|
||||
// Bind CSS Modules global variable `_s` to classNames module
|
||||
const cx = classNames.bind(_s)
|
||||
|
||||
/**
|
||||
* Renders a tab bar item component
|
||||
* @param {bool} [props.isLarge] - to style the tab bar larger
|
||||
@@ -27,7 +24,7 @@ class TabBarItem extends PureComponent {
|
||||
}
|
||||
|
||||
state = {
|
||||
isCurrent: -1,
|
||||
isCurrent: false,
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps) {
|
||||
@@ -55,10 +52,9 @@ class TabBarItem extends PureComponent {
|
||||
|
||||
// Combine state, props, location to make absolutely
|
||||
// sure of active status.
|
||||
const active = isActive ||
|
||||
(isCurrent === -1 ? to === location.pathname : false)
|
||||
const active = isActive || to === location.pathname || isCurrent
|
||||
|
||||
const containerClasses = cx({
|
||||
const containerClasses = CX({
|
||||
default: 1,
|
||||
height53PX: 1,
|
||||
noUnderline: 1,
|
||||
@@ -77,7 +73,7 @@ class TabBarItem extends PureComponent {
|
||||
mr2: !isLarge,
|
||||
})
|
||||
|
||||
const textParentClasses = cx({
|
||||
const textParentClasses = CX({
|
||||
default: 1,
|
||||
height100PC: 1,
|
||||
alignItemsCenter: 1,
|
||||
|
||||
@@ -14,6 +14,7 @@ export default class UserStat extends PureComponent {
|
||||
to: PropTypes.string.isRequired,
|
||||
value: PropTypes.oneOfType([
|
||||
PropTypes.string,
|
||||
PropTypes.number,
|
||||
PropTypes.object,
|
||||
]).isRequired,
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user