forked from mastodon/mastodon
-
Notifications
You must be signed in to change notification settings - Fork 183
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge commit 'ea10febd257b5b729a50aeb3218389763f5f4b97' into glitch-s…
…oc/merge-upstream
- Loading branch information
Showing
24 changed files
with
284 additions
and
246 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,87 +1,121 @@ | ||
import PropTypes from 'prop-types'; | ||
import { PureComponent } from 'react'; | ||
import { useCallback, useEffect, useRef } from 'react'; | ||
|
||
import { injectIntl, defineMessages, FormattedMessage } from 'react-intl'; | ||
import { useIntl, defineMessages, FormattedMessage } from 'react-intl'; | ||
|
||
import { OrderedSet, List as ImmutableList } from 'immutable'; | ||
import ImmutablePropTypes from 'react-immutable-proptypes'; | ||
import { shallowEqual } from 'react-redux'; | ||
import { createSelector } from 'reselect'; | ||
|
||
import Toggle from 'react-toggle'; | ||
|
||
import { fetchAccount } from 'mastodon/actions/accounts'; | ||
import Button from 'mastodon/components/button'; | ||
import { useAppDispatch, useAppSelector } from 'mastodon/store'; | ||
|
||
const messages = defineMessages({ | ||
placeholder: { id: 'report.placeholder', defaultMessage: 'Type or paste additional comments' }, | ||
}); | ||
|
||
class Comment extends 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 => { | ||
const selectRepliedToAccountIds = createSelector( | ||
[ | ||
(state) => state.get('statuses'), | ||
(_, statusIds) => statusIds, | ||
], | ||
(statusesMap, statusIds) => statusIds.map((statusId) => statusesMap.getIn([statusId, 'in_reply_to_account_id'])), | ||
{ | ||
resultEqualityCheck: shallowEqual, | ||
} | ||
); | ||
|
||
const Comment = ({ comment, domain, statusIds, isRemote, isSubmitting, selectedDomains, onSubmit, onChangeComment, onToggleDomain }) => { | ||
const intl = useIntl(); | ||
|
||
const dispatch = useAppDispatch(); | ||
const loadedRef = useRef(false); | ||
|
||
const handleClick = useCallback(() => onSubmit(), [onSubmit]); | ||
const handleChange = useCallback((e) => onChangeComment(e.target.value), [onChangeComment]); | ||
const handleToggleDomain = useCallback(e => onToggleDomain(e.target.value, e.target.checked), [onToggleDomain]); | ||
|
||
const handleKeyDown = useCallback((e) => { | ||
if (e.keyCode === 13 && (e.ctrlKey || e.metaKey)) { | ||
this.handleClick(); | ||
handleClick(); | ||
} | ||
}; | ||
|
||
handleForwardChange = e => { | ||
const { onChangeForward } = this.props; | ||
onChangeForward(e.target.checked); | ||
}; | ||
|
||
render () { | ||
const { comment, isRemote, forward, domain, isSubmitting, intl } = this.props; | ||
|
||
return ( | ||
<> | ||
<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 && ( | ||
<> | ||
<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} /> | ||
}, [handleClick]); | ||
|
||
// Memoize accountIds since we don't want it to trigger `useEffect` on each render | ||
const accountIds = useAppSelector((state) => domain ? selectRepliedToAccountIds(state, statusIds) : ImmutableList()); | ||
|
||
// While we could memoize `availableDomains`, it is pretty inexpensive to recompute | ||
const accountsMap = useAppSelector((state) => state.get('accounts')); | ||
const availableDomains = domain ? OrderedSet([domain]).union(accountIds.map((accountId) => accountsMap.getIn([accountId, 'acct'], '').split('@')[1]).filter(domain => !!domain)) : OrderedSet(); | ||
|
||
useEffect(() => { | ||
if (loadedRef.current) { | ||
return; | ||
} | ||
|
||
loadedRef.current = true; | ||
|
||
// First, pre-select known domains | ||
availableDomains.forEach((domain) => { | ||
onToggleDomain(domain, true); | ||
}); | ||
|
||
// Then, fetch missing replied-to accounts | ||
const unknownAccounts = OrderedSet(accountIds.filter(accountId => accountId && !accountsMap.has(accountId))); | ||
unknownAccounts.forEach((accountId) => { | ||
dispatch(fetchAccount(accountId)); | ||
}); | ||
}); | ||
|
||
return ( | ||
<> | ||
<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={handleChange} | ||
onKeyDown={handleKeyDown} | ||
disabled={isSubmitting} | ||
/> | ||
|
||
{isRemote && ( | ||
<> | ||
<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> | ||
|
||
{ availableDomains.map((domain) => ( | ||
<label className='report-dialog-modal__toggle' key={`toggle-${domain}`}> | ||
<Toggle checked={selectedDomains.includes(domain)} disabled={isSubmitting} onChange={handleToggleDomain} value={domain} /> | ||
<FormattedMessage id='report.forward' defaultMessage='Forward to {target}' values={{ target: domain }} /> | ||
</label> | ||
</> | ||
)} | ||
|
||
<div className='flex-spacer' /> | ||
))} | ||
</> | ||
)} | ||
|
||
<div className='report-dialog-modal__actions'> | ||
<Button onClick={this.handleClick} disabled={isSubmitting}><FormattedMessage id='report.submit' defaultMessage='Submit report' /></Button> | ||
</div> | ||
</> | ||
); | ||
} | ||
<div className='flex-spacer' /> | ||
|
||
<div className='report-dialog-modal__actions'> | ||
<Button onClick={handleClick} disabled={isSubmitting}><FormattedMessage id='report.submit' defaultMessage='Submit report' /></Button> | ||
</div> | ||
</> | ||
); | ||
} | ||
|
||
export default injectIntl(Comment); | ||
Comment.propTypes = { | ||
comment: PropTypes.string.isRequired, | ||
domain: PropTypes.string, | ||
statusIds: ImmutablePropTypes.list.isRequired, | ||
isRemote: PropTypes.bool, | ||
isSubmitting: PropTypes.bool, | ||
selectedDomains: ImmutablePropTypes.set.isRequired, | ||
onSubmit: PropTypes.func.isRequired, | ||
onChangeComment: PropTypes.func.isRequired, | ||
onToggleDomain: PropTypes.func.isRequired, | ||
}; | ||
|
||
export default Comment; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.