You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
687 lines
22 KiB
687 lines
22 KiB
7 years ago
|
import Immutable from 'immutable';
|
||
8 years ago
|
import React from 'react';
|
||
8 years ago
|
import { connect } from 'react-redux';
|
||
8 years ago
|
import PropTypes from 'prop-types';
|
||
7 years ago
|
import classNames from 'classnames';
|
||
8 years ago
|
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||
5 years ago
|
import { createSelector } from 'reselect';
|
||
2 years ago
|
import {
|
||
|
fetchStatus,
|
||
|
muteStatus,
|
||
|
unmuteStatus,
|
||
|
deleteStatus,
|
||
|
editStatus,
|
||
|
hideStatus,
|
||
|
revealStatus,
|
||
|
translateStatus,
|
||
|
undoStatusTranslation,
|
||
|
} from '../../actions/statuses';
|
||
8 years ago
|
import MissingIndicator from '../../components/missing_indicator';
|
||
2 years ago
|
import LoadingIndicator from 'mastodon/components/loading_indicator';
|
||
8 years ago
|
import DetailedStatus from './components/detailed_status';
|
||
|
import ActionBar from './components/action_bar';
|
||
|
import Column from '../ui/components/column';
|
||
|
import {
|
||
|
favourite,
|
||
|
unfavourite,
|
||
5 years ago
|
bookmark,
|
||
|
unbookmark,
|
||
8 years ago
|
reblog,
|
||
8 years ago
|
unreblog,
|
||
7 years ago
|
pin,
|
||
|
unpin,
|
||
8 years ago
|
} from '../../actions/interactions';
|
||
8 years ago
|
import {
|
||
|
replyCompose,
|
||
8 years ago
|
mentionCompose,
|
||
7 years ago
|
directCompose,
|
||
8 years ago
|
} from '../../actions/compose';
|
||
5 years ago
|
import {
|
||
|
unblockAccount,
|
||
|
unmuteAccount,
|
||
|
} from '../../actions/accounts';
|
||
|
import {
|
||
|
blockDomain,
|
||
|
unblockDomain,
|
||
|
} from '../../actions/domain_blocks';
|
||
7 years ago
|
import { initMuteModal } from '../../actions/mutes';
|
||
5 years ago
|
import { initBlockModal } from '../../actions/blocks';
|
||
4 years ago
|
import { initBoostModal } from '../../actions/boosts';
|
||
8 years ago
|
import { initReport } from '../../actions/reports';
|
||
4 years ago
|
import { makeGetStatus, makeGetPictureInPicture } from '../../selectors';
|
||
3 years ago
|
import ScrollContainer from 'mastodon/containers/scroll_container';
|
||
8 years ago
|
import ColumnBackButton from '../../components/column_back_button';
|
||
7 years ago
|
import ColumnHeader from '../../components/column_header';
|
||
8 years ago
|
import StatusContainer from '../../containers/status_container';
|
||
8 years ago
|
import { openModal } from '../../actions/modal';
|
||
5 years ago
|
import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
|
||
8 years ago
|
import ImmutablePureComponent from 'react-immutable-pure-component';
|
||
7 years ago
|
import { HotKeys } from 'react-hotkeys';
|
||
2 years ago
|
import { boostModal, deleteModal } from '../../initial_state';
|
||
6 years ago
|
import { attachFullscreenListener, detachFullscreenListener, isFullscreen } from '../ui/util/fullscreen';
|
||
6 years ago
|
import { textForScreenReader, defaultMediaVisibility } from '../../components/status';
|
||
6 years ago
|
import Icon from 'mastodon/components/icon';
|
||
2 years ago
|
import { Helmet } from 'react-helmet';
|
||
8 years ago
|
|
||
|
const messages = defineMessages({
|
||
|
deleteConfirm: { id: 'confirmations.delete.confirm', defaultMessage: 'Delete' },
|
||
8 years ago
|
deleteMessage: { id: 'confirmations.delete.message', defaultMessage: 'Are you sure you want to delete this status?' },
|
||
7 years ago
|
redraftConfirm: { id: 'confirmations.redraft.confirm', defaultMessage: 'Delete & redraft' },
|
||
6 years ago
|
redraftMessage: { id: 'confirmations.redraft.message', defaultMessage: 'Are you sure you want to delete this status and re-draft it? Favourites and boosts will be lost, and replies to the original post will be orphaned.' },
|
||
7 years ago
|
revealAll: { id: 'status.show_more_all', defaultMessage: 'Show more for all' },
|
||
|
hideAll: { id: 'status.show_less_all', defaultMessage: 'Show less for all' },
|
||
6 years ago
|
detailedStatus: { id: 'status.detailed_status', defaultMessage: 'Detailed conversation view' },
|
||
6 years ago
|
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?' },
|
||
5 years ago
|
blockDomainConfirm: { id: 'confirmations.domain_block.confirm', defaultMessage: 'Hide entire domain' },
|
||
8 years ago
|
});
|
||
8 years ago
|
|
||
8 years ago
|
const makeMapStateToProps = () => {
|
||
|
const getStatus = makeGetStatus();
|
||
4 years ago
|
const getPictureInPicture = makeGetPictureInPicture();
|
||
8 years ago
|
|
||
5 years ago
|
const getAncestorsIds = createSelector([
|
||
|
(_, { id }) => id,
|
||
|
state => state.getIn(['contexts', 'inReplyTos']),
|
||
|
], (statusId, inReplyTos) => {
|
||
7 years ago
|
let ancestorsIds = Immutable.List();
|
||
5 years ago
|
ancestorsIds = ancestorsIds.withMutations(mutable => {
|
||
|
let id = statusId;
|
||
|
|
||
3 years ago
|
while (id && !mutable.includes(id)) {
|
||
5 years ago
|
mutable.unshift(id);
|
||
|
id = inReplyTos.get(id);
|
||
|
}
|
||
|
});
|
||
|
|
||
|
return ancestorsIds;
|
||
|
});
|
||
|
|
||
|
const getDescendantsIds = createSelector([
|
||
|
(_, { id }) => id,
|
||
|
state => state.getIn(['contexts', 'replies']),
|
||
5 years ago
|
state => state.get('statuses'),
|
||
|
], (statusId, contextReplies, statuses) => {
|
||
|
let descendantsIds = [];
|
||
|
const ids = [statusId];
|
||
7 years ago
|
|
||
5 years ago
|
while (ids.length > 0) {
|
||
3 years ago
|
let id = ids.pop();
|
||
5 years ago
|
const replies = contextReplies.get(id);
|
||
7 years ago
|
|
||
5 years ago
|
if (statusId !== id) {
|
||
|
descendantsIds.push(id);
|
||
|
}
|
||
7 years ago
|
|
||
5 years ago
|
if (replies) {
|
||
|
replies.reverse().forEach(reply => {
|
||
3 years ago
|
if (!ids.includes(reply) && !descendantsIds.includes(reply) && statusId !== reply) ids.push(reply);
|
||
5 years ago
|
});
|
||
5 years ago
|
}
|
||
5 years ago
|
}
|
||
|
|
||
|
let insertAt = descendantsIds.findIndex((id) => statuses.get(id).get('in_reply_to_account_id') !== statuses.get(id).get('account'));
|
||
|
if (insertAt !== -1) {
|
||
|
descendantsIds.forEach((id, idx) => {
|
||
|
if (idx > insertAt && statuses.get(id).get('in_reply_to_account_id') === statuses.get(id).get('account')) {
|
||
|
descendantsIds.splice(idx, 1);
|
||
|
descendantsIds.splice(insertAt, 0, id);
|
||
|
insertAt += 1;
|
||
|
}
|
||
|
});
|
||
|
}
|
||
7 years ago
|
|
||
5 years ago
|
return Immutable.List(descendantsIds);
|
||
5 years ago
|
});
|
||
7 years ago
|
|
||
5 years ago
|
const mapStateToProps = (state, props) => {
|
||
|
const status = getStatus(state, { id: props.params.statusId });
|
||
4 years ago
|
|
||
|
let ancestorsIds = Immutable.List();
|
||
5 years ago
|
let descendantsIds = Immutable.List();
|
||
7 years ago
|
|
||
5 years ago
|
if (status) {
|
||
4 years ago
|
ancestorsIds = getAncestorsIds(state, { id: status.get('in_reply_to_id') });
|
||
5 years ago
|
descendantsIds = getDescendantsIds(state, { id: status.get('id') });
|
||
7 years ago
|
}
|
||
|
|
||
|
return {
|
||
2 years ago
|
isLoading: state.getIn(['statuses', props.params.statusId, 'isLoading']),
|
||
7 years ago
|
status,
|
||
|
ancestorsIds,
|
||
|
descendantsIds,
|
||
6 years ago
|
askReplyConfirmation: state.getIn(['compose', 'text']).trim().length !== 0,
|
||
6 years ago
|
domain: state.getIn(['meta', 'domain']),
|
||
4 years ago
|
pictureInPicture: getPictureInPicture(state, { id: props.params.statusId }),
|
||
7 years ago
|
};
|
||
|
};
|
||
8 years ago
|
|
||
|
return mapStateToProps;
|
||
|
};
|
||
8 years ago
|
|
||
2 years ago
|
const truncate = (str, num) => {
|
||
|
if (str.length > num) {
|
||
|
return str.slice(0, num) + '…';
|
||
|
} else {
|
||
|
return str;
|
||
|
}
|
||
|
};
|
||
|
|
||
|
const titleFromStatus = status => {
|
||
|
const displayName = status.getIn(['account', 'display_name']);
|
||
|
const username = status.getIn(['account', 'username']);
|
||
|
const prefix = displayName.trim().length === 0 ? username : displayName;
|
||
|
const text = status.get('search_index');
|
||
|
|
||
|
return `${prefix}: "${truncate(text, 30)}"`;
|
||
|
};
|
||
|
|
||
6 years ago
|
export default @injectIntl
|
||
7 years ago
|
@connect(makeMapStateToProps)
|
||
6 years ago
|
class Status extends ImmutablePureComponent {
|
||
8 years ago
|
|
||
8 years ago
|
static contextTypes = {
|
||
8 years ago
|
router: PropTypes.object,
|
||
2 years ago
|
identity: PropTypes.object,
|
||
8 years ago
|
};
|
||
|
|
||
|
static propTypes = {
|
||
|
params: PropTypes.object.isRequired,
|
||
|
dispatch: PropTypes.func.isRequired,
|
||
|
status: ImmutablePropTypes.map,
|
||
2 years ago
|
isLoading: PropTypes.bool,
|
||
8 years ago
|
ancestorsIds: ImmutablePropTypes.list,
|
||
|
descendantsIds: ImmutablePropTypes.list,
|
||
8 years ago
|
intl: PropTypes.object.isRequired,
|
||
6 years ago
|
askReplyConfirmation: PropTypes.bool,
|
||
5 years ago
|
multiColumn: PropTypes.bool,
|
||
6 years ago
|
domain: PropTypes.string.isRequired,
|
||
4 years ago
|
pictureInPicture: ImmutablePropTypes.contains({
|
||
|
inUse: PropTypes.bool,
|
||
|
available: PropTypes.bool,
|
||
|
}),
|
||
8 years ago
|
};
|
||
8 years ago
|
|
||
7 years ago
|
state = {
|
||
|
fullscreen: false,
|
||
6 years ago
|
showMedia: defaultMediaVisibility(this.props.status),
|
||
6 years ago
|
loadedStatusId: undefined,
|
||
7 years ago
|
};
|
||
|
|
||
8 years ago
|
componentWillMount () {
|
||
7 years ago
|
this.props.dispatch(fetchStatus(this.props.params.statusId));
|
||
8 years ago
|
}
|
||
8 years ago
|
|
||
7 years ago
|
componentDidMount () {
|
||
|
attachFullscreenListener(this.onFullScreenChange);
|
||
|
}
|
||
|
|
||
8 years ago
|
componentWillReceiveProps (nextProps) {
|
||
|
if (nextProps.params.statusId !== this.props.params.statusId && nextProps.params.statusId) {
|
||
7 years ago
|
this._scrolledIntoView = false;
|
||
7 years ago
|
this.props.dispatch(fetchStatus(nextProps.params.statusId));
|
||
8 years ago
|
}
|
||
6 years ago
|
|
||
2 years ago
|
if (nextProps.params.statusId && nextProps.ancestorsIds.size > this.props.ancestorsIds.size) {
|
||
|
this._scrolledIntoView = false;
|
||
|
}
|
||
|
|
||
6 years ago
|
if (nextProps.status && nextProps.status.get('id') !== this.state.loadedStatusId) {
|
||
|
this.setState({ showMedia: defaultMediaVisibility(nextProps.status), loadedStatusId: nextProps.status.get('id') });
|
||
6 years ago
|
}
|
||
|
}
|
||
|
|
||
|
handleToggleMediaVisibility = () => {
|
||
|
this.setState({ showMedia: !this.state.showMedia });
|
||
2 years ago
|
};
|
||
8 years ago
|
|
||
8 years ago
|
handleFavouriteClick = (status) => {
|
||
2 years ago
|
const { dispatch } = this.props;
|
||
|
const { signedIn } = this.context.identity;
|
||
|
|
||
|
if (signedIn) {
|
||
|
if (status.get('favourited')) {
|
||
|
dispatch(unfavourite(status));
|
||
|
} else {
|
||
|
dispatch(favourite(status));
|
||
|
}
|
||
8 years ago
|
} else {
|
||
2 years ago
|
dispatch(openModal('INTERACTION', {
|
||
|
type: 'favourite',
|
||
|
accountId: status.getIn(['account', 'id']),
|
||
|
url: status.get('url'),
|
||
|
}));
|
||
8 years ago
|
}
|
||
2 years ago
|
};
|
||
8 years ago
|
|
||
7 years ago
|
handlePin = (status) => {
|
||
|
if (status.get('pinned')) {
|
||
|
this.props.dispatch(unpin(status));
|
||
|
} else {
|
||
|
this.props.dispatch(pin(status));
|
||
|
}
|
||
2 years ago
|
};
|
||
7 years ago
|
|
||
8 years ago
|
handleReplyClick = (status) => {
|
||
2 years ago
|
const { askReplyConfirmation, dispatch, intl } = this.props;
|
||
|
const { signedIn } = this.context.identity;
|
||
|
|
||
|
if (signedIn) {
|
||
|
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));
|
||
|
}
|
||
6 years ago
|
} else {
|
||
2 years ago
|
dispatch(openModal('INTERACTION', {
|
||
|
type: 'reply',
|
||
|
accountId: status.getIn(['account', 'id']),
|
||
|
url: status.get('url'),
|
||
|
}));
|
||
6 years ago
|
}
|
||
2 years ago
|
};
|
||
8 years ago
|
|
||
4 years ago
|
handleModalReblog = (status, privacy) => {
|
||
|
this.props.dispatch(reblog(status, privacy));
|
||
2 years ago
|
};
|
||
8 years ago
|
|
||
8 years ago
|
handleReblogClick = (status, e) => {
|
||
2 years ago
|
const { dispatch } = this.props;
|
||
|
const { signedIn } = this.context.identity;
|
||
|
|
||
|
if (signedIn) {
|
||
|
if (status.get('reblogged')) {
|
||
|
dispatch(unreblog(status));
|
||
8 years ago
|
} else {
|
||
2 years ago
|
if ((e && e.shiftKey) || !boostModal) {
|
||
|
this.handleModalReblog(status);
|
||
|
} else {
|
||
|
dispatch(initBoostModal({ status, onReblog: this.handleModalReblog }));
|
||
|
}
|
||
8 years ago
|
}
|
||
2 years ago
|
} else {
|
||
|
dispatch(openModal('INTERACTION', {
|
||
|
type: 'reblog',
|
||
|
accountId: status.getIn(['account', 'id']),
|
||
|
url: status.get('url'),
|
||
|
}));
|
||
8 years ago
|
}
|
||
2 years ago
|
};
|
||
8 years ago
|
|
||
5 years ago
|
handleBookmarkClick = (status) => {
|
||
|
if (status.get('bookmarked')) {
|
||
|
this.props.dispatch(unbookmark(status));
|
||
|
} else {
|
||
|
this.props.dispatch(bookmark(status));
|
||
|
}
|
||
2 years ago
|
};
|
||
5 years ago
|
|
||
6 years ago
|
handleDeleteClick = (status, history, withRedraft = false) => {
|
||
8 years ago
|
const { dispatch, intl } = this.props;
|
||
|
|
||
7 years ago
|
if (!deleteModal) {
|
||
6 years ago
|
dispatch(deleteStatus(status.get('id'), history, withRedraft));
|
||
8 years ago
|
} else {
|
||
|
dispatch(openModal('CONFIRM', {
|
||
7 years ago
|
message: intl.formatMessage(withRedraft ? messages.redraftMessage : messages.deleteMessage),
|
||
|
confirm: intl.formatMessage(withRedraft ? messages.redraftConfirm : messages.deleteConfirm),
|
||
6 years ago
|
onConfirm: () => dispatch(deleteStatus(status.get('id'), history, withRedraft)),
|
||
8 years ago
|
}));
|
||
|
}
|
||
2 years ago
|
};
|
||
8 years ago
|
|
||
3 years ago
|
handleEditClick = (status, history) => {
|
||
|
this.props.dispatch(editStatus(status.get('id'), history));
|
||
2 years ago
|
};
|
||
3 years ago
|
|
||
7 years ago
|
handleDirectClick = (account, router) => {
|
||
|
this.props.dispatch(directCompose(account, router));
|
||
2 years ago
|
};
|
||
7 years ago
|
|
||
8 years ago
|
handleMentionClick = (account, router) => {
|
||
8 years ago
|
this.props.dispatch(mentionCompose(account, router));
|
||
2 years ago
|
};
|
||
8 years ago
|
|
||
8 years ago
|
handleOpenMedia = (media, index) => {
|
||
4 years ago
|
this.props.dispatch(openModal('MEDIA', { statusId: this.props.status.get('id'), media, index }));
|
||
2 years ago
|
};
|
||
8 years ago
|
|
||
5 years ago
|
handleOpenVideo = (media, options) => {
|
||
4 years ago
|
this.props.dispatch(openModal('VIDEO', { statusId: this.props.status.get('id'), media, options }));
|
||
2 years ago
|
};
|
||
8 years ago
|
|
||
5 years ago
|
handleHotkeyOpenMedia = e => {
|
||
4 years ago
|
const { status } = this.props;
|
||
5 years ago
|
|
||
|
e.preventDefault();
|
||
|
|
||
|
if (status.get('media_attachments').size > 0) {
|
||
4 years ago
|
if (status.getIn(['media_attachments', 0, 'type']) === 'video') {
|
||
5 years ago
|
this.handleOpenVideo(status.getIn(['media_attachments', 0]), { startTime: 0 });
|
||
5 years ago
|
} else {
|
||
|
this.handleOpenMedia(status.get('media_attachments'), 0);
|
||
|
}
|
||
|
}
|
||
2 years ago
|
};
|
||
5 years ago
|
|
||
7 years ago
|
handleMuteClick = (account) => {
|
||
|
this.props.dispatch(initMuteModal(account));
|
||
2 years ago
|
};
|
||
7 years ago
|
|
||
|
handleConversationMuteClick = (status) => {
|
||
|
if (status.get('muted')) {
|
||
|
this.props.dispatch(unmuteStatus(status.get('id')));
|
||
|
} else {
|
||
|
this.props.dispatch(muteStatus(status.get('id')));
|
||
|
}
|
||
2 years ago
|
};
|
||
7 years ago
|
|
||
7 years ago
|
handleToggleHidden = (status) => {
|
||
|
if (status.get('hidden')) {
|
||
|
this.props.dispatch(revealStatus(status.get('id')));
|
||
|
} else {
|
||
|
this.props.dispatch(hideStatus(status.get('id')));
|
||
|
}
|
||
2 years ago
|
};
|
||
7 years ago
|
|
||
|
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));
|
||
|
}
|
||
2 years ago
|
};
|
||
7 years ago
|
|
||
2 years ago
|
handleTranslate = status => {
|
||
|
const { dispatch } = this.props;
|
||
|
|
||
|
if (status.get('translation')) {
|
||
|
dispatch(undoStatusTranslation(status.get('id')));
|
||
|
} else {
|
||
|
dispatch(translateStatus(status.get('id')));
|
||
|
}
|
||
2 years ago
|
};
|
||
2 years ago
|
|
||
6 years ago
|
handleBlockClick = (status) => {
|
||
5 years ago
|
const { dispatch } = this.props;
|
||
6 years ago
|
const account = status.get('account');
|
||
5 years ago
|
dispatch(initBlockModal(account));
|
||
2 years ago
|
};
|
||
7 years ago
|
|
||
8 years ago
|
handleReport = (status) => {
|
||
8 years ago
|
this.props.dispatch(initReport(status.get('account'), status));
|
||
2 years ago
|
};
|
||
8 years ago
|
|
||
7 years ago
|
handleEmbed = (status) => {
|
||
|
this.props.dispatch(openModal('EMBED', { url: status.get('url') }));
|
||
2 years ago
|
};
|
||
7 years ago
|
|
||
5 years ago
|
handleUnmuteClick = account => {
|
||
|
this.props.dispatch(unmuteAccount(account.get('id')));
|
||
2 years ago
|
};
|
||
5 years ago
|
|
||
|
handleUnblockClick = account => {
|
||
|
this.props.dispatch(unblockAccount(account.get('id')));
|
||
2 years ago
|
};
|
||
5 years ago
|
|
||
|
handleBlockDomainClick = domain => {
|
||
|
this.props.dispatch(openModal('CONFIRM', {
|
||
|
message: <FormattedMessage id='confirmations.domain_block.message' defaultMessage='Are you really, really sure you want to block the entire {domain}? In most cases a few targeted blocks or mutes are sufficient and preferable. You will not see content from that domain in any public timelines or your notifications. Your followers from that domain will be removed.' values={{ domain: <strong>{domain}</strong> }} />,
|
||
|
confirm: this.props.intl.formatMessage(messages.blockDomainConfirm),
|
||
|
onConfirm: () => this.props.dispatch(blockDomain(domain)),
|
||
|
}));
|
||
2 years ago
|
};
|
||
5 years ago
|
|
||
|
handleUnblockDomainClick = domain => {
|
||
|
this.props.dispatch(unblockDomain(domain));
|
||
2 years ago
|
};
|
||
5 years ago
|
|
||
|
|
||
7 years ago
|
handleHotkeyMoveUp = () => {
|
||
|
this.handleMoveUp(this.props.status.get('id'));
|
||
2 years ago
|
};
|
||
7 years ago
|
|
||
|
handleHotkeyMoveDown = () => {
|
||
|
this.handleMoveDown(this.props.status.get('id'));
|
||
2 years ago
|
};
|
||
7 years ago
|
|
||
|
handleHotkeyReply = e => {
|
||
|
e.preventDefault();
|
||
|
this.handleReplyClick(this.props.status);
|
||
2 years ago
|
};
|
||
7 years ago
|
|
||
|
handleHotkeyFavourite = () => {
|
||
|
this.handleFavouriteClick(this.props.status);
|
||
2 years ago
|
};
|
||
7 years ago
|
|
||
|
handleHotkeyBoost = () => {
|
||
|
this.handleReblogClick(this.props.status);
|
||
2 years ago
|
};
|
||
7 years ago
|
|
||
|
handleHotkeyMention = e => {
|
||
|
e.preventDefault();
|
||
6 years ago
|
this.handleMentionClick(this.props.status.get('account'));
|
||
2 years ago
|
};
|
||
7 years ago
|
|
||
|
handleHotkeyOpenProfile = () => {
|
||
3 years ago
|
this.context.router.history.push(`/@${this.props.status.getIn(['account', 'acct'])}`);
|
||
2 years ago
|
};
|
||
7 years ago
|
|
||
7 years ago
|
handleHotkeyToggleHidden = () => {
|
||
|
this.handleToggleHidden(this.props.status);
|
||
2 years ago
|
};
|
||
7 years ago
|
|
||
6 years ago
|
handleHotkeyToggleSensitive = () => {
|
||
|
this.handleToggleMediaVisibility();
|
||
2 years ago
|
};
|
||
6 years ago
|
|
||
7 years ago
|
handleMoveUp = id => {
|
||
|
const { status, ancestorsIds, descendantsIds } = this.props;
|
||
|
|
||
|
if (id === status.get('id')) {
|
||
6 years ago
|
this._selectChild(ancestorsIds.size - 1, true);
|
||
7 years ago
|
} else {
|
||
|
let index = ancestorsIds.indexOf(id);
|
||
|
|
||
|
if (index === -1) {
|
||
|
index = descendantsIds.indexOf(id);
|
||
6 years ago
|
this._selectChild(ancestorsIds.size + index, true);
|
||
7 years ago
|
} else {
|
||
6 years ago
|
this._selectChild(index - 1, true);
|
||
7 years ago
|
}
|
||
|
}
|
||
2 years ago
|
};
|
||
7 years ago
|
|
||
|
handleMoveDown = id => {
|
||
|
const { status, ancestorsIds, descendantsIds } = this.props;
|
||
|
|
||
|
if (id === status.get('id')) {
|
||
6 years ago
|
this._selectChild(ancestorsIds.size + 1, false);
|
||
7 years ago
|
} else {
|
||
|
let index = ancestorsIds.indexOf(id);
|
||
|
|
||
|
if (index === -1) {
|
||
|
index = descendantsIds.indexOf(id);
|
||
6 years ago
|
this._selectChild(ancestorsIds.size + index + 2, false);
|
||
7 years ago
|
} else {
|
||
6 years ago
|
this._selectChild(index + 1, false);
|
||
7 years ago
|
}
|
||
|
}
|
||
2 years ago
|
};
|
||
7 years ago
|
|
||
6 years ago
|
_selectChild (index, align_top) {
|
||
|
const container = this.node;
|
||
|
const element = container.querySelectorAll('.focusable')[index];
|
||
7 years ago
|
|
||
|
if (element) {
|
||
6 years ago
|
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);
|
||
|
}
|
||
7 years ago
|
element.focus();
|
||
|
}
|
||
|
}
|
||
|
|
||
8 years ago
|
renderChildren (list) {
|
||
7 years ago
|
return list.map(id => (
|
||
|
<StatusContainer
|
||
|
key={id}
|
||
|
id={id}
|
||
|
onMoveUp={this.handleMoveUp}
|
||
|
onMoveDown={this.handleMoveDown}
|
||
6 years ago
|
contextType='thread'
|
||
7 years ago
|
/>
|
||
|
));
|
||
|
}
|
||
|
|
||
|
setRef = c => {
|
||
|
this.node = c;
|
||
2 years ago
|
};
|
||
7 years ago
|
|
||
|
componentDidUpdate () {
|
||
7 years ago
|
if (this._scrolledIntoView) {
|
||
|
return;
|
||
|
}
|
||
|
|
||
7 years ago
|
const { status, ancestorsIds } = this.props;
|
||
7 years ago
|
|
||
7 years ago
|
if (status && ancestorsIds && ancestorsIds.size > 0) {
|
||
7 years ago
|
const element = this.node.querySelectorAll('.focusable')[ancestorsIds.size - 1];
|
||
|
|
||
6 years ago
|
window.requestAnimationFrame(() => {
|
||
|
element.scrollIntoView(true);
|
||
|
});
|
||
7 years ago
|
this._scrolledIntoView = true;
|
||
7 years ago
|
}
|
||
8 years ago
|
}
|
||
8 years ago
|
|
||
7 years ago
|
componentWillUnmount () {
|
||
|
detachFullscreenListener(this.onFullScreenChange);
|
||
|
}
|
||
|
|
||
|
onFullScreenChange = () => {
|
||
|
this.setState({ fullscreen: isFullscreen() });
|
||
2 years ago
|
};
|
||
7 years ago
|
|
||
8 years ago
|
render () {
|
||
8 years ago
|
let ancestors, descendants;
|
||
2 years ago
|
const { isLoading, status, ancestorsIds, descendantsIds, intl, domain, multiColumn, pictureInPicture } = this.props;
|
||
7 years ago
|
const { fullscreen } = this.state;
|
||
8 years ago
|
|
||
2 years ago
|
if (isLoading) {
|
||
|
return (
|
||
|
<Column>
|
||
|
<LoadingIndicator />
|
||
|
</Column>
|
||
|
);
|
||
|
}
|
||
|
|
||
8 years ago
|
if (status === null) {
|
||
8 years ago
|
return (
|
||
|
<Column>
|
||
5 years ago
|
<ColumnBackButton multiColumn={multiColumn} />
|
||
8 years ago
|
<MissingIndicator />
|
||
8 years ago
|
</Column>
|
||
|
);
|
||
8 years ago
|
}
|
||
|
|
||
8 years ago
|
if (ancestorsIds && ancestorsIds.size > 0) {
|
||
8 years ago
|
ancestors = <div>{this.renderChildren(ancestorsIds)}</div>;
|
||
|
}
|
||
|
|
||
8 years ago
|
if (descendantsIds && descendantsIds.size > 0) {
|
||
8 years ago
|
descendants = <div>{this.renderChildren(descendantsIds)}</div>;
|
||
|
}
|
||
|
|
||
2 years ago
|
const isLocal = status.getIn(['account', 'acct'], '').indexOf('@') === -1;
|
||
|
const isIndexable = !status.getIn(['account', 'noindex']);
|
||
|
|
||
7 years ago
|
const handlers = {
|
||
|
moveUp: this.handleHotkeyMoveUp,
|
||
|
moveDown: this.handleHotkeyMoveDown,
|
||
|
reply: this.handleHotkeyReply,
|
||
|
favourite: this.handleHotkeyFavourite,
|
||
|
boost: this.handleHotkeyBoost,
|
||
|
mention: this.handleHotkeyMention,
|
||
|
openProfile: this.handleHotkeyOpenProfile,
|
||
7 years ago
|
toggleHidden: this.handleHotkeyToggleHidden,
|
||
6 years ago
|
toggleSensitive: this.handleHotkeyToggleSensitive,
|
||
5 years ago
|
openMedia: this.handleHotkeyOpenMedia,
|
||
7 years ago
|
};
|
||
|
|
||
8 years ago
|
return (
|
||
5 years ago
|
<Column bindToDocument={!multiColumn} label={intl.formatMessage(messages.detailedStatus)}>
|
||
7 years ago
|
<ColumnHeader
|
||
|
showBackButton
|
||
5 years ago
|
multiColumn={multiColumn}
|
||
7 years ago
|
extraButton={(
|
||
2 years ago
|
<button type='button' className='column-header__button' title={intl.formatMessage(status.get('hidden') ? messages.revealAll : messages.hideAll)} aria-label={intl.formatMessage(status.get('hidden') ? messages.revealAll : messages.hideAll)} onClick={this.handleToggleAll}><Icon id={status.get('hidden') ? 'eye-slash' : 'eye'} /></button>
|
||
7 years ago
|
)}
|
||
|
/>
|
||
8 years ago
|
|
||
3 years ago
|
<ScrollContainer scrollKey='thread'>
|
||
6 years ago
|
<div className={classNames('scrollable', { fullscreen })} ref={this.setRef}>
|
||
8 years ago
|
{ancestors}
|
||
8 years ago
|
|
||
7 years ago
|
<HotKeys handlers={handlers}>
|
||
6 years ago
|
<div className={classNames('focusable', 'detailed-status__wrapper')} tabIndex='0' aria-label={textForScreenReader(intl, status, false)}>
|
||
7 years ago
|
<DetailedStatus
|
||
5 years ago
|
key={`details-${status.get('id')}`}
|
||
7 years ago
|
status={status}
|
||
|
onOpenVideo={this.handleOpenVideo}
|
||
|
onOpenMedia={this.handleOpenMedia}
|
||
7 years ago
|
onToggleHidden={this.handleToggleHidden}
|
||
2 years ago
|
onTranslate={this.handleTranslate}
|
||
6 years ago
|
domain={domain}
|
||
6 years ago
|
showMedia={this.state.showMedia}
|
||
|
onToggleMediaVisibility={this.handleToggleMediaVisibility}
|
||
4 years ago
|
pictureInPicture={pictureInPicture}
|
||
7 years ago
|
/>
|
||
|
|
||
|
<ActionBar
|
||
5 years ago
|
key={`action-bar-${status.get('id')}`}
|
||
7 years ago
|
status={status}
|
||
|
onReply={this.handleReplyClick}
|
||
|
onFavourite={this.handleFavouriteClick}
|
||
|
onReblog={this.handleReblogClick}
|
||
5 years ago
|
onBookmark={this.handleBookmarkClick}
|
||
7 years ago
|
onDelete={this.handleDeleteClick}
|
||
3 years ago
|
onEdit={this.handleEditClick}
|
||
7 years ago
|
onDirect={this.handleDirectClick}
|
||
7 years ago
|
onMention={this.handleMentionClick}
|
||
7 years ago
|
onMute={this.handleMuteClick}
|
||
5 years ago
|
onUnmute={this.handleUnmuteClick}
|
||
7 years ago
|
onMuteConversation={this.handleConversationMuteClick}
|
||
|
onBlock={this.handleBlockClick}
|
||
5 years ago
|
onUnblock={this.handleUnblockClick}
|
||
|
onBlockDomain={this.handleBlockDomainClick}
|
||
|
onUnblockDomain={this.handleUnblockDomainClick}
|
||
7 years ago
|
onReport={this.handleReport}
|
||
|
onPin={this.handlePin}
|
||
|
onEmbed={this.handleEmbed}
|
||
|
/>
|
||
|
</div>
|
||
|
</HotKeys>
|
||
8 years ago
|
|
||
8 years ago
|
{descendants}
|
||
8 years ago
|
</div>
|
||
|
</ScrollContainer>
|
||
2 years ago
|
|
||
|
<Helmet>
|
||
2 years ago
|
<title>{titleFromStatus(status)}</title>
|
||
2 years ago
|
<meta name='robots' content={(isLocal && isIndexable) ? 'all' : 'noindex'} />
|
||
2 years ago
|
</Helmet>
|
||
8 years ago
|
</Column>
|
||
8 years ago
|
);
|
||
|
}
|
||
|
|
||
8 years ago
|
}
|