Merge branch 'master' into glitch-soc/merge-upstream
Conflicts: - app/models/media_attachment.rb Upstream raised max image size from 8MB to 10MB while our limit is configurable. Raised the default to 10MB.
This commit is contained in:
@@ -119,6 +119,7 @@ export default class MediaItem extends ImmutablePureComponent {
|
||||
);
|
||||
} else if (['gifv', 'video'].indexOf(attachment.get('type')) !== -1) {
|
||||
const autoPlay = !isIOS() && autoPlayGif;
|
||||
const label = attachment.get('type') === 'video' ? <Icon id='play' /> : 'GIF';
|
||||
|
||||
thumbnail = (
|
||||
<div className={classNames('media-gallery__gifv', { autoplay: autoPlay })}>
|
||||
@@ -135,7 +136,7 @@ export default class MediaItem extends ImmutablePureComponent {
|
||||
muted
|
||||
/>
|
||||
|
||||
<span className='media-gallery__gifv__label'>GIF</span>
|
||||
<span className='media-gallery__gifv__label'>{label}</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
@@ -100,8 +100,10 @@ class AccountGallery extends ImmutablePureComponent {
|
||||
}
|
||||
|
||||
handleOpenMedia = attachment => {
|
||||
if (['video', 'audio'].includes(attachment.get('type'))) {
|
||||
if (attachment.get('type') === 'video') {
|
||||
this.props.dispatch(openModal('VIDEO', { media: attachment, status: attachment.get('status') }));
|
||||
} else if (attachment.get('type') === 'audio') {
|
||||
this.props.dispatch(openModal('AUDIO', { media: attachment, status: attachment.get('status') }));
|
||||
} else {
|
||||
const media = attachment.getIn(['status', 'media_attachments']);
|
||||
const index = media.findIndex(x => x.get('id') === attachment.get('id'));
|
||||
|
@@ -60,12 +60,17 @@ class Search extends React.PureComponent {
|
||||
onShow: PropTypes.func.isRequired,
|
||||
openInRoute: PropTypes.bool,
|
||||
intl: PropTypes.object.isRequired,
|
||||
singleColumn: PropTypes.bool,
|
||||
};
|
||||
|
||||
state = {
|
||||
expanded: false,
|
||||
};
|
||||
|
||||
setRef = c => {
|
||||
this.searchForm = c;
|
||||
}
|
||||
|
||||
handleChange = (e) => {
|
||||
this.props.onChange(e.target.value);
|
||||
}
|
||||
@@ -95,6 +100,13 @@ class Search extends React.PureComponent {
|
||||
handleFocus = () => {
|
||||
this.setState({ expanded: true });
|
||||
this.props.onShow();
|
||||
|
||||
if (this.searchForm && !this.props.singleColumn) {
|
||||
const { left, right } = this.searchForm.getBoundingClientRect();
|
||||
if (left < 0 || right > (window.innerWidth || document.documentElement.clientWidth)) {
|
||||
this.searchForm.scrollIntoView();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
handleBlur = () => {
|
||||
@@ -111,6 +123,7 @@ class Search extends React.PureComponent {
|
||||
<label>
|
||||
<span style={{ display: 'none' }}>{intl.formatMessage(messages.placeholder)}</span>
|
||||
<input
|
||||
ref={this.setRef}
|
||||
className='search__input'
|
||||
type='text'
|
||||
placeholder={intl.formatMessage(messages.placeholder)}
|
||||
|
@@ -11,6 +11,7 @@ import Permalink from 'mastodon/components/permalink';
|
||||
import IconButton from 'mastodon/components/icon_button';
|
||||
import RelativeTimestamp from 'mastodon/components/relative_timestamp';
|
||||
import { HotKeys } from 'react-hotkeys';
|
||||
import { autoPlayGif } from 'mastodon/initial_state';
|
||||
|
||||
const messages = defineMessages({
|
||||
more: { id: 'status.more', defaultMessage: 'More' },
|
||||
@@ -37,9 +38,47 @@ class Conversation extends ImmutablePureComponent {
|
||||
onMoveUp: PropTypes.func,
|
||||
onMoveDown: PropTypes.func,
|
||||
markRead: PropTypes.func.isRequired,
|
||||
delete: PropTypes.func.isRequired,
|
||||
intl: PropTypes.object.isRequired,
|
||||
};
|
||||
|
||||
_updateEmojis () {
|
||||
const node = this.namesNode;
|
||||
|
||||
if (!node || autoPlayGif) {
|
||||
return;
|
||||
}
|
||||
|
||||
const emojis = node.querySelectorAll('.custom-emoji');
|
||||
|
||||
for (var i = 0; i < emojis.length; i++) {
|
||||
let emoji = emojis[i];
|
||||
if (emoji.classList.contains('status-emoji')) {
|
||||
continue;
|
||||
}
|
||||
emoji.classList.add('status-emoji');
|
||||
|
||||
emoji.addEventListener('mouseenter', this.handleEmojiMouseEnter, false);
|
||||
emoji.addEventListener('mouseleave', this.handleEmojiMouseLeave, false);
|
||||
}
|
||||
}
|
||||
|
||||
componentDidMount () {
|
||||
this._updateEmojis();
|
||||
}
|
||||
|
||||
componentDidUpdate () {
|
||||
this._updateEmojis();
|
||||
}
|
||||
|
||||
handleEmojiMouseEnter = ({ target }) => {
|
||||
target.src = target.getAttribute('data-original');
|
||||
}
|
||||
|
||||
handleEmojiMouseLeave = ({ target }) => {
|
||||
target.src = target.getAttribute('data-static');
|
||||
}
|
||||
|
||||
handleClick = () => {
|
||||
if (!this.context.router) {
|
||||
return;
|
||||
@@ -82,6 +121,10 @@ class Conversation extends ImmutablePureComponent {
|
||||
this.props.onToggleHidden(this.props.lastStatus);
|
||||
}
|
||||
|
||||
setNamesRef = (c) => {
|
||||
this.namesNode = c;
|
||||
}
|
||||
|
||||
render () {
|
||||
const { accounts, lastStatus, unread, intl } = this.props;
|
||||
|
||||
@@ -126,7 +169,7 @@ class Conversation extends ImmutablePureComponent {
|
||||
<RelativeTimestamp timestamp={lastStatus.get('created_at')} />
|
||||
</div>
|
||||
|
||||
<div className='conversation__content__names'>
|
||||
<div className='conversation__content__names' ref={this.setNamesRef}>
|
||||
<FormattedMessage id='conversation.with' defaultMessage='With {names}' values={{ names: <span>{names}</span> }} />
|
||||
</div>
|
||||
</div>
|
||||
|
@@ -5,17 +5,23 @@ import PropTypes from 'prop-types';
|
||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||
import LoadingIndicator from '../../components/loading_indicator';
|
||||
import { fetchFavourites } from '../../actions/interactions';
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
|
||||
import AccountContainer from '../../containers/account_container';
|
||||
import Column from '../ui/components/column';
|
||||
import ColumnBackButton from '../../components/column_back_button';
|
||||
import ScrollableList from '../../components/scrollable_list';
|
||||
import Icon from 'mastodon/components/icon';
|
||||
import ColumnHeader from '../../components/column_header';
|
||||
|
||||
const messages = defineMessages({
|
||||
refresh: { id: 'refresh', defaultMessage: 'Refresh' },
|
||||
});
|
||||
|
||||
const mapStateToProps = (state, props) => ({
|
||||
accountIds: state.getIn(['user_lists', 'favourited_by', props.params.statusId]),
|
||||
});
|
||||
|
||||
export default @connect(mapStateToProps)
|
||||
@injectIntl
|
||||
class Favourites extends ImmutablePureComponent {
|
||||
|
||||
static propTypes = {
|
||||
@@ -24,6 +30,7 @@ class Favourites extends ImmutablePureComponent {
|
||||
shouldUpdateScroll: PropTypes.func,
|
||||
accountIds: ImmutablePropTypes.list,
|
||||
multiColumn: PropTypes.bool,
|
||||
intl: PropTypes.object.isRequired,
|
||||
};
|
||||
|
||||
componentWillMount () {
|
||||
@@ -38,8 +45,12 @@ class Favourites extends ImmutablePureComponent {
|
||||
}
|
||||
}
|
||||
|
||||
handleRefresh = () => {
|
||||
this.props.dispatch(fetchFavourites(this.props.params.statusId));
|
||||
}
|
||||
|
||||
render () {
|
||||
const { shouldUpdateScroll, accountIds, multiColumn } = this.props;
|
||||
const { intl, shouldUpdateScroll, accountIds, multiColumn } = this.props;
|
||||
|
||||
if (!accountIds) {
|
||||
return (
|
||||
@@ -52,8 +63,14 @@ class Favourites extends ImmutablePureComponent {
|
||||
const emptyMessage = <FormattedMessage id='empty_column.favourites' defaultMessage='No one has favourited this toot yet. When someone does, they will show up here.' />;
|
||||
|
||||
return (
|
||||
<Column>
|
||||
<ColumnBackButton multiColumn={multiColumn} />
|
||||
<Column bindToDocument={!multiColumn}>
|
||||
<ColumnHeader
|
||||
showBackButton
|
||||
multiColumn={multiColumn}
|
||||
extraButton={(
|
||||
<button className='column-header__button' title={intl.formatMessage(messages.refresh)} aria-label={intl.formatMessage(messages.refresh)} onClick={this.handleRefresh}><Icon id='refresh' /></button>
|
||||
)}
|
||||
/>
|
||||
|
||||
<ScrollableList
|
||||
scrollKey='favourites'
|
||||
|
@@ -5,17 +5,23 @@ import PropTypes from 'prop-types';
|
||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||
import LoadingIndicator from '../../components/loading_indicator';
|
||||
import { fetchReblogs } from '../../actions/interactions';
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
|
||||
import AccountContainer from '../../containers/account_container';
|
||||
import Column from '../ui/components/column';
|
||||
import ColumnBackButton from '../../components/column_back_button';
|
||||
import ScrollableList from '../../components/scrollable_list';
|
||||
import Icon from 'mastodon/components/icon';
|
||||
import ColumnHeader from '../../components/column_header';
|
||||
|
||||
const messages = defineMessages({
|
||||
refresh: { id: 'refresh', defaultMessage: 'Refresh' },
|
||||
});
|
||||
|
||||
const mapStateToProps = (state, props) => ({
|
||||
accountIds: state.getIn(['user_lists', 'reblogged_by', props.params.statusId]),
|
||||
});
|
||||
|
||||
export default @connect(mapStateToProps)
|
||||
@injectIntl
|
||||
class Reblogs extends ImmutablePureComponent {
|
||||
|
||||
static propTypes = {
|
||||
@@ -24,6 +30,7 @@ class Reblogs extends ImmutablePureComponent {
|
||||
shouldUpdateScroll: PropTypes.func,
|
||||
accountIds: ImmutablePropTypes.list,
|
||||
multiColumn: PropTypes.bool,
|
||||
intl: PropTypes.object.isRequired,
|
||||
};
|
||||
|
||||
componentWillMount () {
|
||||
@@ -38,8 +45,12 @@ class Reblogs extends ImmutablePureComponent {
|
||||
}
|
||||
}
|
||||
|
||||
handleRefresh = () => {
|
||||
this.props.dispatch(fetchReblogs(this.props.params.statusId));
|
||||
}
|
||||
|
||||
render () {
|
||||
const { shouldUpdateScroll, accountIds, multiColumn } = this.props;
|
||||
const { intl, shouldUpdateScroll, accountIds, multiColumn } = this.props;
|
||||
|
||||
if (!accountIds) {
|
||||
return (
|
||||
@@ -52,8 +63,14 @@ class Reblogs extends ImmutablePureComponent {
|
||||
const emptyMessage = <FormattedMessage id='status.reblogs.empty' defaultMessage='No one has boosted this toot yet. When someone does, they will show up here.' />;
|
||||
|
||||
return (
|
||||
<Column>
|
||||
<ColumnBackButton multiColumn={multiColumn} />
|
||||
<Column bindToDocument={!multiColumn}>
|
||||
<ColumnHeader
|
||||
showBackButton
|
||||
multiColumn={multiColumn}
|
||||
extraButton={(
|
||||
<button className='column-header__button' title={intl.formatMessage(messages.refresh)} aria-label={intl.formatMessage(messages.refresh)} onClick={this.handleRefresh}><Icon id='refresh' /></button>
|
||||
)}
|
||||
/>
|
||||
|
||||
<ScrollableList
|
||||
scrollKey='reblogs'
|
||||
|
@@ -0,0 +1,76 @@
|
||||
import React from 'react';
|
||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||
import PropTypes from 'prop-types';
|
||||
import Audio from 'mastodon/features/audio';
|
||||
import ImmutablePureComponent from 'react-immutable-pure-component';
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
import { previewState } from './video_modal';
|
||||
import classNames from 'classnames';
|
||||
import Icon from 'mastodon/components/icon';
|
||||
|
||||
export default class AudioModal extends ImmutablePureComponent {
|
||||
|
||||
static propTypes = {
|
||||
media: ImmutablePropTypes.map.isRequired,
|
||||
status: ImmutablePropTypes.map,
|
||||
onClose: 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
handleStatusClick = e => {
|
||||
if (e.button === 0 && !(e.ctrlKey || e.metaKey)) {
|
||||
e.preventDefault();
|
||||
this.context.router.history.push(`/statuses/${this.props.status.get('id')}`);
|
||||
}
|
||||
}
|
||||
|
||||
render () {
|
||||
const { media, status } = this.props;
|
||||
|
||||
return (
|
||||
<div className='modal-root__modal audio-modal'>
|
||||
<div className='audio-modal__container'>
|
||||
<Audio
|
||||
src={media.get('url')}
|
||||
alt={media.get('description')}
|
||||
duration={media.getIn(['meta', 'original', 'duration'], 0)}
|
||||
height={135}
|
||||
preload
|
||||
/>
|
||||
</div>
|
||||
|
||||
{status && (
|
||||
<div className={classNames('media-modal__meta')}>
|
||||
<a href={status.get('url')} onClick={this.handleStatusClick}><Icon id='comments' /> <FormattedMessage id='lightbox.view_context' defaultMessage='View context' /></a>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
}
|
@@ -1,8 +1,13 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import ImmutablePureComponent from 'react-immutable-pure-component';
|
||||
import { FormattedMessage, injectIntl } from 'react-intl';
|
||||
import api from '../../../api';
|
||||
import { defineMessages, FormattedMessage, injectIntl } from 'react-intl';
|
||||
import api from 'mastodon/api';
|
||||
import IconButton from 'mastodon/components/icon_button';
|
||||
|
||||
const messages = defineMessages({
|
||||
close: { id: 'lightbox.close', defaultMessage: 'Close' },
|
||||
});
|
||||
|
||||
export default @injectIntl
|
||||
class EmbedModal extends ImmutablePureComponent {
|
||||
@@ -50,13 +55,17 @@ class EmbedModal extends ImmutablePureComponent {
|
||||
}
|
||||
|
||||
render () {
|
||||
const { intl, onClose } = this.props;
|
||||
const { oembed } = this.state;
|
||||
|
||||
return (
|
||||
<div className='modal-root__modal embed-modal'>
|
||||
<h4><FormattedMessage id='status.embed' defaultMessage='Embed' /></h4>
|
||||
<div className='modal-root__modal report-modal embed-modal'>
|
||||
<div className='report-modal__target'>
|
||||
<IconButton className='media-modal__close' title={intl.formatMessage(messages.close)} icon='times' onClick={onClose} size={16} />
|
||||
<FormattedMessage id='status.embed' defaultMessage='Embed' />
|
||||
</div>
|
||||
|
||||
<div className='embed-modal__container'>
|
||||
<div className='report-modal__container embed-modal__container' style={{ display: 'block' }}>
|
||||
<p className='hint'>
|
||||
<FormattedMessage id='embed.instructions' defaultMessage='Embed this status on your website by copying the code below.' />
|
||||
</p>
|
||||
|
@@ -228,7 +228,7 @@ class MediaModal extends ImmutablePureComponent {
|
||||
|
||||
{status && (
|
||||
<div className={classNames('media-modal__meta', { 'media-modal__meta--shifted': media.size > 1 })}>
|
||||
<a href={status.get('url')} onClick={this.handleStatusClick}><FormattedMessage id='lightbox.view_context' defaultMessage='View context' /></a>
|
||||
<a href={status.get('url')} onClick={this.handleStatusClick}><Icon id='comments' /> <FormattedMessage id='lightbox.view_context' defaultMessage='View context' /></a>
|
||||
</div>
|
||||
)}
|
||||
|
||||
|
@@ -9,6 +9,7 @@ import ActionsModal from './actions_modal';
|
||||
import MediaModal from './media_modal';
|
||||
import VideoModal from './video_modal';
|
||||
import BoostModal from './boost_modal';
|
||||
import AudioModal from './audio_modal';
|
||||
import ConfirmationModal from './confirmation_modal';
|
||||
import FocalPointModal from './focal_point_modal';
|
||||
import {
|
||||
@@ -23,6 +24,7 @@ import {
|
||||
const MODAL_COMPONENTS = {
|
||||
'MEDIA': () => Promise.resolve({ default: MediaModal }),
|
||||
'VIDEO': () => Promise.resolve({ default: VideoModal }),
|
||||
'AUDIO': () => Promise.resolve({ default: AudioModal }),
|
||||
'BOOST': () => Promise.resolve({ default: BoostModal }),
|
||||
'CONFIRM': () => Promise.resolve({ default: ConfirmationModal }),
|
||||
'MUTE': MuteModal,
|
||||
|
@@ -82,7 +82,7 @@ class MuteModal extends React.PureComponent {
|
||||
<p className='mute-modal__explanation'>
|
||||
<FormattedMessage
|
||||
id='confirmations.mute.explanation'
|
||||
defaultMessage='This will hide posts from them and posts mentioning them, but it will still allow them to see your posts follow you.'
|
||||
defaultMessage='This will hide posts from them and posts mentioning them, but it will still allow them to see your posts and follow you.'
|
||||
/>
|
||||
</p>
|
||||
<div className='setting-toggle'>
|
||||
|
@@ -4,6 +4,8 @@ import PropTypes from 'prop-types';
|
||||
import Video from 'mastodon/features/video';
|
||||
import ImmutablePureComponent from 'react-immutable-pure-component';
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
import classNames from 'classnames';
|
||||
import Icon from 'mastodon/components/icon';
|
||||
|
||||
export const previewState = 'previewVideoModal';
|
||||
|
||||
@@ -52,22 +54,25 @@ export default class VideoModal extends ImmutablePureComponent {
|
||||
render () {
|
||||
const { media, status, time, onClose } = this.props;
|
||||
|
||||
const link = status && <a href={status.get('url')} onClick={this.handleStatusClick}><FormattedMessage id='lightbox.view_context' defaultMessage='View context' /></a>;
|
||||
|
||||
return (
|
||||
<div className='modal-root__modal video-modal'>
|
||||
<div>
|
||||
<div className='video-modal__container'>
|
||||
<Video
|
||||
preview={media.get('preview_url')}
|
||||
blurhash={media.get('blurhash')}
|
||||
src={media.get('url')}
|
||||
startTime={time}
|
||||
onCloseVideo={onClose}
|
||||
link={link}
|
||||
detailed
|
||||
alt={media.get('description')}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{status && (
|
||||
<div className={classNames('media-modal__meta')}>
|
||||
<a href={status.get('url')} onClick={this.handleStatusClick}><Icon id='comments' /> <FormattedMessage id='lightbox.view_context' defaultMessage='View context' /></a>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
@@ -327,7 +327,7 @@ class UI extends React.PureComponent {
|
||||
}
|
||||
|
||||
dataTransferIsText = (dataTransfer) => {
|
||||
return (dataTransfer && Array.from(dataTransfer.types).includes('text/plain') && dataTransfer.items.length === 1);
|
||||
return (dataTransfer && Array.from(dataTransfer.types).filter((type) => type === 'text/plain').length === 1);
|
||||
}
|
||||
|
||||
closeUploadModal = () => {
|
||||
|
Reference in New Issue
Block a user