forked from berserker/microblog
Merge branch 'main' of https://github.com/glitch-soc/mastodon into main
commit
655573f9c5
111 changed files with 2567 additions and 791 deletions
@ -1,89 +1,38 @@ |
||||
import api from 'flavours/glitch/util/api'; |
||||
import { openModal, closeModal } from './modal'; |
||||
|
||||
export const REPORT_INIT = 'REPORT_INIT'; |
||||
export const REPORT_CANCEL = 'REPORT_CANCEL'; |
||||
import { openModal } from './modal'; |
||||
|
||||
export const REPORT_SUBMIT_REQUEST = 'REPORT_SUBMIT_REQUEST'; |
||||
export const REPORT_SUBMIT_SUCCESS = 'REPORT_SUBMIT_SUCCESS'; |
||||
export const REPORT_SUBMIT_FAIL = 'REPORT_SUBMIT_FAIL'; |
||||
|
||||
export const REPORT_STATUS_TOGGLE = 'REPORT_STATUS_TOGGLE'; |
||||
export const REPORT_COMMENT_CHANGE = 'REPORT_COMMENT_CHANGE'; |
||||
export const REPORT_FORWARD_CHANGE = 'REPORT_FORWARD_CHANGE'; |
||||
|
||||
export function initReport(account, status) { |
||||
return dispatch => { |
||||
dispatch({ |
||||
type: REPORT_INIT, |
||||
account, |
||||
status, |
||||
}); |
||||
|
||||
dispatch(openModal('REPORT')); |
||||
}; |
||||
}; |
||||
|
||||
export function cancelReport() { |
||||
return { |
||||
type: REPORT_CANCEL, |
||||
}; |
||||
}; |
||||
|
||||
export function toggleStatusReport(statusId, checked) { |
||||
return { |
||||
type: REPORT_STATUS_TOGGLE, |
||||
statusId, |
||||
checked, |
||||
}; |
||||
}; |
||||
|
||||
export function submitReport() { |
||||
return (dispatch, getState) => { |
||||
dispatch(submitReportRequest()); |
||||
|
||||
api(getState).post('/api/v1/reports', { |
||||
account_id: getState().getIn(['reports', 'new', 'account_id']), |
||||
status_ids: getState().getIn(['reports', 'new', 'status_ids']), |
||||
comment: getState().getIn(['reports', 'new', 'comment']), |
||||
forward: getState().getIn(['reports', 'new', 'forward']), |
||||
}).then(response => { |
||||
dispatch(closeModal()); |
||||
dispatch(submitReportSuccess(response.data)); |
||||
}).catch(error => dispatch(submitReportFail(error))); |
||||
}; |
||||
}; |
||||
|
||||
export function submitReportRequest() { |
||||
return { |
||||
type: REPORT_SUBMIT_REQUEST, |
||||
}; |
||||
}; |
||||
|
||||
export function submitReportSuccess(report) { |
||||
return { |
||||
type: REPORT_SUBMIT_SUCCESS, |
||||
report, |
||||
}; |
||||
}; |
||||
|
||||
export function submitReportFail(error) { |
||||
return { |
||||
type: REPORT_SUBMIT_FAIL, |
||||
error, |
||||
}; |
||||
}; |
||||
|
||||
export function changeReportComment(comment) { |
||||
return { |
||||
type: REPORT_COMMENT_CHANGE, |
||||
comment, |
||||
}; |
||||
}; |
||||
|
||||
export function changeReportForward(forward) { |
||||
return { |
||||
type: REPORT_FORWARD_CHANGE, |
||||
forward, |
||||
}; |
||||
}; |
||||
export const initReport = (account, status) => dispatch => |
||||
dispatch(openModal('REPORT', { |
||||
accountId: account.get('id'), |
||||
statusId: status?.get('id'), |
||||
})); |
||||
|
||||
export const submitReport = (params, onSuccess, onFail) => (dispatch, getState) => { |
||||
dispatch(submitReportRequest()); |
||||
|
||||
api(getState).post('/api/v1/reports', params).then(response => { |
||||
dispatch(submitReportSuccess(response.data)); |
||||
if (onSuccess) onSuccess(); |
||||
}).catch(error => { |
||||
dispatch(submitReportFail(error)); |
||||
if (onFail) onFail(); |
||||
}); |
||||
}; |
||||
|
||||
export const submitReportRequest = () => ({ |
||||
type: REPORT_SUBMIT_REQUEST, |
||||
}); |
||||
|
||||
export const submitReportSuccess = report => ({ |
||||
type: REPORT_SUBMIT_SUCCESS, |
||||
report, |
||||
}); |
||||
|
||||
export const submitReportFail = error => ({ |
||||
type: REPORT_SUBMIT_FAIL, |
||||
error, |
||||
}); |
||||
|
@ -0,0 +1,27 @@ |
||||
import api from 'flavours/glitch/util/api'; |
||||
|
||||
export const RULES_FETCH_REQUEST = 'RULES_FETCH_REQUEST'; |
||||
export const RULES_FETCH_SUCCESS = 'RULES_FETCH_SUCCESS'; |
||||
export const RULES_FETCH_FAIL = 'RULES_FETCH_FAIL'; |
||||
|
||||
export const fetchRules = () => (dispatch, getState) => { |
||||
dispatch(fetchRulesRequest()); |
||||
|
||||
api(getState) |
||||
.get('/api/v1/instance').then(({ data }) => dispatch(fetchRulesSuccess(data.rules))) |
||||
.catch(err => dispatch(fetchRulesFail(err))); |
||||
}; |
||||
|
||||
const fetchRulesRequest = () => ({ |
||||
type: RULES_FETCH_REQUEST, |
||||
}); |
||||
|
||||
const fetchRulesSuccess = rules => ({ |
||||
type: RULES_FETCH_SUCCESS, |
||||
rules, |
||||
}); |
||||
|
||||
const fetchRulesFail = error => ({ |
||||
type: RULES_FETCH_FAIL, |
||||
error, |
||||
}); |
@ -0,0 +1,9 @@ |
||||
import React from 'react'; |
||||
|
||||
const Check = () => ( |
||||
<svg width='14' height='11' viewBox='0 0 14 11'> |
||||
<path d='M11.264 0L5.26 6.004 2.103 2.847 0 4.95l5.26 5.26 8.108-8.107L11.264 0' fill='currentColor' fillRule='evenodd' /> |
||||
</svg> |
||||
); |
||||
|
||||
export default Check; |
@ -0,0 +1,101 @@ |
||||
// Package imports.
|
||||
import React from 'react'; |
||||
import ImmutablePropTypes from 'react-immutable-proptypes'; |
||||
import PropTypes from 'prop-types'; |
||||
import { FormattedMessage } from 'react-intl'; |
||||
import ImmutablePureComponent from 'react-immutable-pure-component'; |
||||
import { HotKeys } from 'react-hotkeys'; |
||||
import classNames from 'classnames'; |
||||
|
||||
// Our imports.
|
||||
import Permalink from 'flavours/glitch/components/permalink'; |
||||
import AccountContainer from 'flavours/glitch/containers/account_container'; |
||||
import NotificationOverlayContainer from '../containers/overlay_container'; |
||||
import Icon from 'flavours/glitch/components/icon'; |
||||
|
||||
export default class NotificationFollow extends ImmutablePureComponent { |
||||
|
||||
static propTypes = { |
||||
hidden: PropTypes.bool, |
||||
id: PropTypes.string.isRequired, |
||||
account: ImmutablePropTypes.map.isRequired, |
||||
notification: ImmutablePropTypes.map.isRequired, |
||||
unread: PropTypes.bool, |
||||
}; |
||||
|
||||
handleMoveUp = () => { |
||||
const { notification, onMoveUp } = this.props; |
||||
onMoveUp(notification.get('id')); |
||||
} |
||||
|
||||
handleMoveDown = () => { |
||||
const { notification, onMoveDown } = this.props; |
||||
onMoveDown(notification.get('id')); |
||||
} |
||||
|
||||
handleOpen = () => { |
||||
this.handleOpenProfile(); |
||||
} |
||||
|
||||
handleOpenProfile = () => { |
||||
const { notification } = this.props; |
||||
this.context.router.history.push(`/@${notification.getIn(['account', 'acct'])}`); |
||||
} |
||||
|
||||
handleMention = e => { |
||||
e.preventDefault(); |
||||
|
||||
const { notification, onMention } = this.props; |
||||
onMention(notification.get('account'), this.context.router.history); |
||||
} |
||||
|
||||
getHandlers () { |
||||
return { |
||||
moveUp: this.handleMoveUp, |
||||
moveDown: this.handleMoveDown, |
||||
open: this.handleOpen, |
||||
openProfile: this.handleOpenProfile, |
||||
mention: this.handleMention, |
||||
reply: this.handleMention, |
||||
}; |
||||
} |
||||
|
||||
render () { |
||||
const { account, notification, hidden, unread } = this.props; |
||||
|
||||
// Links to the display name.
|
||||
const displayName = account.get('display_name_html') || account.get('username'); |
||||
const link = ( |
||||
<bdi><Permalink |
||||
className='notification__display-name' |
||||
href={account.get('url')} |
||||
title={account.get('acct')} |
||||
to={`/@${account.get('acct')}`} |
||||
dangerouslySetInnerHTML={{ __html: displayName }} |
||||
/></bdi> |
||||
); |
||||
|
||||
// Renders.
|
||||
return ( |
||||
<HotKeys handlers={this.getHandlers()}> |
||||
<div className={classNames('notification notification-admin-sign-up focusable', { unread })} tabIndex='0'> |
||||
<div className='notification__message'> |
||||
<div className='notification__favourite-icon-wrapper'> |
||||
<Icon fixedWidth id='user-plus' /> |
||||
</div> |
||||
|
||||
<FormattedMessage |
||||
id='notification.admin.sign_up' |
||||
defaultMessage='{name} signed up' |
||||
values={{ name: link }} |
||||
/> |
||||
</div> |
||||
|
||||
<AccountContainer hidden={hidden} id={account.get('id')} withNote={false} /> |
||||
<NotificationOverlayContainer notification={notification} /> |
||||
</div> |
||||
</HotKeys> |
||||
); |
||||
} |
||||
|
||||
} |
@ -0,0 +1,93 @@ |
||||
import React from 'react'; |
||||
import PropTypes from 'prop-types'; |
||||
import { defineMessages, injectIntl, FormattedMessage } from 'react-intl'; |
||||
import Button from 'flavours/glitch/components/button'; |
||||
import Option from './components/option'; |
||||
|
||||
const messages = defineMessages({ |
||||
dislike: { id: 'report.reasons.dislike', defaultMessage: 'I don\'t like it' }, |
||||
dislike_description: { id: 'report.reasons.dislike_description', defaultMessage: 'It is not something you want to see' }, |
||||
spam: { id: 'report.reasons.spam', defaultMessage: 'It\'s spam' }, |
||||
spam_description: { id: 'report.reasons.spam_description', defaultMessage: 'Malicious links, fake engagement, or repetetive replies' }, |
||||
violation: { id: 'report.reasons.violation', defaultMessage: 'It violates server rules' }, |
||||
violation_description: { id: 'report.reasons.violation_description', defaultMessage: 'You are aware that it breaks specific rules' }, |
||||
other: { id: 'report.reasons.other', defaultMessage: 'It\'s something else' }, |
||||
other_description: { id: 'report.reasons.other_description', defaultMessage: 'The issue does not fit into other categories' }, |
||||
status: { id: 'report.category.title_status', defaultMessage: 'post' }, |
||||
account: { id: 'report.category.title_account', defaultMessage: 'profile' }, |
||||
}); |
||||
|
||||
export default @injectIntl |
||||
class Category extends React.PureComponent { |
||||
|
||||
static propTypes = { |
||||
onNextStep: PropTypes.func.isRequired, |
||||
category: PropTypes.string, |
||||
onChangeCategory: PropTypes.func.isRequired, |
||||
startedFrom: PropTypes.oneOf(['status', 'account']), |
||||
intl: PropTypes.object.isRequired, |
||||
}; |
||||
|
||||
handleNextClick = () => { |
||||
const { onNextStep, category } = this.props; |
||||
|
||||
switch(category) { |
||||
case 'dislike': |
||||
onNextStep('thanks'); |
||||
break; |
||||
case 'violation': |
||||
onNextStep('rules'); |
||||
break; |
||||
default: |
||||
onNextStep('statuses'); |
||||
break; |
||||
} |
||||
}; |
||||
|
||||
handleCategoryToggle = (value, checked) => { |
||||
const { onChangeCategory } = this.props; |
||||
|
||||
if (checked) { |
||||
onChangeCategory(value); |
||||
} |
||||
}; |
||||
|
||||
render () { |
||||
const { category, startedFrom, intl } = this.props; |
||||
|
||||
const options = [ |
||||
'dislike', |
||||
'spam', |
||||
'violation', |
||||
'other', |
||||
]; |
||||
|
||||
return ( |
||||
<React.Fragment> |
||||
<h3 className='report-dialog-modal__title'><FormattedMessage id='report.category.title' defaultMessage="Tell us what's going on with this {type}" values={{ type: intl.formatMessage(messages[startedFrom]) }} /></h3> |
||||
<p className='report-dialog-modal__lead'><FormattedMessage id='report.category.subtitle' defaultMessage='Choose the best match' /></p> |
||||
|
||||
<div> |
||||
{options.map(item => ( |
||||
<Option |
||||
key={item} |
||||
name='category' |
||||
value={item} |
||||
checked={category === item} |
||||
onToggle={this.handleCategoryToggle} |
||||
label={intl.formatMessage(messages[item])} |
||||
description={intl.formatMessage(messages[`${item}_description`])} |
||||
/> |
||||
))} |
||||
</div> |
||||
|
||||
<div className='flex-spacer' /> |
||||
|
||||
<div className='report-dialog-modal__actions'> |
||||
<Button onClick={this.handleNextClick} disabled={category === null}><FormattedMessage id='report.next' defaultMessage='Next' /></Button> |
||||
</div> |
||||
</React.Fragment> |
||||
); |
||||
} |
||||
|
||||
} |
@ -0,0 +1,83 @@ |
||||
import React from 'react'; |
||||
import PropTypes from 'prop-types'; |
||||
import { injectIntl, defineMessages, FormattedMessage } from 'react-intl'; |
||||
import Button from 'flavours/glitch/components/button'; |
||||
import Toggle from 'react-toggle'; |
||||
|
||||
const messages = defineMessages({ |
||||
placeholder: { id: 'report.placeholder', defaultMessage: 'Type or paste additional comments' }, |
||||
}); |
||||
|
||||
export default @injectIntl |
||||
class Comment extends React.PureComponent { |
||||
|
||||
static propTypes = { |
||||
onSubmit: PropTypes.func.isRequired, |
||||
comment: PropTypes.string.isRequired, |
||||
onChangeComment: PropTypes.func.isRequired, |
||||
intl: PropTypes.object.isRequired, |
||||
isSubmitting: PropTypes.bool, |
||||
forward: PropTypes.bool, |
||||
isRemote: PropTypes.bool, |
||||
domain: PropTypes.string, |
||||
onChangeForward: PropTypes.func.isRequired, |
||||
}; |
||||
|
||||
handleClick = () => { |
||||
const { onSubmit } = this.props; |
||||
onSubmit(); |
||||
}; |
||||
|
||||
handleChange = e => { |
||||
const { onChangeComment } = this.props; |
||||
onChangeComment(e.target.value); |
||||
}; |
||||
|
||||
handleKeyDown = e => { |
||||
if (e.keyCode === 13 && (e.ctrlKey || e.metaKey)) { |
||||
this.handleClick(); |
||||
} |
||||
}; |
||||
|
||||
handleForwardChange = e => { |
||||
const { onChangeForward } = this.props; |
||||
onChangeForward(e.target.checked); |
||||
}; |
||||
|
||||
render () { |
||||
const { comment, isRemote, forward, domain, isSubmitting, intl } = this.props; |
||||
|
||||
return ( |
||||
<React.Fragment> |
||||
<h3 className='report-dialog-modal__title'><FormattedMessage id='report.comment.title' defaultMessage='Is there anything else you think we should know?' /></h3> |
||||
|
||||
<textarea |
||||
className='report-dialog-modal__textarea' |
||||
placeholder={intl.formatMessage(messages.placeholder)} |
||||
value={comment} |
||||
onChange={this.handleChange} |
||||
onKeyDown={this.handleKeyDown} |
||||
disabled={isSubmitting} |
||||
/> |
||||
|
||||
{isRemote && ( |
||||
<React.Fragment> |
||||
<p className='report-dialog-modal__lead'><FormattedMessage id='report.forward_hint' defaultMessage='The account is from another server. Send an anonymized copy of the report there as well?' /></p> |
||||
|
||||
<label className='report-dialog-modal__toggle'> |
||||
<Toggle checked={forward} disabled={isSubmitting} onChange={this.handleForwardChange} /> |
||||
<FormattedMessage id='report.forward' defaultMessage='Forward to {target}' values={{ target: domain }} /> |
||||
</label> |
||||
</React.Fragment> |
||||
)} |
||||
|
||||
<div className='flex-spacer' /> |
||||
|
||||
<div className='report-dialog-modal__actions'> |
||||
<Button onClick={this.handleClick}><FormattedMessage id='report.submit' defaultMessage='Submit report' /></Button> |
||||
</div> |
||||
</React.Fragment> |
||||
); |
||||
} |
||||
|
||||
} |
@ -0,0 +1,60 @@ |
||||
import React from 'react'; |
||||
import PropTypes from 'prop-types'; |
||||
import classNames from 'classnames'; |
||||
import Check from 'flavours/glitch/components/check'; |
||||
|
||||
export default class Option extends React.PureComponent { |
||||
|
||||
static propTypes = { |
||||
name: PropTypes.string.isRequired, |
||||
value: PropTypes.string.isRequired, |
||||
checked: PropTypes.bool, |
||||
label: PropTypes.node, |
||||
description: PropTypes.node, |
||||
onToggle: PropTypes.func, |
||||
multiple: PropTypes.bool, |
||||
labelComponent: PropTypes.node, |
||||
}; |
||||
|
||||
handleKeyPress = e => { |
||||
const { value, checked, onToggle } = this.props; |
||||
|
||||
if (e.key === 'Enter' || e.key === ' ') { |
||||
e.stopPropagation(); |
||||
e.preventDefault(); |
||||
onToggle(value, !checked); |
||||
} |
||||
} |
||||
|
||||
handleChange = e => { |
||||
const { value, onToggle } = this.props; |
||||
onToggle(value, e.target.checked); |
||||
} |
||||
|
||||
render () { |
||||
const { name, value, checked, label, labelComponent, description, multiple } = this.props; |
||||
|
||||
return ( |
||||
<label className='dialog-option poll__option selectable'> |
||||
<input type={multiple ? 'checkbox' : 'radio'} name={name} value={value} checked={checked} onChange={this.handleChange} /> |
||||
|
||||
<span |
||||
className={classNames('poll__input', { active: checked, checkbox: multiple })} |
||||
tabIndex='0' |
||||
role='radio' |
||||
onKeyPress={this.handleKeyPress} |
||||
aria-checked={checked} |
||||
aria-label={label} |
||||
>{checked && <Check />}</span> |
||||
|
||||
{labelComponent ? labelComponent : ( |
||||
<span className='poll__option__text'> |
||||
<strong>{label}</strong> |
||||
{description} |
||||
</span> |
||||
)} |
||||
</label> |
||||
); |
||||
} |
||||
|
||||
} |
@ -1,19 +1,15 @@ |
||||
import { connect } from 'react-redux'; |
||||
import StatusCheckBox from '../components/status_check_box'; |
||||
import { toggleStatusReport } from 'flavours/glitch/actions/reports'; |
||||
import { Set as ImmutableSet } from 'immutable'; |
||||
import { makeGetStatus } from 'flavours/glitch/selectors'; |
||||
|
||||
const mapStateToProps = (state, { id }) => ({ |
||||
status: state.getIn(['statuses', id]), |
||||
checked: state.getIn(['reports', 'new', 'status_ids'], ImmutableSet()).includes(id), |
||||
}); |
||||
const makeMapStateToProps = () => { |
||||
const getStatus = makeGetStatus(); |
||||
|
||||
const mapDispatchToProps = (dispatch, { id }) => ({ |
||||
const mapStateToProps = (state, { id }) => ({ |
||||
status: getStatus(state, { id }), |
||||
}); |
||||
|
||||
onToggle (e) { |
||||
dispatch(toggleStatusReport(id, e.target.checked)); |
||||
}, |
||||
return mapStateToProps; |
||||
}; |
||||
|
||||
}); |
||||
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(StatusCheckBox); |
||||
export default connect(makeMapStateToProps)(StatusCheckBox); |
||||
|
@ -0,0 +1,64 @@ |
||||
import React from 'react'; |
||||
import PropTypes from 'prop-types'; |
||||
import ImmutablePropTypes from 'react-immutable-proptypes'; |
||||
import { connect } from 'react-redux'; |
||||
import { FormattedMessage } from 'react-intl'; |
||||
import Button from 'flavours/glitch/components/button'; |
||||
import Option from './components/option'; |
||||
|
||||
const mapStateToProps = state => ({ |
||||
rules: state.get('rules'), |
||||
}); |
||||
|
||||
export default @connect(mapStateToProps) |
||||
class Rules extends React.PureComponent { |
||||
|
||||
static propTypes = { |
||||
onNextStep: PropTypes.func.isRequired, |
||||
rules: ImmutablePropTypes.list, |
||||
selectedRuleIds: ImmutablePropTypes.set.isRequired, |
||||
onToggle: PropTypes.func.isRequired, |
||||
}; |
||||
|
||||
handleNextClick = () => { |
||||
const { onNextStep } = this.props; |
||||
onNextStep('statuses'); |
||||
}; |
||||
|
||||
handleRulesToggle = (value, checked) => { |
||||
const { onToggle } = this.props; |
||||
onToggle(value, checked); |
||||
}; |
||||
|
||||
render () { |
||||
const { rules, selectedRuleIds } = this.props; |
||||
|
||||
return ( |
||||
<React.Fragment> |
||||
<h3 className='report-dialog-modal__title'><FormattedMessage id='report.rules.title' defaultMessage='Which rules are being violated?' /></h3> |
||||
<p className='report-dialog-modal__lead'><FormattedMessage id='report.rules.subtitle' defaultMessage='Select all that apply' /></p> |
||||
|
||||
<div> |
||||
{rules.map(item => ( |
||||
<Option |
||||
key={item.get('id')} |
||||
name='rule_ids' |
||||
value={item.get('id')} |
||||
checked={selectedRuleIds.includes(item.get('id'))} |
||||
onToggle={this.handleRulesToggle} |
||||
label={item.get('text')} |
||||
multiple |
||||
/> |
||||
))} |
||||
</div> |
||||
|
||||
<div className='flex-spacer' /> |
||||
|
||||
<div className='report-dialog-modal__actions'> |
||||
<Button onClick={this.handleNextClick} disabled={selectedRuleIds.size < 1}><FormattedMessage id='report.next' defaultMessage='Next' /></Button> |
||||
</div> |
||||
</React.Fragment> |
||||
); |
||||
} |
||||
|
||||
} |
@ -0,0 +1,58 @@ |
||||
import React from 'react'; |
||||
import PropTypes from 'prop-types'; |
||||
import ImmutablePropTypes from 'react-immutable-proptypes'; |
||||
import { connect } from 'react-redux'; |
||||
import StatusCheckBox from 'flavours/glitch/features/report/containers/status_check_box_container'; |
||||
import { OrderedSet } from 'immutable'; |
||||
import { FormattedMessage } from 'react-intl'; |
||||
import Button from 'flavours/glitch/components/button'; |
||||
|
||||
const mapStateToProps = (state, { accountId }) => ({ |
||||
availableStatusIds: OrderedSet(state.getIn(['timelines', `account:${accountId}:with_replies`, 'items'])), |
||||
}); |
||||
|
||||
export default @connect(mapStateToProps) |
||||
class Statuses extends React.PureComponent { |
||||
|
||||
static propTypes = { |
||||
onNextStep: PropTypes.func.isRequired, |
||||
accountId: PropTypes.string.isRequired, |
||||
availableStatusIds: ImmutablePropTypes.set.isRequired, |
||||
selectedStatusIds: ImmutablePropTypes.set.isRequired, |
||||
onToggle: PropTypes.func.isRequired, |
||||
}; |
||||
|
||||
handleNextClick = () => { |
||||
const { onNextStep } = this.props; |
||||
onNextStep('comment'); |
||||
}; |
||||
|
||||
render () { |
||||
const { availableStatusIds, selectedStatusIds, onToggle } = this.props; |
||||
|
||||
return ( |
||||
<React.Fragment> |
||||
<h3 className='report-dialog-modal__title'><FormattedMessage id='report.statuses.title' defaultMessage='Are there any posts that back up this report?' /></h3> |
||||
<p className='report-dialog-modal__lead'><FormattedMessage id='report.statuses.subtitle' defaultMessage='Select all that apply' /></p> |
||||
|
||||
<div className='report-dialog-modal__statuses'> |
||||
{availableStatusIds.union(selectedStatusIds).map(statusId => ( |
||||
<StatusCheckBox |
||||
id={statusId} |
||||
key={statusId} |
||||
checked={selectedStatusIds.includes(statusId)} |
||||
onToggle={onToggle} |
||||
/> |
||||
))} |
||||
</div> |
||||
|
||||
<div className='flex-spacer' /> |
||||
|
||||
<div className='report-dialog-modal__actions'> |
||||
<Button onClick={this.handleNextClick}><FormattedMessage id='report.next' defaultMessage='Next' /></Button> |
||||
</div> |
||||
</React.Fragment> |
||||
); |
||||
} |
||||
|
||||
} |
@ -0,0 +1,84 @@ |
||||
import React from 'react'; |
||||
import PropTypes from 'prop-types'; |
||||
import ImmutablePropTypes from 'react-immutable-proptypes'; |
||||
import { FormattedMessage } from 'react-intl'; |
||||
import Button from 'flavours/glitch/components/button'; |
||||
import { connect } from 'react-redux'; |
||||
import { |
||||
unfollowAccount, |
||||
muteAccount, |
||||
blockAccount, |
||||
} from 'mastodon/actions/accounts'; |
||||
|
||||
const mapStateToProps = () => ({}); |
||||
|
||||
export default @connect(mapStateToProps) |
||||
class Thanks extends React.PureComponent { |
||||
|
||||
static propTypes = { |
||||
submitted: PropTypes.bool, |
||||
onClose: PropTypes.func.isRequired, |
||||
account: ImmutablePropTypes.map.isRequired, |
||||
dispatch: PropTypes.func.isRequired, |
||||
}; |
||||
|
||||
handleCloseClick = () => { |
||||
const { onClose } = this.props; |
||||
onClose(); |
||||
}; |
||||
|
||||
handleUnfollowClick = () => { |
||||
const { dispatch, account, onClose } = this.props; |
||||
dispatch(unfollowAccount(account.get('id'))); |
||||
onClose(); |
||||
}; |
||||
|
||||
handleMuteClick = () => { |
||||
const { dispatch, account, onClose } = this.props; |
||||
dispatch(muteAccount(account.get('id'))); |
||||
onClose(); |
||||
}; |
||||
|
||||
handleBlockClick = () => { |
||||
const { dispatch, account, onClose } = this.props; |
||||
dispatch(blockAccount(account.get('id'))); |
||||
onClose(); |
||||
}; |
||||
|
||||
render () { |
||||
const { account, submitted } = this.props; |
||||
|
||||
return ( |
||||
<React.Fragment> |
||||
<h3 className='report-dialog-modal__title'>{submitted ? <FormattedMessage id='report.thanks.title_actionable' defaultMessage="Thanks for reporting, we'll look into this." /> : <FormattedMessage id='report.thanks.title' defaultMessage="Don't want to see this?" />}</h3> |
||||
<p className='report-dialog-modal__lead'>{submitted ? <FormattedMessage id='report.thanks.take_action_actionable' defaultMessage='While we review this, you can take action against @{name}:' values={{ name: account.get('username') }} /> : <FormattedMessage id='report.thanks.take_action' defaultMessage='Here are your options for controlling what you see on Mastodon:' />}</p> |
||||
|
||||
{account.getIn(['relationship', 'following']) && ( |
||||
<React.Fragment> |
||||
<h4 className='report-dialog-modal__subtitle'><FormattedMessage id='report.unfollow' defaultMessage='Unfollow @{name}' values={{ name: account.get('username') }} /></h4> |
||||
<p className='report-dialog-modal__lead'><FormattedMessage id='report.unfollow_explanation' defaultMessage='You are following this account. To not see their posts in your home feed anymore, unfollow them.' /></p> |
||||
<Button secondary onClick={this.handleUnfollowClick}><FormattedMessage id='account.unfollow' defaultMessage='Unfollow' /></Button> |
||||
<hr /> |
||||
</React.Fragment> |
||||
)} |
||||
|
||||
<h4 className='report-dialog-modal__subtitle'><FormattedMessage id='account.mute' defaultMessage='Mute @{name}' values={{ name: account.get('username') }} /></h4> |
||||
<p className='report-dialog-modal__lead'><FormattedMessage id='report.mute_explanation' defaultMessage='You will not see their posts. They can still follow you and see your posts and will not know that they are muted.' /></p> |
||||
<Button secondary onClick={this.handleMuteClick}>{!account.getIn(['relationship', 'muting']) ? <FormattedMessage id='report.mute' defaultMessage='Mute' /> : <FormattedMessage id='account.muted' defaultMessage='Muted' />}</Button> |
||||
|
||||
<hr /> |
||||
|
||||
<h4 className='report-dialog-modal__subtitle'><FormattedMessage id='account.block' defaultMessage='Block @{name}' values={{ name: account.get('username') }} /></h4> |
||||
<p className='report-dialog-modal__lead'><FormattedMessage id='report.block_explanation' defaultMessage='You will not see their posts. They will not be able to see your posts or follow you. They will be able to tell that they are blocked.' /></p> |
||||
<Button secondary onClick={this.handleBlockClick}>{!account.getIn(['relationship', 'blocking']) ? <FormattedMessage id='report.block' defaultMessage='Block' /> : <FormattedMessage id='account.blocked' defaultMessage='Blocked' />}</Button> |
||||
|
||||
<div className='flex-spacer' /> |
||||
|
||||
<div className='report-dialog-modal__actions'> |
||||
<Button onClick={this.handleCloseClick}><FormattedMessage id='report.close' defaultMessage='Done' /></Button> |
||||
</div> |
||||
</React.Fragment> |
||||
); |
||||
} |
||||
|
||||
} |
@ -1,77 +0,0 @@ |
||||
import { |
||||
REPORT_INIT, |
||||
REPORT_SUBMIT_REQUEST, |
||||
REPORT_SUBMIT_SUCCESS, |
||||
REPORT_SUBMIT_FAIL, |
||||
REPORT_CANCEL, |
||||
REPORT_STATUS_TOGGLE, |
||||
REPORT_COMMENT_CHANGE, |
||||
REPORT_FORWARD_CHANGE, |
||||
} from 'flavours/glitch/actions/reports'; |
||||
import { |
||||
TIMELINE_DELETE, |
||||
} from 'flavours/glitch/actions/timelines'; |
||||
import { Map as ImmutableMap, Set as ImmutableSet } from 'immutable'; |
||||
|
||||
const initialState = ImmutableMap({ |
||||
new: ImmutableMap({ |
||||
isSubmitting: false, |
||||
account_id: null, |
||||
status_ids: ImmutableSet(), |
||||
comment: '', |
||||
forward: false, |
||||
}), |
||||
}); |
||||
|
||||
const deleteStatus = (state, id, references) => { |
||||
references.forEach(ref => { |
||||
state = deleteStatus(state, ref[0], []); |
||||
}); |
||||
|
||||
return state.updateIn(['new', 'status_ids'], ImmutableSet(), set => set.remove(id)); |
||||
}; |
||||
|
||||
export default function reports(state = initialState, action) { |
||||
switch(action.type) { |
||||
case REPORT_INIT: |
||||
return state.withMutations(map => { |
||||
map.setIn(['new', 'isSubmitting'], false); |
||||
map.setIn(['new', 'account_id'], action.account.get('id')); |
||||
|
||||
if (state.getIn(['new', 'account_id']) !== action.account.get('id')) { |
||||
map.setIn(['new', 'status_ids'], action.status ? ImmutableSet([action.status.getIn(['reblog', 'id'], action.status.get('id'))]) : ImmutableSet()); |
||||
map.setIn(['new', 'comment'], ''); |
||||
} else if (action.status) { |
||||
map.updateIn(['new', 'status_ids'], ImmutableSet(), set => set.add(action.status.getIn(['reblog', 'id'], action.status.get('id')))); |
||||
} |
||||
}); |
||||
case REPORT_STATUS_TOGGLE: |
||||
return state.updateIn(['new', 'status_ids'], ImmutableSet(), set => { |
||||
if (action.checked) { |
||||
return set.add(action.statusId); |
||||
} |
||||
|
||||
return set.remove(action.statusId); |
||||
}); |
||||
case REPORT_COMMENT_CHANGE: |
||||
return state.setIn(['new', 'comment'], action.comment); |
||||
case REPORT_FORWARD_CHANGE: |
||||
return state.setIn(['new', 'forward'], action.forward); |
||||
case REPORT_SUBMIT_REQUEST: |
||||
return state.setIn(['new', 'isSubmitting'], true); |
||||
case REPORT_SUBMIT_FAIL: |
||||
return state.setIn(['new', 'isSubmitting'], false); |
||||
case REPORT_CANCEL: |
||||
case REPORT_SUBMIT_SUCCESS: |
||||
return state.withMutations(map => { |
||||
map.setIn(['new', 'account_id'], null); |
||||
map.setIn(['new', 'status_ids'], ImmutableSet()); |
||||
map.setIn(['new', 'comment'], ''); |
||||
map.setIn(['new', 'isSubmitting'], false); |
||||
}); |
||||
case TIMELINE_DELETE: |
||||
return deleteStatus(state, action.id, action.references); |
||||
default: |
||||
return state; |
||||
} |
||||
}; |
@ -0,0 +1,13 @@ |
||||
import { RULES_FETCH_SUCCESS } from 'flavours/glitch/actions/rules'; |
||||
import { List as ImmutableList, fromJS } from 'immutable'; |
||||
|
||||
const initialState = ImmutableList(); |
||||
|
||||
export default function rules(state = initialState, action) { |
||||
switch (action.type) { |
||||
case RULES_FETCH_SUCCESS: |
||||
return fromJS(action.rules); |
||||
default: |
||||
return state; |
||||
} |
||||
} |
@ -1,89 +1,38 @@ |
||||
import api from '../api'; |
||||
import { openModal, closeModal } from './modal'; |
||||
|
||||
export const REPORT_INIT = 'REPORT_INIT'; |
||||
export const REPORT_CANCEL = 'REPORT_CANCEL'; |
||||
import { openModal } from './modal'; |
||||
|
||||
export const REPORT_SUBMIT_REQUEST = 'REPORT_SUBMIT_REQUEST'; |
||||
export const REPORT_SUBMIT_SUCCESS = 'REPORT_SUBMIT_SUCCESS'; |
||||
export const REPORT_SUBMIT_FAIL = 'REPORT_SUBMIT_FAIL'; |
||||
|
||||
export const REPORT_STATUS_TOGGLE = 'REPORT_STATUS_TOGGLE'; |
||||
export const REPORT_COMMENT_CHANGE = 'REPORT_COMMENT_CHANGE'; |
||||
export const REPORT_FORWARD_CHANGE = 'REPORT_FORWARD_CHANGE'; |
||||
|
||||
export function initReport(account, status) { |
||||
return dispatch => { |
||||
dispatch({ |
||||
type: REPORT_INIT, |
||||
account, |
||||
status, |
||||
}); |
||||
|
||||
dispatch(openModal('REPORT')); |
||||
}; |
||||
}; |
||||
|
||||
export function cancelReport() { |
||||
return { |
||||
type: REPORT_CANCEL, |
||||
}; |
||||
}; |
||||
|
||||
export function toggleStatusReport(statusId, checked) { |
||||
return { |
||||
type: REPORT_STATUS_TOGGLE, |
||||
statusId, |
||||
checked, |
||||
}; |
||||
}; |
||||
|
||||
export function submitReport() { |
||||
return (dispatch, getState) => { |
||||
dispatch(submitReportRequest()); |
||||
|
||||
api(getState).post('/api/v1/reports', { |
||||
account_id: getState().getIn(['reports', 'new', 'account_id']), |
||||
status_ids: getState().getIn(['reports', 'new', 'status_ids']), |
||||
comment: getState().getIn(['reports', 'new', 'comment']), |
||||
forward: getState().getIn(['reports', 'new', 'forward']), |
||||
}).then(response => { |
||||
dispatch(closeModal()); |
||||
dispatch(submitReportSuccess(response.data)); |
||||
}).catch(error => dispatch(submitReportFail(error))); |
||||
}; |
||||
}; |
||||
|
||||
export function submitReportRequest() { |
||||
return { |
||||
type: REPORT_SUBMIT_REQUEST, |
||||
}; |
||||
}; |
||||
|
||||
export function submitReportSuccess(report) { |
||||
return { |
||||
type: REPORT_SUBMIT_SUCCESS, |
||||
report, |
||||
}; |
||||
}; |
||||
|
||||
export function submitReportFail(error) { |
||||
return { |
||||
type: REPORT_SUBMIT_FAIL, |
||||
error, |
||||
}; |
||||
}; |
||||
|
||||
export function changeReportComment(comment) { |
||||
return { |
||||
type: REPORT_COMMENT_CHANGE, |
||||
comment, |
||||
}; |
||||
}; |
||||
|
||||
export function changeReportForward(forward) { |
||||
return { |
||||
type: REPORT_FORWARD_CHANGE, |
||||
forward, |
||||
}; |
||||
}; |
||||
export const initReport = (account, status) => dispatch => |
||||
dispatch(openModal('REPORT', { |
||||
accountId: account.get('id'), |
||||
statusId: status?.get('id'), |
||||
})); |
||||
|
||||
export const submitReport = (params, onSuccess, onFail) => (dispatch, getState) => { |
||||
dispatch(submitReportRequest()); |
||||
|
||||
api(getState).post('/api/v1/reports', params).then(response => { |
||||
dispatch(submitReportSuccess(response.data)); |
||||
if (onSuccess) onSuccess(); |
||||
}).catch(error => { |
||||
dispatch(submitReportFail(error)); |
||||
if (onFail) onFail(); |
||||
}); |
||||
}; |
||||
|
||||
export const submitReportRequest = () => ({ |
||||
type: REPORT_SUBMIT_REQUEST, |
||||
}); |
||||
|
||||
export const submitReportSuccess = report => ({ |
||||
type: REPORT_SUBMIT_SUCCESS, |
||||
report, |
||||
}); |
||||
|
||||
export const submitReportFail = error => ({ |
||||
type: REPORT_SUBMIT_FAIL, |
||||
error, |
||||
}); |
||||
|
@ -0,0 +1,27 @@ |
||||
import api from '../api'; |
||||
|
||||
export const RULES_FETCH_REQUEST = 'RULES_FETCH_REQUEST'; |
||||
export const RULES_FETCH_SUCCESS = 'RULES_FETCH_SUCCESS'; |
||||
export const RULES_FETCH_FAIL = 'RULES_FETCH_FAIL'; |
||||
|
||||
export const fetchRules = () => (dispatch, getState) => { |
||||
dispatch(fetchRulesRequest()); |
||||
|
||||
api(getState) |
||||
.get('/api/v1/instance').then(({ data }) => dispatch(fetchRulesSuccess(data.rules))) |
||||
.catch(err => dispatch(fetchRulesFail(err))); |
||||
}; |
||||
|
||||
const fetchRulesRequest = () => ({ |
||||
type: RULES_FETCH_REQUEST, |
||||
}); |
||||
|
||||
const fetchRulesSuccess = rules => ({ |
||||
type: RULES_FETCH_SUCCESS, |
||||
rules, |
||||
}); |
||||
|
||||
const fetchRulesFail = error => ({ |
||||
type: RULES_FETCH_FAIL, |
||||
error, |
||||
}); |
@ -0,0 +1,9 @@ |
||||
import React from 'react'; |
||||
|
||||
const Check = () => ( |
||||
<svg width='14' height='11' viewBox='0 0 14 11'> |
||||
<path d='M11.264 0L5.26 6.004 2.103 2.847 0 4.95l5.26 5.26 8.108-8.107L11.264 0' fill='currentColor' fillRule='evenodd' /> |
||||
</svg> |
||||
); |
||||
|
||||
export default Check; |
@ -0,0 +1,93 @@ |
||||
import React from 'react'; |
||||
import PropTypes from 'prop-types'; |
||||
import { defineMessages, injectIntl, FormattedMessage } from 'react-intl'; |
||||
import Button from 'mastodon/components/button'; |
||||
import Option from './components/option'; |
||||
|
||||
const messages = defineMessages({ |
||||
dislike: { id: 'report.reasons.dislike', defaultMessage: 'I don\'t like it' }, |
||||
dislike_description: { id: 'report.reasons.dislike_description', defaultMessage: 'It is not something you want to see' }, |
||||
spam: { id: 'report.reasons.spam', defaultMessage: 'It\'s spam' }, |
||||
spam_description: { id: 'report.reasons.spam_description', defaultMessage: 'Malicious links, fake engagement, or repetetive replies' }, |
||||
violation: { id: 'report.reasons.violation', defaultMessage: 'It violates server rules' }, |
||||
violation_description: { id: 'report.reasons.violation_description', defaultMessage: 'You are aware that it breaks specific rules' }, |
||||
other: { id: 'report.reasons.other', defaultMessage: 'It\'s something else' }, |
||||
other_description: { id: 'report.reasons.other_description', defaultMessage: 'The issue does not fit into other categories' }, |
||||
status: { id: 'report.category.title_status', defaultMessage: 'post' }, |
||||
account: { id: 'report.category.title_account', defaultMessage: 'profile' }, |
||||
}); |
||||
|
||||
export default @injectIntl |
||||
class Category extends React.PureComponent { |
||||
|
||||
static propTypes = { |
||||
onNextStep: PropTypes.func.isRequired, |
||||
category: PropTypes.string, |
||||
onChangeCategory: PropTypes.func.isRequired, |
||||
startedFrom: PropTypes.oneOf(['status', 'account']), |
||||
intl: PropTypes.object.isRequired, |
||||
}; |
||||
|
||||
handleNextClick = () => { |
||||
const { onNextStep, category } = this.props; |
||||
|
||||
switch(category) { |
||||
case 'dislike': |
||||
onNextStep('thanks'); |
||||
break; |
||||
case 'violation': |
||||
onNextStep('rules'); |
||||
break; |
||||
default: |
||||
onNextStep('statuses'); |
||||
break; |
||||
} |
||||
}; |
||||
|
||||
handleCategoryToggle = (value, checked) => { |
||||
const { onChangeCategory } = this.props; |
||||
|
||||
if (checked) { |
||||
onChangeCategory(value); |
||||
} |
||||
}; |
||||
|
||||
render () { |
||||
const { category, startedFrom, intl } = this.props; |
||||
|
||||
const options = [ |
||||
'dislike', |
||||
'spam', |
||||
'violation', |
||||
'other', |
||||
]; |
||||
|
||||
return ( |
||||
<React.Fragment> |
||||
<h3 className='report-dialog-modal__title'><FormattedMessage id='report.category.title' defaultMessage="Tell us what's going on with this {type}" values={{ type: intl.formatMessage(messages[startedFrom]) }} /></h3> |
||||
<p className='report-dialog-modal__lead'><FormattedMessage id='report.category.subtitle' defaultMessage='Choose the best match' /></p> |
||||
|
||||
<div> |
||||
{options.map(item => ( |
||||
<Option |
||||
key={item} |
||||
name='category' |
||||
value={item} |
||||
checked={category === item} |
||||
onToggle={this.handleCategoryToggle} |
||||
label={intl.formatMessage(messages[item])} |
||||
description={intl.formatMessage(messages[`${item}_description`])} |
||||
/> |
||||
))} |
||||
</div> |
||||
|
||||
<div className='flex-spacer' /> |
||||
|
||||
<div className='report-dialog-modal__actions'> |
||||
<Button onClick={this.handleNextClick} disabled={category === null}><FormattedMessage id='report.next' defaultMessage='Next' /></Button> |
||||
</div> |
||||
</React.Fragment> |
||||
); |
||||
} |
||||
|
||||
} |
@ -0,0 +1,83 @@ |
||||
import React from 'react'; |
||||
import PropTypes from 'prop-types'; |
||||
import { injectIntl, defineMessages, FormattedMessage } from 'react-intl'; |
||||
import Button from 'mastodon/components/button'; |
||||
import Toggle from 'react-toggle'; |
||||
|
||||
const messages = defineMessages({ |
||||
placeholder: { id: 'report.placeholder', defaultMessage: 'Type or paste additional comments' }, |
||||
}); |
||||
|
||||
export default @injectIntl |
||||
class Comment extends React.PureComponent { |
||||
|
||||
static propTypes = { |
||||
onSubmit: PropTypes.func.isRequired, |
||||
comment: PropTypes.string.isRequired, |
||||
onChangeComment: PropTypes.func.isRequired, |
||||
intl: PropTypes.object.isRequired, |
||||
isSubmitting: PropTypes.bool, |
||||
forward: PropTypes.bool, |
||||
isRemote: PropTypes.bool, |
||||
domain: PropTypes.string, |
||||
onChangeForward: PropTypes.func.isRequired, |
||||
}; |
||||
|
||||
handleClick = () => { |
||||
const { onSubmit } = this.props; |
||||
onSubmit(); |
||||
}; |
||||
|
||||
handleChange = e => { |
||||
const { onChangeComment } = this.props; |
||||
onChangeComment(e.target.value); |
||||
}; |
||||
|
||||
handleKeyDown = e => { |
||||
if (e.keyCode === 13 && (e.ctrlKey || e.metaKey)) { |
||||
this.handleClick(); |
||||
} |
||||
}; |
||||
|
||||
handleForwardChange = e => { |
||||
const { onChangeForward } = this.props; |
||||
onChangeForward(e.target.checked); |
||||
}; |
||||
|
||||
render () { |
||||
const { comment, isRemote, forward, domain, isSubmitting, intl } = this.props; |
||||
|
||||
return ( |
||||
<React.Fragment> |
||||
<h3 className='report-dialog-modal__title'><FormattedMessage id='report.comment.title' defaultMessage='Is there anything else you think we should know?' /></h3> |
||||
|
||||
<textarea |
||||
className='report-dialog-modal__textarea' |
||||
placeholder={intl.formatMessage(messages.placeholder)} |
||||
value={comment} |
||||
onChange={this.handleChange} |
||||
onKeyDown={this.handleKeyDown} |
||||
disabled={isSubmitting} |
||||
/> |
||||
|
||||
{isRemote && ( |
||||
<React.Fragment> |
||||
<p className='report-dialog-modal__lead'><FormattedMessage id='report.forward_hint' defaultMessage='The account is from another server. Send an anonymized copy of the report there as well?' /></p> |
||||
|
||||
<label className='report-dialog-modal__toggle'> |
||||
<Toggle checked={forward} disabled={isSubmitting} onChange={this.handleForwardChange} /> |
||||
<FormattedMessage id='report.forward' defaultMessage='Forward to {target}' values={{ target: domain }} /> |
||||
</label> |
||||
</React.Fragment> |
||||
)} |
||||
|
||||
<div className='flex-spacer' /> |
||||
|
||||
<div className='report-dialog-modal__actions'> |
||||
<Button onClick={this.handleClick}><FormattedMessage id='report.submit' defaultMessage='Submit report' /></Button> |
||||
</div> |
||||
</React.Fragment> |
||||
); |
||||
} |
||||
|
||||
} |
@ -0,0 +1,60 @@ |
||||
import React from 'react'; |
||||
import PropTypes from 'prop-types'; |
||||
import classNames from 'classnames'; |
||||
import Check from 'mastodon/components/check'; |
||||
|
||||
export default class Option extends React.PureComponent { |
||||
|
||||
static propTypes = { |
||||
name: PropTypes.string.isRequired, |
||||
value: PropTypes.string.isRequired, |
||||
checked: PropTypes.bool, |
||||
label: PropTypes.node, |
||||
description: PropTypes.node, |
||||
onToggle: PropTypes.func, |
||||
multiple: PropTypes.bool, |
||||
labelComponent: PropTypes.node, |
||||
}; |
||||
|
||||
handleKeyPress = e => { |
||||
const { value, checked, onToggle } = this.props; |
||||
|
||||
if (e.key === 'Enter' || e.key === ' ') { |
||||
e.stopPropagation(); |
||||
e.preventDefault(); |
||||
onToggle(value, !checked); |
||||
} |
||||
} |
||||
|
||||
handleChange = e => { |
||||
const { value, onToggle } = this.props; |
||||
onToggle(value, e.target.checked); |
||||
} |
||||
|
||||
render () { |
||||
const { name, value, checked, label, labelComponent, description, multiple } = this.props; |
||||
|
||||
return ( |
||||
<label className='dialog-option poll__option selectable'> |
||||
<input type={multiple ? 'checkbox' : 'radio'} name={name} value={value} checked={checked} onChange={this.handleChange} /> |
||||
|
||||
<span |
||||
className={classNames('poll__input', { active: checked, checkbox: multiple })} |
||||
tabIndex='0' |
||||
role='radio' |
||||
onKeyPress={this.handleKeyPress} |
||||
aria-checked={checked} |
||||
aria-label={label} |
||||
>{checked && <Check />}</span> |
||||
|
||||
{labelComponent ? labelComponent : ( |
||||
<span className='poll__option__text'> |
||||
<strong>{label}</strong> |
||||
{description} |
||||
</span> |
||||
)} |
||||
</label> |
||||
); |
||||
} |
||||
|
||||
} |
@ -1,19 +1,15 @@ |
||||
import { connect } from 'react-redux'; |
||||
import StatusCheckBox from '../components/status_check_box'; |
||||
import { toggleStatusReport } from '../../../actions/reports'; |
||||
import { Set as ImmutableSet } from 'immutable'; |
||||
import { makeGetStatus } from 'mastodon/selectors'; |
||||
|
||||
const mapStateToProps = (state, { id }) => ({ |
||||
status: state.getIn(['statuses', id]), |
||||
checked: state.getIn(['reports', 'new', 'status_ids'], ImmutableSet()).includes(id), |
||||
}); |
||||
const makeMapStateToProps = () => { |
||||
const getStatus = makeGetStatus(); |
||||
|
||||
const mapDispatchToProps = (dispatch, { id }) => ({ |
||||
const mapStateToProps = (state, { id }) => ({ |
||||
status: getStatus(state, { id }), |
||||
}); |
||||
|
||||
onToggle (e) { |
||||
dispatch(toggleStatusReport(id, e.target.checked)); |
||||
}, |
||||
return mapStateToProps; |
||||
}; |
||||
|
||||
}); |
||||
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(StatusCheckBox); |
||||
export default connect(makeMapStateToProps)(StatusCheckBox); |
||||
|
@ -0,0 +1,64 @@ |
||||
import React from 'react'; |
||||
import PropTypes from 'prop-types'; |
||||
import ImmutablePropTypes from 'react-immutable-proptypes'; |
||||
import { connect } from 'react-redux'; |
||||
import { FormattedMessage } from 'react-intl'; |
||||
import Button from 'mastodon/components/button'; |
||||
import Option from './components/option'; |
||||
|
||||
const mapStateToProps = state => ({ |
||||
rules: state.get('rules'), |
||||
}); |
||||
|
||||
export default @connect(mapStateToProps) |
||||
class Rules extends React.PureComponent { |
||||
|
||||
static propTypes = { |
||||
onNextStep: PropTypes.func.isRequired, |
||||
rules: ImmutablePropTypes.list, |
||||
selectedRuleIds: ImmutablePropTypes.set.isRequired, |
||||
onToggle: PropTypes.func.isRequired, |
||||
}; |
||||
|
||||
handleNextClick = () => { |
||||
const { onNextStep } = this.props; |
||||
onNextStep('statuses'); |
||||
}; |
||||
|
||||
handleRulesToggle = (value, checked) => { |
||||
const { onToggle } = this.props; |
||||
onToggle(value, checked); |
||||
}; |
||||
|
||||
render () { |
||||
const { rules, selectedRuleIds } = this.props; |
||||
|
||||
return ( |
||||
<React.Fragment> |
||||
<h3 className='report-dialog-modal__title'><FormattedMessage id='report.rules.title' defaultMessage='Which rules are being violated?' /></h3> |
||||
<p className='report-dialog-modal__lead'><FormattedMessage id='report.rules.subtitle' defaultMessage='Select all that apply' /></p> |
||||
|
||||
<div> |
||||
{rules.map(item => ( |
||||
<Option |
||||
key={item.get('id')} |
||||
name='rule_ids' |
||||
value={item.get('id')} |
||||
checked={selectedRuleIds.includes(item.get('id'))} |
||||
onToggle={this.handleRulesToggle} |
||||
label={item.get('text')} |
||||
multiple |
||||
/> |
||||
))} |
||||
</div> |
||||
|
||||
<div className='flex-spacer' /> |
||||
|
||||
<div className='report-dialog-modal__actions'> |
||||
<Button onClick={this.handleNextClick} disabled={selectedRuleIds.size < 1}><FormattedMessage id='report.next' defaultMessage='Next' /></Button> |
||||
</div> |
||||
</React.Fragment> |
||||
); |
||||
} |
||||
|
||||
} |
@ -0,0 +1,58 @@ |
||||
import React from 'react'; |
||||
import PropTypes from 'prop-types'; |
||||
import ImmutablePropTypes from 'react-immutable-proptypes'; |
||||
import { connect } from 'react-redux'; |
||||
import StatusCheckBox from 'mastodon/features/report/containers/status_check_box_container'; |
||||
import { OrderedSet } from 'immutable'; |
||||
import { FormattedMessage } from 'react-intl'; |
||||
import Button from 'mastodon/components/button'; |
||||
|
||||
const mapStateToProps = (state, { accountId }) => ({ |
||||
availableStatusIds: OrderedSet(state.getIn(['timelines', `account:${accountId}:with_replies`, 'items'])), |
||||
}); |
||||
|
||||
export default @connect(mapStateToProps) |
||||
class Statuses extends React.PureComponent { |
||||
|
||||
static propTypes = { |
||||
onNextStep: PropTypes.func.isRequired, |
||||
accountId: PropTypes.string.isRequired, |
||||
availableStatusIds: ImmutablePropTypes.set.isRequired, |
||||
selectedStatusIds: ImmutablePropTypes.set.isRequired, |
||||
onToggle: PropTypes.func.isRequired, |
||||
}; |
||||
|
||||
handleNextClick = () => { |
||||
const { onNextStep } = this.props; |
||||
onNextStep('comment'); |
||||
}; |
||||
|
||||
render () { |
||||
const { availableStatusIds, selectedStatusIds, onToggle } = this.props; |
||||
|
||||
return ( |
||||
<React.Fragment> |
||||
<h3 className='report-dialog-modal__title'><FormattedMessage id='report.statuses.title' defaultMessage='Are there any posts that back up this report?' /></h3> |
||||
<p className='report-dialog-modal__lead'><FormattedMessage id='report.statuses.subtitle' defaultMessage='Select all that apply' /></p> |
||||
|
||||
<div className='report-dialog-modal__statuses'> |
||||
{availableStatusIds.union(selectedStatusIds).map(statusId => ( |
||||
<StatusCheckBox |
||||
id={statusId} |
||||
key={statusId} |
||||
checked={selectedStatusIds.includes(statusId)} |
||||
onToggle={onToggle} |
||||
/> |
||||
))} |
||||
</div> |
||||
|
||||
<div className='flex-spacer' /> |
||||
|
||||
<div className='report-dialog-modal__actions'> |
||||
<Button onClick={this.handleNextClick}><FormattedMessage id='report.next' defaultMessage='Next' /></Button> |
||||
</div> |
||||
</React.Fragment> |
||||
); |
||||
} |
||||
|
||||
} |
@ -0,0 +1,84 @@ |
||||
import React from 'react'; |
||||
import PropTypes from 'prop-types'; |
||||
import ImmutablePropTypes from 'react-immutable-proptypes'; |
||||
import { FormattedMessage } from 'react-intl'; |
||||
import Button from 'mastodon/components/button'; |
||||
import { connect } from 'react-redux'; |
||||
import { |
||||
unfollowAccount, |
||||
muteAccount, |
||||
blockAccount, |
||||
} from 'mastodon/actions/accounts'; |
||||
|
||||
const mapStateToProps = () => ({}); |
||||
|
||||
export default @connect(mapStateToProps) |
||||
class Thanks extends React.PureComponent { |
||||
|
||||
static propTypes = { |
||||
submitted: PropTypes.bool, |
||||
onClose: PropTypes.func.isRequired, |
||||
account: ImmutablePropTypes.map.isRequired, |
||||
dispatch: PropTypes.func.isRequired, |
||||
}; |
||||
|
||||
handleCloseClick = () => { |
||||
const { onClose } = this.props; |
||||
onClose(); |
||||
}; |
||||
|
||||
handleUnfollowClick = () => { |
||||
const { dispatch, account, onClose } = this.props; |
||||
dispatch(unfollowAccount(account.get('id'))); |
||||
onClose(); |
||||
}; |
||||
|
||||
handleMuteClick = () => { |
||||
const { dispatch, account, onClose } = this.props; |
||||
dispatch(muteAccount(account.get('id'))); |
||||
onClose(); |
||||
}; |
||||
|
||||
handleBlockClick = () => { |
||||
const { dispatch, account, onClose } = this.props; |
||||
dispatch(blockAccount(account.get('id'))); |
||||
onClose(); |
||||
}; |
||||
|
||||
render () { |
||||
const { account, submitted } = this.props; |
||||
|
||||
return ( |
||||
<React.Fragment> |
||||
<h3 className='report-dialog-modal__title'>{submitted ? <FormattedMessage id='report.thanks.title_actionable' defaultMessage="Thanks for reporting, we'll look into this." /> : <FormattedMessage id='report.thanks.title' defaultMessage="Don't want to see this?" />}</h3> |
||||
<p className='report-dialog-modal__lead'>{submitted ? <FormattedMessage id='report.thanks.take_action_actionable' defaultMessage='While we review this, you can take action against @{name}:' values={{ name: account.get('username') }} /> : <FormattedMessage id='report.thanks.take_action' defaultMessage='Here are your options for controlling what you see on Mastodon:' />}</p> |
||||
|
||||
{account.getIn(['relationship', 'following']) && ( |
||||
<React.Fragment> |
||||
<h4 className='report-dialog-modal__subtitle'><FormattedMessage id='report.unfollow' defaultMessage='Unfollow @{name}' values={{ name: account.get('username') }} /></h4> |
||||
<p className='report-dialog-modal__lead'><FormattedMessage id='report.unfollow_explanation' defaultMessage='You are following this account. To not see their posts in your home feed anymore, unfollow them.' /></p> |
||||
<Button secondary onClick={this.handleUnfollowClick}><FormattedMessage id='account.unfollow' defaultMessage='Unfollow' /></Button> |
||||
<hr /> |
||||
</React.Fragment> |
||||
)} |
||||
|
||||
<h4 className='report-dialog-modal__subtitle'><FormattedMessage id='account.mute' defaultMessage='Mute @{name}' values={{ name: account.get('username') }} /></h4> |
||||
<p className='report-dialog-modal__lead'><FormattedMessage id='report.mute_explanation' defaultMessage='You will not see their posts. They can still follow you and see your posts and will not know that they are muted.' /></p> |
||||
<Button secondary onClick={this.handleMuteClick}>{!account.getIn(['relationship', 'muting']) ? <FormattedMessage id='report.mute' defaultMessage='Mute' /> : <FormattedMessage id='account.muted' defaultMessage='Muted' />}</Button> |
||||
|
||||
<hr /> |
||||
|
||||
<h4 className='report-dialog-modal__subtitle'><FormattedMessage id='account.block' defaultMessage='Block @{name}' values={{ name: account.get('username') }} /></h4> |
||||
<p className='report-dialog-modal__lead'><FormattedMessage id='report.block_explanation' defaultMessage='You will not see their posts. They will not be able to see your posts or follow you. They will be able to tell that they are blocked.' /></p> |
||||
<Button secondary onClick={this.handleBlockClick}>{!account.getIn(['relationship', 'blocking']) ? <FormattedMessage id='report.block' defaultMessage='Block' /> : <FormattedMessage id='account.blocked' defaultMessage='Blocked' />}</Button> |
||||
|
||||
<div className='flex-spacer' /> |
||||
|
||||
<div className='report-dialog-modal__actions'> |
||||
<Button onClick={this.handleCloseClick}><FormattedMessage id='report.close' defaultMessage='Done' /></Button> |
||||
</div> |
||||
</React.Fragment> |
||||
); |
||||
} |
||||
|
||||
} |
@ -1,64 +0,0 @@ |
||||
import { |
||||
REPORT_INIT, |
||||
REPORT_SUBMIT_REQUEST, |
||||
REPORT_SUBMIT_SUCCESS, |
||||
REPORT_SUBMIT_FAIL, |
||||
REPORT_CANCEL, |
||||
REPORT_STATUS_TOGGLE, |
||||
REPORT_COMMENT_CHANGE, |
||||
REPORT_FORWARD_CHANGE, |
||||
} from '../actions/reports'; |
||||
import { Map as ImmutableMap, Set as ImmutableSet } from 'immutable'; |
||||
|
||||
const initialState = ImmutableMap({ |
||||
new: ImmutableMap({ |
||||
isSubmitting: false, |
||||
account_id: null, |
||||
status_ids: ImmutableSet(), |
||||
comment: '', |
||||
forward: false, |
||||
}), |
||||
}); |
||||
|
||||
export default function reports(state = initialState, action) { |
||||
switch(action.type) { |
||||
case REPORT_INIT: |
||||
return state.withMutations(map => { |
||||
map.setIn(['new', 'isSubmitting'], false); |
||||
map.setIn(['new', 'account_id'], action.account.get('id')); |
||||
|
||||
if (state.getIn(['new', 'account_id']) !== action.account.get('id')) { |
||||
map.setIn(['new', 'status_ids'], action.status ? ImmutableSet([action.status.getIn(['reblog', 'id'], action.status.get('id'))]) : ImmutableSet()); |
||||
map.setIn(['new', 'comment'], ''); |
||||
} else if (action.status) { |
||||
map.updateIn(['new', 'status_ids'], ImmutableSet(), set => set.add(action.status.getIn(['reblog', 'id'], action.status.get('id')))); |
||||
} |
||||
}); |
||||
case REPORT_STATUS_TOGGLE: |
||||
return state.updateIn(['new', 'status_ids'], ImmutableSet(), set => { |
||||
if (action.checked) { |
||||
return set.add(action.statusId); |
||||
} |
||||
|
||||
return set.remove(action.statusId); |
||||
}); |
||||
case REPORT_COMMENT_CHANGE: |
||||
return state.setIn(['new', 'comment'], action.comment); |
||||
case REPORT_FORWARD_CHANGE: |
||||
return state.setIn(['new', 'forward'], action.forward); |
||||
case REPORT_SUBMIT_REQUEST: |
||||
return state.setIn(['new', 'isSubmitting'], true); |
||||
case REPORT_SUBMIT_FAIL: |
||||
return state.setIn(['new', 'isSubmitting'], false); |
||||
case REPORT_CANCEL: |
||||
case REPORT_SUBMIT_SUCCESS: |
||||
return state.withMutations(map => { |
||||
map.setIn(['new', 'account_id'], null); |
||||
map.setIn(['new', 'status_ids'], ImmutableSet()); |
||||
map.setIn(['new', 'comment'], ''); |
||||
map.setIn(['new', 'isSubmitting'], false); |
||||
}); |
||||
default: |
||||
return state; |
||||
} |
||||
}; |
@ -0,0 +1,13 @@ |
||||
import { RULES_FETCH_SUCCESS } from 'mastodon/actions/rules'; |
||||
import { List as ImmutableList, fromJS } from 'immutable'; |
||||
|
||||
const initialState = ImmutableList(); |
||||
|
||||
export default function rules(state = initialState, action) { |
||||
switch (action.type) { |
||||
case RULES_FETCH_SUCCESS: |
||||
return fromJS(action.rules); |
||||
default: |
||||
return state; |
||||
} |
||||
} |
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in new issue