* Change report modal to include category selection in web UI * Various fixes and improvements - Change thank you text to be different based on category - Change starting headline to be different for account and status reports - Change toggle components to have a checkmark when checked - Fix report dialog being cut off on small screens - Fix thank you screen offering mute or block if already muted or blocked - Refactor toggle components in report dialog into one component * Change wording on final screen * Change checkboxes to be square when multiple options are possibleclosed-social-glitch-2
@ -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> | |||
); | |||
} | |||
} |
@ -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; | |||
} | |||
} |