Merge upstream tag 'v3.5.2'
This commit is contained in:
commit
d161ca885c
2205 changed files with 91260 additions and 41616 deletions
|
|
@ -4,7 +4,6 @@ import PropTypes from 'prop-types';
|
|||
import Audio from 'mastodon/features/audio';
|
||||
import { connect } from 'react-redux';
|
||||
import ImmutablePureComponent from 'react-immutable-pure-component';
|
||||
import { previewState } from './video_modal';
|
||||
import Footer from 'mastodon/features/picture_in_picture/components/footer';
|
||||
|
||||
const mapStateToProps = (state, { statusId }) => ({
|
||||
|
|
@ -25,32 +24,6 @@ class AudioModal extends ImmutablePureComponent {
|
|||
onChangeBackgroundColor: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
static contextTypes = {
|
||||
router: PropTypes.object,
|
||||
};
|
||||
|
||||
componentDidMount () {
|
||||
if (this.context.router) {
|
||||
const history = this.context.router.history;
|
||||
|
||||
history.push(history.location.pathname, previewState);
|
||||
|
||||
this.unlistenHistory = history.listen(() => {
|
||||
this.props.onClose();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
componentWillUnmount () {
|
||||
if (this.context.router) {
|
||||
this.unlistenHistory();
|
||||
|
||||
if (this.context.router.history.location.state === previewState) {
|
||||
this.context.router.history.goBack();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
render () {
|
||||
const { media, accountStaticAvatar, statusId, onClose } = this.props;
|
||||
const options = this.props.options || {};
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import React from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||
import PropTypes from 'prop-types';
|
||||
import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
|
||||
|
|
@ -10,7 +11,9 @@ import DisplayName from '../../../components/display_name';
|
|||
import ImmutablePureComponent from 'react-immutable-pure-component';
|
||||
import Icon from 'mastodon/components/icon';
|
||||
import AttachmentList from 'mastodon/components/attachment_list';
|
||||
import PrivacyDropdown from 'mastodon/features/compose/components/privacy_dropdown';
|
||||
import classNames from 'classnames';
|
||||
import { changeBoostPrivacy } from 'mastodon/actions/boosts';
|
||||
|
||||
const messages = defineMessages({
|
||||
cancel_reblog: { id: 'status.cancel_reblog_private', defaultMessage: 'Unboost' },
|
||||
|
|
@ -21,7 +24,22 @@ const messages = defineMessages({
|
|||
direct_short: { id: 'privacy.direct.short', defaultMessage: 'Direct' },
|
||||
});
|
||||
|
||||
export default @injectIntl
|
||||
const mapStateToProps = state => {
|
||||
return {
|
||||
privacy: state.getIn(['boosts', 'new', 'privacy']),
|
||||
};
|
||||
};
|
||||
|
||||
const mapDispatchToProps = dispatch => {
|
||||
return {
|
||||
onChangeBoostPrivacy(value) {
|
||||
dispatch(changeBoostPrivacy(value));
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
export default @connect(mapStateToProps, mapDispatchToProps)
|
||||
@injectIntl
|
||||
class BoostModal extends ImmutablePureComponent {
|
||||
|
||||
static contextTypes = {
|
||||
|
|
@ -32,6 +50,8 @@ class BoostModal extends ImmutablePureComponent {
|
|||
status: ImmutablePropTypes.map.isRequired,
|
||||
onReblog: PropTypes.func.isRequired,
|
||||
onClose: PropTypes.func.isRequired,
|
||||
onChangeBoostPrivacy: PropTypes.func.isRequired,
|
||||
privacy: PropTypes.string.isRequired,
|
||||
intl: PropTypes.object.isRequired,
|
||||
};
|
||||
|
||||
|
|
@ -40,7 +60,7 @@ class BoostModal extends ImmutablePureComponent {
|
|||
}
|
||||
|
||||
handleReblog = () => {
|
||||
this.props.onReblog(this.props.status);
|
||||
this.props.onReblog(this.props.status, this.props.privacy);
|
||||
this.props.onClose();
|
||||
}
|
||||
|
||||
|
|
@ -48,16 +68,20 @@ class BoostModal extends ImmutablePureComponent {
|
|||
if (e.button === 0 && !(e.ctrlKey || e.metaKey)) {
|
||||
e.preventDefault();
|
||||
this.props.onClose();
|
||||
this.context.router.history.push(`/accounts/${this.props.status.getIn(['account', 'id'])}`);
|
||||
this.context.router.history.push(`/@${this.props.status.getIn(['account', 'acct'])}`);
|
||||
}
|
||||
}
|
||||
|
||||
_findContainer = () => {
|
||||
return document.getElementsByClassName('modal-root__container')[0];
|
||||
};
|
||||
|
||||
setRef = (c) => {
|
||||
this.button = c;
|
||||
}
|
||||
|
||||
render () {
|
||||
const { status, intl } = this.props;
|
||||
const { status, privacy, intl } = this.props;
|
||||
const buttonText = status.get('reblogged') ? messages.cancel_reblog : messages.reblog;
|
||||
|
||||
const visibilityIconInfo = {
|
||||
|
|
@ -102,6 +126,14 @@ class BoostModal extends ImmutablePureComponent {
|
|||
|
||||
<div className='boost-modal__action-bar'>
|
||||
<div><FormattedMessage id='boost_modal.combo' defaultMessage='You can press {combo} to skip this next time' values={{ combo: <span>Shift + <Icon id='retweet' /></span> }} /></div>
|
||||
{status.get('visibility') !== 'private' && !status.get('reblogged') && (
|
||||
<PrivacyDropdown
|
||||
noDirect
|
||||
value={privacy}
|
||||
container={this._findContainer}
|
||||
onChange={this.props.onChangeBoostPrivacy}
|
||||
/>
|
||||
)}
|
||||
<Button text={intl.formatMessage(buttonText)} onClick={this.handleReblog} ref={this.setRef} />
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -53,7 +53,7 @@ const messages = defineMessages({
|
|||
publish: { id: 'compose_form.publish', defaultMessage: 'Toot' },
|
||||
});
|
||||
|
||||
const shouldHideFAB = path => path.match(/^\/statuses\/|^\/search|^\/getting-started/);
|
||||
const shouldHideFAB = path => path.match(/^\/statuses\/|^\/@[^/]+\/\d+|^\/publish|^\/explore|^\/getting-started|^\/start/);
|
||||
|
||||
export default @(component => injectIntl(component, { withRef: true }))
|
||||
class ColumnsArea extends ImmutablePureComponent {
|
||||
|
|
@ -70,8 +70,12 @@ class ColumnsArea extends ImmutablePureComponent {
|
|||
children: PropTypes.node,
|
||||
};
|
||||
|
||||
// Corresponds to (max-width: 600px + (285px * 1) + (10px * 1)) in SCSS
|
||||
mediaQuery = 'matchMedia' in window && window.matchMedia('(max-width: 895px)');
|
||||
|
||||
state = {
|
||||
shouldAnimate: false,
|
||||
renderComposePanel: !(this.mediaQuery && this.mediaQuery.matches),
|
||||
}
|
||||
|
||||
componentWillReceiveProps() {
|
||||
|
|
@ -85,6 +89,15 @@ class ColumnsArea extends ImmutablePureComponent {
|
|||
this.node.addEventListener('wheel', this.handleWheel, supportsPassiveEvents ? { passive: true } : false);
|
||||
}
|
||||
|
||||
if (this.mediaQuery) {
|
||||
if (this.mediaQuery.addEventListener) {
|
||||
this.mediaQuery.addEventListener('change', this.handleLayoutChange);
|
||||
} else {
|
||||
this.mediaQuery.addListener(this.handleLayoutChange);
|
||||
}
|
||||
this.setState({ renderComposePanel: !this.mediaQuery.matches });
|
||||
}
|
||||
|
||||
this.lastIndex = getIndex(this.context.router.history.location.pathname);
|
||||
this.isRtlLayout = document.getElementsByTagName('body')[0].classList.contains('rtl');
|
||||
|
||||
|
|
@ -114,6 +127,14 @@ class ColumnsArea extends ImmutablePureComponent {
|
|||
if (!this.props.singleColumn) {
|
||||
this.node.removeEventListener('wheel', this.handleWheel);
|
||||
}
|
||||
|
||||
if (this.mediaQuery) {
|
||||
if (this.mediaQuery.removeEventListener) {
|
||||
this.mediaQuery.removeEventListener('change', this.handleLayoutChange);
|
||||
} else {
|
||||
this.mediaQuery.removeListener(this.handleLayouteChange);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
handleChildrenContentChange() {
|
||||
|
|
@ -123,6 +144,10 @@ class ColumnsArea extends ImmutablePureComponent {
|
|||
}
|
||||
}
|
||||
|
||||
handleLayoutChange = (e) => {
|
||||
this.setState({ renderComposePanel: !e.matches });
|
||||
}
|
||||
|
||||
handleSwipe = (index) => {
|
||||
this.pendingIndex = index;
|
||||
|
||||
|
|
@ -186,12 +211,12 @@ class ColumnsArea extends ImmutablePureComponent {
|
|||
|
||||
render () {
|
||||
const { columns, children, singleColumn, isModalOpen, intl } = this.props;
|
||||
const { shouldAnimate } = this.state;
|
||||
const { shouldAnimate, renderComposePanel } = this.state;
|
||||
|
||||
const columnIndex = getIndex(this.context.router.history.location.pathname);
|
||||
|
||||
if (singleColumn) {
|
||||
const floatingActionButton = shouldHideFAB(this.context.router.history.location.pathname) ? null : <Link key='floating-action-button' to='/statuses/new' className='floating-action-button' aria-label={intl.formatMessage(messages.publish)}><Icon id='pencil' /></Link>;
|
||||
const floatingActionButton = shouldHideFAB(this.context.router.history.location.pathname) ? null : <Link key='floating-action-button' to='/publish' className='floating-action-button' aria-label={intl.formatMessage(messages.publish)}><Icon id='pencil' /></Link>;
|
||||
|
||||
const content = columnIndex !== -1 ? (
|
||||
<ReactSwipeableViews key='content' hysteresis={0.2} threshold={15} index={columnIndex} onChangeIndex={this.handleSwipe} onTransitionEnd={this.handleAnimationEnd} animateTransitions={shouldAnimate} springConfig={{ duration: '400ms', delay: '0s', easeFunction: 'ease' }} style={{ height: '100%' }} disabled={disableSwiping}>
|
||||
|
|
@ -205,7 +230,7 @@ class ColumnsArea extends ImmutablePureComponent {
|
|||
<div className='columns-area__panels'>
|
||||
<div className='columns-area__panels__pane columns-area__panels__pane--compositional'>
|
||||
<div className='columns-area__panels__pane__inner'>
|
||||
<ComposePanel />
|
||||
{renderComposePanel && <ComposePanel />}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,99 @@
|
|||
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 { closeModal } from 'mastodon/actions/modal';
|
||||
import emojify from 'mastodon/features/emoji/emoji';
|
||||
import escapeTextContentForBrowser from 'escape-html';
|
||||
import InlineAccount from 'mastodon/components/inline_account';
|
||||
import IconButton from 'mastodon/components/icon_button';
|
||||
import RelativeTimestamp from 'mastodon/components/relative_timestamp';
|
||||
import MediaAttachments from 'mastodon/components/media_attachments';
|
||||
|
||||
const mapStateToProps = (state, { statusId }) => ({
|
||||
versions: state.getIn(['history', statusId, 'items']),
|
||||
});
|
||||
|
||||
const mapDispatchToProps = dispatch => ({
|
||||
|
||||
onClose() {
|
||||
dispatch(closeModal());
|
||||
},
|
||||
|
||||
});
|
||||
|
||||
export default @connect(mapStateToProps, mapDispatchToProps)
|
||||
class CompareHistoryModal extends React.PureComponent {
|
||||
|
||||
static propTypes = {
|
||||
onClose: PropTypes.func.isRequired,
|
||||
index: PropTypes.number.isRequired,
|
||||
statusId: PropTypes.string.isRequired,
|
||||
versions: ImmutablePropTypes.list.isRequired,
|
||||
};
|
||||
|
||||
render () {
|
||||
const { index, versions, onClose } = this.props;
|
||||
const currentVersion = versions.get(index);
|
||||
|
||||
const emojiMap = currentVersion.get('emojis').reduce((obj, emoji) => {
|
||||
obj[`:${emoji.get('shortcode')}:`] = emoji.toJS();
|
||||
return obj;
|
||||
}, {});
|
||||
|
||||
const content = { __html: emojify(currentVersion.get('content'), emojiMap) };
|
||||
const spoilerContent = { __html: emojify(escapeTextContentForBrowser(currentVersion.get('spoiler_text')), emojiMap) };
|
||||
|
||||
const formattedDate = <RelativeTimestamp timestamp={currentVersion.get('created_at')} short={false} />;
|
||||
const formattedName = <InlineAccount accountId={currentVersion.get('account')} />;
|
||||
|
||||
const label = currentVersion.get('original') ? (
|
||||
<FormattedMessage id='status.history.created' defaultMessage='{name} created {date}' values={{ name: formattedName, date: formattedDate }} />
|
||||
) : (
|
||||
<FormattedMessage id='status.history.edited' defaultMessage='{name} edited {date}' values={{ name: formattedName, date: formattedDate }} />
|
||||
);
|
||||
|
||||
return (
|
||||
<div className='modal-root__modal compare-history-modal'>
|
||||
<div className='report-modal__target'>
|
||||
<IconButton className='report-modal__close' icon='times' onClick={onClose} size={20} />
|
||||
{label}
|
||||
</div>
|
||||
|
||||
<div className='compare-history-modal__container'>
|
||||
<div className='status__content'>
|
||||
{currentVersion.get('spoiler_text').length > 0 && (
|
||||
<React.Fragment>
|
||||
<div className='translate' dangerouslySetInnerHTML={spoilerContent} />
|
||||
<hr />
|
||||
</React.Fragment>
|
||||
)}
|
||||
|
||||
<div className='status__content__text status__content__text--visible translate' dangerouslySetInnerHTML={content} />
|
||||
|
||||
{!!currentVersion.get('poll') && (
|
||||
<div className='poll'>
|
||||
<ul>
|
||||
{currentVersion.getIn(['poll', 'options']).map(option => (
|
||||
<li key={option.get('title')}>
|
||||
<span className='poll__input disabled' />
|
||||
|
||||
<span
|
||||
className='poll__option__text translate'
|
||||
dangerouslySetInnerHTML={{ __html: emojify(escapeTextContentForBrowser(option.get('title')), emojiMap) }}
|
||||
/>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<MediaAttachments status={currentVersion} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -13,15 +13,22 @@ class ConfirmationModal extends React.PureComponent {
|
|||
onConfirm: PropTypes.func.isRequired,
|
||||
secondary: PropTypes.string,
|
||||
onSecondary: PropTypes.func,
|
||||
closeWhenConfirm: PropTypes.bool,
|
||||
intl: PropTypes.object.isRequired,
|
||||
};
|
||||
|
||||
static defaultProps = {
|
||||
closeWhenConfirm: true,
|
||||
};
|
||||
|
||||
componentDidMount() {
|
||||
this.button.focus();
|
||||
}
|
||||
|
||||
handleClick = () => {
|
||||
this.props.onClose();
|
||||
if (this.props.closeWhenConfirm) {
|
||||
this.props.onClose();
|
||||
}
|
||||
this.props.onConfirm();
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ import PropTypes from 'prop-types';
|
|||
import ImmutablePureComponent from 'react-immutable-pure-component';
|
||||
import { connect } from 'react-redux';
|
||||
import classNames from 'classnames';
|
||||
import { changeUploadCompose, uploadThumbnail } from '../../../actions/compose';
|
||||
import { changeUploadCompose, uploadThumbnail, onChangeMediaDescription, onChangeMediaFocus } from '../../../actions/compose';
|
||||
import { getPointerPosition } from '../../video';
|
||||
import { FormattedMessage, defineMessages, injectIntl } from 'react-intl';
|
||||
import IconButton from 'mastodon/components/icon_button';
|
||||
|
|
@ -27,14 +27,22 @@ import { assetHost } from 'mastodon/utils/config';
|
|||
const messages = defineMessages({
|
||||
close: { id: 'lightbox.close', defaultMessage: 'Close' },
|
||||
apply: { id: 'upload_modal.apply', defaultMessage: 'Apply' },
|
||||
applying: { id: 'upload_modal.applying', defaultMessage: 'Applying…' },
|
||||
placeholder: { id: 'upload_modal.description_placeholder', defaultMessage: 'A quick brown fox jumps over the lazy dog' },
|
||||
chooseImage: { id: 'upload_modal.choose_image', defaultMessage: 'Choose image' },
|
||||
discardMessage: { id: 'confirmations.discard_edit_media.message', defaultMessage: 'You have unsaved changes to the media description or preview, discard them anyway?' },
|
||||
discardConfirm: { id: 'confirmations.discard_edit_media.confirm', defaultMessage: 'Discard' },
|
||||
});
|
||||
|
||||
const mapStateToProps = (state, { id }) => ({
|
||||
media: state.getIn(['compose', 'media_attachments']).find(item => item.get('id') === id),
|
||||
account: state.getIn(['accounts', me]),
|
||||
isUploadingThumbnail: state.getIn(['compose', 'isUploadingThumbnail']),
|
||||
description: state.getIn(['compose', 'media_modal', 'description']),
|
||||
focusX: state.getIn(['compose', 'media_modal', 'focusX']),
|
||||
focusY: state.getIn(['compose', 'media_modal', 'focusY']),
|
||||
dirty: state.getIn(['compose', 'media_modal', 'dirty']),
|
||||
is_changing_upload: state.getIn(['compose', 'is_changing_upload']),
|
||||
});
|
||||
|
||||
const mapDispatchToProps = (dispatch, { id }) => ({
|
||||
|
|
@ -43,6 +51,14 @@ const mapDispatchToProps = (dispatch, { id }) => ({
|
|||
dispatch(changeUploadCompose(id, { description, focus: `${x.toFixed(2)},${y.toFixed(2)}` }));
|
||||
},
|
||||
|
||||
onChangeDescription: (description) => {
|
||||
dispatch(onChangeMediaDescription(description));
|
||||
},
|
||||
|
||||
onChangeFocus: (focusX, focusY) => {
|
||||
dispatch(onChangeMediaFocus(focusX, focusY));
|
||||
},
|
||||
|
||||
onSelectThumbnail: files => {
|
||||
dispatch(uploadThumbnail(id, files[0]));
|
||||
},
|
||||
|
|
@ -83,8 +99,8 @@ class ImageLoader extends React.PureComponent {
|
|||
|
||||
}
|
||||
|
||||
export default @connect(mapStateToProps, mapDispatchToProps)
|
||||
@injectIntl
|
||||
export default @connect(mapStateToProps, mapDispatchToProps, null, { forwardRef: true })
|
||||
@(component => injectIntl(component, { withRef: true }))
|
||||
class FocalPointModal extends ImmutablePureComponent {
|
||||
|
||||
static propTypes = {
|
||||
|
|
@ -92,34 +108,21 @@ class FocalPointModal extends ImmutablePureComponent {
|
|||
account: ImmutablePropTypes.map.isRequired,
|
||||
isUploadingThumbnail: PropTypes.bool,
|
||||
onSave: PropTypes.func.isRequired,
|
||||
onChangeDescription: PropTypes.func.isRequired,
|
||||
onChangeFocus: PropTypes.func.isRequired,
|
||||
onSelectThumbnail: PropTypes.func.isRequired,
|
||||
onClose: PropTypes.func.isRequired,
|
||||
intl: PropTypes.object.isRequired,
|
||||
};
|
||||
|
||||
state = {
|
||||
x: 0,
|
||||
y: 0,
|
||||
focusX: 0,
|
||||
focusY: 0,
|
||||
dragging: false,
|
||||
description: '',
|
||||
dirty: false,
|
||||
progress: 0,
|
||||
loading: true,
|
||||
ocrStatus: '',
|
||||
};
|
||||
|
||||
componentWillMount () {
|
||||
this.updatePositionFromMedia(this.props.media);
|
||||
}
|
||||
|
||||
componentWillReceiveProps (nextProps) {
|
||||
if (this.props.media.get('id') !== nextProps.media.get('id')) {
|
||||
this.updatePositionFromMedia(nextProps.media);
|
||||
}
|
||||
}
|
||||
|
||||
componentWillUnmount () {
|
||||
document.removeEventListener('mousemove', this.handleMouseMove);
|
||||
document.removeEventListener('mouseup', this.handleMouseUp);
|
||||
|
|
@ -164,54 +167,37 @@ class FocalPointModal extends ImmutablePureComponent {
|
|||
const focusX = (x - .5) * 2;
|
||||
const focusY = (y - .5) * -2;
|
||||
|
||||
this.setState({ x, y, focusX, focusY, dirty: true });
|
||||
}
|
||||
|
||||
updatePositionFromMedia = media => {
|
||||
const focusX = media.getIn(['meta', 'focus', 'x']);
|
||||
const focusY = media.getIn(['meta', 'focus', 'y']);
|
||||
const description = media.get('description') || '';
|
||||
|
||||
if (focusX && focusY) {
|
||||
const x = (focusX / 2) + .5;
|
||||
const y = (focusY / -2) + .5;
|
||||
|
||||
this.setState({
|
||||
x,
|
||||
y,
|
||||
focusX,
|
||||
focusY,
|
||||
description,
|
||||
dirty: false,
|
||||
});
|
||||
} else {
|
||||
this.setState({
|
||||
x: 0.5,
|
||||
y: 0.5,
|
||||
focusX: 0,
|
||||
focusY: 0,
|
||||
description,
|
||||
dirty: false,
|
||||
});
|
||||
}
|
||||
this.props.onChangeFocus(focusX, focusY);
|
||||
}
|
||||
|
||||
handleChange = e => {
|
||||
this.setState({ description: e.target.value, dirty: true });
|
||||
this.props.onChangeDescription(e.target.value);
|
||||
}
|
||||
|
||||
handleKeyDown = (e) => {
|
||||
if (e.keyCode === 13 && (e.ctrlKey || e.metaKey)) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
this.setState({ description: e.target.value, dirty: true });
|
||||
this.props.onChangeDescription(e.target.value);
|
||||
this.handleSubmit();
|
||||
}
|
||||
}
|
||||
|
||||
handleSubmit = () => {
|
||||
this.props.onSave(this.state.description, this.state.focusX, this.state.focusY);
|
||||
this.props.onClose();
|
||||
this.props.onSave(this.props.description, this.props.focusX, this.props.focusY);
|
||||
}
|
||||
|
||||
getCloseConfirmationMessage = () => {
|
||||
const { intl, dirty } = this.props;
|
||||
|
||||
if (dirty) {
|
||||
return {
|
||||
message: intl.formatMessage(messages.discardMessage),
|
||||
confirm: intl.formatMessage(messages.discardConfirm),
|
||||
};
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
setRef = c => {
|
||||
|
|
@ -219,6 +205,10 @@ class FocalPointModal extends ImmutablePureComponent {
|
|||
}
|
||||
|
||||
handleTextDetection = () => {
|
||||
this._detectText();
|
||||
}
|
||||
|
||||
_detectText = (refreshCache = false) => {
|
||||
const { media } = this.props;
|
||||
|
||||
this.setState({ detecting: true });
|
||||
|
|
@ -227,7 +217,7 @@ class FocalPointModal extends ImmutablePureComponent {
|
|||
const worker = createWorker({
|
||||
workerPath: tesseractWorkerPath,
|
||||
corePath: tesseractCorePath,
|
||||
langPath: assetHost,
|
||||
langPath: `${assetHost}/ocr/lang-data/`,
|
||||
logger: ({ status, progress }) => {
|
||||
if (status === 'recognizing text') {
|
||||
this.setState({ ocrStatus: 'detecting', progress });
|
||||
|
|
@ -235,6 +225,7 @@ class FocalPointModal extends ImmutablePureComponent {
|
|||
this.setState({ ocrStatus: 'preparing', progress });
|
||||
}
|
||||
},
|
||||
cacheMethod: refreshCache ? 'refresh' : 'write',
|
||||
});
|
||||
|
||||
let media_url = media.get('url');
|
||||
|
|
@ -247,14 +238,21 @@ class FocalPointModal extends ImmutablePureComponent {
|
|||
}
|
||||
}
|
||||
|
||||
(async () => {
|
||||
return (async () => {
|
||||
await worker.load();
|
||||
await worker.loadLanguage('eng');
|
||||
await worker.initialize('eng');
|
||||
const { data: { text } } = await worker.recognize(media_url);
|
||||
this.setState({ description: removeExtraLineBreaks(text), dirty: true, detecting: false });
|
||||
this.setState({ detecting: false });
|
||||
this.props.onChangeDescription(removeExtraLineBreaks(text));
|
||||
await worker.terminate();
|
||||
})();
|
||||
})().catch((e) => {
|
||||
if (refreshCache) {
|
||||
throw e;
|
||||
} else {
|
||||
this._detectText(true);
|
||||
}
|
||||
});
|
||||
}).catch((e) => {
|
||||
console.error(e);
|
||||
this.setState({ detecting: false });
|
||||
|
|
@ -263,7 +261,6 @@ class FocalPointModal extends ImmutablePureComponent {
|
|||
|
||||
handleThumbnailChange = e => {
|
||||
if (e.target.files.length > 0) {
|
||||
this.setState({ dirty: true });
|
||||
this.props.onSelectThumbnail(e.target.files);
|
||||
}
|
||||
}
|
||||
|
|
@ -277,8 +274,10 @@ class FocalPointModal extends ImmutablePureComponent {
|
|||
}
|
||||
|
||||
render () {
|
||||
const { media, intl, account, onClose, isUploadingThumbnail } = this.props;
|
||||
const { x, y, dragging, description, dirty, detecting, progress, ocrStatus } = this.state;
|
||||
const { media, intl, account, onClose, isUploadingThumbnail, description, focusX, focusY, dirty, is_changing_upload } = this.props;
|
||||
const { dragging, detecting, progress, ocrStatus } = this.state;
|
||||
const x = (focusX / 2) + .5;
|
||||
const y = (focusY / -2) + .5;
|
||||
|
||||
const width = media.getIn(['meta', 'original', 'width']) || null;
|
||||
const height = media.getIn(['meta', 'original', 'height']) || null;
|
||||
|
|
@ -309,7 +308,7 @@ class FocalPointModal extends ImmutablePureComponent {
|
|||
return (
|
||||
<div className='modal-root__modal report-modal' style={{ maxWidth: 960 }}>
|
||||
<div className='report-modal__target'>
|
||||
<IconButton className='media-modal__close' title={intl.formatMessage(messages.close)} icon='times' onClick={onClose} size={16} />
|
||||
<IconButton className='report-modal__close' title={intl.formatMessage(messages.close)} icon='times' onClick={onClose} size={20} />
|
||||
<FormattedMessage id='upload_modal.edit_media' defaultMessage='Edit media' />
|
||||
</div>
|
||||
|
||||
|
|
@ -333,7 +332,7 @@ class FocalPointModal extends ImmutablePureComponent {
|
|||
accept='image/png,image/jpeg'
|
||||
onChange={this.handleThumbnailChange}
|
||||
style={{ display: 'none' }}
|
||||
disabled={isUploadingThumbnail}
|
||||
disabled={isUploadingThumbnail || is_changing_upload}
|
||||
/>
|
||||
</label>
|
||||
|
||||
|
|
@ -352,7 +351,7 @@ class FocalPointModal extends ImmutablePureComponent {
|
|||
value={detecting ? '…' : description}
|
||||
onChange={this.handleChange}
|
||||
onKeyDown={this.handleKeyDown}
|
||||
disabled={detecting}
|
||||
disabled={detecting || is_changing_upload}
|
||||
autoFocus
|
||||
/>
|
||||
|
||||
|
|
@ -362,11 +361,11 @@ class FocalPointModal extends ImmutablePureComponent {
|
|||
</div>
|
||||
|
||||
<div className='setting-text__toolbar'>
|
||||
<button disabled={detecting || media.get('type') !== 'image'} className='link-button' onClick={this.handleTextDetection}><FormattedMessage id='upload_modal.detect_text' defaultMessage='Detect text from picture' /></button>
|
||||
<button disabled={detecting || media.get('type') !== 'image' || is_changing_upload} className='link-button' onClick={this.handleTextDetection}><FormattedMessage id='upload_modal.detect_text' defaultMessage='Detect text from picture' /></button>
|
||||
<CharacterCounter max={1500} text={detecting ? '' : description} />
|
||||
</div>
|
||||
|
||||
<Button disabled={!dirty || detecting || isUploadingThumbnail || length(description) > 1500} text={intl.formatMessage(messages.apply)} onClick={this.handleSubmit} />
|
||||
<Button disabled={!dirty || detecting || isUploadingThumbnail || length(description) > 1500 || is_changing_upload} text={intl.formatMessage(is_changing_upload ? messages.applying : messages.apply)} onClick={this.handleSubmit} />
|
||||
</div>
|
||||
|
||||
<div className='focal-point-modal__content'>
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ import React from 'react';
|
|||
import PropTypes from 'prop-types';
|
||||
import { FormattedMessage, defineMessages, injectIntl } from 'react-intl';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { invitesEnabled, version, repository, source_url } from 'mastodon/initial_state';
|
||||
import { invitesEnabled, limitedFederationMode, version, repository, source_url, profile_directory as profileDirectory } from 'mastodon/initial_state';
|
||||
import { logOut } from 'mastodon/utils/log_out';
|
||||
import { openModal } from 'mastodon/actions/modal';
|
||||
|
||||
|
|
@ -17,6 +17,7 @@ const mapDispatchToProps = (dispatch, { intl }) => ({
|
|||
dispatch(openModal('CONFIRM', {
|
||||
message: intl.formatMessage(messages.logoutMessage),
|
||||
confirm: intl.formatMessage(messages.logoutConfirm),
|
||||
closeWhenConfirm: false,
|
||||
onConfirm: () => logOut(),
|
||||
}));
|
||||
},
|
||||
|
|
@ -50,7 +51,8 @@ class LinkFooter extends React.PureComponent {
|
|||
{invitesEnabled && <li><a href='/invites' target='_blank'><FormattedMessage id='getting_started.invite' defaultMessage='Invite people' /></a> · </li>}
|
||||
{withHotkeys && <li><Link to='/keyboard-shortcuts'><FormattedMessage id='navigation_bar.keyboard_shortcuts' defaultMessage='Hotkeys' /></Link> · </li>}
|
||||
<li><a href='/auth/edit'><FormattedMessage id='getting_started.security' defaultMessage='Security' /></a> · </li>
|
||||
<li><a href='/about/more' target='_blank'><FormattedMessage id='navigation_bar.info' defaultMessage='About this server' /></a> · </li>
|
||||
{!limitedFederationMode && <li><a href='/about/more' target='_blank'><FormattedMessage id='navigation_bar.info' defaultMessage='About this server' /></a> · </li>}
|
||||
{profileDirectory && <li><Link to='/directory'><FormattedMessage id='getting_started.directory' defaultMessage='Profile directory' /></Link> · </li>}
|
||||
<li><a href='https://joinmastodon.org/apps' target='_blank'><FormattedMessage id='navigation_bar.apps' defaultMessage='Mobile apps' /></a> · </li>
|
||||
<li><a href='/terms' target='_blank'><FormattedMessage id='getting_started.terms' defaultMessage='Terms of service' /></a> · </li>
|
||||
<li><a href='/settings/applications' target='_blank'><FormattedMessage id='getting_started.developers' defaultMessage='Developers' /></a> · </li>
|
||||
|
|
|
|||
|
|
@ -46,7 +46,7 @@ class ListPanel extends ImmutablePureComponent {
|
|||
<hr />
|
||||
|
||||
{lists.map(list => (
|
||||
<NavLink key={list.get('id')} className='column-link column-link--transparent' strict to={`/timelines/list/${list.get('id')}`}><Icon className='column-link__icon' id='list-ul' fixedWidth />{list.get('title')}</NavLink>
|
||||
<NavLink key={list.get('id')} className='column-link column-link--transparent' strict to={`/lists/${list.get('id')}`}><Icon className='column-link__icon' id='list-ul' fixedWidth />{list.get('title')}</NavLink>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -20,8 +20,6 @@ const messages = defineMessages({
|
|||
next: { id: 'lightbox.next', defaultMessage: 'Next' },
|
||||
});
|
||||
|
||||
export const previewState = 'previewMediaModal';
|
||||
|
||||
export default @injectIntl
|
||||
class MediaModal extends ImmutablePureComponent {
|
||||
|
||||
|
|
@ -32,10 +30,9 @@ class MediaModal extends ImmutablePureComponent {
|
|||
onClose: PropTypes.func.isRequired,
|
||||
intl: PropTypes.object.isRequired,
|
||||
onChangeBackgroundColor: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
static contextTypes = {
|
||||
router: PropTypes.object,
|
||||
currentTime: PropTypes.number,
|
||||
autoPlay: PropTypes.bool,
|
||||
volume: PropTypes.number,
|
||||
};
|
||||
|
||||
state = {
|
||||
|
|
@ -95,16 +92,6 @@ class MediaModal extends ImmutablePureComponent {
|
|||
componentDidMount () {
|
||||
window.addEventListener('keydown', this.handleKeyDown, false);
|
||||
|
||||
if (this.context.router) {
|
||||
const history = this.context.router.history;
|
||||
|
||||
history.push(history.location.pathname, previewState);
|
||||
|
||||
this.unlistenHistory = history.listen(() => {
|
||||
this.props.onClose();
|
||||
});
|
||||
}
|
||||
|
||||
this._sendBackgroundColor();
|
||||
}
|
||||
|
||||
|
|
@ -128,14 +115,6 @@ class MediaModal extends ImmutablePureComponent {
|
|||
componentWillUnmount () {
|
||||
window.removeEventListener('keydown', this.handleKeyDown);
|
||||
|
||||
if (this.context.router) {
|
||||
this.unlistenHistory();
|
||||
|
||||
if (this.context.router.history.location.state === previewState) {
|
||||
this.context.router.history.goBack();
|
||||
}
|
||||
}
|
||||
|
||||
this.props.onChangeBackgroundColor(null);
|
||||
}
|
||||
|
||||
|
|
@ -149,13 +128,6 @@ class MediaModal extends ImmutablePureComponent {
|
|||
}));
|
||||
};
|
||||
|
||||
handleStatusClick = e => {
|
||||
if (e.button === 0 && !(e.ctrlKey || e.metaKey)) {
|
||||
e.preventDefault();
|
||||
this.context.router.history.push(`/statuses/${this.props.statusId}`);
|
||||
}
|
||||
}
|
||||
|
||||
render () {
|
||||
const { media, statusId, intl, onClose } = this.props;
|
||||
const { navigationHidden } = this.state;
|
||||
|
|
@ -183,7 +155,7 @@ class MediaModal extends ImmutablePureComponent {
|
|||
/>
|
||||
);
|
||||
} else if (image.get('type') === 'video') {
|
||||
const { time } = this.props;
|
||||
const { currentTime, autoPlay, volume } = this.props;
|
||||
|
||||
return (
|
||||
<Video
|
||||
|
|
@ -192,7 +164,10 @@ class MediaModal extends ImmutablePureComponent {
|
|||
src={image.get('url')}
|
||||
width={image.get('width')}
|
||||
height={image.get('height')}
|
||||
currentTime={time || 0}
|
||||
frameRate={image.getIn(['meta', 'original', 'frame_rate'])}
|
||||
currentTime={currentTime || 0}
|
||||
autoPlay={autoPlay || false}
|
||||
volume={volume || 1}
|
||||
onCloseVideo={onClose}
|
||||
detailed
|
||||
alt={image.get('description')}
|
||||
|
|
|
|||
|
|
@ -19,7 +19,8 @@ import {
|
|||
EmbedModal,
|
||||
ListEditor,
|
||||
ListAdder,
|
||||
} from '../../../features/ui/util/async-components';
|
||||
CompareHistoryModal,
|
||||
} from 'mastodon/features/ui/util/async-components';
|
||||
|
||||
const MODAL_COMPONENTS = {
|
||||
'MEDIA': () => Promise.resolve({ default: MediaModal }),
|
||||
|
|
@ -34,7 +35,8 @@ const MODAL_COMPONENTS = {
|
|||
'EMBED': EmbedModal,
|
||||
'LIST_EDITOR': ListEditor,
|
||||
'FOCAL_POINT': () => Promise.resolve({ default: FocalPointModal }),
|
||||
'LIST_ADDER':ListAdder,
|
||||
'LIST_ADDER': ListAdder,
|
||||
'COMPARE_HISTORY': CompareHistoryModal,
|
||||
};
|
||||
|
||||
export default class ModalRoot extends React.PureComponent {
|
||||
|
|
@ -43,6 +45,7 @@ export default class ModalRoot extends React.PureComponent {
|
|||
type: PropTypes.string,
|
||||
props: PropTypes.object,
|
||||
onClose: PropTypes.func.isRequired,
|
||||
ignoreFocus: PropTypes.bool,
|
||||
};
|
||||
|
||||
state = {
|
||||
|
|
@ -77,16 +80,33 @@ export default class ModalRoot extends React.PureComponent {
|
|||
return <BundleModalError {...props} onClose={onClose} />;
|
||||
}
|
||||
|
||||
handleClose = (ignoreFocus = false) => {
|
||||
const { onClose } = this.props;
|
||||
let message = null;
|
||||
try {
|
||||
message = this._modal?.getWrappedInstance?.().getCloseConfirmationMessage?.();
|
||||
} catch (_) {
|
||||
// injectIntl defines `getWrappedInstance` but errors out if `withRef`
|
||||
// isn't set.
|
||||
// This would be much smoother with react-intl 3+ and `forwardRef`.
|
||||
}
|
||||
onClose(message, ignoreFocus);
|
||||
}
|
||||
|
||||
setModalRef = (c) => {
|
||||
this._modal = c;
|
||||
}
|
||||
|
||||
render () {
|
||||
const { type, props, onClose } = this.props;
|
||||
const { type, props, ignoreFocus } = this.props;
|
||||
const { backgroundColor } = this.state;
|
||||
const visible = !!type;
|
||||
|
||||
return (
|
||||
<Base backgroundColor={backgroundColor} onClose={onClose}>
|
||||
<Base backgroundColor={backgroundColor} onClose={this.handleClose} ignoreFocus={ignoreFocus}>
|
||||
{visible && (
|
||||
<BundleContainer fetchComponent={MODAL_COMPONENTS[type]} loading={this.renderLoading(type)} error={this.renderError} renderDelay={200}>
|
||||
{(SpecificComponent) => <SpecificComponent {...props} onChangeBackgroundColor={this.setBackgroundColor} onClose={onClose} />}
|
||||
{(SpecificComponent) => <SpecificComponent {...props} onChangeBackgroundColor={this.setBackgroundColor} onClose={this.handleClose} ref={this.setModalRef} />}
|
||||
</BundleContainer>
|
||||
)}
|
||||
</Base>
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ import React from 'react';
|
|||
import { NavLink, withRouter } from 'react-router-dom';
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
import Icon from 'mastodon/components/icon';
|
||||
import { profile_directory, showTrends } from 'mastodon/initial_state';
|
||||
import { showTrends } from 'mastodon/initial_state';
|
||||
import NotificationsCounterIcon from './notifications_counter_icon';
|
||||
import FollowRequestsNavLink from './follow_requests_nav_link';
|
||||
import ListPanel from './list_panel';
|
||||
|
|
@ -10,16 +10,16 @@ import TrendsContainer from 'mastodon/features/getting_started/containers/trends
|
|||
|
||||
const NavigationPanel = () => (
|
||||
<div className='navigation-panel'>
|
||||
<NavLink className='column-link column-link--transparent' to='/timelines/home' data-preview-title-id='column.home' data-preview-icon='home' ><Icon className='column-link__icon' id='home' fixedWidth /><FormattedMessage id='tabs_bar.home' defaultMessage='Home' /></NavLink>
|
||||
<NavLink className='column-link column-link--transparent' to='/home' data-preview-title-id='column.home' data-preview-icon='home' ><Icon className='column-link__icon' id='home' fixedWidth /><FormattedMessage id='tabs_bar.home' defaultMessage='Home' /></NavLink>
|
||||
<NavLink className='column-link column-link--transparent' to='/notifications' data-preview-title-id='column.notifications' data-preview-icon='bell' ><NotificationsCounterIcon className='column-link__icon' /><FormattedMessage id='tabs_bar.notifications' defaultMessage='Notifications' /></NavLink>
|
||||
<FollowRequestsNavLink />
|
||||
<NavLink className='column-link column-link--transparent' to='/timelines/public/local' data-preview-title-id='column.community' data-preview-icon='users' ><Icon className='column-link__icon' id='users' fixedWidth /><FormattedMessage id='tabs_bar.local_timeline' defaultMessage='Local' /></NavLink>
|
||||
<NavLink className='column-link column-link--transparent' exact to='/timelines/public' data-preview-title-id='column.public' data-preview-icon='globe' ><Icon className='column-link__icon' id='globe' fixedWidth /><FormattedMessage id='tabs_bar.federated_timeline' defaultMessage='Federated' /></NavLink>
|
||||
<NavLink className='column-link column-link--transparent' to='/timelines/direct'><Icon className='column-link__icon' id='envelope' fixedWidth /><FormattedMessage id='navigation_bar.direct' defaultMessage='Direct messages' /></NavLink>
|
||||
<NavLink className='column-link column-link--transparent' to='/explore' data-preview-title-id='explore.title' data-preview-icon='hashtag'><Icon className='column-link__icon' id='hashtag' fixedWidth /><FormattedMessage id='explore.title' defaultMessage='Explore' /></NavLink>
|
||||
<NavLink className='column-link column-link--transparent' to='/public/local' data-preview-title-id='column.community' data-preview-icon='users' ><Icon className='column-link__icon' id='users' fixedWidth /><FormattedMessage id='tabs_bar.local_timeline' defaultMessage='Local' /></NavLink>
|
||||
<NavLink className='column-link column-link--transparent' exact to='/public' data-preview-title-id='column.public' data-preview-icon='globe' ><Icon className='column-link__icon' id='globe' fixedWidth /><FormattedMessage id='tabs_bar.federated_timeline' defaultMessage='Federated' /></NavLink>
|
||||
<NavLink className='column-link column-link--transparent' to='/conversations'><Icon className='column-link__icon' id='at' fixedWidth /><FormattedMessage id='navigation_bar.direct' defaultMessage='Direct messages' /></NavLink>
|
||||
<NavLink className='column-link column-link--transparent' to='/favourites'><Icon className='column-link__icon' id='star' fixedWidth /><FormattedMessage id='navigation_bar.favourites' defaultMessage='Favourites' /></NavLink>
|
||||
<NavLink className='column-link column-link--transparent' to='/bookmarks'><Icon className='column-link__icon' id='bookmark' fixedWidth /><FormattedMessage id='navigation_bar.bookmarks' defaultMessage='Bookmarks' /></NavLink>
|
||||
<NavLink className='column-link column-link--transparent' to='/lists'><Icon className='column-link__icon' id='list-ul' fixedWidth /><FormattedMessage id='navigation_bar.lists' defaultMessage='Lists' /></NavLink>
|
||||
{profile_directory && <NavLink className='column-link column-link--transparent' to='/directory'><Icon className='column-link__icon' id='address-book-o' fixedWidth /><FormattedMessage id='getting_started.directory' defaultMessage='Profile directory' /></NavLink>}
|
||||
|
||||
<ListPanel />
|
||||
|
||||
|
|
|
|||
|
|
@ -1,38 +1,31 @@
|
|||
import React from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import { changeReportComment, changeReportForward, submitReport } from '../../../actions/reports';
|
||||
import { expandAccountTimeline } from '../../../actions/timelines';
|
||||
import { submitReport } from 'mastodon/actions/reports';
|
||||
import { expandAccountTimeline } from 'mastodon/actions/timelines';
|
||||
import { fetchRules } from 'mastodon/actions/rules';
|
||||
import PropTypes from 'prop-types';
|
||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||
import { makeGetAccount } from '../../../selectors';
|
||||
import { makeGetAccount } from 'mastodon/selectors';
|
||||
import { defineMessages, FormattedMessage, injectIntl } from 'react-intl';
|
||||
import StatusCheckBox from '../../report/containers/status_check_box_container';
|
||||
import { OrderedSet } from 'immutable';
|
||||
import ImmutablePureComponent from 'react-immutable-pure-component';
|
||||
import Button from '../../../components/button';
|
||||
import Toggle from 'react-toggle';
|
||||
import IconButton from '../../../components/icon_button';
|
||||
import IconButton from 'mastodon/components/icon_button';
|
||||
import Category from 'mastodon/features/report/category';
|
||||
import Statuses from 'mastodon/features/report/statuses';
|
||||
import Rules from 'mastodon/features/report/rules';
|
||||
import Comment from 'mastodon/features/report/comment';
|
||||
import Thanks from 'mastodon/features/report/thanks';
|
||||
|
||||
const messages = defineMessages({
|
||||
close: { id: 'lightbox.close', defaultMessage: 'Close' },
|
||||
placeholder: { id: 'report.placeholder', defaultMessage: 'Additional comments' },
|
||||
submit: { id: 'report.submit', defaultMessage: 'Submit' },
|
||||
});
|
||||
|
||||
const makeMapStateToProps = () => {
|
||||
const getAccount = makeGetAccount();
|
||||
|
||||
const mapStateToProps = state => {
|
||||
const accountId = state.getIn(['reports', 'new', 'account_id']);
|
||||
|
||||
return {
|
||||
isSubmitting: state.getIn(['reports', 'new', 'isSubmitting']),
|
||||
account: getAccount(state, accountId),
|
||||
comment: state.getIn(['reports', 'new', 'comment']),
|
||||
forward: state.getIn(['reports', 'new', 'forward']),
|
||||
statusIds: OrderedSet(state.getIn(['timelines', `account:${accountId}:with_replies`, 'items'])).union(state.getIn(['reports', 'new', 'status_ids'])),
|
||||
};
|
||||
};
|
||||
const mapStateToProps = (state, { accountId }) => ({
|
||||
account: getAccount(state, accountId),
|
||||
});
|
||||
|
||||
return mapStateToProps;
|
||||
};
|
||||
|
|
@ -42,92 +35,182 @@ export default @connect(makeMapStateToProps)
|
|||
class ReportModal extends ImmutablePureComponent {
|
||||
|
||||
static propTypes = {
|
||||
isSubmitting: PropTypes.bool,
|
||||
account: ImmutablePropTypes.map,
|
||||
statusIds: ImmutablePropTypes.orderedSet.isRequired,
|
||||
comment: PropTypes.string.isRequired,
|
||||
forward: PropTypes.bool,
|
||||
accountId: PropTypes.string.isRequired,
|
||||
statusId: PropTypes.string,
|
||||
dispatch: PropTypes.func.isRequired,
|
||||
intl: PropTypes.object.isRequired,
|
||||
account: ImmutablePropTypes.map.isRequired,
|
||||
};
|
||||
|
||||
handleCommentChange = e => {
|
||||
this.props.dispatch(changeReportComment(e.target.value));
|
||||
}
|
||||
|
||||
handleForwardChange = e => {
|
||||
this.props.dispatch(changeReportForward(e.target.checked));
|
||||
}
|
||||
state = {
|
||||
step: 'category',
|
||||
selectedStatusIds: OrderedSet(this.props.statusId ? [this.props.statusId] : []),
|
||||
comment: '',
|
||||
category: null,
|
||||
selectedRuleIds: OrderedSet(),
|
||||
forward: true,
|
||||
isSubmitting: false,
|
||||
isSubmitted: false,
|
||||
};
|
||||
|
||||
handleSubmit = () => {
|
||||
this.props.dispatch(submitReport());
|
||||
}
|
||||
const { dispatch, accountId } = this.props;
|
||||
const { selectedStatusIds, comment, category, selectedRuleIds, forward } = this.state;
|
||||
|
||||
handleKeyDown = e => {
|
||||
if (e.keyCode === 13 && (e.ctrlKey || e.metaKey)) {
|
||||
this.handleSubmit();
|
||||
this.setState({ isSubmitting: true });
|
||||
|
||||
dispatch(submitReport({
|
||||
account_id: accountId,
|
||||
status_ids: selectedStatusIds.toArray(),
|
||||
comment,
|
||||
forward,
|
||||
category,
|
||||
rule_ids: selectedRuleIds.toArray(),
|
||||
}, this.handleSuccess, this.handleFail));
|
||||
};
|
||||
|
||||
handleSuccess = () => {
|
||||
this.setState({ isSubmitting: false, isSubmitted: true, step: 'thanks' });
|
||||
};
|
||||
|
||||
handleFail = () => {
|
||||
this.setState({ isSubmitting: false });
|
||||
};
|
||||
|
||||
handleStatusToggle = (statusId, checked) => {
|
||||
const { selectedStatusIds } = this.state;
|
||||
|
||||
if (checked) {
|
||||
this.setState({ selectedStatusIds: selectedStatusIds.add(statusId) });
|
||||
} else {
|
||||
this.setState({ selectedStatusIds: selectedStatusIds.remove(statusId) });
|
||||
}
|
||||
};
|
||||
|
||||
handleRuleToggle = (ruleId, checked) => {
|
||||
const { selectedRuleIds } = this.state;
|
||||
|
||||
if (checked) {
|
||||
this.setState({ selectedRuleIds: selectedRuleIds.add(ruleId) });
|
||||
} else {
|
||||
this.setState({ selectedRuleIds: selectedRuleIds.remove(ruleId) });
|
||||
}
|
||||
}
|
||||
|
||||
handleChangeCategory = category => {
|
||||
this.setState({ category });
|
||||
};
|
||||
|
||||
handleChangeComment = comment => {
|
||||
this.setState({ comment });
|
||||
};
|
||||
|
||||
handleChangeForward = forward => {
|
||||
this.setState({ forward });
|
||||
};
|
||||
|
||||
handleNextStep = step => {
|
||||
this.setState({ step });
|
||||
};
|
||||
|
||||
componentDidMount () {
|
||||
this.props.dispatch(expandAccountTimeline(this.props.account.get('id'), { withReplies: true }));
|
||||
}
|
||||
const { dispatch, accountId } = this.props;
|
||||
|
||||
componentWillReceiveProps (nextProps) {
|
||||
if (this.props.account !== nextProps.account && nextProps.account) {
|
||||
this.props.dispatch(expandAccountTimeline(nextProps.account.get('id'), { withReplies: true }));
|
||||
}
|
||||
dispatch(expandAccountTimeline(accountId, { withReplies: true }));
|
||||
dispatch(fetchRules());
|
||||
}
|
||||
|
||||
render () {
|
||||
const { account, comment, intl, statusIds, isSubmitting, forward, onClose } = this.props;
|
||||
const {
|
||||
accountId,
|
||||
account,
|
||||
intl,
|
||||
onClose,
|
||||
} = this.props;
|
||||
|
||||
if (!account) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const domain = account.get('acct').split('@')[1];
|
||||
const {
|
||||
step,
|
||||
selectedStatusIds,
|
||||
selectedRuleIds,
|
||||
comment,
|
||||
forward,
|
||||
category,
|
||||
isSubmitting,
|
||||
isSubmitted,
|
||||
} = this.state;
|
||||
|
||||
const domain = account.get('acct').split('@')[1];
|
||||
const isRemote = !!domain;
|
||||
|
||||
let stepComponent;
|
||||
|
||||
switch(step) {
|
||||
case 'category':
|
||||
stepComponent = (
|
||||
<Category
|
||||
onNextStep={this.handleNextStep}
|
||||
startedFrom={this.props.statusId ? 'status' : 'account'}
|
||||
category={category}
|
||||
onChangeCategory={this.handleChangeCategory}
|
||||
/>
|
||||
);
|
||||
break;
|
||||
case 'rules':
|
||||
stepComponent = (
|
||||
<Rules
|
||||
onNextStep={this.handleNextStep}
|
||||
selectedRuleIds={selectedRuleIds}
|
||||
onToggle={this.handleRuleToggle}
|
||||
/>
|
||||
);
|
||||
break;
|
||||
case 'statuses':
|
||||
stepComponent = (
|
||||
<Statuses
|
||||
onNextStep={this.handleNextStep}
|
||||
accountId={accountId}
|
||||
selectedStatusIds={selectedStatusIds}
|
||||
onToggle={this.handleStatusToggle}
|
||||
/>
|
||||
);
|
||||
break;
|
||||
case 'comment':
|
||||
stepComponent = (
|
||||
<Comment
|
||||
onSubmit={this.handleSubmit}
|
||||
isSubmitting={isSubmitting}
|
||||
isRemote={isRemote}
|
||||
comment={comment}
|
||||
forward={forward}
|
||||
domain={domain}
|
||||
onChangeComment={this.handleChangeComment}
|
||||
onChangeForward={this.handleChangeForward}
|
||||
/>
|
||||
);
|
||||
break;
|
||||
case 'thanks':
|
||||
stepComponent = (
|
||||
<Thanks
|
||||
submitted={isSubmitted}
|
||||
account={account}
|
||||
onClose={onClose}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className='modal-root__modal report-modal'>
|
||||
<div className='modal-root__modal report-dialog-modal'>
|
||||
<div className='report-modal__target'>
|
||||
<IconButton className='media-modal__close' title={intl.formatMessage(messages.close)} icon='times' onClick={onClose} size={16} />
|
||||
<IconButton className='report-modal__close' title={intl.formatMessage(messages.close)} icon='times' onClick={onClose} size={20} />
|
||||
<FormattedMessage id='report.target' defaultMessage='Report {target}' values={{ target: <strong>{account.get('acct')}</strong> }} />
|
||||
</div>
|
||||
|
||||
<div className='report-modal__container'>
|
||||
<div className='report-modal__comment'>
|
||||
<p><FormattedMessage id='report.hint' defaultMessage='The report will be sent to your server moderators. You can provide an explanation of why you are reporting this account below:' /></p>
|
||||
|
||||
<textarea
|
||||
className='setting-text light'
|
||||
placeholder={intl.formatMessage(messages.placeholder)}
|
||||
value={comment}
|
||||
onChange={this.handleCommentChange}
|
||||
onKeyDown={this.handleKeyDown}
|
||||
disabled={isSubmitting}
|
||||
autoFocus
|
||||
/>
|
||||
|
||||
{domain && (
|
||||
<div>
|
||||
<p><FormattedMessage id='report.forward_hint' defaultMessage='The account is from another server. Send an anonymized copy of the report there as well?' /></p>
|
||||
|
||||
<div className='setting-toggle'>
|
||||
<Toggle id='report-forward' checked={forward} disabled={isSubmitting} onChange={this.handleForwardChange} />
|
||||
<label htmlFor='report-forward' className='setting-toggle__label'><FormattedMessage id='report.forward' defaultMessage='Forward to {target}' values={{ target: domain }} /></label>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<Button disabled={isSubmitting} text={intl.formatMessage(messages.submit)} onClick={this.handleSubmit} />
|
||||
</div>
|
||||
|
||||
<div className='report-modal__statuses'>
|
||||
<div>
|
||||
{statusIds.map(statusId => <StatusCheckBox id={statusId} key={statusId} disabled={isSubmitting} />)}
|
||||
</div>
|
||||
</div>
|
||||
<div className='report-dialog-modal__container'>
|
||||
{stepComponent}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -8,11 +8,11 @@ import Icon from 'mastodon/components/icon';
|
|||
import NotificationsCounterIcon from './notifications_counter_icon';
|
||||
|
||||
export const links = [
|
||||
<NavLink className='tabs-bar__link' to='/timelines/home' data-preview-title-id='column.home' data-preview-icon='home' ><Icon id='home' fixedWidth /><FormattedMessage id='tabs_bar.home' defaultMessage='Home' /></NavLink>,
|
||||
<NavLink className='tabs-bar__link' to='/home' data-preview-title-id='column.home' data-preview-icon='home' ><Icon id='home' fixedWidth /><FormattedMessage id='tabs_bar.home' defaultMessage='Home' /></NavLink>,
|
||||
<NavLink className='tabs-bar__link' to='/notifications' data-preview-title-id='column.notifications' data-preview-icon='bell' ><NotificationsCounterIcon /><FormattedMessage id='tabs_bar.notifications' defaultMessage='Notifications' /></NavLink>,
|
||||
<NavLink className='tabs-bar__link' to='/timelines/public/local' data-preview-title-id='column.community' data-preview-icon='users' ><Icon id='users' fixedWidth /><FormattedMessage id='tabs_bar.local_timeline' defaultMessage='Local' /></NavLink>,
|
||||
<NavLink className='tabs-bar__link' exact to='/timelines/public' data-preview-title-id='column.public' data-preview-icon='globe' ><Icon id='globe' fixedWidth /><FormattedMessage id='tabs_bar.federated_timeline' defaultMessage='Federated' /></NavLink>,
|
||||
<NavLink className='tabs-bar__link optional' to='/search' data-preview-title-id='tabs_bar.search' data-preview-icon='bell' ><Icon id='search' fixedWidth /><FormattedMessage id='tabs_bar.search' defaultMessage='Search' /></NavLink>,
|
||||
<NavLink className='tabs-bar__link' to='/public/local' data-preview-title-id='column.community' data-preview-icon='users' ><Icon id='users' fixedWidth /><FormattedMessage id='tabs_bar.local_timeline' defaultMessage='Local' /></NavLink>,
|
||||
<NavLink className='tabs-bar__link' exact to='/public' data-preview-title-id='column.public' data-preview-icon='globe' ><Icon id='globe' fixedWidth /><FormattedMessage id='tabs_bar.federated_timeline' defaultMessage='Federated' /></NavLink>,
|
||||
<NavLink className='tabs-bar__link optional' to='/explore' data-preview-title-id='tabs_bar.search' data-preview-icon='search' ><Icon id='search' fixedWidth /><FormattedMessage id='tabs_bar.search' defaultMessage='Search' /></NavLink>,
|
||||
<NavLink className='tabs-bar__link' style={{ flexGrow: '0', flexBasis: '30px' }} to='/getting-started' data-preview-title-id='getting_started.heading' data-preview-icon='bars' ><Icon id='bars' fixedWidth /></NavLink>,
|
||||
];
|
||||
|
||||
|
|
|
|||
|
|
@ -6,8 +6,6 @@ import ImmutablePureComponent from 'react-immutable-pure-component';
|
|||
import Footer from 'mastodon/features/picture_in_picture/components/footer';
|
||||
import { getAverageFromBlurhash } from 'mastodon/blurhash';
|
||||
|
||||
export const previewState = 'previewVideoModal';
|
||||
|
||||
export default class VideoModal extends ImmutablePureComponent {
|
||||
|
||||
static propTypes = {
|
||||
|
|
@ -22,18 +20,8 @@ export default class VideoModal extends ImmutablePureComponent {
|
|||
onChangeBackgroundColor: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
static contextTypes = {
|
||||
router: PropTypes.object,
|
||||
};
|
||||
|
||||
componentDidMount () {
|
||||
const { router } = this.context;
|
||||
const { media, onChangeBackgroundColor, onClose } = this.props;
|
||||
|
||||
if (router) {
|
||||
router.history.push(router.history.location.pathname, previewState);
|
||||
this.unlistenHistory = router.history.listen(() => onClose());
|
||||
}
|
||||
const { media, onChangeBackgroundColor } = this.props;
|
||||
|
||||
const backgroundColor = getAverageFromBlurhash(media.get('blurhash'));
|
||||
|
||||
|
|
@ -42,18 +30,6 @@ export default class VideoModal extends ImmutablePureComponent {
|
|||
}
|
||||
}
|
||||
|
||||
componentWillUnmount () {
|
||||
const { router } = this.context;
|
||||
|
||||
if (router) {
|
||||
this.unlistenHistory();
|
||||
|
||||
if (router.history.location.state === previewState) {
|
||||
router.history.goBack();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
render () {
|
||||
const { media, statusId, onClose } = this.props;
|
||||
const options = this.props.options || {};
|
||||
|
|
|
|||
|
|
@ -1,15 +1,26 @@
|
|||
import { connect } from 'react-redux';
|
||||
import { closeModal } from '../../../actions/modal';
|
||||
import { openModal, closeModal } from '../../../actions/modal';
|
||||
import ModalRoot from '../components/modal_root';
|
||||
|
||||
const mapStateToProps = state => ({
|
||||
type: state.get('modal').modalType,
|
||||
props: state.get('modal').modalProps,
|
||||
ignoreFocus: state.getIn(['modal', 'ignoreFocus']),
|
||||
type: state.getIn(['modal', 'stack', 0, 'modalType'], null),
|
||||
props: state.getIn(['modal', 'stack', 0, 'modalProps'], {}),
|
||||
});
|
||||
|
||||
const mapDispatchToProps = dispatch => ({
|
||||
onClose () {
|
||||
dispatch(closeModal());
|
||||
onClose (confirmationMessage, ignoreFocus = false) {
|
||||
if (confirmationMessage) {
|
||||
dispatch(
|
||||
openModal('CONFIRM', {
|
||||
message: confirmationMessage.message,
|
||||
confirm: confirmationMessage.confirm,
|
||||
onConfirm: () => dispatch(closeModal(undefined, { ignoreFocus })),
|
||||
}),
|
||||
);
|
||||
} else {
|
||||
dispatch(closeModal(undefined, { ignoreFocus }));
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ import { uploadCompose, resetCompose, changeComposeSpoilerness } from '../../act
|
|||
import { expandHomeTimeline } from '../../actions/timelines';
|
||||
import { expandNotifications } from '../../actions/notifications';
|
||||
import { fetchFilters } from '../../actions/filters';
|
||||
import { fetchRules } from '../../actions/rules';
|
||||
import { clearHeight } from '../../actions/height_cache';
|
||||
import { focusApp, unfocusApp, changeLayout } from 'mastodon/actions/app';
|
||||
import { synchronouslySubmitMarkers, submitMarkers, fetchMarkers } from 'mastodon/actions/markers';
|
||||
|
|
@ -49,12 +50,12 @@ import {
|
|||
Mutes,
|
||||
PinnedStatuses,
|
||||
Lists,
|
||||
Search,
|
||||
Directory,
|
||||
Explore,
|
||||
FollowRecommendations,
|
||||
} from './util/async-components';
|
||||
import { me } from '../../initial_state';
|
||||
import { previewState as previewMediaState } from './components/media_modal';
|
||||
import { previewState as previewVideoState } from './components/video_modal';
|
||||
import { closeOnboarding, INTRODUCTION_VERSION } from 'mastodon/actions/onboarding';
|
||||
|
||||
// Dummy import, to make sure that <Status /> ends up in the application bundle.
|
||||
// Without this it ends up in ~8 very commonly used bundles.
|
||||
|
|
@ -71,6 +72,8 @@ const mapStateToProps = state => ({
|
|||
hasMediaAttachments: state.getIn(['compose', 'media_attachments']).size > 0,
|
||||
canUploadMore: !state.getIn(['compose', 'media_attachments']).some(x => ['audio', 'video'].includes(x.get('type'))) && state.getIn(['compose', 'media_attachments']).size < 4,
|
||||
dropdownMenuIsOpen: state.getIn(['dropdown_menu', 'openId']) !== null,
|
||||
firstLaunch: state.getIn(['settings', 'introductionVersion'], 0) < INTRODUCTION_VERSION,
|
||||
username: state.getIn(['accounts', me, 'username']),
|
||||
});
|
||||
|
||||
const keyMap = {
|
||||
|
|
@ -135,10 +138,6 @@ class SwitchingColumnsArea extends React.PureComponent {
|
|||
}
|
||||
}
|
||||
|
||||
shouldUpdateScroll (_, { location }) {
|
||||
return location.state !== previewMediaState && location.state !== previewVideoState;
|
||||
}
|
||||
|
||||
setRef = c => {
|
||||
if (c) {
|
||||
this.node = c.getWrappedInstance();
|
||||
|
|
@ -147,7 +146,7 @@ class SwitchingColumnsArea extends React.PureComponent {
|
|||
|
||||
render () {
|
||||
const { children, mobile } = this.props;
|
||||
const redirect = mobile ? <Redirect from='/' to='/timelines/home' exact /> : <Redirect from='/' to='/getting-started' exact />;
|
||||
const redirect = mobile ? <Redirect from='/' to='/home' exact /> : <Redirect from='/' to='/getting-started' exact />;
|
||||
|
||||
return (
|
||||
<ColumnsAreaContainer ref={this.setRef} singleColumn={mobile}>
|
||||
|
|
@ -155,37 +154,45 @@ class SwitchingColumnsArea extends React.PureComponent {
|
|||
{redirect}
|
||||
<WrappedRoute path='/getting-started' component={GettingStarted} content={children} />
|
||||
<WrappedRoute path='/keyboard-shortcuts' component={KeyboardShortcuts} content={children} />
|
||||
<WrappedRoute path='/timelines/home' component={HomeTimeline} content={children} componentParams={{ shouldUpdateScroll: this.shouldUpdateScroll }} />
|
||||
<WrappedRoute path='/timelines/public' exact component={PublicTimeline} content={children} componentParams={{ shouldUpdateScroll: this.shouldUpdateScroll }} />
|
||||
<WrappedRoute path='/timelines/public/local' exact component={CommunityTimeline} content={children} componentParams={{ shouldUpdateScroll: this.shouldUpdateScroll }} />
|
||||
<WrappedRoute path='/timelines/direct' component={DirectTimeline} content={children} componentParams={{ shouldUpdateScroll: this.shouldUpdateScroll }} />
|
||||
<WrappedRoute path='/timelines/tag/:id' component={HashtagTimeline} content={children} componentParams={{ shouldUpdateScroll: this.shouldUpdateScroll }} />
|
||||
<WrappedRoute path='/timelines/list/:id' component={ListTimeline} content={children} componentParams={{ shouldUpdateScroll: this.shouldUpdateScroll }} />
|
||||
|
||||
<WrappedRoute path='/notifications' component={Notifications} content={children} componentParams={{ shouldUpdateScroll: this.shouldUpdateScroll }} />
|
||||
<WrappedRoute path='/favourites' component={FavouritedStatuses} content={children} componentParams={{ shouldUpdateScroll: this.shouldUpdateScroll }} />
|
||||
<WrappedRoute path={['/home', '/timelines/home']} component={HomeTimeline} content={children} />
|
||||
<WrappedRoute path={['/public', '/timelines/public']} exact component={PublicTimeline} content={children} />
|
||||
<WrappedRoute path={['/public/local', '/timelines/public/local']} exact component={CommunityTimeline} content={children} />
|
||||
<WrappedRoute path={['/conversations', '/timelines/direct']} component={DirectTimeline} content={children} />
|
||||
<WrappedRoute path='/tags/:id' component={HashtagTimeline} content={children} />
|
||||
<WrappedRoute path='/lists/:id' component={ListTimeline} content={children} />
|
||||
<WrappedRoute path='/notifications' component={Notifications} content={children} />
|
||||
<WrappedRoute path='/favourites' component={FavouritedStatuses} content={children} />
|
||||
|
||||
<WrappedRoute path='/bookmarks' component={BookmarkedStatuses} content={children} />
|
||||
<WrappedRoute path='/pinned' component={PinnedStatuses} content={children} componentParams={{ shouldUpdateScroll: this.shouldUpdateScroll }} />
|
||||
<WrappedRoute path='/pinned' component={PinnedStatuses} content={children} />
|
||||
|
||||
<WrappedRoute path='/search' component={Search} content={children} />
|
||||
<WrappedRoute path='/directory' component={Directory} content={children} componentParams={{ shouldUpdateScroll: this.shouldUpdateScroll }} />
|
||||
<WrappedRoute path='/start' component={FollowRecommendations} content={children} />
|
||||
<WrappedRoute path='/directory' component={Directory} content={children} />
|
||||
<WrappedRoute path={['/explore', '/search']} component={Explore} content={children} />
|
||||
<WrappedRoute path={['/publish', '/statuses/new']} component={Compose} content={children} />
|
||||
|
||||
<WrappedRoute path='/statuses/new' component={Compose} content={children} />
|
||||
<WrappedRoute path='/statuses/:statusId' exact component={Status} content={children} componentParams={{ shouldUpdateScroll: this.shouldUpdateScroll }} />
|
||||
<WrappedRoute path='/statuses/:statusId/reblogs' component={Reblogs} content={children} componentParams={{ shouldUpdateScroll: this.shouldUpdateScroll }} />
|
||||
<WrappedRoute path='/statuses/:statusId/favourites' component={Favourites} content={children} componentParams={{ shouldUpdateScroll: this.shouldUpdateScroll }} />
|
||||
<WrappedRoute path={['/@:acct', '/accounts/:id']} exact component={AccountTimeline} content={children} />
|
||||
<WrappedRoute path={['/@:acct/with_replies', '/accounts/:id/with_replies']} component={AccountTimeline} content={children} componentParams={{ withReplies: true }} />
|
||||
<WrappedRoute path={['/@:acct/followers', '/accounts/:id/followers']} component={Followers} content={children} />
|
||||
<WrappedRoute path={['/@:acct/following', '/accounts/:id/following']} component={Following} content={children} />
|
||||
<WrappedRoute path={['/@:acct/media', '/accounts/:id/media']} component={AccountGallery} content={children} />
|
||||
<WrappedRoute path='/@:acct/:statusId' exact component={Status} content={children} />
|
||||
<WrappedRoute path='/@:acct/:statusId/reblogs' component={Reblogs} content={children} />
|
||||
<WrappedRoute path='/@:acct/:statusId/favourites' component={Favourites} content={children} />
|
||||
|
||||
<WrappedRoute path='/accounts/:accountId' exact component={AccountTimeline} content={children} componentParams={{ shouldUpdateScroll: this.shouldUpdateScroll }} />
|
||||
<WrappedRoute path='/accounts/:accountId/with_replies' component={AccountTimeline} content={children} componentParams={{ shouldUpdateScroll: this.shouldUpdateScroll, withReplies: true }} />
|
||||
<WrappedRoute path='/accounts/:accountId/followers' component={Followers} content={children} componentParams={{ shouldUpdateScroll: this.shouldUpdateScroll }} />
|
||||
<WrappedRoute path='/accounts/:accountId/following' component={Following} content={children} componentParams={{ shouldUpdateScroll: this.shouldUpdateScroll }} />
|
||||
<WrappedRoute path='/accounts/:accountId/media' component={AccountGallery} content={children} componentParams={{ shouldUpdateScroll: this.shouldUpdateScroll }} />
|
||||
{/* Legacy routes, cannot be easily factored with other routes because they share a param name */}
|
||||
<WrappedRoute path='/timelines/tag/:id' component={HashtagTimeline} content={children} />
|
||||
<WrappedRoute path='/timelines/list/:id' component={ListTimeline} content={children} />
|
||||
<WrappedRoute path='/statuses/:statusId' exact component={Status} content={children} />
|
||||
<WrappedRoute path='/statuses/:statusId/reblogs' component={Reblogs} content={children} />
|
||||
<WrappedRoute path='/statuses/:statusId/favourites' component={Favourites} content={children} />
|
||||
|
||||
<WrappedRoute path='/follow_requests' component={FollowRequests} content={children} componentParams={{ shouldUpdateScroll: this.shouldUpdateScroll }} />
|
||||
<WrappedRoute path='/blocks' component={Blocks} content={children} componentParams={{ shouldUpdateScroll: this.shouldUpdateScroll }} />
|
||||
<WrappedRoute path='/domain_blocks' component={DomainBlocks} content={children} componentParams={{ shouldUpdateScroll: this.shouldUpdateScroll }} />
|
||||
<WrappedRoute path='/mutes' component={Mutes} content={children} componentParams={{ shouldUpdateScroll: this.shouldUpdateScroll }} />
|
||||
<WrappedRoute path='/lists' component={Lists} content={children} componentParams={{ shouldUpdateScroll: this.shouldUpdateScroll }} />
|
||||
<WrappedRoute path='/follow_requests' component={FollowRequests} content={children} />
|
||||
<WrappedRoute path='/blocks' component={Blocks} content={children} />
|
||||
<WrappedRoute path='/domain_blocks' component={DomainBlocks} content={children} />
|
||||
<WrappedRoute path='/mutes' component={Mutes} content={children} />
|
||||
<WrappedRoute path='/lists' component={Lists} content={children} />
|
||||
|
||||
<WrappedRoute component={GenericNotFound} content={children} />
|
||||
</WrappedSwitch>
|
||||
|
|
@ -215,6 +222,8 @@ class UI extends React.PureComponent {
|
|||
intl: PropTypes.object.isRequired,
|
||||
dropdownMenuIsOpen: PropTypes.bool,
|
||||
layout: PropTypes.string.isRequired,
|
||||
firstLaunch: PropTypes.bool,
|
||||
username: PropTypes.string,
|
||||
};
|
||||
|
||||
state = {
|
||||
|
|
@ -350,11 +359,17 @@ class UI extends React.PureComponent {
|
|||
navigator.serviceWorker.addEventListener('message', this.handleServiceWorkerPostMessage);
|
||||
}
|
||||
|
||||
// On first launch, redirect to the follow recommendations page
|
||||
if (this.props.firstLaunch) {
|
||||
this.context.router.history.replace('/start');
|
||||
this.props.dispatch(closeOnboarding());
|
||||
}
|
||||
|
||||
this.props.dispatch(fetchMarkers());
|
||||
this.props.dispatch(expandHomeTimeline());
|
||||
this.props.dispatch(expandNotifications());
|
||||
|
||||
setTimeout(() => this.props.dispatch(fetchFilters()), 500);
|
||||
setTimeout(() => this.props.dispatch(fetchRules()), 3000);
|
||||
|
||||
this.hotkeys.__mousetrap__.stopCallback = (e, element) => {
|
||||
return ['TEXTAREA', 'SELECT', 'INPUT'].includes(element.tagName);
|
||||
|
|
@ -447,7 +462,7 @@ class UI extends React.PureComponent {
|
|||
}
|
||||
|
||||
handleHotkeyGoToHome = () => {
|
||||
this.context.router.history.push('/timelines/home');
|
||||
this.context.router.history.push('/home');
|
||||
}
|
||||
|
||||
handleHotkeyGoToNotifications = () => {
|
||||
|
|
@ -455,15 +470,15 @@ class UI extends React.PureComponent {
|
|||
}
|
||||
|
||||
handleHotkeyGoToLocal = () => {
|
||||
this.context.router.history.push('/timelines/public/local');
|
||||
this.context.router.history.push('/public/local');
|
||||
}
|
||||
|
||||
handleHotkeyGoToFederated = () => {
|
||||
this.context.router.history.push('/timelines/public');
|
||||
this.context.router.history.push('/public');
|
||||
}
|
||||
|
||||
handleHotkeyGoToDirect = () => {
|
||||
this.context.router.history.push('/timelines/direct');
|
||||
this.context.router.history.push('/conversations');
|
||||
}
|
||||
|
||||
handleHotkeyGoToStart = () => {
|
||||
|
|
@ -479,7 +494,7 @@ class UI extends React.PureComponent {
|
|||
}
|
||||
|
||||
handleHotkeyGoToProfile = () => {
|
||||
this.context.router.history.push(`/accounts/${me}`);
|
||||
this.context.router.history.push(`/@${this.props.username}`);
|
||||
}
|
||||
|
||||
handleHotkeyGoToBlocked = () => {
|
||||
|
|
|
|||
|
|
@ -138,10 +138,6 @@ export function ListAdder () {
|
|||
return import(/*webpackChunkName: "features/list_adder" */'../../list_adder');
|
||||
}
|
||||
|
||||
export function Search () {
|
||||
return import(/*webpackChunkName: "features/search" */'../../search');
|
||||
}
|
||||
|
||||
export function Tesseract () {
|
||||
return import(/*webpackChunkName: "tesseract" */'tesseract.js');
|
||||
}
|
||||
|
|
@ -153,3 +149,15 @@ export function Audio () {
|
|||
export function Directory () {
|
||||
return import(/* webpackChunkName: "features/directory" */'../../directory');
|
||||
}
|
||||
|
||||
export function FollowRecommendations () {
|
||||
return import(/* webpackChunkName: "features/follow_recommendations" */'../../follow_recommendations');
|
||||
}
|
||||
|
||||
export function CompareHistoryModal () {
|
||||
return import(/*webpackChunkName: "modals/compare_history_modal" */'../components/compare_history_modal');
|
||||
}
|
||||
|
||||
export function Explore () {
|
||||
return import(/* webpackChunkName: "features/explore" */'../../explore');
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue