This commit is contained in:
mgabdev 2020-03-24 00:39:12 -04:00
parent 65af72faae
commit 0d9dbdfecd
79 changed files with 1847 additions and 946 deletions

View File

@ -13,7 +13,6 @@ export const MUTES_EXPAND_SUCCESS = 'MUTES_EXPAND_SUCCESS';
export const MUTES_EXPAND_FAIL = 'MUTES_EXPAND_FAIL';
export const MUTES_INIT_MODAL = 'MUTES_INIT_MODAL';
export const MUTES_TOGGLE_HIDE_NOTIFICATIONS = 'MUTES_TOGGLE_HIDE_NOTIFICATIONS';
export function fetchMutes() {
return (dispatch, getState) => {
@ -103,9 +102,3 @@ export function initMuteModal(account) {
dispatch(openModal('MUTE'));
};
}
export function toggleHideNotifications() {
return dispatch => {
dispatch({ type: MUTES_TOGGLE_HIDE_NOTIFICATIONS });
};
}

View File

@ -110,7 +110,7 @@ class Account extends ImmutablePureComponent {
const blocking = account.getIn(['relationship', 'blocking'])
if (requested || blocking) {
buttonText = intl.formatMessage(requested ? messages.requested : messages.blocking)
buttonText = intl.formatMessage(requested ? messages.requested : messages.unblock)
buttonOptions = {
narrow: true,
onClick: requested ? this.handleUnrequest : this.handleBlock,

View File

@ -260,10 +260,10 @@ export default class AutosuggestTextbox extends ImmutablePureComponent {
/>
<ContentEditable
tabindex='0'
ariaLabel='Gab text'
tabIndex='0'
aria-label='Gab text'
role='textbox'
ariaAutocomplete='list'
aria-autocomplete='list'
style={{
userSelect: 'text',
'white-space': 'pre-wrap',

View File

@ -1,11 +1,11 @@
import { defineMessages, injectIntl } from 'react-intl';
import IconButton from '../icon_button';
import { defineMessages, injectIntl } from 'react-intl'
import IconButton from './icon_button'
const messages = defineMessages({
title: { id: 'bundle_column_error.title', defaultMessage: 'Network error' },
body: { id: 'bundle_column_error.body', defaultMessage: 'Something went wrong while loading this component.' },
retry: { id: 'bundle_column_error.retry', defaultMessage: 'Try again' },
});
})
export default
@injectIntl
@ -17,18 +17,18 @@ class BundleColumnError extends PureComponent {
}
handleRetry = () => {
this.props.onRetry();
this.props.onRetry()
}
render () {
const { intl: { formatMessage } } = this.props;
const { intl: { formatMessage } } = this.props
return (
<div className='error-column'>
<IconButton title={formatMessage(messages.retry)} icon='refresh' onClick={this.handleRetry} size={64} />
{formatMessage(messages.body)}
</div>
);
)
}
}

View File

@ -1,29 +0,0 @@
.error-column {
color: $dark-text-color;
background: $ui-base-color;
padding: 40px;
cursor: default;
flex: 1 1 auto;
min-height: 160px;
@include flex(center, center, column);
@include text-sizing(15px, 400, 1, center);
@supports(display: grid) {
// hack to fix Chrome <57
contain: strict;
}
&>span {
max-width: 400px;
}
a {
color: $highlight-text-color;
text-decoration: none;
&:hover {
text-decoration: underline;
}
}
}

View File

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

View File

@ -21,7 +21,7 @@ export default
class DisplayName extends ImmutablePureComponent {
static propTypes = {
account: ImmutablePropTypes.map.isRequired,
account: ImmutablePropTypes.map,
openUserInfoPopover: PropTypes.func.isRequired,
closeUserInfoPopover: PropTypes.func.isRequired,
multiline: PropTypes.bool,
@ -32,7 +32,6 @@ class DisplayName extends ImmutablePureComponent {
}
handleMouseEnter = debounce(() => {
console.log("SHOW - USER POPOVER")
this.props.openUserInfoPopover({
targetRef: this.node,
position: 'top',
@ -41,7 +40,6 @@ class DisplayName extends ImmutablePureComponent {
}, 1000, { leading: true })
handleMouseLeave = debounce(() => {
console.log("HIDE - USER POPOVER")
this.props.closeUserInfoPopover()
}, 1000, { leading: true })

View File

@ -1,24 +1,24 @@
import { is } from 'immutable';
import { setHeight } from '../actions/height_cache';
import scheduleIdleTask from '../utils/schedule_idle_task';
import getRectFromEntry from '../utils/get_rect_from_entry';
import { is } from 'immutable'
import { setHeight } from '../actions/height_cache'
import scheduleIdleTask from '../utils/schedule_idle_task'
import getRectFromEntry from '../utils/get_rect_from_entry'
// Diff these props in the "rendered" state
const updateOnPropsForRendered = ['id', 'index', 'listLength'];
const updateOnPropsForRendered = ['id', 'index', 'listLength']
// Diff these props in the "unrendered" state
const updateOnPropsForUnrendered = ['id', 'index', 'listLength', 'cachedHeight'];
const updateOnPropsForUnrendered = ['id', 'index', 'listLength', 'cachedHeight']
const makeMapStateToProps = (state, props) => ({
cachedHeight: state.getIn(['height_cache', props.saveHeightKey, props.id]),
});
})
const mapDispatchToProps = (dispatch) => ({
onHeightChange(key, id, height) {
dispatch(setHeight(key, id, height));
dispatch(setHeight(key, id, height))
},
});
})
export default
@connect(makeMapStateToProps, mapDispatchToProps)
@ -33,7 +33,7 @@ class IntersectionObserverArticle extends Component {
cachedHeight: PropTypes.number,
onHeightChange: PropTypes.func,
children: PropTypes.node,
};
}
state = {
isHidden: false, // set to true in requestIdleCallback to trigger un-render
@ -91,32 +91,32 @@ class IntersectionObserverArticle extends Component {
}
calculateHeight = () => {
const { onHeightChange, saveHeightKey, id } = this.props;
const { onHeightChange, saveHeightKey, id } = this.props
// Save the height of the fully-rendered element (this is expensive
// on Chrome, where we need to fall back to getBoundingClientRect)
this.height = getRectFromEntry(this.entry).height;
this.height = getRectFromEntry(this.entry).height
if (onHeightChange && saveHeightKey) {
onHeightChange(saveHeightKey, id, this.height);
onHeightChange(saveHeightKey, id, this.height)
}
}
hideIfNotIntersecting = () => {
if (!this.componentMounted) return;
if (!this.componentMounted) return
// When the browser gets a chance, test if we're still not intersecting,
// and if so, set our isHidden to true to trigger an unrender. The point of
// this is to save DOM nodes and avoid using up too much memory.
this.setState((prevState) => ({ isHidden: !prevState.isIntersecting }));
this.setState((prevState) => ({ isHidden: !prevState.isIntersecting }))
}
handleRef = (node) => {
this.node = node;
this.node = node
}
render() {
const { children, id, index, listLength, cachedHeight } = this.props;
const { isIntersecting, isHidden } = this.state;
const { children, id, index, listLength, cachedHeight } = this.props
const { isIntersecting, isHidden } = this.state
if (!isIntersecting && (isHidden || cachedHeight)) {
return (
@ -130,7 +130,7 @@ class IntersectionObserverArticle extends Component {
>
{children && React.cloneElement(children, { hidden: true })}
</article>
);
)
}
return (
@ -143,7 +143,7 @@ class IntersectionObserverArticle extends Component {
>
{children && React.cloneElement(children, { hidden: false })}
</article>
);
)
}
}

View File

@ -7,16 +7,27 @@ const cx = classNames.bind(_s)
export default class ListItem extends PureComponent {
static propTypes = {
icon: PropTypes.string,
isLast: PropTypes.bool,
to: PropTypes.string,
href: PropTypes.string,
title: PropTypes.string,
onClick: PropTypes.func,
small: PropTypes.bool,
hideArrow: PropTypes.bool,
}
render() {
const { title, isLast, to, href, onClick, small } = this.props
const {
title,
isLast,
to,
href,
onClick,
small,
icon,
hideArrow,
} = this.props
const containerClasses = cx({
default: 1,
@ -44,16 +55,30 @@ export default class ListItem extends PureComponent {
className={containerClasses}
noClasses
>
{
!!icon &&
<Icon
id={icon}
width='10px'
height='10px'
className={[_s.mr10, _s.fillColorBlack].join(' ')}
/>
}
<Text color='primary' size={textSize}>
{title}
</Text>
{
!hideArrow &&
<Icon
id='angle-right'
width='10px'
height='10px'
className={[_s.marginLeftAuto, _s.fillColorBlack].join(' ')}
/>
}
</Button>
)
}

View File

@ -0,0 +1,59 @@
import { defineMessages, injectIntl } from 'react-intl'
import ImmutablePureComponent from 'react-immutable-pure-component'
import Button from '../button'
import Text from '../text'
import ModalLayout from './modal_layout'
const messages = defineMessages({
title: { id: 'promo.gab_pro', defaultMessage: 'Upgrade to GabPRO' },
text: { id: 'pro_upgrade_modal.text', defaultMessage: 'Gab is fully funded by people like you. Please consider supporting us on our mission to defend free expression online for all people.' },
benefits: { id: 'pro_upgrade_modal.benefits', defaultMessage: 'Here are just some of the benefits that thousands of GabPRO members receive:' },
})
export default
@injectIntl
class HomeTimelineSettingsModal extends ImmutablePureComponent {
static propTypes = {
intl: PropTypes.object.isRequired,
}
render() {
const { intl } = this.props
return (
<div>
<Text>
{intl.formatMessage(messages.text)}
</Text>
<Text>
{intl.formatMessage(messages.benefits)}
</Text>
<div className={[_s.default, _s.my10].join(' ')}>
<Text> Schedule Posts</Text>
<Text> Get Verified</Text>
<Text> Create Groups</Text>
<Text> Larger Video and Image Uploads</Text>
<Text> Receive the PRO Badge</Text>
<Text> Remove in-feed promotions</Text>
</div>
<Button
centered
backgroundColor='brand'
color='white'
icon='pro'
href='https://pro.gab.com'
className={_s.justifyContentCenter}
iconClassName={[_s.mr5, _s.fillColorWhite].join(' ')}
>
<Text color='inherit' weight='bold' align='center'>
{intl.formatMessage(messages.title)}
</Text>
</Button>
</div>
)
}
}

View File

@ -0,0 +1,73 @@
import { injectIntl, defineMessages } from 'react-intl'
import { makeGetAccount } from '../../selectors'
import { closeModal } from '../../actions/modal'
import { blockAccount } from '../../actions/accounts'
import ConfirmationModal from './confirmation_modal'
const messages = defineMessages({
title: { id: 'block_title', defaultMessage: 'Block {name}' },
muteMessage: { id: 'confirmations.block.message', defaultMessage: 'Are you sure you want to block {name}?' },
block: { id: 'confirmations.block.confirm', defaultMessage: 'Block' },
})
const mapStateToProps = (state, { accountId }) => {
const getAccount = makeGetAccount()
return {
account: getAccount(state, accountId),
}
}
const mapDispatchToProps = dispatch => {
return {
onConfirm(account) {
dispatch(blockAccount(account.get('id')))
},
onClose() {
dispatch(closeModal())
},
}
}
export default
@connect(mapStateToProps, mapDispatchToProps)
@injectIntl
class BlockAccountModal extends PureComponent {
static propTypes = {
account: PropTypes.object.isRequired,
onConfirm: PropTypes.func.isRequired,
onClose: PropTypes.func.isRequired,
intl: PropTypes.object.isRequired,
}
handleClick = () => {
this.props.onConfirm(this.props.account)
}
handleClose = () => {
this.props.onClose()
}
render() {
const { account, intl } = this.props
const title = intl.formatMessage(messages.title, {
name: !!account ? account.get('acct') : '',
})
const message = intl.formatMessage(messages.muteMessage, {
name: !!account ? account.get('acct') : '',
})
return (
<ConfirmationModal
title={title}
message={message}
confirm={intl.formatMessage(messages.block)}
onClose={this.handleClose}
onConfirm={this.handleClick}
/>
)
}
}

View File

@ -0,0 +1,72 @@
import { injectIntl, defineMessages } from 'react-intl'
import { muteAccount } from '../../actions/accounts'
const messages = defineMessages({
muteMessage: { id: 'confirmations.mute.message', defaultMessage: 'Are you sure you want to mute {name}?' },
cancel: { id: 'confirmation_modal.cancel', defaultMessage: 'Cancel' },
confirm: { id: 'confirmations.mute.confirm', defaultMessage: 'Mute' },
})
const mapStateToProps = state => {
return {
isSubmitting: state.getIn(['reports', 'new', 'isSubmitting']),
account: state.getIn(['mutes', 'new', 'account']),
}
}
const mapDispatchToProps = dispatch => {
return {
onConfirm(account, notifications) {
dispatch(muteAccount(account.get('id'), notifications))
},
}
}
export default
@connect(mapStateToProps, mapDispatchToProps)
@injectIntl
class BlockDomainModal extends PureComponent {
static propTypes = {
isSubmitting: PropTypes.bool.isRequired,
account: PropTypes.object.isRequired,
onConfirm: PropTypes.func.isRequired,
intl: PropTypes.object.isRequired,
}
componentDidMount() {
this.button.focus()
}
handleClick = () => {
this.props.onClose()
this.props.onConfirm(this.props.account, this.props.notifications)
}
handleCancel = () => {
this.props.onClose()
}
render() {
const { account, intl } = this.props
// dispatch(openModal('CONFIRM', {
// message: <FormattedMessage 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.' values={{ domain: <strong>{domain}</strong> }} />,
// confirm: intl.formatMessage(messages.blockDomainConfirm),
// onConfirm: () => dispatch(blockDomain(domain)),
// }));
return (
<ConfirmationModal
title={`Mute @${account.get('acct')}`}
message={<FormattedMessage id='confirmations.mute.message' defaultMessage='Are you sure you want to mute @{name}?' values={{ name: account.get('acct') }} />}
confirm={<FormattedMessage id='mute' defaultMessage='Mute' />}
onConfirm={() => {
// dispatch(blockDomain(domain))
// dispatch(blockDomain(domain))
}}
/>
)
}
}

View File

@ -0,0 +1,72 @@
import { injectIntl, defineMessages } from 'react-intl'
import { muteAccount } from '../../actions/accounts'
const messages = defineMessages({
muteMessage: { id: 'confirmations.mute.message', defaultMessage: 'Are you sure you want to mute {name}?' },
cancel: { id: 'confirmation_modal.cancel', defaultMessage: 'Cancel' },
confirm: { id: 'confirmations.mute.confirm', defaultMessage: 'Mute' },
})
const mapStateToProps = state => {
return {
isSubmitting: state.getIn(['reports', 'new', 'isSubmitting']),
account: state.getIn(['mutes', 'new', 'account']),
}
}
const mapDispatchToProps = dispatch => {
return {
onConfirm(account, notifications) {
dispatch(muteAccount(account.get('id'), notifications))
},
}
}
export default
@connect(mapStateToProps, mapDispatchToProps)
@injectIntl
class UnfollowModal extends PureComponent {
static propTypes = {
isSubmitting: PropTypes.bool.isRequired,
account: PropTypes.object.isRequired,
onConfirm: PropTypes.func.isRequired,
intl: PropTypes.object.isRequired,
}
componentDidMount() {
this.button.focus()
}
handleClick = () => {
this.props.onClose()
this.props.onConfirm(this.props.account, this.props.notifications)
}
handleCancel = () => {
this.props.onClose()
}
render() {
const { account, intl } = this.props
// , {
// message: <FormattedMessage id='confirmations.unfollow.message' defaultMessage='Are you sure you want to unfollow {name}?' values={{ name: <strong>@{account.get('acct')}</strong> }} />,
// confirm: intl.formatMessage(messages.unfollowConfirm),
// onConfirm: () => dispatch(unfollowAccount(account.get('id'))),
// }));
return (
<ConfirmationModal
title={`Mute @${account.get('acct')}`}
message={<FormattedMessage id='confirmations.mute.message' defaultMessage='Are you sure you want to mute @{name}?' values={{ name: account.get('acct') }} />}
confirm={<FormattedMessage id='mute' defaultMessage='Mute' />}
onConfirm={() => {
// dispatch(blockDomain(domain))
// dispatch(blockDomain(domain))
}}
/>
)
}
}

View File

@ -0,0 +1,72 @@
import { injectIntl, defineMessages } from 'react-intl'
import { muteAccount } from '../../actions/accounts'
const messages = defineMessages({
muteMessage: { id: 'confirmations.mute.message', defaultMessage: 'Are you sure you want to mute {name}?' },
cancel: { id: 'confirmation_modal.cancel', defaultMessage: 'Cancel' },
confirm: { id: 'confirmations.mute.confirm', defaultMessage: 'Mute' },
})
const mapStateToProps = state => {
return {
isSubmitting: state.getIn(['reports', 'new', 'isSubmitting']),
account: state.getIn(['mutes', 'new', 'account']),
}
}
const mapDispatchToProps = dispatch => {
return {
onConfirm(account, notifications) {
dispatch(muteAccount(account.get('id'), notifications))
},
}
}
export default
@connect(mapStateToProps, mapDispatchToProps)
@injectIntl
class UnfollowModal extends PureComponent {
static propTypes = {
isSubmitting: PropTypes.bool.isRequired,
account: PropTypes.object.isRequired,
onConfirm: PropTypes.func.isRequired,
intl: PropTypes.object.isRequired,
}
componentDidMount() {
this.button.focus()
}
handleClick = () => {
this.props.onClose()
this.props.onConfirm(this.props.account, this.props.notifications)
}
handleCancel = () => {
this.props.onClose()
}
render() {
const { account, intl } = this.props
// , {
// message: <FormattedMessage id='confirmations.unfollow.message' defaultMessage='Are you sure you want to unfollow {name}?' values={{ name: <strong>@{account.get('acct')}</strong> }} />,
// confirm: intl.formatMessage(messages.unfollowConfirm),
// onConfirm: () => dispatch(unfollowAccount(account.get('id'))),
// }));
return (
<ConfirmationModal
title={`Mute @${account.get('acct')}`}
message={<FormattedMessage id='confirmations.mute.message' defaultMessage='Are you sure you want to mute @{name}?' values={{ name: account.get('acct') }} />}
confirm={<FormattedMessage id='mute' defaultMessage='Mute' />}
onConfirm={() => {
// dispatch(blockDomain(domain))
// dispatch(blockDomain(domain))
}}
/>
)
}
}

View File

@ -0,0 +1,59 @@
import { defineMessages, injectIntl } from 'react-intl'
import ImmutablePureComponent from 'react-immutable-pure-component'
import Button from '../button'
import Text from '../text'
import ModalLayout from './modal_layout'
const messages = defineMessages({
title: { id: 'promo.gab_pro', defaultMessage: 'Upgrade to GabPRO' },
text: { id: 'pro_upgrade_modal.text', defaultMessage: 'Gab is fully funded by people like you. Please consider supporting us on our mission to defend free expression online for all people.' },
benefits: { id: 'pro_upgrade_modal.benefits', defaultMessage: 'Here are just some of the benefits that thousands of GabPRO members receive:' },
})
export default
@injectIntl
class HomeTimelineSettingsModal extends ImmutablePureComponent {
static propTypes = {
intl: PropTypes.object.isRequired,
}
render() {
const { intl } = this.props
return (
<div>
<Text>
{intl.formatMessage(messages.text)}
</Text>
<Text>
{intl.formatMessage(messages.benefits)}
</Text>
<div className={[_s.default, _s.my10].join(' ')}>
<Text> Schedule Posts</Text>
<Text> Get Verified</Text>
<Text> Create Groups</Text>
<Text> Larger Video and Image Uploads</Text>
<Text> Receive the PRO Badge</Text>
<Text> Remove in-feed promotions</Text>
</div>
<Button
centered
backgroundColor='brand'
color='white'
icon='pro'
href='https://pro.gab.com'
className={_s.justifyContentCenter}
iconClassName={[_s.mr5, _s.fillColorWhite].join(' ')}
>
<Text color='inherit' weight='bold' align='center'>
{intl.formatMessage(messages.title)}
</Text>
</Button>
</div>
)
}
}

View File

@ -1,73 +1,119 @@
import ImmutablePropTypes from 'react-immutable-proptypes';
import ImmutablePureComponent from 'react-immutable-pure-component';
import { FormattedMessage } from 'react-intl';
import Video from '../../features/video';
import { defineMessages, injectIntl } from 'react-intl'
import ImmutablePureComponent from 'react-immutable-pure-component'
import ImmutablePropTypes from 'react-immutable-proptypes'
import { closeModal } from '../../actions/modal'
import { changeSetting, saveSettings } from '../../actions/settings'
import ModalLayout from './modal_layout'
import Button from '../button'
import SettingSwitch from '../setting_switch'
import Text from '../text'
export const previewState = 'previewVideoModal';
const messages = defineMessages({
title: { id: 'home_timeline_settings', defaultMessage: 'Home Timeline Settings' },
saveAndClose: { id: 'saveClose', defaultMessage: 'Save & Close' },
showVideos: { id: 'home.column_settings.show_videos', defaultMessage: 'Show videos' },
showPhotos: { id: 'home.column_settings.show_photos', defaultMessage: 'Show photos' },
showPolls: { id: 'home.column_settings.show_polls', defaultMessage: 'Show polls' },
showReposts: { id: 'home.column_settings.show_reposts', defaultMessage: 'Show comments' },
showReplies: { id: 'home.column_settings.show_replies', defaultMessage: 'Show replies' },
})
export default class VideoModal extends ImmutablePureComponent {
const mapStateToProps = state => ({
settings: state.getIn(['settings', 'home']),
})
const mapDispatchToProps = dispatch => {
return {
onChange(key, checked) {
dispatch(changeSetting(['home', ...key], checked))
},
onSave() {
dispatch(saveSettings())
dispatch(closeModal())
},
}
}
export default
@connect(mapStateToProps, mapDispatchToProps)
@injectIntl
class HomeTimelineSettingsModal extends ImmutablePureComponent {
static propTypes = {
media: ImmutablePropTypes.map.isRequired,
status: ImmutablePropTypes.map,
time: PropTypes.number,
onClose: PropTypes.func.isRequired,
};
static contextTypes = {
router: PropTypes.object,
};
componentDidMount () {
if (this.context.router) {
const history = this.context.router.history;
history.push(history.location.pathname, previewState);
this.unlistenHistory = history.listen(() => {
this.props.onClose();
});
}
intl: PropTypes.object.isRequired,
settings: ImmutablePropTypes.map.isRequired,
onChange: PropTypes.func.isRequired,
onSave: PropTypes.func.isRequired,
}
componentWillUnmount () {
if (this.context.router) {
this.unlistenHistory();
if (this.context.router.history.location.state === previewState) {
this.context.router.history.goBack();
}
}
}
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')}`);
}
handleSaveAndClose = () => {
this.props.onSave()
}
render() {
const { media, status, time, onClose } = this.props;
const link = status && <a href={status.get('url')} onClick={this.handleStatusClick}><FormattedMessage id='lightbox.view_context' defaultMessage='View context' /></a>;
const { intl, settings, onChange } = this.props
return (
<div className='modal-root__modal video-modal'>
<div>
<Video
preview={media.get('preview_url')}
blurhash={media.get('blurhash')}
src={media.get('url')}
startTime={time}
onCloseVideo={onClose}
link={link}
detailed
alt={media.get('description')}
<ModalLayout
width='320'
title={intl.formatMessage(messages.title)}
>
<div className={[_s.default, _s.my10, _s.pb10].join(' ')}>
<SettingSwitch
prefix='home_timeline'
settings={settings}
settingPath={['shows', 'polls']}
onChange={onChange}
label={intl.formatMessage(messages.showPolls)}
/>
<SettingSwitch
prefix='home_timeline'
settings={settings}
settingPath={['shows', 'photos']}
onChange={onChange}
label={intl.formatMessage(messages.showPhotos)}
/>
<SettingSwitch
prefix='home_timeline'
settings={settings}
settingPath={['shows', 'videos']}
onChange={onChange}
label={intl.formatMessage(messages.showVideos)}
/>
<SettingSwitch
prefix='home_timeline'
settings={settings}
settingPath={['shows', 'repost']}
onChange={onChange}
label={intl.formatMessage(messages.showReposts)}
/>
<SettingSwitch
prefix='home_timeline'
settings={settings}
settingPath={['shows', 'reply']}
onChange={onChange}
label={intl.formatMessage(messages.showReplies)}
/>
</div>
</div>
);
}
<Button
centered
backgroundColor='brand'
color='white'
className={_s.justifyContentCenter}
onClick={this.handleSaveAndClose}
>
<Text color='inherit' weight='bold' align='center'>
{intl.formatMessage(messages.saveAndClose)}
</Text>
</Button>
</ModalLayout>
)
}
}

View File

@ -0,0 +1,72 @@
import { injectIntl, defineMessages } from 'react-intl'
import { muteAccount } from '../../actions/accounts'
const messages = defineMessages({
muteMessage: { id: 'confirmations.mute.message', defaultMessage: 'Are you sure you want to mute {name}?' },
cancel: { id: 'confirmation_modal.cancel', defaultMessage: 'Cancel' },
confirm: { id: 'confirmations.mute.confirm', defaultMessage: 'Mute' },
})
const mapStateToProps = state => {
return {
isSubmitting: state.getIn(['reports', 'new', 'isSubmitting']),
account: state.getIn(['mutes', 'new', 'account']),
}
}
const mapDispatchToProps = dispatch => {
return {
onConfirm(account, notifications) {
dispatch(muteAccount(account.get('id'), notifications))
},
}
}
export default
@connect(mapStateToProps, mapDispatchToProps)
@injectIntl
class UnfollowModal extends PureComponent {
static propTypes = {
isSubmitting: PropTypes.bool.isRequired,
account: PropTypes.object.isRequired,
onConfirm: PropTypes.func.isRequired,
intl: PropTypes.object.isRequired,
}
componentDidMount() {
this.button.focus()
}
handleClick = () => {
this.props.onClose()
this.props.onConfirm(this.props.account, this.props.notifications)
}
handleCancel = () => {
this.props.onClose()
}
render() {
const { account, intl } = this.props
// , {
// message: <FormattedMessage id='confirmations.unfollow.message' defaultMessage='Are you sure you want to unfollow {name}?' values={{ name: <strong>@{account.get('acct')}</strong> }} />,
// confirm: intl.formatMessage(messages.unfollowConfirm),
// onConfirm: () => dispatch(unfollowAccount(account.get('id'))),
// }));
return (
<ConfirmationModal
title={`Mute @${account.get('acct')}`}
message={<FormattedMessage id='confirmations.mute.message' defaultMessage='Are you sure you want to mute @{name}?' values={{ name: account.get('acct') }} />}
confirm={<FormattedMessage id='mute' defaultMessage='Mute' />}
onConfirm={() => {
// dispatch(blockDomain(domain))
// dispatch(blockDomain(domain))
}}
/>
)
}
}

View File

@ -0,0 +1,72 @@
import { injectIntl, defineMessages } from 'react-intl'
import { muteAccount } from '../../actions/accounts'
const messages = defineMessages({
muteMessage: { id: 'confirmations.mute.message', defaultMessage: 'Are you sure you want to mute {name}?' },
cancel: { id: 'confirmation_modal.cancel', defaultMessage: 'Cancel' },
confirm: { id: 'confirmations.mute.confirm', defaultMessage: 'Mute' },
})
const mapStateToProps = state => {
return {
isSubmitting: state.getIn(['reports', 'new', 'isSubmitting']),
account: state.getIn(['mutes', 'new', 'account']),
}
}
const mapDispatchToProps = dispatch => {
return {
onConfirm(account, notifications) {
dispatch(muteAccount(account.get('id'), notifications))
},
}
}
export default
@connect(mapStateToProps, mapDispatchToProps)
@injectIntl
class UnfollowModal extends PureComponent {
static propTypes = {
isSubmitting: PropTypes.bool.isRequired,
account: PropTypes.object.isRequired,
onConfirm: PropTypes.func.isRequired,
intl: PropTypes.object.isRequired,
}
componentDidMount() {
this.button.focus()
}
handleClick = () => {
this.props.onClose()
this.props.onConfirm(this.props.account, this.props.notifications)
}
handleCancel = () => {
this.props.onClose()
}
render() {
const { account, intl } = this.props
// , {
// message: <FormattedMessage id='confirmations.unfollow.message' defaultMessage='Are you sure you want to unfollow {name}?' values={{ name: <strong>@{account.get('acct')}</strong> }} />,
// confirm: intl.formatMessage(messages.unfollowConfirm),
// onConfirm: () => dispatch(unfollowAccount(account.get('id'))),
// }));
return (
<ConfirmationModal
title={`Mute @${account.get('acct')}`}
message={<FormattedMessage id='confirmations.mute.message' defaultMessage='Are you sure you want to mute @{name}?' values={{ name: account.get('acct') }} />}
confirm={<FormattedMessage id='mute' defaultMessage='Mute' />}
onConfirm={() => {
// dispatch(blockDomain(domain))
// dispatch(blockDomain(domain))
}}
/>
)
}
}

View File

@ -1,8 +1,11 @@
import { defineMessages, injectIntl } from 'react-intl'
import classNames from 'classnames/bind'
import Button from '../button'
import Block from '../block'
import Heading from '../heading'
const cx = classNames.bind(_s)
const messages = defineMessages({
close: { id: 'lightbox.close', defaultMessage: 'Close' },
})
@ -14,6 +17,13 @@ class ModalLayout extends PureComponent {
title: PropTypes.string,
children: PropTypes.node,
onClose: PropTypes.func.isRequired,
width: PropTypes.number,
hideClose: PropTypes.bool,
noPadding: PropTypes.bool,
}
static defaultProps = {
width: 600,
}
render() {
@ -22,15 +32,26 @@ class ModalLayout extends PureComponent {
children,
intl,
onClose,
width,
hideClose,
noPadding
} = this.props
const childrenContainerClasses = cx({
default: 1,
px15: !noPadding,
py10: !noPadding,
})
return (
<div className={[_s.width645PX].join(' ')}>
<div style={{width: `${width}px`}}>
<Block>
<div className={[_s.default, _s.flexRow, _s.alignItemsCenter, _s.justifyContentCenter, _s.borderBottom1PX, _s.borderColorSecondary, _s.height53PX, _s.px15].join(' ')}>
<Heading size='h3'>
{title}
</Heading>
{
!hideClose &&
<Button
backgroundColor='none'
title={intl.formatMessage(messages.close)}
@ -40,8 +61,9 @@ class ModalLayout extends PureComponent {
iconWidth='10px'
iconWidth='10px'
/>
}
</div>
<div className={[_s.default, _s.px15, _s.py10].join(' ')}>
<div className={childrenContainerClasses}>
{children}
</div>
</Block>

View File

@ -9,37 +9,51 @@ import {
// 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 BlockAccountModal from './block_account_modal'
import BlockDomainModal from './block_domain_modal'
import BoostModal from './boost_modal'
import ConfirmationModal from './confirmation_modal'
import HotkeysModal from './hotkeys_modal'
import ComposeModal from './compose_modal'
import UnauthorizedModal from './unauthorized_modal'
import ProUpgradeModal from './pro_upgrade_modal'
import ConfirmationModal from './confirmation_modal'
import GroupAdderModal from './group_adder_modal'
import GroupEditorModal from './group_editor_modal'
import HomeTimelineSettingsModal from './home_timeline_settings_modal'
import HotkeysModal from './hotkeys_modal'
import ListAdderModal from './list_adder_modal'
import ListEditorModal from './list_editor_modal'
import MediaModal from './media_modal'
import ModalLoading from './modal_loading'
import ProUpgradeModal from './pro_upgrade_modal'
import VideoModal from './video_modal'
import UnauthorizedModal from './unauthorized_modal'
import UnfollowModal from './unfollow_modal'
const MODAL_COMPONENTS = {
'ACTIONS': () => Promise.resolve({ default: ActionsModal }),
'BOOST': () => Promise.resolve({ default: BoostModal }),
'COMPOSE': () => Promise.resolve({ default: ComposeModal }),
'CONFIRM': () => Promise.resolve({ default: ConfirmationModal }),
'EMBED': EmbedModal,
'HOTKEYS': () => Promise.resolve({ default: HotkeysModal }),
'MEDIA': () => Promise.resolve({ default: MediaModal }),
ACTIONS: () => Promise.resolve({ default: ActionsModal }),
BLOCK_ACCOUNT: () => Promise.resolve({ default: BlockAccountModal }),
BLOCK_DOMAIN: () => Promise.resolve({ default: BlockDomainModal }),
BOOST: () => Promise.resolve({ default: BoostModal }),
COMPOSE: () => Promise.resolve({ default: ComposeModal }),
CONFIRM: () => Promise.resolve({ default: ConfirmationModal }),
EMBED: () => Promise.resolve({ default: EmbedModal }),
GROUP_EDITOR: () => Promise.resolve({ default: GroupEditorModal }),
GROUP_ADDER: () => Promise.resolve({ default: GroupAdderModal }),
HOME_TIMELINE_SETTINGS: () => Promise.resolve({ default: HomeTimelineSettingsModal }),
HOTKEYS: () => Promise.resolve({ default: HotkeysModal }),
LIST_EDITOR: () => Promise.resolve({ default: ListEditorModal }),
LIST_ADDER: () => Promise.resolve({ default: ListAdderModal }),
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
PRO_UPGRADE: () => Promise.resolve({ default: ProUpgradeModal }),
REPORT: ReportModal,
STATUS_REVISION: () => Promise.resolve({ default: StatusRevisionModal }),
UNAUTHORIZED: () => Promise.resolve({ default: UnauthorizedModal }),
UNFOLLOW: () => Promise.resolve({ default: UnfollowModal }),
VIDEO: () => Promise.resolve({ default: VideoModal }),
}
const mapStateToProps = state => ({

View File

@ -1,40 +1,34 @@
import { injectIntl, defineMessages } from 'react-intl';
import { closeModal } from '../../actions/modal';
import { muteAccount } from '../../actions/accounts';
import { toggleHideNotifications } from '../../actions/mutes';
import ToggleSwitch from '../toggle_switch';
import Button from '../button';
import { injectIntl, defineMessages } from 'react-intl'
import { makeGetAccount } from '../../selectors'
import { closeModal } from '../../actions/modal'
import { muteAccount } from '../../actions/accounts'
import ConfirmationModal from './confirmation_modal'
const messages = defineMessages({
title: { id: 'mute_title', defaultMessage: 'Mute {name}' },
muteMessage: { id: 'confirmations.mute.message', defaultMessage: 'Are you sure you want to mute {name}?' },
hideNotifications: { id: 'mute_modal.hide_notifications', defaultMessage: 'Hide notifications from this user?' },
cancel: { id: 'confirmation_modal.cancel', defaultMessage: 'Cancel' },
confirm: { id: 'confirmations.mute.confirm', defaultMessage: 'Mute' },
});
mute: { id: 'confirmations.mute.confirm', defaultMessage: 'Mute' },
})
const mapStateToProps = (state, { accountId }) => {
const getAccount = makeGetAccount()
const mapStateToProps = state => {
return {
isSubmitting: state.getIn(['reports', 'new', 'isSubmitting']),
account: state.getIn(['mutes', 'new', 'account']),
notifications: state.getIn(['mutes', 'new', 'notifications']),
};
};
account: getAccount(state, accountId),
}
}
const mapDispatchToProps = dispatch => {
return {
onConfirm(account, notifications) {
dispatch(muteAccount(account.get('id'), notifications));
dispatch(closeModal())
dispatch(muteAccount(account.get('id'), notifications))
},
onClose() {
dispatch(closeModal());
dispatch(closeModal())
},
onToggleNotifications() {
dispatch(toggleHideNotifications());
},
};
};
}
}
export default
@connect(mapStateToProps, mapDispatchToProps)
@ -42,64 +36,39 @@ export default
class MuteModal extends PureComponent {
static propTypes = {
isSubmitting: PropTypes.bool.isRequired,
account: PropTypes.object.isRequired,
notifications: PropTypes.bool.isRequired,
onClose: PropTypes.func.isRequired,
onConfirm: PropTypes.func.isRequired,
onToggleNotifications: PropTypes.func.isRequired,
onClose: PropTypes.func.isRequired,
intl: PropTypes.object.isRequired,
};
componentDidMount() {
this.button.focus();
}
handleClick = () => {
this.props.onClose();
this.props.onConfirm(this.props.account, this.props.notifications);
this.props.onConfirm(this.props.account)
}
handleCancel = () => {
this.props.onClose();
}
setRef = (c) => {
this.button = c;
}
toggleNotifications = () => {
this.props.onToggleNotifications();
handleClose = () => {
this.props.onClose()
}
render() {
const { account, notifications, intl } = this.props;
const { account, intl } = this.props
const title = intl.formatMessage(messages.title, {
name: !!account ? account.get('acct') : '',
})
const message = intl.formatMessage(messages.muteMessage, {
name: !!account ? account.get('acct') : '',
})
return (
<div className='modal-root__modal mute-modal'>
<div className='mute-modal__container'>
<p>
{intl.formatMessage(messages.muteMessage, { name: <strong>@{account.get('acct')}</strong> })}
</p>
<div>
<label htmlFor='mute-modal__hide-notifications-checkbox'>
{intl.formatMessage(messages.hideNotifications)}
{' '}
<ToggleSwitch id='mute-modal__hide-notifications-checkbox' checked={notifications} onChange={this.toggleNotifications} />
</label>
</div>
</div>
<div className='mute-modal__action-bar'>
<Button onClick={this.handleCancel} className='mute-modal__cancel-button'>
{intl.formatMessage(messages.cancel)}
</Button>
<Button onClick={this.handleClick} ref={this.setRef}>
{intl.formatMessage(messages.confirm)}
</Button>
</div>
</div>
);
<ConfirmationModal
title={title}
message={message}
confirm={intl.formatMessage(messages.mute)}
onClose={this.handleClose}
onConfirm={this.handleClick}
/>
)
}
}

View File

@ -0,0 +1,59 @@
import { defineMessages, injectIntl } from 'react-intl'
import ImmutablePureComponent from 'react-immutable-pure-component'
import Button from '../button'
import Text from '../text'
import ModalLayout from './modal_layout'
const messages = defineMessages({
title: { id: 'promo.gab_pro', defaultMessage: 'Upgrade to GabPRO' },
text: { id: 'pro_upgrade_modal.text', defaultMessage: 'Gab is fully funded by people like you. Please consider supporting us on our mission to defend free expression online for all people.' },
benefits: { id: 'pro_upgrade_modal.benefits', defaultMessage: 'Here are just some of the benefits that thousands of GabPRO members receive:' },
})
export default
@injectIntl
class HomeTimelineSettingsModal extends ImmutablePureComponent {
static propTypes = {
intl: PropTypes.object.isRequired,
}
render() {
const { intl } = this.props
return (
<div>
<Text>
{intl.formatMessage(messages.text)}
</Text>
<Text>
{intl.formatMessage(messages.benefits)}
</Text>
<div className={[_s.default, _s.my10].join(' ')}>
<Text> Schedule Posts</Text>
<Text> Get Verified</Text>
<Text> Create Groups</Text>
<Text> Larger Video and Image Uploads</Text>
<Text> Receive the PRO Badge</Text>
<Text> Remove in-feed promotions</Text>
</div>
<Button
centered
backgroundColor='brand'
color='white'
icon='pro'
href='https://pro.gab.com'
className={_s.justifyContentCenter}
iconClassName={[_s.mr5, _s.fillColorWhite].join(' ')}
>
<Text color='inherit' weight='bold' align='center'>
{intl.formatMessage(messages.title)}
</Text>
</Button>
</div>
)
}
}

View File

@ -1,14 +1,16 @@
import ImmutablePropTypes from 'react-immutable-proptypes';
import { defineMessages, injectIntl } from 'react-intl';
import { OrderedSet } from 'immutable';
import ImmutablePureComponent from 'react-immutable-pure-component';
import { changeReportComment, changeReportForward, submitReport } from '../../actions/reports';
import { expandAccountTimeline } from '../../actions/timelines';
import { makeGetAccount } from '../../selectors';
import StatusCheckBox from '../status_check_box';
import ToggleSwitch from '../toggle_switch';
import Button from '../button';
import IconButton from '../icon_button';
import { defineMessages, injectIntl } from 'react-intl'
import { OrderedSet } from 'immutable'
import ImmutablePropTypes from 'react-immutable-proptypes'
import ImmutablePureComponent from 'react-immutable-pure-component'
import { changeReportComment, changeReportForward, submitReport } from '../../actions/reports'
import { expandAccountTimeline } from '../../actions/timelines'
import { makeGetAccount } from '../../selectors'
import ModalLayout from './modal_layout'
import Button from '../button'
import StatusCheckBox from '../status_check_box'
import Switch from '../switch'
import Text from '../Text'
import Textarea from '../Textarea'
const messages = defineMessages({
close: { id: 'lightbox.close', defaultMessage: 'Close' },
@ -82,29 +84,36 @@ class ReportModal extends ImmutablePureComponent {
}
render () {
const { account, comment, intl, statusIds, isSubmitting, forward, onClose } = this.props;
const {
account,
comment,
intl,
statusIds,
isSubmitting,
forward,
onClose
} = this.props
if (!account) {
return null;
}
if (!account) return null
const domain = account.get('acct').split('@')[1];
return (
<div className='modal-root__modal report-modal'>
<div className='report-modal__target'>
<IconButton className='media-modal__close' title={intl.formatMessage(messages.close)} icon='times' onClick={onClose} size={16} />
{intl.formatMessage(messages.target, {
target: <strong>{account.get('acct')}</strong>
<ModalLayout
noPadding
title={intl.formatMessage(messages.target, {
target: account.get('acct')
})}
</div>
>
<div className='report-modal__container'>
<div className='report-modal__comment'>
<p>{intl.formatMessage(messages.hint)}</p>
<div className={[_s.default, _s.flexRow].join(' ')}>
<div className={[_s.default, _s.width50PC, _s.py10, _s.px15, _s.borderRight1PX, _s.borderColorSecondary].join(' ')}>
<Text color='secondary' size='small'>
{intl.formatMessage(messages.hint)}
</Text>
<textarea
className='setting-text light'
<div className={_s.my10}>
<Textarea
placeholder={intl.formatMessage(messages.placeholder)}
value={comment}
onChange={this.handleCommentChange}
@ -112,32 +121,53 @@ class ReportModal extends ImmutablePureComponent {
disabled={isSubmitting}
autoFocus
/>
</div>
{domain && (
{
domain &&
<div>
<p>{intl.formatMessage(messages.forwardHint)}</p>
<Text color='secondary' size='small'>
{intl.formatMessage(messages.forwardHint)}
</Text>
<div className='setting-toggle'>
<ToggleSwitch id='report-forward' checked={forward} disabled={isSubmitting} onChange={this.handleForwardChange} />
<label htmlFor='report-forward' className='setting-toggle__label'>
{intl.formatMessage(messages.forward, {
target: domain
<Switch
id='report-forward'
checked={forward}
disabled={isSubmitting}
onChange={this.handleForwardChange}
label={intl.formatMessage(messages.forward, {
target: domain,
})}
</label>
labelProps={{
size: 'small',
color: 'secondary',
}}
/>
</div>
</div>
)}
}
<Button disabled={isSubmitting} text={intl.formatMessage(messages.submit)} onClick={this.handleSubmit} />
<Button
disabled={isSubmitting}
onClick={this.handleSubmit}
className={_s.marginTopAuto}
>
{intl.formatMessage(messages.submit)}
</Button>
</div>
<div className='report-modal__statuses'>
<div>
{statusIds.map(statusId => <StatusCheckBox id={statusId} key={statusId} disabled={isSubmitting} />)}
</div>
<div className={[_s.default, _s.width50PC].join(' ')}>
<div className={[_s.default, _s.heightMax80VH, _s.overflowYScroll, _s.pr15, _s.py10].join(' ')}>
{
statusIds.map(statusId => (
<StatusCheckBox id={statusId} key={statusId} disabled={isSubmitting} />
))
}
</div>
</div>
</div>
</ModalLayout>
);
}

View File

@ -0,0 +1,72 @@
import { injectIntl, defineMessages } from 'react-intl'
import { muteAccount } from '../../actions/accounts'
const messages = defineMessages({
muteMessage: { id: 'confirmations.mute.message', defaultMessage: 'Are you sure you want to mute {name}?' },
cancel: { id: 'confirmation_modal.cancel', defaultMessage: 'Cancel' },
confirm: { id: 'confirmations.mute.confirm', defaultMessage: 'Mute' },
})
const mapStateToProps = state => {
return {
isSubmitting: state.getIn(['reports', 'new', 'isSubmitting']),
account: state.getIn(['mutes', 'new', 'account']),
}
}
const mapDispatchToProps = dispatch => {
return {
onConfirm(account, notifications) {
dispatch(muteAccount(account.get('id'), notifications))
},
}
}
export default
@connect(mapStateToProps, mapDispatchToProps)
@injectIntl
class UnfollowModal extends PureComponent {
static propTypes = {
isSubmitting: PropTypes.bool.isRequired,
account: PropTypes.object.isRequired,
onConfirm: PropTypes.func.isRequired,
intl: PropTypes.object.isRequired,
}
componentDidMount() {
this.button.focus()
}
handleClick = () => {
this.props.onClose()
this.props.onConfirm(this.props.account, this.props.notifications)
}
handleCancel = () => {
this.props.onClose()
}
render() {
const { account, intl } = this.props
// , {
// message: <FormattedMessage id='confirmations.unfollow.message' defaultMessage='Are you sure you want to unfollow {name}?' values={{ name: <strong>@{account.get('acct')}</strong> }} />,
// confirm: intl.formatMessage(messages.unfollowConfirm),
// onConfirm: () => dispatch(unfollowAccount(account.get('id'))),
// }));
return (
<ConfirmationModal
title={`Mute @${account.get('acct')}`}
message={<FormattedMessage id='confirmations.mute.message' defaultMessage='Are you sure you want to mute @{name}?' values={{ name: account.get('acct') }} />}
confirm={<FormattedMessage id='mute' defaultMessage='Mute' />}
onConfirm={() => {
// dispatch(blockDomain(domain))
// dispatch(blockDomain(domain))
}}
/>
)
}
}

View File

@ -28,7 +28,7 @@ class MediaGalleryPanel extends ImmutablePureComponent {
static propTypes = {
dispatch: PropTypes.func.isRequired,
accountId: PropTypes.string,
account: ImmutablePropTypes.map.isRequired,
account: ImmutablePropTypes.map,
attachments: ImmutablePropTypes.list.isRequired,
intl: PropTypes.object.isRequired,
}
@ -54,8 +54,6 @@ class MediaGalleryPanel extends ImmutablePureComponent {
attachments
} = this.props
console.log("account, attachments:", account, attachments)
if (!account || !attachments) return null
if (attachments.size === 0) return null

View File

@ -39,7 +39,7 @@ class ProfileInfoPanel extends ImmutablePureComponent {
static propTypes = {
identityProofs: ImmutablePropTypes.list,
account: ImmutablePropTypes.map.isRequired,
account: ImmutablePropTypes.map,
intl: PropTypes.object.isRequired,
}
@ -50,15 +50,15 @@ class ProfileInfoPanel extends ImmutablePureComponent {
render() {
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' })
if (!account) return null
const fields = account.get('fields')
const content = { __html: account.get('note_emojified') }
const memberSinceDate = intl.formatDate(account.get('created_at'), { month: 'long', year: 'numeric' })
const hasNote = !!content ? (account.get('note').length > 0 && account.get('note') !== '<p></p>') : false
return (
<PanelLayout title={intl.formatMessage(messages.title)}>
{
!!account &&
<div className={[_s.default].join(' ')}>
{
hasNote &&
@ -136,7 +136,6 @@ class ProfileInfoPanel extends ImmutablePureComponent {
)}
</div>
}
</PanelLayout>
)
}

View File

@ -10,7 +10,7 @@ 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' },
likes: { id: 'likes', defaultMessage: 'Likes' },
})
export default
@ -18,7 +18,7 @@ export default
class ProfileStatsPanel extends ImmutablePureComponent {
static propTypes = {
account: ImmutablePropTypes.list.isRequired,
account: ImmutablePropTypes.map,
intl: PropTypes.object.isRequired,
}
@ -50,9 +50,9 @@ class ProfileStatsPanel extends ImmutablePureComponent {
{
account.get('id') === me &&
<UserStat
title={intl.formatMessage(messages.favorites)}
title={intl.formatMessage(messages.likes)}
value={shortNumberFormat(favouritesCount)}
to={`/${account.get('acct')}/favorites`}
to={`/${account.get('acct')}/likes`}
/>
}
</div>

View File

@ -2,19 +2,24 @@ 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 classNames from 'classnames/bind'
import { me } from '../../initial_state'
import { makeGetAccount } from '../../selectors'
import { shortNumberFormat } from '../../utils/numbers'
import Button from '../button'
import DisplayName from '../display_name'
import Avatar from '../avatar'
import Image from '../image'
import UserStat from '../user_stat'
import PanelLayout from './panel_layout'
const cx = classNames.bind(_s)
const messages = defineMessages({
gabs: { id: 'account.posts', defaultMessage: 'Gabs' },
followers: { id: 'account.followers', defaultMessage: 'Followers' },
follows: { id: 'account.follows', defaultMessage: 'Follows' }
follows: { id: 'account.follows', defaultMessage: 'Follows' },
edit_profile: { id: 'account.edit_profile', defaultMessage: 'Edit profile' },
})
const mapStateToProps = state => {
@ -32,15 +37,51 @@ class UserPanel extends ImmutablePureComponent {
intl: PropTypes.object.isRequired,
}
state = {
hovering: false,
}
handleOnMouseEnter = () => {
this.setState({ hovering: true })
}
handleOnMouseLeave = () => {
this.setState({ hovering: false })
}
render() {
const { account, intl } = this.props
const { hovering } = this.state
const buttonClasses = cx({
positionAbsolute: 1,
mt10: 1,
mr10: 1,
top0: 1,
right0: 1,
displayNone: !hovering,
})
return (
<PanelLayout noPadding>
<div
className={[_s.default, _s.height122PX].join(' ')}
onMouseEnter={() => this.handleOnMouseEnter()}
onMouseLeave={() => this.handleOnMouseLeave()}
>
<Image
className={_s.height122PX}
src={account.get('header_static')}
/>
<Button
color='secondary'
backgroundColor='secondary'
radiusSmall
className={buttonClasses}
>
{intl.formatMessage(messages.edit_profile)}
</Button>
</div>
<NavLink
className={[_s.default, _s.flexRow, _s.py10, _s.px15, _s.noUnderline].join(' ')}

View File

@ -21,11 +21,11 @@ const mapStateToProps = state => ({
const mapDispatchToProps = (dispatch, { status, items }) => ({
onOpen(id, onItemClick, popoverPlacement, keyboard) {
dispatch(isUserTouching() ? openModal('ACTIONS', {
status,
actions: items,
onClick: onItemClick,
}) : openPopover(id, popoverPlacement, keyboard))
// dispatch(isUserTouching() ? openModal('ACTIONS', {
// status,
// actions: items,
// onClick: onItemClick,
// }) : openPopover(id, popoverPlacement, keyboard))
},
onClose(id) {
dispatch(closeModal())
@ -42,9 +42,6 @@ class PopoverBase extends ImmutablePureComponent {
}
static propTypes = {
icon: PropTypes.string.isRequired,
items: PropTypes.array.isRequired,
size: PropTypes.number.isRequired,
title: PropTypes.string,
disabled: PropTypes.bool,
status: ImmutablePropTypes.map,
@ -68,28 +65,12 @@ class PopoverBase extends ImmutablePureComponent {
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
@ -127,13 +108,8 @@ class PopoverBase extends ImmutablePureComponent {
render() {
const {
icon,
children,
visible,
items,
size,
title,
disabled,
position,
openPopoverType,
targetRef,

View File

@ -1,11 +1,16 @@
import Block from '../block'
export default class PopoverLayout extends PureComponent {
static propTypes = {
children: PropTypes.node,
className: PropTypes.string,
}
render() {
const { children } = this.props
const { children, className } = this.props
return (
<div>
<div className={className}>
<Block>
{children}
</Block>

View File

@ -11,7 +11,7 @@ import {
import {
mentionCompose,
} from '../../actions/compose'
import { initMuteModal } from '../../actions/mutes'
import { muteAccount } from '../../actions/accounts'
import { initReport } from '../../actions/reports'
import { openModal } from '../../actions/modal'
import { blockDomain, unblockDomain } from '../../actions/domain_blocks'
@ -23,7 +23,6 @@ import List from '../list'
const messages = defineMessages({
unfollowConfirm: { id: 'confirmations.unfollow.confirm', defaultMessage: 'Unfollow' },
blockConfirm: { id: 'confirmations.block.confirm', defaultMessage: 'Block' },
blockDomainConfirm: { id: 'confirmations.domain_block.confirm', defaultMessage: 'Hide entire domain' },
blockAndReport: { id: 'confirmations.block.block_and_report', defaultMessage: 'Block & Report' },
unfollow: { id: 'account.unfollow', defaultMessage: 'Unfollow' },
@ -45,14 +44,12 @@ const messages = defineMessages({
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' },
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' },
mutes: { id: 'navigation_bar.mutes', defaultMessage: 'Muted users' },
endorse: { id: 'account.endorse', defaultMessage: 'Feature on profile' },
unendorse: { id: 'account.unendorse', defaultMessage: 'Don\'t feature on profile' },
admin_account: { id: 'status.admin_account', defaultMessage: 'Open moderation interface for @{name}' },
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' },
add_or_remove_from_shortcuts: { id: 'account.add_or_remove_from_shortcuts', defaultMessage: 'Add or Remove from shortcuts' },
accountFollowsYou: { id: 'account.follows_you', defaultMessage: 'Follows you' },
accountBlocked: { id: 'account.blocked', defaultMessage: 'Blocked' },
accountMuted: { id: 'account.muted', defaultMessage: 'Muted' },
@ -75,16 +72,14 @@ const mapDispatchToProps = (dispatch, { intl }) => ({
onFollow(account) {
if (account.getIn(['relationship', 'following']) || account.getIn(['relationship', 'requested'])) {
if (unfollowModal) {
dispatch(openModal('CONFIRM', {
message: <FormattedMessage id='confirmations.unfollow.message' defaultMessage='Are you sure you want to unfollow {name}?' values={{ name: <strong>@{account.get('acct')}</strong> }} />,
confirm: intl.formatMessage(messages.unfollowConfirm),
onConfirm: () => dispatch(unfollowAccount(account.get('id'))),
}));
dispatch(openModal('UNFOLLOW', {
accountId: account.get('id'),
}))
} else {
dispatch(unfollowAccount(account.get('id')));
dispatch(unfollowAccount(account.get('id')))
}
} else {
dispatch(followAccount(account.get('id')));
dispatch(followAccount(account.get('id')))
}
},
@ -92,21 +87,14 @@ const mapDispatchToProps = (dispatch, { intl }) => ({
if (account.getIn(['relationship', 'blocking'])) {
dispatch(unblockAccount(account.get('id')));
} else {
dispatch(openModal('CONFIRM', {
message: <FormattedMessage id='confirmations.block.message' defaultMessage='Are you sure you want to block {name}?' values={{ name: <strong>@{account.get('acct')}</strong> }} />,
confirm: intl.formatMessage(messages.blockConfirm),
onConfirm: () => dispatch(blockAccount(account.get('id'))),
secondary: intl.formatMessage(messages.blockAndReport),
onSecondary: () => {
dispatch(blockAccount(account.get('id')));
dispatch(initReport(account));
},
dispatch(openModal('BLOCK_ACCOUNT', {
accountId: account.get('id'),
}));
}
},
onMention (account, router) {
dispatch(mentionCompose(account, router));
onMention(account) {
dispatch(mentionCompose(account));
},
onRepostToggle(account) {
@ -117,14 +105,6 @@ const mapDispatchToProps = (dispatch, { intl }) => ({
}
},
onEndorseToggle (account) {
if (account.getIn(['relationship', 'endorsed'])) {
dispatch(unpinAccount(account.get('id')));
} else {
dispatch(pinAccount(account.get('id')));
}
},
onReport(account) {
dispatch(initReport(account));
},
@ -133,15 +113,15 @@ const mapDispatchToProps = (dispatch, { intl }) => ({
if (account.getIn(['relationship', 'muting'])) {
dispatch(unmuteAccount(account.get('id')));
} else {
dispatch(initMuteModal(account));
dispatch(openModal('MUTE', {
accountId: account.get('id'),
}))
}
},
onBlockDomain(domain) {
dispatch(openModal('CONFIRM', {
message: <FormattedMessage 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.' values={{ domain: <strong>{domain}</strong> }} />,
confirm: intl.formatMessage(messages.blockDomainConfirm),
onConfirm: () => dispatch(blockDomain(domain)),
dispatch(openModal('BLOCK_DOMAIN', {
domain,
}));
},
@ -168,73 +148,163 @@ class ProfileOptionsPopover extends PureComponent {
let menu = [];
if (!account) {
return [];
}
if (!account) return menu
if (account.get('id') === me) return menu
if ('share' in navigator) {
menu.push({ title: intl.formatMessage(messages.share, { name: account.get('username') }), onClick: this.handleShare });
menu.push({
hideArrow: true,
icon: 'share',
title: intl.formatMessage(messages.share, { name: account.get('username') }),
onClick: this.handleShare
});
}
if (account.get('id') === me) {
menu.push({ title: intl.formatMessage(messages.edit_profile), href: '/settings/profile' });
menu.push({ title: intl.formatMessage(messages.preferences), href: '/settings/preferences' });
menu.push({ title: intl.formatMessage(messages.follow_requests), to: '/follow_requests' });
menu.push({ title: intl.formatMessage(messages.mutes), to: '/mutes' });
menu.push({ title: intl.formatMessage(messages.blocks), to: '/blocks' });
menu.push({ title: intl.formatMessage(messages.domain_blocks), to: '/domain_blocks' });
} else {
menu.push({ title: intl.formatMessage(messages.mention, { name: account.get('acct') }), onClick: this.props.onMention });
menu.push({
hideArrow: true,
icon: 'comment',
title: intl.formatMessage(messages.mention, { name: account.get('acct') }),
onClick: this.handleOnMention
});
if (account.getIn(['relationship', 'following'])) {
if (account.getIn(['relationship', 'showing_reblogs'])) {
menu.push({ title: intl.formatMessage(messages.hideReposts, { name: account.get('username') }), onClick: this.props.onRepostToggle });
} else {
menu.push({ title: intl.formatMessage(messages.showReposts, { name: account.get('username') }), onClick: this.props.onRepostToggle });
const showingReblogs = account.getIn(['relationship', 'showing_reblogs'])
menu.push({
hideArrow: true,
icon: 'repost',
title: intl.formatMessage(showingReblogs ? messages.hideReposts : messages.showReposts, {
name: account.get('username')
}),
onClick: this.handleRepostToggle,
})
}
menu.push({ title: intl.formatMessage(messages.add_or_remove_from_list), onClick: this.props.onAddToList });
menu.push({ title: intl.formatMessage(account.getIn(['relationship', 'endorsed']) ? messages.unendorse : messages.endorse), onClick: this.props.onEndorseToggle });
}
const isMuting = account.getIn(['relationship', 'muting'])
menu.push({
hideArrow: true,
icon: 'audio-mute',
title: intl.formatMessage(isMuting ? messages.unmute : messages.mute, {
name: account.get('username')
}),
onClick: this.handleMute,
})
if (account.getIn(['relationship', 'muting'])) {
menu.push({ title: intl.formatMessage(messages.unmute, { name: account.get('username') }), onClick: this.props.onMute });
} else {
menu.push({ title: intl.formatMessage(messages.mute, { name: account.get('username') }), onClick: this.props.onMute });
}
const isBlocking = account.getIn(['relationship', 'blocking'])
menu.push({
hideArrow: true,
icon: 'block',
title: intl.formatMessage(isBlocking ? messages.unblock : messages.block, {
name: account.get('username')
}),
onClick: this.handleBlock
})
if (account.getIn(['relationship', 'blocking'])) {
menu.push({ title: intl.formatMessage(messages.unblock, { name: account.get('username') }), onClick: this.props.onBlock });
} else {
menu.push({ title: intl.formatMessage(messages.block, { name: account.get('username') }), onClick: this.props.onBlock });
}
menu.push({ title: intl.formatMessage(messages.report, { name: account.get('username') }), onClick: this.props.onReport });
}
menu.push({
hideArrow: true,
icon: 'report',
title: intl.formatMessage(messages.report, { name: account.get('username') }),
onClick: this.handleReport
})
if (account.get('acct') !== account.get('username')) {
const domain = account.get('acct').split('@')[1];
if (account.getIn(['relationship', 'domain_blocking'])) {
menu.push({ title: intl.formatMessage(messages.unblockDomain, { domain }), onClick: this.props.onUnblockDomain });
} else {
menu.push({ title: intl.formatMessage(messages.blockDomain, { domain }), onClick: this.props.onBlockDomain });
}
const isBlockingDomain = account.getIn(['relationship', 'domain_blocking'])
menu.push({
hideArrow: true,
icon: 'block',
title: intl.formatMessage(isBlockingDomain ? messages.unblockDomain : messages.blockDomain, {
domain,
}),
onClick: this.handleUnblockDomain
})
}
if (account.get('id') !== me && isStaff) {
menu.push({ title: intl.formatMessage(messages.admin_account, { name: account.get('username') }), href: `/admin/accounts/${account.get('id')}` });
menu.push({
hideArrow: true,
icon: 'list',
title: intl.formatMessage(messages.add_or_remove_from_list),
onClick: this.handleAddToList
})
menu.push({
hideArrow: true,
icon: 'circle',
title: intl.formatMessage(messages.add_or_remove_from_shortcuts),
onClick: this.handleAddToShortcuts
})
if (isStaff) {
menu.push({
hideArrow: true,
icon: 'circle',
title: intl.formatMessage(messages.admin_account),
href: `/admin/accounts/${account.get('id')}`
})
}
return menu;
return menu
}
handleShare = () => {
// : todo :
}
handleFollow = () => {
this.props.onFollow(this.props.account);
}
handleBlock = () => {
this.props.onBlock(this.props.account);
}
handleOnMention = () => {
this.props.onMention(this.props.account);
}
handleReport = () => {
this.props.onReport(this.props.account);
}
handleRepostToggle = () => {
this.props.onRepostToggle(this.props.account);
}
handleMute = () => {
this.props.onMute(this.props.account);
}
handleBlockDomain = () => {
const domain = this.props.account.get('acct').split('@')[1];
// : todo : alert
if (!domain) return;
this.props.onBlockDomain(domain);
}
handleUnblockDomain = () => {
const domain = this.props.account.get('acct').split('@')[1];
// : todo : alert
if (!domain) return;
this.props.onUnblockDomain(domain);
}
handleAddToList = () => {
this.props.onAddToList(this.props.account);
}
handleAddToShortcuts = () => {
// : todo :
}
render() {
const listItems = this.makeMenu()
return (
<PopoverLayout>
<PopoverLayout className={_s.width250PX}>
<List
scrollKey='profile_options'
items={listItems}

View File

@ -4,7 +4,7 @@ import List from '../list'
export default class SidebarMorePopover extends PureComponent {
render() {
return (
<PopoverLayout>
<PopoverLayout className={_s.width240PX}>
<List
scrollKey='profile_options'
items={[

View File

@ -1,3 +1,4 @@
import axios from 'axios'
import ImmutablePropTypes from 'react-immutable-proptypes'
import ImmutablePureComponent from 'react-immutable-pure-component'
import { defineMessages, injectIntl, FormattedMessage } from 'react-intl'
@ -11,7 +12,7 @@ import {
import { openPopover, closePopover } from '../actions/popover'
import { initReport } from '../actions/reports'
import { openModal } from '../actions/modal'
import { unfollowModal } from '../initial_state'
import { unfollowModal, me } from '../initial_state'
import Avatar from './avatar'
import Image from './image'
import Text from './text'
@ -22,6 +23,10 @@ import TabBar from './tab_bar'
const cx = classNames.bind(_s)
const messages = defineMessages({
follow: { id: 'follow', defaultMessage: 'Follow' },
unfollow: { id: 'unfollow', defaultMessage: 'Unfollow' },
requested: { id: 'requested', defaultMessage: 'Requested' },
unblock: { id: 'unblock', defaultMessage: 'Unblock' },
followers: { id: 'account.followers', defaultMessage: 'Followers' },
follows: { id: 'account.follows', defaultMessage: 'Follows' },
profile: { id: 'account.profile', defaultMessage: 'Profile' },
@ -43,10 +48,8 @@ const mapDispatchToProps = (dispatch, { intl }) => ({
onFollow(account) {
if (account.getIn(['relationship', 'following']) || account.getIn(['relationship', 'requested'])) {
if (unfollowModal) {
dispatch(openModal('CONFIRM', {
message: <FormattedMessage id='confirmations.unfollow.message' defaultMessage='Are you sure you want to unfollow {name}?' values={{ name: <strong>@{account.get('acct')}</strong> }} />,
confirm: intl.formatMessage(messages.unfollowConfirm),
onConfirm: () => dispatch(unfollowAccount(account.get('id'))),
dispatch(openModal('UNFOLLOW', {
accountId: account.get('id'),
}));
} else {
dispatch(unfollowAccount(account.get('id')));
@ -60,15 +63,8 @@ const mapDispatchToProps = (dispatch, { intl }) => ({
if (account.getIn(['relationship', 'blocking'])) {
dispatch(unblockAccount(account.get('id')));
} else {
dispatch(openModal('CONFIRM', {
message: <FormattedMessage id='confirmations.block.message' defaultMessage='Are you sure you want to block {name}?' values={{ name: <strong>@{account.get('acct')}</strong> }} />,
confirm: intl.formatMessage(messages.blockConfirm),
onConfirm: () => dispatch(blockAccount(account.get('id'))),
secondary: intl.formatMessage(messages.blockAndReport),
onSecondary: () => {
dispatch(blockAccount(account.get('id')));
dispatch(initReport(account));
},
dispatch(openModal('BLOCK_ACCOUNT', {
accountId: account.get('id'),
}));
}
},
@ -105,12 +101,16 @@ class ProfileHeader extends ImmutablePureComponent {
})
}
handleStartChat = () => {
handleFollow = () => {
this.props.onFollow(this.props.account)
}
handleFollow = () => {
handleUnrequest = () => {
//
}
handleBlock = () => {
// this.props.onBlock(this.props.account)
}
makeInfo() {
@ -195,6 +195,37 @@ class ProfileHeader extends ImmutablePureComponent {
const avatarSize = headerMissing ? '75' : '150'
let buttonText;
let buttonOptions;
if (!account) {
}
else {
if (account.get('id') !== me && account.get('relationship', null) !== null) {
const following = account.getIn(['relationship', 'following'])
const requested = account.getIn(['relationship', 'requested'])
const blocking = account.getIn(['relationship', 'blocking'])
if (requested || blocking) {
buttonText = intl.formatMessage(requested ? messages.requested : messages.unblock)
buttonOptions = {
narrow: true,
onClick: requested ? this.handleUnrequest : this.handleBlock,
color: 'primary',
backgroundColor: 'tertiary',
}
} else if (!account.get('moved') || following) {
buttonOptions = {
narrow: true,
outline: !following,
color: !following ? 'brand' : 'white',
backgroundColor: !following ? 'none' : 'brand',
onClick: this.handleFollow,
}
buttonText = intl.formatMessage(following ? messages.unfollow : messages.follow)
}
}
}
return (
<div className={[_s.default, _s.z1, _s.width100PC].join(' ')}>
@ -225,6 +256,30 @@ class ProfileHeader extends ImmutablePureComponent {
<TabBar tabs={tabs} large />
</div>
{
account && account.get('id') === me &&
<div className={[_s.default, _s.flexRow, _s.marginLeftAuto, _s.py5].join(' ')}>
<Button
outline
backgroundColor='none'
color='brand'
className={[_s.justifyContentCenter, _s.alignItemsCenter].join(' ')}
href=''
>
<Text
color='inherit'
weight='bold'
size='medium'
className={[_s.px15].join(' ')}
>
Edit Profile
</Text>
</Button>
</div>
}
{
account && account.get('id') !== me &&
<div className={[_s.default, _s.flexRow, _s.marginLeftAuto, _s.py5].join(' ')}>
<div ref={this.setOpenMoreNodeRef}>
<Button
@ -240,7 +295,9 @@ class ProfileHeader extends ImmutablePureComponent {
/>
</div>
<form action='https://chat.gab.com/private-message' method='POST'>
<Button
type='submit'
outline
icon='chat'
iconWidth='18px'
@ -249,26 +306,28 @@ class ProfileHeader extends ImmutablePureComponent {
color='brand'
backgroundColor='none'
className={[_s.justifyContentCenter, _s.alignItemsCenter, _s.mr10, _s.px10].join(' ')}
onClick={this.handleStartChat}
/>
<input type='hidden' value={account.get('username')} name='username' />
</form>
<Button
{...buttonOptions}
className={[_s.justifyContentCenter, _s.alignItemsCenter].join(' ')}
onClick={this.handleFollow}
>
<span className={[_s.px15].join(' ')}>
<Text
color='white'
color='inherit'
weight='bold'
size='medium'
className={[_s.px15].join(' ')}
>
Follow
{buttonText}
</Text>
</span>
</Button>
</div>
}
</div>
</div>
</div>

View File

@ -0,0 +1,43 @@
import ImmutablePropTypes from 'react-immutable-proptypes'
import ImmutablePureComponent from 'react-immutable-pure-component'
import Switch from './switch'
export default class SettingSwitch extends ImmutablePureComponent {
static propTypes = {
prefix: PropTypes.string,
settings: ImmutablePropTypes.map.isRequired,
settingPath: PropTypes.array.isRequired,
description: PropTypes.string,
label: PropTypes.string.isRequired,
onChange: PropTypes.func.isRequired,
}
onChange = ({ target }) => {
this.props.onChange(this.props.settingPath, target.checked)
}
render () {
const {
prefix,
settings,
settingPath,
label,
description
} = this.props
const id = ['setting-toggle', prefix, ...settingPath].filter(Boolean).join('-')
return (
<Switch
description={description}
label={label}
id={id}
checked={settings.getIn(settingPath)}
onChange={this.onChange}
onKeyDown={this.onKeyDown}
/>
)
}
}

View File

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

View File

@ -1,32 +0,0 @@
import ImmutablePropTypes from 'react-immutable-proptypes';
import ImmutablePureComponent from 'react-immutable-pure-component';
import ToggleSwitch from '../toggle_switch';
export default class SettingToggle extends ImmutablePureComponent {
static propTypes = {
prefix: PropTypes.string,
settings: ImmutablePropTypes.map.isRequired,
settingPath: PropTypes.array.isRequired,
label: PropTypes.node.isRequired,
onChange: PropTypes.func.isRequired,
}
onChange = ({ target }) => {
this.props.onChange(this.props.settingPath, target.checked);
}
render () {
const { prefix, settings, settingPath, label } = this.props;
const id = ['setting-toggle', prefix, ...settingPath].filter(Boolean).join('-');
return (
<div className='setting-toggle'>
<ToggleSwitch id={id} checked={settings.getIn(settingPath)} onChange={this.onChange} onKeyDown={this.onKeyDown} />
<label htmlFor={id} className='setting-toggle__label'>{label}</label>
</div>
);
}
}

View File

@ -1,12 +0,0 @@
.setting-toggle {
display: block;
line-height: 24px;
&__label {
color: $darker-text-color;
display: inline-block;
margin-bottom: 14px;
margin-left: 8px;
vertical-align: middle;
}
}

View File

@ -514,7 +514,7 @@ class Status extends ImmutablePureComponent {
<StatusActionBar status={status} account={account} {...other} />
<div className={[_s.default, _s.borderTop1PX, _s.borderColorSecondary, _s.pt10, _s.px15, _s.mb10].join(' ')}>
{/*<ComposeFormContainer replyToId={status.get('id')} shouldCondense />*/}
<ComposeFormContainer replyToId={status.get('id')} shouldCondense />
</div>
</div>
</div>

View File

@ -2,11 +2,11 @@ import ImmutablePropTypes from 'react-immutable-proptypes';
import ImmutablePureComponent from 'react-immutable-pure-component';
import { Set as ImmutableSet } from 'immutable';
import noop from 'lodash/noop';
import { toggleStatusReport } from '../../actions/reports';
import { MediaGallery, Video } from '../../features/ui/util/async-components';
import Bundle from '../../features/ui/util/bundle';
import StatusContent from '../status_content';
import ToggleSwitch from '../toggle_switch';
import { toggleStatusReport } from '../actions/reports';
import { MediaGallery, Video } from '../features/ui/util/async-components';
import Bundle from '../features/ui/util/bundle';
import StatusContent from './status_content';
import Switch from './switch';
const mapStateToProps = (state, { id }) => ({
status: state.getIn(['statuses', id]),
@ -34,9 +34,7 @@ class StatusCheckBox extends ImmutablePureComponent {
const { status, checked, onToggle, disabled } = this.props;
let media = null;
if (status.get('reblog')) {
return null;
}
if (status.get('reblog')) return null
if (status.get('media_attachments').size > 0) {
if (status.get('media_attachments').some(item => item.get('type') === 'unknown')) {
@ -72,17 +70,17 @@ class StatusCheckBox extends ImmutablePureComponent {
}
return (
<div className='status-check-box'>
<div className='status-check-box__status'>
<div className={[_s.default, _s.flexRow].join(' ')}>
<div className={[_s.default].join(' ')}>
<StatusContent status={status} />
{media}
</div>
<div className='status-check-box-toggle'>
<ToggleSwitch checked={checked} onChange={onToggle} disabled={disabled} />
<div className={[_s.default, _s.marginLeftAuto].join(' ')}>
<Switch checked={checked} onChange={onToggle} disabled={disabled} />
</div>
</div>
);
)
}
}

View File

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

View File

@ -1,34 +0,0 @@
.status-check-box {
display: flex;
border-bottom: 1px solid $ui-secondary-color;
&__status {
margin: 10px 0 10px 10px;
flex: 1;
.media-gallery {
max-width: 250px;
}
.status__content {
padding: 0;
white-space: normal;
}
.video-player {
margin-top: 8px;
max-width: 250px;
}
.media-gallery__item-thumbnail {
cursor: default;
}
}
&__toggle {
flex: 0 0 auto;
padding: 10px;
@include flex(center, center);
}
}

View File

@ -130,15 +130,15 @@ class StatusContent extends ImmutablePureComponent {
if (this.props.onExpandedToggle) {
// The parent manages the state
this.props.onExpandedToggle();
this.props.onExpandedToggle()
} else {
this.setState({ hidden: !this.state.hidden });
this.setState({ hidden: !this.state.hidden })
}
}
handleCollapsedClick = (e) => {
handleReadMore = (e) => {
e.preventDefault();
this.setState({ collapsed: !this.state.collapsed });
this.setState({ collapsed: false });
}
setRef = (c) => {
@ -156,7 +156,8 @@ class StatusContent extends ImmutablePureComponent {
}
render () {
const { status, intl, isComment } = this.props;
const { status, intl, isComment } = this.props
const { collapsed } = this.state
if (status.get('content').length === 0) return null;
@ -213,12 +214,18 @@ class StatusContent extends ImmutablePureComponent {
mb15: hasMarginBottom,
})
const statusContentClasses = cx({
statusContent: 1,
height220PX: collapsed,
overflowHidden: collapsed,
})
return (
<div className={containerClasses}>
<div
ref={this.setRef}
tabIndex='0'
className={[_s.statusContent].join(' ')}
className={statusContentClasses}
style={directionStyle}
dangerouslySetInnerHTML={content}
lang={status.get('language')}
@ -229,7 +236,7 @@ class StatusContent extends ImmutablePureComponent {
this.state.collapsed &&
<button
className={[_s.default, _s.displayFlex, _s.cursorPointer, _s.py2, _s.text, _s.colorPrimary, _s.fontWeightBold, _s.fontSize15PX].join(' ')}
onClick={this.props.onClick}
onClick={this.handleReadMore}
>
{intl.formatMessage(messages.readMore)}
</button>

View File

@ -0,0 +1,68 @@
import classNames from 'classnames/bind'
import Text from './text'
const cx = classNames.bind(_s)
export default class Switch extends PureComponent {
static propTypes = {
id: PropTypes.string.isRequired,
description: PropTypes.string,
label: PropTypes.string,
checked: PropTypes.bool,
onChange: PropTypes.func,
onKeyDown: PropTypes.func,
disabled: PropTypes.bool,
labelProps: PropTypes.object,
}
render() {
const {
id,
description,
label,
checked,
onChange,
onKeyDown,
disabled,
labelProps
} = this.props
const checkboxContainerClasses = cx({
cursorPointer: 1,
default: 1,
height24PX: 1,
width50PX: 1,
circle: 1,
border1PX: 1,
marginLeftAuto: 1,
borderColorSecondary: 1,
backgroundColorBrand: checked,
})
const checkboxLabelClasses = cx({
default: 1,
margin1PX: 1,
height20PX: 1,
width20PX: 1,
circle: 1,
positionAbsolute: 1,
backgroundSubtle2: !checked,
backgroundColorPrimary: checked,
left0: !checked,
right0: checked,
})
return (
<div className={[_s.default, _s.flexRow, _s.py5, _s.alignItemsCenter].join(' ')}>
<Text {...labelProps}>
{label}
</Text>
<label className={checkboxContainerClasses} htmlFor={id}>
<span className={checkboxLabelClasses} />
<input type='checkbox' id={id} onChange={onChange} disabled={disabled} className={[_s.visibilityHidden].join(' ')} />
</label>
</div>
)
}
}

View File

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

View File

@ -1,7 +0,0 @@
import Toggle from 'react-toggle'
export default class ToggleSwitch extends PureComponent {
render() {
return <Toggle {...this.props} />
}
}

View File

@ -1,95 +0,0 @@
.react-toggle {
display: inline-block;
position: relative;
cursor: pointer;
background-color: transparent;
border: 0;
padding: 0;
-webkit-tap-highlight-color: rgba($base-overlay-background, 0);
-webkit-tap-highlight-color: transparent;
@include unselectable;
&--disabled {
cursor: not-allowed;
opacity: 0.5;
transition: opacity 0.25s;
}
}
.react-toggle-screenreader-only {
border: 0;
clip: rect(0 0 0 0);
margin: -1px;
overflow: hidden;
padding: 0;
position: absolute;
@include size(1px);
}
.react-toggle-track {
padding: 0;
border-radius: 30px;
background-color: $ui-base-color;
transition: background-color 0.2s ease;
@include size(50px, 24px);
}
.react-toggle:hover:not(.react-toggle--disabled) .react-toggle-track {
background-color: darken($ui-base-color, 10%);
}
.react-toggle--checked .react-toggle-track {
background-color: $gab-brand-default;
}
.react-toggle--checked:hover:not(.react-toggle--disabled) .react-toggle-track {
background-color: lighten($gab-brand-default, 10%);
}
.react-toggle-track-check {
line-height: 0;
opacity: 0;
transition: opacity 0.25s ease;
@include abs-position(0, auto, 0, 8px);
@include size(14px, 10px);
@include vertical-margin(auto);
}
.react-toggle--checked .react-toggle-track-check {
opacity: 1;
transition: opacity 0.25s ease;
}
.react-toggle-track-x {
line-height: 0;
opacity: 1;
transition: opacity 0.25s ease;
@include abs-position(0, 10px, 0);
@include size(10px);
@include vertical-margin(auto);
}
.react-toggle--checked .react-toggle-track-x {
opacity: 0;
}
.react-toggle-thumb {
border: 1px solid $ui-base-color;
background-color: darken($simple-background-color, 2%);
box-sizing: border-box;
transition: all 0.25s ease;
transition-property: border-color, left;
@include abs-position(1, auto, auto, 1px);
@include circle(22px);
}
.react-toggle--checked .react-toggle-thumb {
left: 27px;
border-color: $gab-brand-default;
}

View File

@ -44,6 +44,10 @@ export default class TrendingItem extends ImmutablePureComponent {
underline: hovering,
})
return null;
// : todo :
return (
<NavLink
to='/test'

View File

@ -32,10 +32,8 @@ const mapDispatchToProps = (dispatch, { intl }) => ({
onFollow (account) {
if (account.getIn(['relationship', 'following']) || account.getIn(['relationship', 'requested'])) {
if (unfollowModal) {
dispatch(openModal('CONFIRM', {
message: <FormattedMessage id='confirmations.unfollow.message' defaultMessage='Are you sure you want to unfollow {name}?' values={{ name: <strong>@{account.get('acct')}</strong> }} />,
confirm: intl.formatMessage(messages.unfollowConfirm),
onConfirm: () => dispatch(unfollowAccount(account.get('id'))),
dispatch(openModal('UNFOLLOW', {
accountId: account.get('id'),
}))
} else {
dispatch(unfollowAccount(account.get('id')))

View File

@ -1,24 +1,22 @@
import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
import { blockDomain, unblockDomain } from '../actions/domain_blocks';
import { openModal } from '../actions/modal';
import Domain from '../components/domain';
import { defineMessages, injectIntl, FormattedMessage } from 'react-intl'
import { blockDomain, unblockDomain } from '../actions/domain_blocks'
import { openModal } from '../actions/modal'
import Domain from '../components/domain'
const messages = defineMessages({
blockDomainConfirm: { id: 'confirmations.domain_block.confirm', defaultMessage: 'Hide entire domain' },
});
})
const mapDispatchToProps = (dispatch, { intl }) => ({
onBlockDomain (domain) {
dispatch(openModal('CONFIRM', {
message: <FormattedMessage 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.' values={{ domain: <strong>{domain}</strong> }} />,
confirm: intl.formatMessage(messages.blockDomainConfirm),
onConfirm: () => dispatch(blockDomain(domain)),
}));
dispatch(openModal('BLOCK_DOMAIN', {
domain,
}))
},
onUnblockDomain (domain) {
dispatch(unblockDomain(domain));
dispatch(unblockDomain(domain))
},
});
})
export default injectIntl(connect(null, mapDispatchToProps)(Domain));
export default injectIntl(connect(null, mapDispatchToProps)(Domain))

View File

@ -36,7 +36,6 @@ import Status from '../components/status';
const messages = defineMessages({
deleteConfirm: { id: 'confirmations.delete.confirm', defaultMessage: 'Delete' },
deleteMessage: { id: 'confirmations.delete.message', defaultMessage: 'Are you sure you want to delete this status?' },
blockConfirm: { id: 'confirmations.block.confirm', defaultMessage: 'Block' },
replyConfirm: { id: 'confirmations.reply.confirm', defaultMessage: 'Reply' },
replyMessage: { id: 'confirmations.reply.message', defaultMessage: 'Replying now will overwrite the message you are currently composing. Are you sure you want to proceed?' },
quoteConfirm: { id: 'confirmations.quote.confirm', defaultMessage: 'Quote' },
@ -162,17 +161,10 @@ const mapDispatchToProps = (dispatch, { intl }) => ({
},
onBlock (status) {
const account = status.get('account');
dispatch(openModal('CONFIRM', {
message: <FormattedMessage id='confirmations.block.message' defaultMessage='Are you sure you want to block {name}?' values={{ name: <strong>@{account.get('acct')}</strong> }} />,
confirm: intl.formatMessage(messages.blockConfirm),
onConfirm: () => dispatch(blockAccount(account.get('id'))),
secondary: intl.formatMessage(messages.blockAndReport),
onSecondary: () => {
dispatch(blockAccount(account.get('id')));
dispatch(initReport(account, status));
},
}));
const account = status.get('account')
dispatch(openModal('BLOCK_ACCOUNT', {
accountId: account.get('id'),
}))
},
onReport (status) {

View File

@ -22,7 +22,6 @@ import Header from '../components/header';
const messages = defineMessages({
unfollowConfirm: { id: 'confirmations.unfollow.confirm', defaultMessage: 'Unfollow' },
blockConfirm: { id: 'confirmations.block.confirm', defaultMessage: 'Block' },
blockDomainConfirm: { id: 'confirmations.domain_block.confirm', defaultMessage: 'Hide entire domain' },
blockAndReport: { id: 'confirmations.block.block_and_report', defaultMessage: 'Block & Report' },
});
@ -44,16 +43,14 @@ const mapDispatchToProps = (dispatch, { intl }) => ({
onFollow (account) {
if (account.getIn(['relationship', 'following']) || account.getIn(['relationship', 'requested'])) {
if (unfollowModal) {
dispatch(openModal('CONFIRM', {
message: <FormattedMessage id='confirmations.unfollow.message' defaultMessage='Are you sure you want to unfollow {name}?' values={{ name: <strong>@{account.get('acct')}</strong> }} />,
confirm: intl.formatMessage(messages.unfollowConfirm),
onConfirm: () => dispatch(unfollowAccount(account.get('id'))),
}));
dispatch(openModal('UNFOLLOW', {
accountId: account.get('id'),
}))
} else {
dispatch(unfollowAccount(account.get('id')));
dispatch(unfollowAccount(account.get('id')))
}
} else {
dispatch(followAccount(account.get('id')));
dispatch(followAccount(account.get('id')))
}
},
@ -61,15 +58,8 @@ const mapDispatchToProps = (dispatch, { intl }) => ({
if (account.getIn(['relationship', 'blocking'])) {
dispatch(unblockAccount(account.get('id')));
} else {
dispatch(openModal('CONFIRM', {
message: <FormattedMessage id='confirmations.block.message' defaultMessage='Are you sure you want to block {name}?' values={{ name: <strong>@{account.get('acct')}</strong> }} />,
confirm: intl.formatMessage(messages.blockConfirm),
onConfirm: () => dispatch(blockAccount(account.get('id'))),
secondary: intl.formatMessage(messages.blockAndReport),
onSecondary: () => {
dispatch(blockAccount(account.get('id')));
dispatch(initReport(account));
},
dispatch(openModal('BLOCK_ACCOUNT', {
accountId: account.get('id'),
}));
}
},
@ -107,10 +97,8 @@ const mapDispatchToProps = (dispatch, { intl }) => ({
},
onBlockDomain (domain) {
dispatch(openModal('CONFIRM', {
message: <FormattedMessage 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.' values={{ domain: <strong>{domain}</strong> }} />,
confirm: intl.formatMessage(messages.blockDomainConfirm),
onConfirm: () => dispatch(blockDomain(domain)),
dispatch(openModal('BLOCK_DOMAIN', {
domain,
}));
},

View File

@ -1,20 +1,20 @@
import { defineMessages, injectIntl, FormattedMessage } from 'react-intl'
import { defineMessages, injectIntl } from 'react-intl'
import ImmutablePureComponent from 'react-immutable-pure-component'
import ImmutablePropTypes from 'react-immutable-proptypes'
import { debounce } from 'lodash'
import ColumnIndicator from '../components/column_indicator'
import AccountContainer from '../containers/account_container'
import { fetchBlocks, expandBlocks } from '../actions/blocks'
import AccountContainer from '../containers/account_container'
import ColumnIndicator from '../components/column_indicator'
import ScrollableList from '../components/scrollable_list'
const messages = defineMessages({
heading: { id: 'column.blocks', defaultMessage: 'Blocked users' },
});
empty: { id: 'empty_column.blocks', defaultMessage: 'You haven\'t blocked any users yet.' },
})
const mapStateToProps = state => ({
accountIds: state.getIn(['user_lists', 'blocks', 'items']),
hasMore: !!state.getIn(['user_lists', 'blocks', 'next']),
});
})
export default
@connect(mapStateToProps)
@ -27,35 +27,43 @@ class Blocks extends ImmutablePureComponent {
accountIds: ImmutablePropTypes.list,
hasMore: PropTypes.bool,
intl: PropTypes.object.isRequired,
};
}
componentWillMount() {
this.props.dispatch(fetchBlocks());
this.props.dispatch(fetchBlocks())
}
handleLoadMore = debounce(() => {
this.props.dispatch(expandBlocks());
}, 300, { leading: true });
this.props.dispatch(expandBlocks())
}, 300, { leading: true })
render() {
const { intl, accountIds, hasMore } = this.props;
const {
intl,
accountIds,
hasMore
} = this.props
if (!accountIds) {
return (<ColumnIndicator type='loading' />);
return <ColumnIndicator type='loading' />
}
const emptyMessage = intl.formatMessage(messages.empty)
return (
<ScrollableList
scrollKey='blocks'
onLoadMore={this.handleLoadMore}
hasMore={hasMore}
emptyMessage={<FormattedMessage id='empty_column.blocks' defaultMessage="You haven't blocked any users yet." />}
emptyMessage={emptyMessage}
>
{accountIds.map(id =>
<AccountContainer key={id} id={id} />
)}
{
accountIds.map(id =>
<AccountContainer key={`blocked-${id}`} id={id} compact />
)
}
</ScrollableList>
);
)
}
}

View File

@ -1,7 +1,7 @@
import ImmutablePropTypes from 'react-immutable-proptypes';
import ImmutablePureComponent from 'react-immutable-pure-component';
import { FormattedMessage } from 'react-intl';
import SettingToggle from '../../../../components/setting_toggle';
import SettingSwitch from '../../../../components/setting_switch';
import { changeSetting } from '../../../../actions/settings';
const mapStateToProps = state => ({
@ -30,13 +30,13 @@ class ColumnSettings extends ImmutablePureComponent {
return (
<div>
<SettingToggle
<SettingSwitch
settings={settings}
settingPath={['other', 'onlyMedia']}
onChange={onChange}
label={<FormattedMessage id='community.column_settings.media_only' defaultMessage='Media Only' />}
/>
<SettingToggle
<SettingSwitch
settings={settings}
settingPath={['other', 'allFediverse']}
onChange={onChange}

View File

@ -49,7 +49,7 @@ class ComposeForm extends ImmutablePureComponent {
static propTypes = {
intl: PropTypes.object.isRequired,
edit: PropTypes.bool.isRequired,
edit: PropTypes.bool,
text: PropTypes.string.isRequired,
suggestions: ImmutablePropTypes.list,
account: ImmutablePropTypes.map.isRequired,

View File

@ -29,7 +29,6 @@ class EmojiPickerButton extends PureComponent {
unavailable: PropTypes.bool,
active: PropTypes.bool,
onClick: PropTypes.func.isRequired,
intl: PropTypes.object.isRequired,
small: PropTypes.bool,
}

View File

@ -27,7 +27,7 @@ class SpoilerButton extends PureComponent {
static propTypes = {
active: PropTypes.bool,
intl: PropTypes.map,
intl: PropTypes.object.isRequired,
small: PropTypes.bool,
}

View File

@ -60,7 +60,7 @@ class Favorites extends ImmutablePureComponent {
>
{
accountIds.map(id =>
<AccountContainer key={id} id={id} withNote={false} />
<AccountContainer key={id} id={id} />
)
}
</ScrollableList>

View File

@ -88,7 +88,7 @@ class Followers extends ImmutablePureComponent {
>
{
!!accountIds && accountIds.map((id) => (
<AccountContainer key={`follower-${id}`} id={id} withNote={false} compact />
<AccountContainer key={`follower-${id}`} id={id} compact />
))
}
</ScrollableList>

View File

@ -88,7 +88,7 @@ class Following extends ImmutablePureComponent {
>
{
!!accountIds && accountIds.map((id) => (
<AccountContainer key={`following-${id}`} id={id} withNote={false} compact />
<AccountContainer key={`following-${id}`} id={id} compact />
))
}
</ScrollableList>

View File

@ -71,7 +71,7 @@ class GroupMembers extends ImmutablePureComponent {
return (
<div className="group-account-wrapper" key={id}>
<AccountContainer id={id} withNote={false} actionIcon="none" onActionClick={() => true} />
<AccountContainer id={id} actionIcon="none" onActionClick={() => true} />
{ /*
menu.length > 0 && <DropdownMenuContainer items={menu} icon='ellipsis-h' size={18} direction='right' />
*/

View File

@ -1,6 +1,6 @@
import ImmutablePropTypes from 'react-immutable-proptypes';
import { injectIntl, FormattedMessage } from 'react-intl';
import SettingToggle from '../../../../components/setting_toggle';
import SettingSwitch from '../../../../components/setting_switch';
export default
@injectIntl
@ -20,7 +20,7 @@ class ColumnSettings extends PureComponent {
<span className='column-settings__section'><FormattedMessage id='home.column_settings.basic' defaultMessage='Basic' /></span>
<div className='column-settings__row'>
<SettingToggle prefix='home_timeline' settings={settings} settingPath={['shows', 'reply']} onChange={onChange} label={<FormattedMessage id='home.column_settings.show_replies' defaultMessage='Show replies' />} />
<SettingSwitch prefix='home_timeline' settings={settings} settingPath={['shows', 'reply']} onChange={onChange} label={<FormattedMessage id='home.column_settings.show_replies' defaultMessage='Show replies' />} />
</div>
</div>
);

View File

@ -42,7 +42,7 @@ getActionButton() {
const menu = [
{ text: intl.formatMessage(messages.edit), to: `/groups/${group.get('id')}/edit` },
{ text: intl.formatMessage(messages.removed_accounts), to: `/groups/${group.get('id')}/removed_accounts` },
{ text: intl.formatMessage(messages.removed_accounts), to: `/groups/${group.get('id')}/removed-accounts` },
];
// <DropdownMenuContainer items={menu} icon='ellipsis-v' size={24} direction='right' />;

View File

@ -2,7 +2,7 @@ import ImmutablePropTypes from 'react-immutable-proptypes';
import ImmutablePureComponent from 'react-immutable-pure-component';
import { FormattedMessage } from 'react-intl';
import { changeSetting, saveSettings } from '../../../../actions/settings';
import SettingToggle from '../../../../components/setting_toggle';
import SettingSwitch from '../../../../components/setting_switch';
const mapStateToProps = state => ({
settings: state.getIn(['settings', 'home']),
@ -33,7 +33,7 @@ class ColumnSettings extends ImmutablePureComponent {
<div>
<FormattedMessage id='home.column_settings.basic' defaultMessage='Basic' />
<SettingToggle
<SettingSwitch
prefix='home_timeline'
settings={settings}
settingPath={['shows', 'repost']}
@ -41,7 +41,7 @@ class ColumnSettings extends ImmutablePureComponent {
label={<FormattedMessage id='home.column_settings.show_reposts' defaultMessage='Show reposts' />}
/>
<SettingToggle
<SettingSwitch
prefix='home_timeline'
settings={settings}
settingPath={['shows', 'reply']}

View File

@ -18,7 +18,7 @@ const mapStateToProps = (state, { params: { username } }) => {
export default
@connect(mapStateToProps)
class Favorites extends ImmutablePureComponent {
class LikedStatuses extends ImmutablePureComponent {
static propTypes = {
dispatch: PropTypes.func.isRequired,
@ -43,16 +43,14 @@ class Favorites extends ImmutablePureComponent {
return <ColumnIndicator type='missing' />
}
console.log("statusIds:", statusIds)
return (
<StatusList
statusIds={statusIds}
scrollKey='favorited_statuses'
scrollKey='liked_statuses'
hasMore={hasMore}
isLoading={isLoading}
onLoadMore={this.handleLoadMore}
emptyMessage={<FormattedMessage id='empty_column.favorited_statuses' defaultMessage="You don't have any favorite gabs yet. When you favorite one, it will show up here." />}
emptyMessage={<FormattedMessage id='empty_column.liked_statuses' defaultMessage="You don't have any liked gabs yet. When you like one, it will show up here." />}
/>
)
}

View File

@ -2,7 +2,7 @@ import ImmutablePropTypes from 'react-immutable-proptypes';
import ImmutablePureComponent from 'react-immutable-pure-component';
import { FormattedMessage } from 'react-intl';
import ColumnHeaderSettingButton from '../../../../components/column_header_setting_button';
import SettingToggle from '../../../../components/setting_toggle';
import SettingSwitch from '../../../../components/setting_switch';
export default class ColumnSettings extends ImmutablePureComponent {
@ -39,48 +39,48 @@ export default class ColumnSettings extends ImmutablePureComponent {
<div role='group' aria-labelledby='notifications-filter-bar'>
<FormattedMessage id='notifications.column_settings.filter_bar.category' defaultMessage='Quick filter bar' />
<SettingToggle id='show-filter-bar' prefix='notifications' settings={settings} settingPath={['quickFilter', 'show']} onChange={onChange} label={filterShowStr} />
<SettingToggle id='show-filter-bar' prefix='notifications' settings={settings} settingPath={['quickFilter', 'advanced']} onChange={onChange} label={filterAdvancedStr} />
<SettingSwitch id='show-filter-bar' prefix='notifications' settings={settings} settingPath={['quickFilter', 'show']} onChange={onChange} label={filterShowStr} />
<SettingSwitch id='show-filter-bar' prefix='notifications' settings={settings} settingPath={['quickFilter', 'advanced']} onChange={onChange} label={filterAdvancedStr} />
</div>
<div role='group' aria-labelledby='notifications-follow'>
<FormattedMessage id='notifications.column_settings.follow' defaultMessage='New followers:' />
<SettingToggle prefix='notifications_desktop' settings={settings} settingPath={['alerts', 'follow']} onChange={onChange} label={alertStr} />
{showPushSettings && <SettingToggle prefix='notifications_push' settings={pushSettings} settingPath={['alerts', 'follow']} onChange={this.onPushChange} label={pushStr} />}
<SettingToggle prefix='notifications' settings={settings} settingPath={['shows', 'follow']} onChange={onChange} label={showStr} />
<SettingToggle prefix='notifications' settings={settings} settingPath={['sounds', 'follow']} onChange={onChange} label={soundStr} />
<SettingSwitch prefix='notifications_desktop' settings={settings} settingPath={['alerts', 'follow']} onChange={onChange} label={alertStr} />
{showPushSettings && <SettingSwitch prefix='notifications_push' settings={pushSettings} settingPath={['alerts', 'follow']} onChange={this.onPushChange} label={pushStr} />}
<SettingSwitch prefix='notifications' settings={settings} settingPath={['shows', 'follow']} onChange={onChange} label={showStr} />
<SettingSwitch prefix='notifications' settings={settings} settingPath={['sounds', 'follow']} onChange={onChange} label={soundStr} />
</div>
<div role='group' aria-labelledby='notifications-favorite'>
<FormattedMessage id='notifications.column_settings.favorite' defaultMessage='Favorites:' />
<SettingToggle prefix='notifications_desktop' settings={settings} settingPath={['alerts', 'favorite']} onChange={onChange} label={alertStr} />
{showPushSettings && <SettingToggle prefix='notifications_push' settings={pushSettings} settingPath={['alerts', 'favorite']} onChange={this.onPushChange} label={pushStr} />}
<SettingToggle prefix='notifications' settings={settings} settingPath={['shows', 'favorite']} onChange={onChange} label={showStr} />
<SettingToggle prefix='notifications' settings={settings} settingPath={['sounds', 'favorite']} onChange={onChange} label={soundStr} />
<SettingSwitch prefix='notifications_desktop' settings={settings} settingPath={['alerts', 'favorite']} onChange={onChange} label={alertStr} />
{showPushSettings && <SettingSwitch prefix='notifications_push' settings={pushSettings} settingPath={['alerts', 'favorite']} onChange={this.onPushChange} label={pushStr} />}
<SettingSwitch prefix='notifications' settings={settings} settingPath={['shows', 'favorite']} onChange={onChange} label={showStr} />
<SettingSwitch prefix='notifications' settings={settings} settingPath={['sounds', 'favorite']} onChange={onChange} label={soundStr} />
</div>
<div role='group' aria-labelledby='notifications-mention'>
<FormattedMessage id='notifications.column_settings.mention' defaultMessage='Mentions:' />
<SettingToggle prefix='notifications_desktop' settings={settings} settingPath={['alerts', 'mention']} onChange={onChange} label={alertStr} />
{showPushSettings && <SettingToggle prefix='notifications_push' settings={pushSettings} settingPath={['alerts', 'mention']} onChange={this.onPushChange} label={pushStr} />}
<SettingToggle prefix='notifications' settings={settings} settingPath={['shows', 'mention']} onChange={onChange} label={showStr} />
<SettingToggle prefix='notifications' settings={settings} settingPath={['sounds', 'mention']} onChange={onChange} label={soundStr} />
<SettingSwitch prefix='notifications_desktop' settings={settings} settingPath={['alerts', 'mention']} onChange={onChange} label={alertStr} />
{showPushSettings && <SettingSwitch prefix='notifications_push' settings={pushSettings} settingPath={['alerts', 'mention']} onChange={this.onPushChange} label={pushStr} />}
<SettingSwitch prefix='notifications' settings={settings} settingPath={['shows', 'mention']} onChange={onChange} label={showStr} />
<SettingSwitch prefix='notifications' settings={settings} settingPath={['sounds', 'mention']} onChange={onChange} label={soundStr} />
</div>
<div role='group' aria-labelledby='notifications-repost'>
<FormattedMessage id='notifications.column_settings.repost' defaultMessage='Reposts:' />
<SettingToggle prefix='notifications_desktop' settings={settings} settingPath={['alerts', 'repost']} onChange={onChange} label={alertStr} />
{showPushSettings && <SettingToggle prefix='notifications_push' settings={pushSettings} settingPath={['alerts', 'repost']} onChange={this.onPushChange} label={pushStr} />}
<SettingToggle prefix='notifications' settings={settings} settingPath={['shows', 'repost']} onChange={onChange} label={showStr} />
<SettingToggle prefix='notifications' settings={settings} settingPath={['sounds', 'repost']} onChange={onChange} label={soundStr} />
<SettingSwitch prefix='notifications_desktop' settings={settings} settingPath={['alerts', 'repost']} onChange={onChange} label={alertStr} />
{showPushSettings && <SettingSwitch prefix='notifications_push' settings={pushSettings} settingPath={['alerts', 'repost']} onChange={this.onPushChange} label={pushStr} />}
<SettingSwitch prefix='notifications' settings={settings} settingPath={['shows', 'repost']} onChange={onChange} label={showStr} />
<SettingSwitch prefix='notifications' settings={settings} settingPath={['sounds', 'repost']} onChange={onChange} label={soundStr} />
</div>
<div role='group' aria-labelledby='notifications-poll'>
<FormattedMessage id='notifications.column_settings.poll' defaultMessage='Poll results:' />
<SettingToggle prefix='notifications_desktop' settings={settings} settingPath={['alerts', 'poll']} onChange={onChange} label={alertStr} />
{showPushSettings && <SettingToggle prefix='notifications_push' settings={pushSettings} settingPath={['alerts', 'poll']} onChange={this.onPushChange} label={pushStr} />}
<SettingToggle prefix='notifications' settings={settings} settingPath={['shows', 'poll']} onChange={onChange} label={showStr} />
<SettingToggle prefix='notifications' settings={settings} settingPath={['sounds', 'poll']} onChange={onChange} label={soundStr} />
<SettingSwitch prefix='notifications_desktop' settings={settings} settingPath={['alerts', 'poll']} onChange={onChange} label={alertStr} />
{showPushSettings && <SettingSwitch prefix='notifications_push' settings={pushSettings} settingPath={['alerts', 'poll']} onChange={this.onPushChange} label={pushStr} />}
<SettingSwitch prefix='notifications' settings={settings} settingPath={['shows', 'poll']} onChange={onChange} label={showStr} />
<SettingSwitch prefix='notifications' settings={settings} settingPath={['sounds', 'poll']} onChange={onChange} label={soundStr} />
</div>
</div>
);

View File

@ -117,7 +117,7 @@ class Notification extends ImmutablePureComponent {
</span>
</div>
<AccountContainer id={account.get('id')} withNote={false} hidden={this.props.hidden} />
<AccountContainer id={account.get('id')} hidden={this.props.hidden} />
</div>
</HotKeys>
);

View File

@ -60,7 +60,7 @@ class Reposts extends ImmutablePureComponent {
>
{
accountIds.map(id =>
<AccountContainer key={id} id={id} withNote={false} />
<AccountContainer key={id} id={id} />
)
}
</ScrollableList>

View File

@ -30,7 +30,6 @@ import DetailedStatus from '../components/detailed_status';
const messages = defineMessages({
deleteConfirm: { id: 'confirmations.delete.confirm', defaultMessage: 'Delete' },
deleteMessage: { id: 'confirmations.delete.message', defaultMessage: 'Are you sure you want to delete this status?' },
blockConfirm: { id: 'confirmations.block.confirm', defaultMessage: 'Block' },
replyConfirm: { id: 'confirmations.reply.confirm', defaultMessage: 'Reply' },
replyMessage: { id: 'confirmations.reply.message', defaultMessage: 'Replying now will overwrite the message you are currently composing. Are you sure you want to proceed?' },
blockAndReport: { id: 'confirmations.block.block_and_report', defaultMessage: 'Block & Report' },
@ -128,17 +127,10 @@ const mapDispatchToProps = (dispatch, { intl }) => ({
},
onBlock (status) {
const account = status.get('account');
dispatch(openModal('CONFIRM', {
message: <FormattedMessage id='confirmations.block.message' defaultMessage='Are you sure you want to block {name}?' values={{ name: <strong>@{account.get('acct')}</strong> }} />,
confirm: intl.formatMessage(messages.blockConfirm),
onConfirm: () => dispatch(blockAccount(account.get('id'))),
secondary: intl.formatMessage(messages.blockAndReport),
onSecondary: () => {
dispatch(blockAccount(account.get('id')));
dispatch(initReport(account, status));
},
}));
const account = status.get('account')
dispatch(openModal('BLOCK_ACCOUNT', {
accountId: account.get('id'),
}))
},
onReport (status) {

View File

@ -40,7 +40,6 @@ const messages = defineMessages({
deleteMessage: { id: 'confirmations.delete.message', defaultMessage: 'Are you sure you want to delete this status?' },
redraftConfirm: { id: 'confirmations.redraft.confirm', defaultMessage: 'Delete & redraft' },
redraftMessage: { id: 'confirmations.redraft.message', defaultMessage: 'Are you sure you want to delete this status and re-draft it? Favorites and reposts will be lost, and replies to the original post will be orphaned.' },
blockConfirm: { id: 'confirmations.block.confirm', defaultMessage: 'Block' },
revealAll: { id: 'status.show_more_all', defaultMessage: 'Show more for all' },
hideAll: { id: 'status.show_less_all', defaultMessage: 'Show less for all' },
detailedStatus: { id: 'status.detailed_status', defaultMessage: 'Detailed conversation view' },
@ -237,19 +236,12 @@ class Status extends ImmutablePureComponent {
}
handleBlockClick = (status) => {
const { dispatch, intl } = this.props;
const account = status.get('account');
const { dispatch } = this.props
const account = status.get('account')
dispatch(openModal('CONFIRM', {
message: <FormattedMessage id='confirmations.block.message' defaultMessage='Are you sure you want to block {name}?' values={{ name: <strong>@{account.get('acct')}</strong> }} />,
confirm: intl.formatMessage(messages.blockConfirm),
onConfirm: () => dispatch(blockAccount(account.get('id'))),
secondary: intl.formatMessage(messages.blockAndReport),
onSecondary: () => {
dispatch(blockAccount(account.get('id')));
dispatch(initReport(account, status));
},
}));
dispatch(openModal('BLOCK_ACCOUNT', {
accountId: account.get('id'),
}))
}
handleReport = (status) => {

View File

@ -10,6 +10,7 @@ import {
initializeNotifications,
expandNotifications,
} from '../../actions/notifications'
import LoadingBarContainer from '../../containers/loading_bar_container'
import { fetchFilters } from '../../actions/filters'
import { clearHeight } from '../../actions/height_cache'
import { openModal } from '../../actions/modal'
@ -37,7 +38,7 @@ import {
CommunityTimeline,
DomainBlocks,
Favorites,
FavoritedStatuses,
Filters,
Followers,
Following,
FollowRequests,
@ -50,6 +51,7 @@ import {
GroupTimeline,
HashtagTimeline,
HomeTimeline,
LikedStatuses,
ListCreate,
ListsDirectory,
ListEdit,
@ -148,7 +150,7 @@ class SwitchingArea extends PureComponent {
<WrappedRoute path='/groups/create' page={ModalPage} component={GroupCreate} content={children} componentParams={{ title: 'Create Group' }} />
<WrappedRoute path='/groups/:id/members' page={GroupPage} component={GroupMembers} content={children} />
<WrappedRoute path='/groups/:id/removed_accounts' page={GroupPage} component={GroupRemovedAccounts} content={children} />
<WrappedRoute path='/groups/:id/removed-accounts' page={GroupPage} component={GroupRemovedAccounts} content={children} />
<WrappedRoute path='/groups/:id/edit' page={ModalPage} component={GroupCreate} content={children} componentParams={{ title: 'Edit Group' }} />
<WrappedRoute path='/groups/:id' page={GroupPage} component={GroupTimeline} content={children} />
@ -169,22 +171,21 @@ class SwitchingArea extends PureComponent {
{ /*
<WrappedRoute path='/settings/account' exact page={SettingsPage} component={AccountSettings} content={children} />
<WrappedRoute path='/settings/profile' exact page={SettingsPage} component={ProfileSettings} content={children} />
<WrappedRoute path='/settings/domain_blocks' exact page={SettingsPage} component={DomainBlocks} content={children} />
<WrappedRoute path='/settings/relationships' exact page={SettingsPage} component={RelationshipSettings} content={children} />
<WrappedRoute path='/settings/filters' exact page={SettingsPage} component={Filters} content={children} />
<WrappedRoute path='/settings/blocks' exact page={SettingsPage} component={Blocks} content={children} />
<WrappedRoute path='/settings/mutes' exact page={SettingsPage} component={Mutes} content={children} />
<WrappedRoute path='/settings/development' exact page={SettingsPage} component={Development} content={children} />
<WrappedRoute path='/settings/billing' exact page={SettingsPage} component={Billing} content={children} />
*/ }
<WrappedRoute path='/settings/blocks' exact page={SettingsPage} component={Blocks} content={children} componentParams={{ title: 'Blocked Accounts' }} />
<WrappedRoute path='/settings/domain-blocks' exact page={SettingsPage} component={DomainBlocks} content={children} componentParams={{ title: 'Domain Blocks' }} />
<WrappedRoute path='/settings/filters' exact page={SettingsPage} component={Filters} content={children} componentParams={{ title: 'Muted Words' }} />
<WrappedRoute path='/settings/mutes' exact page={SettingsPage} component={Mutes} content={children} componentParams={{ title: 'Muted Accounts' }} />
<Redirect from='/@:username' to='/:username' exact />
<WrappedRoute path='/:username' publicRoute exact page={ProfilePage} component={AccountTimeline} content={children} />
{ /*
<Redirect from='/@:username/comments' to='/:username/comments' />
<WrappedRoute path='/:username/comments' page={ProfilePage} component={AccountTimeline} content={children} componentParams={{ commentsOnly: true }} />
*/ }
<Redirect from='/@:username/followers' to='/:username/followers' />
<WrappedRoute path='/:username/followers' page={ProfilePage} component={Followers} content={children} />
@ -195,8 +196,8 @@ class SwitchingArea extends PureComponent {
<Redirect from='/@:username/media' to='/:username/media' />
<WrappedRoute path='/:username/media' page={ProfilePage} component={AccountGallery} content={children} />
<Redirect from='/@:username/favorites' to='/:username/favorites' />
<WrappedRoute path='/:username/favorites' page={ProfilePage} component={FavoritedStatuses} content={children} />
<Redirect from='/@:username/likes' to='/:username/likes' />
<WrappedRoute path='/:username/likes' page={ProfilePage} component={LikedStatuses} content={children} />
<Redirect from='/@:username/posts/:statusId' to='/:username/posts/:statusId' exact />
<WrappedRoute path='/:username/posts/:statusId' publicRoute exact page={BasicPage} component={Status} content={children} componentParams={{ title: 'Status' }} />
@ -489,15 +490,15 @@ class UI extends PureComponent {
return (
<div ref={this.setRef}>
<SwitchingArea
location={location}
onLayoutChange={this.handleLayoutChange}
>
<LoadingBarContainer className={[_s.height1PX, _s.z3, _s.backgroundColorBrandLight].join(' ')} />
<SwitchingArea location={location} onLayoutChange={this.handleLayoutChange}>
{children}
</SwitchingArea>
<ModalRoot />
<PopoverRoot />
<UploadArea active={draggingOver} onClose={this.closeUploadModal} />
</div>
)

View File

@ -42,8 +42,8 @@ export function FollowRequests() {
return import(/* webpackChunkName: "features/follow_requests" */'../../follow_requests')
}
export function FavoritedStatuses() {
return import(/* webpackChunkName: "features/favorited_statuses" */'../../favorited_statuses')
export function LikedStatuses() {
return import(/* webpackChunkName: "features/liked_statuses" */'../../liked_statuses')
}
export function GenericNotFound() {

View File

@ -0,0 +1,50 @@
import Sticky from 'react-stickynode'
import Search from '../components/search'
import ColumnHeader from '../components/column_header'
import Sidebar from '../components/sidebar'
export default class SettingsLayout extends PureComponent {
static propTypes = {
actions: PropTypes.array,
tabs: PropTypes.array,
title: PropTypes.string,
}
render() {
const { children, actions, tabs, title } = this.props
return (
<div className={[_s.default, _s.flexRow, _s.width100PC, _s.heightMin100VH, _s.backgroundColorSecondary3].join(' ')}>
<Sidebar />
<main role='main' className={[_s.default, _s.flexShrink1, _s.flexGrow1, _s.borderColorSecondary2, _s.borderLeft1PX].join(' ')}>
<div className={[_s.default, _s.height53PX, _s.borderBottom1PX, _s.borderColorSecondary2, _s.backgroundColorSecondary3, _s.z3, _s.top0, _s.positionFixed].join(' ')}>
<div className={[_s.default, _s.height53PX, _s.pl15, _s.width1015PX, _s.flexRow, _s.justifyContentSpaceBetween].join(' ')}>
<div className={[_s.default, _s.width100PC].join(' ')}>
<ColumnHeader
title={title}
showBackBtn={true}
actions={actions}
tabs={tabs}
/>
</div>
</div>
</div>
<div className={[_s.default, _s.height53PX].join(' ')}></div>
<div className={[_s.default, _s.width1015PX, _s.flexRow, _s.justifyContentSpaceBetween, _s.pl15, _s.py15].join(' ')}>
<div className={[_s.default, _s.z1, _s.width100PC].join(' ')}>
{children}
</div>
</div>
</main>
</div>
)
}
}

View File

@ -1,4 +1,5 @@
import { Fragment } from 'react'
import { openModal } from '../actions/modal'
import GroupSidebarPanel from '../components/panel/groups_panel'
import LinkFooter from '../components/link_footer'
import WhoToFollowPanel from '../components/panel/who_to_follow_panel'
@ -10,18 +11,26 @@ import DefaultLayout from '../layouts/default_layout'
import TimelineComposeBlock from '../components/timeline_compose_block'
import Divider from '../components/divider'
export default class HomePage extends PureComponent {
const mapDispatchToProps = (dispatch) => ({
onOpenHomePageSettingsModal() {
dispatch(openModal('HOME_TIMELINE_SETTINGS'))
},
})
export default
@connect(null, mapDispatchToProps)
class HomePage extends PureComponent {
static propTypes = {
onOpenHomePageSettingsModal: PropTypes.func.isRequired,
}
componentDidMount() {
document.title = '(1) Home - Gab'
}
handleEditHomeTimeline () {
console.log("handleEditHomeTimeline")
}
render() {
const { children } = this.props
const { children, onOpenHomePageSettingsModal } = this.props
return (
<DefaultLayout
@ -29,7 +38,7 @@ export default class HomePage extends PureComponent {
actions={[
{
icon: 'ellipsis',
onClick: this.handleEditHomeTimeline
onClick: onOpenHomePageSettingsModal,
},
]}
layout={(

View File

@ -13,13 +13,13 @@ export default class SearchPage extends PureComponent {
return (
<SearchLayout
showBackBtn
layout={(
<Fragment>
<SearchFilterPanel />
<LinkFooter />
</Fragment>
)}
showBackBtn
>
{children}
</SearchLayout>

View File

@ -1,12 +1,22 @@
import SettingsLayout from '../layouts/settings_layout'
export default class SettingsPage extends PureComponent {
static propTypes = {
tabs: PropTypes.array,
title: PropTypes.string,
}
render() {
const { children } = this.props;
const { children, title, tabs } = this.props;
return (
<div>
<SettingsLayout
title={title}
tabs={tabs}
>
{children}
</div>
</SettingsLayout>
)
}
}

View File

@ -2,7 +2,6 @@ import Immutable from 'immutable';
import {
MUTES_INIT_MODAL,
MUTES_TOGGLE_HIDE_NOTIFICATIONS,
} from '../actions/mutes';
const initialState = Immutable.Map({
@ -21,8 +20,6 @@ export default function mutes(state = initialState, action) {
state.setIn(['new', 'account'], action.account);
state.setIn(['new', 'notifications'], true);
});
case MUTES_TOGGLE_HIDE_NOTIFICATIONS:
return state.updateIn(['new', 'notifications'], (old) => !old);
default:
return state;
}

View File

@ -220,6 +220,10 @@ body {
margin: auto;
}
.margin1PX {
margin: 1px;
}
.displayNone {
display: none;
}
@ -428,6 +432,10 @@ body {
min-height: 100vh;
}
.heightMax80VH {
max-height: 80vh;
}
.heightMin50VH {
min-height: 50vh;
}
@ -440,14 +448,26 @@ body {
height: 100%;
}
.height24PX {
height: 24px;
}
.height22PX {
height: 22px;
}
.height20PX {
height: 20px;
}
.height4PX {
height: 4px;
}
.height1PX {
height: 1px;
}
.height50PX {
height: 50px;
}
@ -496,6 +516,10 @@ body {
width: 330px;
}
.width250PX {
width: 240px;
}
.width240PX {
width: 240px;
}
@ -504,6 +528,14 @@ body {
width: 72px;
}
.width50PX {
width: 50px;
}
.width20PX {
width: 20px;
}
.width100PC {
width: 100%;
}
@ -933,3 +965,7 @@ body {
display: block;
position: absolute;
} */
.visibilityHidden {
visibility: hidden;
}

View File

@ -75,7 +75,6 @@
"@babel/preset-react": "^7.0.0",
"@babel/runtime": "^7.3.4",
"@clusterws/cws": "^0.14.0",
"@storybook/react": "^5.3.14",
"array-includes": "^3.0.3",
"autoprefixer": "^9.5.1",
"axios": "^0.19.0",