Merge branch 'master' into glitch-soc/merge-upstream

Conflicts:
- app/controllers/settings/preferences_controller.rb
- app/lib/user_settings_decorator.rb
- app/models/user.rb

Conflicts due to the addition of a new preference upstream,
“advanced layout”.
This commit is contained in:
Thibaut Girka
2019-05-26 15:41:40 +02:00
40 changed files with 623 additions and 135 deletions

View File

@ -46,7 +46,7 @@ class ActionBar extends React.PureComponent {
return (
<div className='compose__action-bar'>
<div className='compose__action-bar-dropdown'>
<DropdownMenuContainer items={menu} icon='ellipsis-v' size={24} direction='right' />
<DropdownMenuContainer items={menu} icon='chevron-down' size={16} direction='right' />
</div>
</div>
);

View File

@ -20,7 +20,7 @@ export default class NavigationBar extends ImmutablePureComponent {
<div className='navigation-bar'>
<Permalink href={this.props.account.get('url')} to={`/accounts/${this.props.account.get('id')}`}>
<span style={{ display: 'none' }}>{this.props.account.get('acct')}</span>
<Avatar account={this.props.account} size={40} />
<Avatar account={this.props.account} size={48} />
</Permalink>
<div className='navigation-bar__profile'>

View File

@ -47,6 +47,10 @@ class SearchPopout extends React.PureComponent {
export default @injectIntl
class Search extends React.PureComponent {
static contextTypes = {
router: PropTypes.object.isRequired,
};
static propTypes = {
value: PropTypes.string.isRequired,
submitted: PropTypes.bool,
@ -54,6 +58,7 @@ class Search extends React.PureComponent {
onSubmit: PropTypes.func.isRequired,
onClear: PropTypes.func.isRequired,
onShow: PropTypes.func.isRequired,
openInRoute: PropTypes.bool,
intl: PropTypes.object.isRequired,
};
@ -76,7 +81,12 @@ class Search extends React.PureComponent {
handleKeyUp = (e) => {
if (e.key === 'Enter') {
e.preventDefault();
this.props.onSubmit();
if (this.props.openInRoute) {
this.context.router.history.push('/search');
}
} else if (e.key === 'Escape') {
document.querySelector('.ui').parentElement.focus();
}

View File

@ -56,7 +56,7 @@ class FollowRequests extends ImmutablePureComponent {
const emptyMessage = <FormattedMessage id='empty_column.follow_requests' defaultMessage="You don't have any follow requests yet. When you receive one, it will show up here." />;
return (
<Column icon='users' heading={intl.formatMessage(messages.heading)}>
<Column icon='user-plus' heading={intl.formatMessage(messages.heading)}>
<ColumnBackButtonSlim />
<ScrollableList
scrollKey='follow_requests'

View File

@ -9,12 +9,10 @@ import ImmutablePropTypes from 'react-immutable-proptypes';
import ImmutablePureComponent from 'react-immutable-pure-component';
import { me, invitesEnabled, version, profile_directory, repository, source_url } from '../../initial_state';
import { fetchFollowRequests } from 'mastodon/actions/accounts';
import { changeSetting } from 'mastodon/actions/settings';
import { List as ImmutableList } from 'immutable';
import { Link } from 'react-router-dom';
import NavigationBar from '../compose/components/navigation_bar';
import Icon from 'mastodon/components/icon';
import Toggle from 'react-toggle';
const messages = defineMessages({
home_timeline: { id: 'tabs_bar.home', defaultMessage: 'Home' },
@ -41,12 +39,10 @@ const messages = defineMessages({
const mapStateToProps = state => ({
myAccount: state.getIn(['accounts', me]),
unreadFollowRequests: state.getIn(['user_lists', 'follow_requests', 'items'], ImmutableList()).size,
forceSingleColumn: state.getIn(['settings', 'forceSingleColumn'], false),
});
const mapDispatchToProps = dispatch => ({
fetchFollowRequests: () => dispatch(fetchFollowRequests()),
changeForceSingleColumn: checked => dispatch(changeSetting(['forceSingleColumn'], checked)),
});
const badgeDisplay = (number, limit) => {
@ -59,10 +55,16 @@ const badgeDisplay = (number, limit) => {
}
};
const NAVIGATION_PANEL_BREAKPOINT = 600 + (285 * 2) + (10 * 2);
export default @connect(mapStateToProps, mapDispatchToProps)
@injectIntl
class GettingStarted extends ImmutablePureComponent {
static contextTypes = {
router: PropTypes.object.isRequired,
};
static propTypes = {
intl: PropTypes.object.isRequired,
myAccount: ImmutablePropTypes.map.isRequired,
@ -71,24 +73,23 @@ class GettingStarted extends ImmutablePureComponent {
fetchFollowRequests: PropTypes.func.isRequired,
unreadFollowRequests: PropTypes.number,
unreadNotifications: PropTypes.number,
forceSingleColumn: PropTypes.bool,
changeForceSingleColumn: PropTypes.func.isRequired,
};
componentDidMount () {
const { myAccount, fetchFollowRequests } = this.props;
const { myAccount, fetchFollowRequests, multiColumn } = this.props;
if (!multiColumn && window.innerWidth >= NAVIGATION_PANEL_BREAKPOINT) {
this.context.router.history.replace('/timelines/home');
return;
}
if (myAccount.get('locked')) {
fetchFollowRequests();
}
}
handleForceSingleColumnChange = ({ target }) => {
this.props.changeForceSingleColumn(target.checked);
}
render () {
const { intl, myAccount, multiColumn, unreadFollowRequests, forceSingleColumn } = this.props;
const { intl, myAccount, multiColumn, unreadFollowRequests } = this.props;
const navItems = [];
let i = 1;
@ -133,7 +134,7 @@ class GettingStarted extends ImmutablePureComponent {
height += 48*3;
if (myAccount.get('locked')) {
navItems.push(<ColumnLink key={i++} icon='users' text={intl.formatMessage(messages.follow_requests)} badge={badgeDisplay(unreadFollowRequests, 40)} to='/follow_requests' />);
navItems.push(<ColumnLink key={i++} icon='user-plus' text={intl.formatMessage(messages.follow_requests)} badge={badgeDisplay(unreadFollowRequests, 40)} to='/follow_requests' />);
height += 48;
}
@ -187,11 +188,6 @@ class GettingStarted extends ImmutablePureComponent {
</p>
</div>
</div>
<label className='navigational-toggle'>
<FormattedMessage id='getting_started.use_simple_layout' defaultMessage='Use simple layout' />
<Toggle checked={forceSingleColumn} onChange={this.handleForceSingleColumnChange} />
</label>
</Column>
);
}

View File

@ -60,6 +60,10 @@ class KeyboardShortcuts extends ImmutablePureComponent {
<td><kbd>x</kbd></td>
<td><FormattedMessage id='keyboard_shortcuts.toggle_hidden' defaultMessage='to show/hide text behind CW' /></td>
</tr>
<tr>
<td><kbd>h</kbd></td>
<td><FormattedMessage id='keyboard_shortcuts.toggle_sensitivity' defaultMessage='to show/hide media' /></td>
</tr>
<tr>
<td><kbd>up</kbd>, <kbd>k</kbd></td>
<td><FormattedMessage id='keyboard_shortcuts.up' defaultMessage='to move up in the list' /></td>

View File

@ -0,0 +1,17 @@
import React from 'react';
import SearchContainer from 'mastodon/features/compose/containers/search_container';
import SearchResultsContainer from 'mastodon/features/compose/containers/search_results_container';
const Search = () => (
<div className='column search-page'>
<SearchContainer />
<div className='drawer__pager'>
<div className='drawer__inner darker'>
<SearchResultsContainer />
</div>
</div>
</div>
);
export default Search;

View File

@ -30,6 +30,8 @@ export default class DetailedStatus extends ImmutablePureComponent {
onHeightChange: PropTypes.func,
domain: PropTypes.string.isRequired,
compact: PropTypes.bool,
showMedia: PropTypes.bool,
onToggleMediaVisibility: PropTypes.func,
};
state = {
@ -122,6 +124,8 @@ export default class DetailedStatus extends ImmutablePureComponent {
inline
onOpenVideo={this.handleOpenVideo}
sensitive={status.get('sensitive')}
visible={this.props.showMedia}
onToggleVisibility={this.props.onToggleMediaVisibility}
/>
);
} else {
@ -132,6 +136,8 @@ export default class DetailedStatus extends ImmutablePureComponent {
media={status.get('media_attachments')}
height={300}
onOpenMedia={this.props.onOpenMedia}
visible={this.props.showMedia}
onToggleVisibility={this.props.onToggleMediaVisibility}
/>
);
}

View File

@ -43,7 +43,7 @@ import ImmutablePureComponent from 'react-immutable-pure-component';
import { HotKeys } from 'react-hotkeys';
import { boostModal, deleteModal } from '../../initial_state';
import { attachFullscreenListener, detachFullscreenListener, isFullscreen } from '../ui/util/fullscreen';
import { textForScreenReader } from '../../components/status';
import { textForScreenReader, defaultMediaVisibility } from '../../components/status';
import Icon from 'mastodon/components/icon';
const messages = defineMessages({
@ -131,6 +131,7 @@ class Status extends ImmutablePureComponent {
state = {
fullscreen: false,
showMedia: defaultMediaVisibility(this.props.status),
};
componentWillMount () {
@ -146,6 +147,14 @@ class Status extends ImmutablePureComponent {
this._scrolledIntoView = false;
this.props.dispatch(fetchStatus(nextProps.params.statusId));
}
if (!Immutable.is(nextProps.status, this.props.status) && nextProps.status) {
this.setState({ showMedia: defaultMediaVisibility(nextProps.status) });
}
}
handleToggleMediaVisibility = () => {
this.setState({ showMedia: !this.state.showMedia });
}
handleFavouriteClick = (status) => {
@ -312,6 +321,10 @@ class Status extends ImmutablePureComponent {
this.handleToggleHidden(this.props.status);
}
handleHotkeyToggleSensitive = () => {
this.handleToggleMediaVisibility();
}
handleMoveUp = id => {
const { status, ancestorsIds, descendantsIds } = this.props;
@ -432,6 +445,7 @@ class Status extends ImmutablePureComponent {
mention: this.handleHotkeyMention,
openProfile: this.handleHotkeyOpenProfile,
toggleHidden: this.handleHotkeyToggleHidden,
toggleSensitive: this.handleHotkeyToggleSensitive,
};
return (
@ -455,6 +469,8 @@ class Status extends ImmutablePureComponent {
onOpenMedia={this.handleOpenMedia}
onToggleHidden={this.handleToggleHidden}
domain={domain}
showMedia={this.state.showMedia}
onToggleMediaVisibility={this.handleToggleMediaVisibility}
/>
<ActionBar

View File

@ -14,6 +14,8 @@ import DrawerLoading from './drawer_loading';
import BundleColumnError from './bundle_column_error';
import { Compose, Notifications, HomeTimeline, CommunityTimeline, PublicTimeline, HashtagTimeline, DirectTimeline, FavouritedStatuses, ListTimeline } from '../../ui/util/async-components';
import Icon from 'mastodon/components/icon';
import ComposePanel from './compose_panel';
import NavigationPanel from './navigation_panel';
import detectPassiveEvents from 'detect-passive-events';
import { scrollRight } from '../../../scroll';
@ -173,14 +175,22 @@ class ColumnsArea extends ImmutablePureComponent {
return (
<div className='columns-area__panels'>
<div className='columns-area__panels__pane' />
<div className='columns-area__panels__pane columns-area__panels__pane--compositional'>
<div className='columns-area__panels__pane__inner'>
<ComposePanel />
</div>
</div>
<div className='columns-area__panels__main'>
<TabsBar key='tabs' />
{content}
</div>
<div className='columns-area__panels__pane' />
<div className='columns-area__panels__pane columns-area__panels__pane--start columns-area__panels__pane--navigational'>
<div className='columns-area__panels__pane__inner'>
<NavigationPanel />
</div>
</div>
{floatingActionButton}
</div>

View File

@ -0,0 +1,41 @@
import React from 'react';
import SearchContainer from 'mastodon/features/compose/containers/search_container';
import ComposeFormContainer from 'mastodon/features/compose/containers/compose_form_container';
import NavigationContainer from 'mastodon/features/compose/containers/navigation_container';
import { invitesEnabled, version, repository, source_url } from 'mastodon/initial_state';
import { Link } from 'react-router-dom';
import { FormattedMessage } from 'react-intl';
const ComposePanel = () => (
<div className='compose-panel'>
<SearchContainer openInRoute />
<NavigationContainer />
<ComposeFormContainer />
<div className='flex-spacer' />
<div className='getting-started__footer'>
<ul>
{invitesEnabled && <li><a href='/invites' target='_blank'><FormattedMessage id='getting_started.invite' defaultMessage='Invite people' /></a> · </li>}
<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>
<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>
<li><a href='https://docs.joinmastodon.org' target='_blank'><FormattedMessage id='getting_started.documentation' defaultMessage='Documentation' /></a> · </li>
<li><a href='/auth/sign_out' data-method='delete'><FormattedMessage id='navigation_bar.logout' defaultMessage='Logout' /></a></li>
</ul>
<p>
<FormattedMessage
id='getting_started.open_source_notice'
defaultMessage='Mastodon is open source software. You can contribute or report issues on GitHub at {github}.'
values={{ github: <span><a href={source_url} rel='noopener' target='_blank'>{repository}</a> (v{version})</span> }}
/>
</p>
</div>
</div>
);
export default ComposePanel;

View File

@ -0,0 +1,44 @@
import React from 'react';
import PropTypes from 'prop-types';
import { fetchFollowRequests } from 'mastodon/actions/accounts';
import { connect } from 'react-redux';
import { NavLink, withRouter } from 'react-router-dom';
import IconWithBadge from 'mastodon/components/icon_with_badge';
import { me } from 'mastodon/initial_state';
import { List as ImmutableList } from 'immutable';
import { FormattedMessage } from 'react-intl';
const mapStateToProps = state => ({
locked: state.getIn(['accounts', me, 'locked']),
count: state.getIn(['user_lists', 'follow_requests', 'items'], ImmutableList()).size,
});
export default @withRouter
@connect(mapStateToProps)
class FollowRequestsNavLink extends React.Component {
static propTypes = {
dispatch: PropTypes.func.isRequired,
locked: PropTypes.bool,
count: PropTypes.number.isRequired,
};
componentDidMount () {
const { dispatch, locked } = this.props;
if (locked) {
dispatch(fetchFollowRequests());
}
}
render () {
const { locked, count } = this.props;
if (!locked || count === 0) {
return null;
}
return <NavLink className='column-link column-link--transparent' to='/follow_requests'><IconWithBadge className='column-link__icon' id='user-plus' count={count} /><FormattedMessage id='navigation_bar.follow_requests' defaultMessage='Follow requests' /></NavLink>;
}
}

View File

@ -0,0 +1,55 @@
import React from 'react';
import PropTypes from 'prop-types';
import ImmutablePropTypes from 'react-immutable-proptypes';
import ImmutablePureComponent from 'react-immutable-pure-component';
import { fetchLists } from 'mastodon/actions/lists';
import { connect } from 'react-redux';
import { createSelector } from 'reselect';
import { NavLink, withRouter } from 'react-router-dom';
import Icon from 'mastodon/components/icon';
const getOrderedLists = createSelector([state => state.get('lists')], lists => {
if (!lists) {
return lists;
}
return lists.toList().filter(item => !!item).sort((a, b) => a.get('title').localeCompare(b.get('title'))).take(4);
});
const mapStateToProps = state => ({
lists: getOrderedLists(state),
});
export default @withRouter
@connect(mapStateToProps)
class ListPanel extends ImmutablePureComponent {
static propTypes = {
dispatch: PropTypes.func.isRequired,
lists: ImmutablePropTypes.list,
};
componentDidMount () {
const { dispatch } = this.props;
dispatch(fetchLists());
}
render () {
const { lists } = this.props;
if (!lists || lists.isEmpty()) {
return null;
}
return (
<div>
<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>
))}
</div>
);
}
}

View File

@ -0,0 +1,29 @@
import React from 'react';
import { NavLink, withRouter } from 'react-router-dom';
import { FormattedMessage } from 'react-intl';
import Icon from 'mastodon/components/icon';
import NotificationsCounterIcon from './notifications_counter_icon';
import FollowRequestsNavLink from './follow_requests_nav_link';
import ListPanel from './list_panel';
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='/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='/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='/lists'><Icon className='column-link__icon' id='list-ul' fixedWidth /><FormattedMessage id='navigation_bar.lists' defaultMessage='Lists' /></NavLink>
<ListPanel />
<hr />
<a className='column-link column-link--transparent' href='/settings/preferences' target='_blank'><Icon className='column-link__icon' id='cog' fixedWidth /><FormattedMessage id='navigation_bar.preferences' defaultMessage='Preferences' /></a>
<a className='column-link column-link--transparent' href='/relationships' target='_blank'><Icon className='column-link__icon' id='address-book-o' fixedWidth /><FormattedMessage id='navigation_bar.follows_and_followers' defaultMessage='Follows and followers' /></a>
</div>
);
export default withRouter(NavigationPanel);

View File

@ -1,23 +1,9 @@
import React from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import Icon from 'mastodon/components/icon';
import IconWithBadge from 'mastodon/components/icon_with_badge';
const mapStateToProps = state => ({
count: state.getIn(['notifications', 'unread']),
id: 'bell',
});
const formatNumber = num => num > 99 ? '99+' : num;
const NotificationsCounterIcon = ({ count }) => (
<i className='icon-with-badge'>
<Icon id='bell' fixedWidth />
{count > 0 && <i className='icon-with-badge__badge'>{formatNumber(count)}</i>}
</i>
);
NotificationsCounterIcon.propTypes = {
count: PropTypes.number.isRequired,
};
export default connect(mapStateToProps)(NotificationsCounterIcon);
export default connect(mapStateToProps)(IconWithBadge);

View File

@ -8,14 +8,12 @@ import Icon from 'mastodon/components/icon';
import NotificationsCounterIcon from './notifications_counter_icon';
export const links = [
<NavLink className='tabs-bar__link primary' 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 primary' 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 secondary' 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 secondary' 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 primary' 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 primary' style={{ flexGrow: '0', flexBasis: '30px' }} to='/getting-started' data-preview-title-id='getting_started.heading' data-preview-icon='bars' ><Icon id='bars' fixedWidth /></NavLink>,
<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='/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' style={{ flexGrow: '0', flexBasis: '30px' }} to='/getting-started' data-preview-title-id='getting_started.heading' data-preview-icon='bars' ><Icon id='bars' fixedWidth /></NavLink>,
];
export function getIndex (path) {

View File

@ -44,8 +44,9 @@ import {
Mutes,
PinnedStatuses,
Lists,
Search,
} from './util/async-components';
import { me } from '../../initial_state';
import { me, forceSingleColumn } from '../../initial_state';
import { previewState as previewMediaState } from './components/media_modal';
import { previewState as previewVideoState } from './components/video_modal';
@ -62,7 +63,6 @@ const mapStateToProps = state => ({
hasComposingText: state.getIn(['compose', 'text']).trim().length !== 0,
hasMediaAttachments: state.getIn(['compose', 'media_attachments']).size > 0,
dropdownMenuIsOpen: state.getIn(['dropdown_menu', 'openId']) !== null,
forceSingleColumn: state.getIn(['settings', 'forceSingleColumn'], false),
});
const keyMap = {
@ -93,6 +93,7 @@ const keyMap = {
goToMuted: 'g m',
goToRequests: 'g r',
toggleHidden: 'x',
toggleSensitive: 'h',
};
class SwitchingColumnsArea extends React.PureComponent {
@ -101,7 +102,6 @@ class SwitchingColumnsArea extends React.PureComponent {
children: PropTypes.node,
location: PropTypes.object,
onLayoutChange: PropTypes.func.isRequired,
forceSingleColumn: PropTypes.bool,
};
state = {
@ -140,7 +140,7 @@ class SwitchingColumnsArea extends React.PureComponent {
}
render () {
const { children, forceSingleColumn } = this.props;
const { children } = this.props;
const { mobile } = this.state;
const singleColumn = forceSingleColumn || mobile;
const redirect = singleColumn ? <Redirect from='/' to='/timelines/home' exact /> : <Redirect from='/' to='/getting-started' exact />;
@ -162,7 +162,7 @@ class SwitchingColumnsArea extends React.PureComponent {
<WrappedRoute path='/favourites' component={FavouritedStatuses} content={children} componentParams={{ shouldUpdateScroll: this.shouldUpdateScroll }} />
<WrappedRoute path='/pinned' component={PinnedStatuses} content={children} componentParams={{ shouldUpdateScroll: this.shouldUpdateScroll }} />
<WrappedRoute path='/search' component={Compose} content={children} componentParams={{ isSearchPage: true }} />
<WrappedRoute path='/search' component={Search} content={children} />
<WrappedRoute path='/statuses/new' component={Compose} content={children} />
<WrappedRoute path='/statuses/:statusId' exact component={Status} content={children} componentParams={{ shouldUpdateScroll: this.shouldUpdateScroll }} />
@ -207,7 +207,6 @@ class UI extends React.PureComponent {
location: PropTypes.object,
intl: PropTypes.object.isRequired,
dropdownMenuIsOpen: PropTypes.bool,
forceSingleColumn: PropTypes.bool,
};
state = {
@ -456,7 +455,7 @@ class UI extends React.PureComponent {
render () {
const { draggingOver } = this.state;
const { children, isComposing, location, dropdownMenuIsOpen, forceSingleColumn } = this.props;
const { children, isComposing, location, dropdownMenuIsOpen } = this.props;
const handlers = {
help: this.handleHotkeyToggleHelp,
@ -482,7 +481,7 @@ class UI extends React.PureComponent {
return (
<HotKeys keyMap={keyMap} handlers={handlers} ref={this.setHotkeysRef} attach={window} focused>
<div className={classNames('ui', { 'is-composing': isComposing })} ref={this.setRef} style={{ pointerEvents: dropdownMenuIsOpen ? 'none' : null }}>
<SwitchingColumnsArea location={location} onLayoutChange={this.handleLayoutChange} forceSingleColumn={forceSingleColumn}>
<SwitchingColumnsArea location={location} onLayoutChange={this.handleLayoutChange}>
{children}
</SwitchingColumnsArea>

View File

@ -129,3 +129,7 @@ export function ListEditor () {
export function ListAdder () {
return import(/*webpackChunkName: "features/list_adder" */'../../list_adder');
}
export function Search () {
return import(/*webpackChunkName: "features/search" */'../../search');
}

View File

@ -1,7 +1,7 @@
import React from 'react';
import PropTypes from 'prop-types';
import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
import { fromJS } from 'immutable';
import { fromJS, is } from 'immutable';
import { throttle } from 'lodash';
import classNames from 'classnames';
import { isFullscreen, requestFullscreen, exitFullscreen } from '../ui/util/fullscreen';
@ -102,6 +102,8 @@ class Video extends React.PureComponent {
detailed: PropTypes.bool,
inline: PropTypes.bool,
cacheWidth: PropTypes.func,
visible: PropTypes.bool,
onToggleVisibility: PropTypes.func,
intl: PropTypes.object.isRequired,
blurhash: PropTypes.string,
link: PropTypes.node,
@ -117,7 +119,7 @@ class Video extends React.PureComponent {
fullscreen: false,
hovered: false,
muted: false,
revealed: displayMedia !== 'hide_all' && !this.props.sensitive || displayMedia === 'show_all',
revealed: this.props.visible !== undefined ? this.props.visible : (displayMedia !== 'hide_all' && !this.props.sensitive || displayMedia === 'show_all'),
};
// hard coded in components.scss
@ -280,7 +282,16 @@ class Video extends React.PureComponent {
document.removeEventListener('MSFullscreenChange', this.handleFullscreenChange, true);
}
componentDidUpdate (prevProps) {
componentWillReceiveProps (nextProps) {
if (!is(nextProps.visible, this.props.visible) && nextProps.visible !== undefined) {
this.setState({ revealed: nextProps.visible });
}
}
componentDidUpdate (prevProps, prevState) {
if (prevState.revealed && !this.state.revealed && this.video) {
this.video.pause();
}
if (prevProps.blurhash !== this.props.blurhash && this.props.blurhash) {
this._decode();
}
@ -316,11 +327,11 @@ class Video extends React.PureComponent {
}
toggleReveal = () => {
if (this.state.revealed) {
this.video.pause();
if (this.props.onToggleVisibility) {
this.props.onToggleVisibility();
} else {
this.setState({ revealed: !this.state.revealed });
}
this.setState({ revealed: !this.state.revealed });
}
handleLoadedData = () => {