gab-social/app/javascript/gabsocial/features/status/status.js

454 lines
13 KiB
JavaScript
Raw Normal View History

2020-03-14 17:31:29 +00:00
import Immutable from 'immutable'
import ImmutablePropTypes from 'react-immutable-proptypes'
import { defineMessages, injectIntl, FormattedMessage } from 'react-intl'
import ImmutablePureComponent from 'react-immutable-pure-component'
import { HotKeys } from 'react-hotkeys'
import { fetchStatus } from '../../actions/statuses'
import {
2020-03-04 22:26:01 +00:00
favorite,
unfavorite,
2020-03-08 23:02:28 +00:00
repost,
unrepost,
pin,
unpin,
2020-03-14 17:31:29 +00:00
} from '../../actions/interactions'
import {
replyCompose,
mentionCompose,
2020-03-14 17:31:29 +00:00
} from '../../actions/compose'
import { blockAccount } from '../../actions/accounts'
import {
muteStatus,
unmuteStatus,
deleteStatus,
hideStatus,
revealStatus,
2020-03-14 17:31:29 +00:00
} from '../../actions/statuses'
import { initMuteModal } from '../../actions/mutes'
import { initReport } from '../../actions/reports'
import { openModal } from '../../actions/modal'
import { boostModal, deleteModal, me } from '../../initial_state'
import { makeGetStatus } from '../../selectors'
import StatusContainer from '../../containers/status_container'
import { textForScreenReader, defaultMediaVisibility } from '../../components/status/status'
import ColumnIndicator from '../../components/column_indicator'
2020-03-08 23:02:28 +00:00
import Block from '../../components/block'
import Comment from '../../components/comment'
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?' },
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' },
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' },
});
const makeMapStateToProps = () => {
2020-03-08 23:02:28 +00:00
const getStatus = makeGetStatus()
const mapStateToProps = (state, props) => {
const status = getStatus(state, {
id: props.params.statusId,
username: props.params.username,
2020-03-08 23:02:28 +00:00
})
2020-03-08 23:02:28 +00:00
let ancestorsIds = Immutable.List()
let descendantsIds = Immutable.List();
if (status) {
ancestorsIds = ancestorsIds.withMutations(mutable => {
let id = status.get('in_reply_to_id');
while (id) {
mutable.unshift(id);
id = state.getIn(['contexts', 'inReplyTos', id]);
}
});
2020-03-08 23:02:28 +00:00
// ONLY Direct descendants
descendantsIds = state.getIn(['contexts', 'replies', status.get('id')])
2020-03-11 23:56:18 +00:00
console.log("descendantsIds:", descendantsIds)
}
return {
status,
ancestorsIds,
descendantsIds,
askReplyConfirmation: state.getIn(['compose', 'text']).trim().length !== 0,
domain: state.getIn(['meta', 'domain']),
};
};
return mapStateToProps;
};
2020-02-25 16:04:44 +00:00
export default
@injectIntl
@connect(makeMapStateToProps)
class Status extends ImmutablePureComponent {
static contextTypes = {
router: PropTypes.object,
};
static propTypes = {
params: PropTypes.object.isRequired,
dispatch: PropTypes.func.isRequired,
status: ImmutablePropTypes.map,
ancestorsIds: ImmutablePropTypes.list,
descendantsIds: ImmutablePropTypes.list,
intl: PropTypes.object.isRequired,
askReplyConfirmation: PropTypes.bool,
domain: PropTypes.string.isRequired,
};
state = {
fullscreen: false,
showMedia: defaultMediaVisibility(this.props.status),
loadedStatusId: undefined,
};
2020-02-24 23:25:55 +00:00
componentWillMount() {
this.props.dispatch(fetchStatus(this.props.params.statusId));
}
2020-02-24 23:25:55 +00:00
componentWillReceiveProps(nextProps) {
if (nextProps.params.statusId !== this.props.params.statusId && nextProps.params.statusId) {
this._scrolledIntoView = false;
this.props.dispatch(fetchStatus(nextProps.params.statusId));
}
if (nextProps.status && nextProps.status.get('id') !== this.state.loadedStatusId) {
this.setState({ showMedia: defaultMediaVisibility(nextProps.status), loadedStatusId: nextProps.status.get('id') });
}
}
handleToggleMediaVisibility = () => {
this.setState({ showMedia: !this.state.showMedia });
}
2020-03-04 22:26:01 +00:00
handleFavoriteClick = (status) => {
if (status.get('favourited')) {
2020-03-04 22:26:01 +00:00
this.props.dispatch(unfavorite(status));
} else {
2020-03-04 22:26:01 +00:00
this.props.dispatch(favorite(status));
}
}
handlePin = (status) => {
if (status.get('pinned')) {
this.props.dispatch(unpin(status));
} else {
this.props.dispatch(pin(status));
}
}
handleReplyClick = (status) => {
let { askReplyConfirmation, dispatch, intl } = this.props;
if (askReplyConfirmation) {
dispatch(openModal('CONFIRM', {
message: intl.formatMessage(messages.replyMessage),
confirm: intl.formatMessage(messages.replyConfirm),
onConfirm: () => dispatch(replyCompose(status, this.context.router.history)),
}));
} else {
dispatch(replyCompose(status, this.context.router.history));
}
}
2020-03-04 22:26:01 +00:00
handleModalRepost = (status) => {
this.props.dispatch(repost(status));
}
2020-03-04 22:26:01 +00:00
handleRepostClick = (status, e) => {
if (status.get('reblogged')) {
2020-03-04 22:26:01 +00:00
this.props.dispatch(unrepost(status));
} else {
if ((e && e.shiftKey) || !boostModal) {
2020-03-04 22:26:01 +00:00
this.handleModalRepost(status);
} else {
2020-03-04 22:26:01 +00:00
this.props.dispatch(openModal('BOOST', { status, onRepost: this.handleModalRepost }));
}
}
}
handleDeleteClick = (status, history, withRedraft = false) => {
const { dispatch, intl } = this.props;
if (!deleteModal) {
dispatch(deleteStatus(status.get('id'), history, withRedraft));
} else {
dispatch(openModal('CONFIRM', {
message: intl.formatMessage(withRedraft ? messages.redraftMessage : messages.deleteMessage),
confirm: intl.formatMessage(withRedraft ? messages.redraftConfirm : messages.deleteConfirm),
onConfirm: () => dispatch(deleteStatus(status.get('id'), history, withRedraft)),
}));
}
}
handleMentionClick = (account, router) => {
this.props.dispatch(mentionCompose(account, router));
}
handleOpenMedia = (media, index) => {
this.props.dispatch(openModal('MEDIA', { media, index }));
}
handleOpenVideo = (media, time) => {
this.props.dispatch(openModal('VIDEO', { media, time }));
}
handleMuteClick = (account) => {
this.props.dispatch(initMuteModal(account));
}
handleConversationMuteClick = (status) => {
if (status.get('muted')) {
this.props.dispatch(unmuteStatus(status.get('id')));
} else {
this.props.dispatch(muteStatus(status.get('id')));
}
}
handleToggleHidden = (status) => {
if (status.get('hidden')) {
this.props.dispatch(revealStatus(status.get('id')));
} else {
this.props.dispatch(hideStatus(status.get('id')));
}
}
handleToggleAll = () => {
const { status, ancestorsIds, descendantsIds } = this.props;
const statusIds = [status.get('id')].concat(ancestorsIds.toJS(), descendantsIds.toJS());
if (status.get('hidden')) {
this.props.dispatch(revealStatus(statusIds));
} else {
this.props.dispatch(hideStatus(statusIds));
}
}
handleBlockClick = (status) => {
const { dispatch, intl } = 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));
},
}));
}
handleReport = (status) => {
this.props.dispatch(initReport(status.get('account'), status));
}
handleEmbed = (status) => {
this.props.dispatch(openModal('EMBED', { url: status.get('url') }));
}
handleHotkeyMoveUp = () => {
this.handleMoveUp(this.props.status.get('id'));
}
handleHotkeyMoveDown = () => {
this.handleMoveDown(this.props.status.get('id'));
}
handleHotkeyReply = e => {
e.preventDefault();
this.handleReplyClick(this.props.status);
}
2020-03-04 22:26:01 +00:00
handleHotkeyFavorite = () => {
this.handleFavoriteClick(this.props.status);
}
handleHotkeyBoost = () => {
2020-03-04 22:26:01 +00:00
this.handleRepostClick(this.props.status);
}
handleHotkeyMention = e => {
e.preventDefault();
this.handleMentionClick(this.props.status.get('account'));
}
handleHotkeyOpenProfile = () => {
this.context.router.history.push(`/${this.props.status.getIn(['account', 'acct'])}`);
}
handleHotkeyToggleHidden = () => {
this.handleToggleHidden(this.props.status);
}
handleHotkeyToggleSensitive = () => {
this.handleToggleMediaVisibility();
}
handleMoveUp = id => {
const { status, ancestorsIds, descendantsIds } = this.props;
if (id === status.get('id')) {
this._selectChild(ancestorsIds.size - 1, true);
} else {
let index = ancestorsIds.indexOf(id);
if (index === -1) {
index = descendantsIds.indexOf(id);
this._selectChild(ancestorsIds.size + index, true);
} else {
this._selectChild(index - 1, true);
}
}
}
handleMoveDown = id => {
const { status, ancestorsIds, descendantsIds } = this.props;
if (id === status.get('id')) {
this._selectChild(ancestorsIds.size + 1, false);
} else {
let index = ancestorsIds.indexOf(id);
if (index === -1) {
index = descendantsIds.indexOf(id);
this._selectChild(ancestorsIds.size + index + 2, false);
} else {
this._selectChild(index + 1, false);
}
}
}
2020-02-24 23:25:55 +00:00
_selectChild(index, align_top) {
const container = this.node;
const element = container.querySelectorAll('.focusable')[index];
if (element) {
if (align_top && container.scrollTop > element.offsetTop) {
element.scrollIntoView(true);
} else if (!align_top && container.scrollTop + container.clientHeight < element.offsetTop + element.offsetHeight) {
element.scrollIntoView(false);
}
element.focus();
}
}
2020-02-24 23:25:55 +00:00
renderChildren(list) {
2020-03-08 23:02:28 +00:00
console.log("list:", list)
2020-03-07 04:53:28 +00:00
// : todo : comments
return list.map(id => (
2020-03-08 23:02:28 +00:00
<Comment
key={`comment-${id}`}
id={id}
onMoveUp={this.handleMoveUp}
onMoveDown={this.handleMoveDown}
/>
2020-03-08 23:02:28 +00:00
))
}
setRef = c => {
this.node = c;
}
2020-02-24 23:25:55 +00:00
componentDidUpdate() {
2020-03-08 23:02:28 +00:00
if (this._scrolledIntoView) return
2020-03-08 23:02:28 +00:00
const { status, ancestorsIds } = this.props
if (status && ancestorsIds && ancestorsIds.size > 0) {
const element = this.node.querySelectorAll('.focusable')[ancestorsIds.size - 1];
window.requestAnimationFrame(() => {
element.scrollIntoView(true);
});
this._scrolledIntoView = true;
}
}
2020-02-24 23:25:55 +00:00
render() {
2020-03-08 23:02:28 +00:00
const {
status,
ancestorsIds,
descendantsIds,
intl,
domain
} = this.props
let ancestors, descendants
if (status === null) {
2020-03-08 23:02:28 +00:00
return <ColumnIndicator type='loading' />
}
if (ancestorsIds && ancestorsIds.size > 0) {
2020-03-08 23:02:28 +00:00
ancestors = this.renderChildren(ancestorsIds)
}
if (descendantsIds && descendantsIds.size > 0) {
2020-03-08 23:02:28 +00:00
descendants = this.renderChildren(descendantsIds)
}
const handlers = {
moveUp: this.handleHotkeyMoveUp,
moveDown: this.handleHotkeyMoveDown,
reply: this.handleHotkeyReply,
2020-03-04 22:26:01 +00:00
favorite: this.handleHotkeyFavorite,
boost: this.handleHotkeyBoost,
mention: this.handleHotkeyMention,
openProfile: this.handleHotkeyOpenProfile,
toggleHidden: this.handleHotkeyToggleHidden,
toggleSensitive: this.handleHotkeyToggleSensitive,
};
return (
2020-02-24 23:25:55 +00:00
<div ref={this.setRef}>
2020-03-08 23:02:28 +00:00
<Block>
{
/* ancestors */
}
<HotKeys handlers={handlers}>
<div className={_s.outlineNone} tabIndex='0' aria-label={textForScreenReader(intl, status, false)}>
<StatusContainer
id={status.get('id')}
contextType={'timelineId'}
showThread
borderless={descendantsIds && descendantsIds.size > 0}
// onOpenVideo={this.handleOpenVideo}
// onOpenMedia={this.handleOpenMedia}
// onToggleHidden={this.handleToggleHidden}
// domain={domain}
// showMedia={this.state.showMedia}
// onToggleMediaVisibility={this.handleToggleMediaVisibility}
/>
</div>
</HotKeys>
{
descendantsIds && descendantsIds.size > 0 &&
2020-03-11 23:56:18 +00:00
<div className={[_s.default, _s.mr10, _s.ml10, _s.mb10, _s.borderColorSecondary, _s.borderBottom1PX].join(' ')}/>
2020-03-08 23:02:28 +00:00
}
{descendants}
</Block>
2020-02-24 23:25:55 +00:00
</div>
2020-03-08 23:02:28 +00:00
)
}
}