Merge branch 'develop' of https://code.gab.com/gab/social/gab-social into develop
This commit is contained in:
commit
daee22dcbb
@ -1,20 +1,39 @@
|
|||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
import { connect } from 'react-redux';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
|
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import { FormattedMessage, injectIntl, defineMessages } from 'react-intl';
|
import { FormattedMessage, injectIntl, defineMessages } from 'react-intl';
|
||||||
import { Link } from 'react-router-dom';
|
import { Link } from 'react-router-dom';
|
||||||
import Icon from 'gabsocial/components/icon';
|
import Icon from 'gabsocial/components/icon';
|
||||||
|
import { me } from 'gabsocial/initial_state';
|
||||||
|
import { fetchLists } from 'gabsocial/actions/lists';
|
||||||
|
import { createSelector } from 'reselect';
|
||||||
|
|
||||||
const messages = defineMessages({
|
const messages = defineMessages({
|
||||||
show: { id: 'column_header.show_settings', defaultMessage: 'Show settings' },
|
show: { id: 'column_header.show_settings', defaultMessage: 'Show settings' },
|
||||||
hide: { id: 'column_header.hide_settings', defaultMessage: 'Hide settings' },
|
hide: { id: 'column_header.hide_settings', defaultMessage: 'Hide settings' },
|
||||||
homeTitle: { id: 'home_column_header.home', defaultMessage: 'Home' },
|
homeTitle: { id: 'home_column_header.home', defaultMessage: 'Home' },
|
||||||
allTitle: { id: 'home_column_header.all', defaultMessage: 'All' },
|
allTitle: { id: 'home_column_header.all', defaultMessage: 'All' },
|
||||||
|
listTitle: { id: 'home_column.lists', defaultMessage: 'Lists' },
|
||||||
});
|
});
|
||||||
|
|
||||||
export default @injectIntl
|
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')));
|
||||||
|
});
|
||||||
|
|
||||||
|
const mapStateToProps = state => {
|
||||||
|
return {
|
||||||
|
lists: getOrderedLists(state),
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
class ColumnHeader extends React.PureComponent {
|
class ColumnHeader extends React.PureComponent {
|
||||||
|
|
||||||
static contextTypes = {
|
static contextTypes = {
|
||||||
@ -23,16 +42,24 @@ class ColumnHeader extends React.PureComponent {
|
|||||||
|
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
intl: PropTypes.object.isRequired,
|
intl: PropTypes.object.isRequired,
|
||||||
|
dispatch: PropTypes.func.isRequired,
|
||||||
active: PropTypes.bool,
|
active: PropTypes.bool,
|
||||||
children: PropTypes.node,
|
children: PropTypes.node,
|
||||||
activeItem: PropTypes.string,
|
activeItem: PropTypes.string,
|
||||||
|
activeSubItem: PropTypes.string,
|
||||||
|
lists: ImmutablePropTypes.list,
|
||||||
};
|
};
|
||||||
|
|
||||||
state = {
|
state = {
|
||||||
collapsed: true,
|
collapsed: true,
|
||||||
animating: false,
|
animating: false,
|
||||||
|
expandedFor: null, //lists, groups, etc.
|
||||||
};
|
};
|
||||||
|
|
||||||
|
componentDidMount() {
|
||||||
|
this.props.dispatch(fetchLists());
|
||||||
|
}
|
||||||
|
|
||||||
handleToggleClick = (e) => {
|
handleToggleClick = (e) => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
this.setState({ collapsed: !this.state.collapsed, animating: true });
|
this.setState({ collapsed: !this.state.collapsed, animating: true });
|
||||||
@ -42,9 +69,15 @@ class ColumnHeader extends React.PureComponent {
|
|||||||
this.setState({ animating: false });
|
this.setState({ animating: false });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
expandLists = () => {
|
||||||
|
this.setState({
|
||||||
|
expandedFor: 'lists',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
const { active, children, intl: { formatMessage }, activeItem } = this.props;
|
const { active, children, intl: { formatMessage }, activeItem, activeSubItem, lists } = this.props;
|
||||||
const { collapsed, animating } = this.state;
|
const { collapsed, animating, expandedFor } = this.state;
|
||||||
|
|
||||||
const wrapperClassName = classNames('column-header__wrapper', {
|
const wrapperClassName = classNames('column-header__wrapper', {
|
||||||
'active': active,
|
'active': active,
|
||||||
@ -63,6 +96,10 @@ class ColumnHeader extends React.PureComponent {
|
|||||||
'active': !collapsed,
|
'active': !collapsed,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const expansionClassName = classNames('column-header column-header__expansion', {
|
||||||
|
'open': expandedFor,
|
||||||
|
});
|
||||||
|
|
||||||
let extraContent, collapseButton;
|
let extraContent, collapseButton;
|
||||||
|
|
||||||
if (children) {
|
if (children) {
|
||||||
@ -79,6 +116,23 @@ class ColumnHeader extends React.PureComponent {
|
|||||||
extraContent,
|
extraContent,
|
||||||
];
|
];
|
||||||
|
|
||||||
|
let expandedContent = null;
|
||||||
|
if ((expandedFor === 'lists' || activeItem === 'lists') && lists) {
|
||||||
|
expandedContent = lists.map(list =>
|
||||||
|
<Link
|
||||||
|
key={list.get('id')}
|
||||||
|
to={`/list/${list.get('id')}`}
|
||||||
|
className={
|
||||||
|
classNames('btn btn--sub grouped', {
|
||||||
|
'active': list.get('id') === activeSubItem
|
||||||
|
})
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{list.get('title')}
|
||||||
|
</Link>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={wrapperClassName}>
|
<div className={wrapperClassName}>
|
||||||
<h1 className={buttonClassName}>
|
<h1 className={buttonClassName}>
|
||||||
@ -92,11 +146,31 @@ class ColumnHeader extends React.PureComponent {
|
|||||||
{formatMessage(messages.allTitle)}
|
{formatMessage(messages.allTitle)}
|
||||||
</Link>
|
</Link>
|
||||||
|
|
||||||
|
{ lists.size > 0 &&
|
||||||
|
<a onClick={this.expandLists} className={classNames('btn grouped', {'active': 'lists' === activeItem})}>
|
||||||
|
<Icon id='list' fixedWidth className='column-header__icon' />
|
||||||
|
{formatMessage(messages.listTitle)}
|
||||||
|
</a>
|
||||||
|
}
|
||||||
|
{ lists.size == 0 &&
|
||||||
|
<Link to='/lists' className='btn grouped'>
|
||||||
|
<Icon id='list' fixedWidth className='column-header__icon' />
|
||||||
|
{formatMessage(messages.listTitle)}
|
||||||
|
</Link>
|
||||||
|
}
|
||||||
|
|
||||||
<div className='column-header__buttons'>
|
<div className='column-header__buttons'>
|
||||||
{collapseButton}
|
{collapseButton}
|
||||||
</div>
|
</div>
|
||||||
</h1>
|
</h1>
|
||||||
|
|
||||||
|
{
|
||||||
|
expandedContent &&
|
||||||
|
<h1 className={expansionClassName}>
|
||||||
|
{expandedContent}
|
||||||
|
</h1>
|
||||||
|
}
|
||||||
|
|
||||||
<div className={collapsibleClassName} tabIndex={collapsed ? -1 : null} onTransitionEnd={this.handleTransitionEnd}>
|
<div className={collapsibleClassName} tabIndex={collapsed ? -1 : null} onTransitionEnd={this.handleTransitionEnd}>
|
||||||
<div className='column-header__collapsible-inner'>
|
<div className='column-header__collapsible-inner'>
|
||||||
{(!collapsed || animating) && collapsedContent}
|
{(!collapsed || animating) && collapsedContent}
|
||||||
@ -105,5 +179,6 @@ class ColumnHeader extends React.PureComponent {
|
|||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export default injectIntl(connect(mapStateToProps)(ColumnHeader));
|
||||||
|
@ -82,7 +82,7 @@ export default class ScrollableList extends PureComponent {
|
|||||||
|
|
||||||
componentDidMount () {
|
componentDidMount () {
|
||||||
this.window = window;
|
this.window = window;
|
||||||
this.documentElement = document.documentElement;
|
this.documentElement = document.scrollingElement || document.documentElement;
|
||||||
|
|
||||||
this.attachScrollListener();
|
this.attachScrollListener();
|
||||||
this.attachIntersectionObserver();
|
this.attachIntersectionObserver();
|
||||||
@ -124,10 +124,11 @@ export default class ScrollableList extends PureComponent {
|
|||||||
|
|
||||||
handleScroll = throttle(() => {
|
handleScroll = throttle(() => {
|
||||||
if (this.window) {
|
if (this.window) {
|
||||||
const { scrollTop, scrollHeight, clientHeight } = this.documentElement;
|
const { scrollTop, scrollHeight } = this.documentElement;
|
||||||
const offset = scrollHeight - scrollTop - clientHeight;
|
const { innerHeight } = this.window;
|
||||||
|
const offset = scrollHeight - scrollTop - innerHeight;
|
||||||
|
|
||||||
if (600 > offset && this.props.onLoadMore && this.props.hasMore && !this.props.isLoading) {
|
if (400 > offset && this.props.onLoadMore && this.props.hasMore && !this.props.isLoading) {
|
||||||
this.props.onLoadMore();
|
this.props.onLoadMore();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -174,6 +175,7 @@ export default class ScrollableList extends PureComponent {
|
|||||||
|
|
||||||
componentWillUnmount () {
|
componentWillUnmount () {
|
||||||
this.clearMouseIdleTimer();
|
this.clearMouseIdleTimer();
|
||||||
|
this.detachScrollListener();
|
||||||
this.detachIntersectionObserver();
|
this.detachIntersectionObserver();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -43,6 +43,7 @@ const messages = defineMessages({
|
|||||||
endorse: { id: 'account.endorse', defaultMessage: 'Feature on profile' },
|
endorse: { id: 'account.endorse', defaultMessage: 'Feature on profile' },
|
||||||
unendorse: { id: 'account.unendorse', defaultMessage: 'Don\'t feature on profile' },
|
unendorse: { id: 'account.unendorse', defaultMessage: 'Don\'t feature on profile' },
|
||||||
admin_account: { id: 'status.admin_account', defaultMessage: 'Open moderation interface for @{name}' },
|
admin_account: { id: 'status.admin_account', defaultMessage: 'Open moderation interface for @{name}' },
|
||||||
|
add_or_remove_from_list: { id: 'account.add_or_remove_from_list', defaultMessage: 'Add or Remove from lists' },
|
||||||
});
|
});
|
||||||
|
|
||||||
const dateFormatOptions = {
|
const dateFormatOptions = {
|
||||||
@ -128,6 +129,7 @@ class Header extends ImmutablePureComponent {
|
|||||||
menu.push({ text: intl.formatMessage(messages.showReblogs, { name: account.get('username') }), action: this.props.onReblogToggle });
|
menu.push({ text: intl.formatMessage(messages.showReblogs, { name: account.get('username') }), action: this.props.onReblogToggle });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
menu.push({ text: intl.formatMessage(messages.add_or_remove_from_list), action: this.props.onAddToList });
|
||||||
menu.push({ text: intl.formatMessage(account.getIn(['relationship', 'endorsed']) ? messages.unendorse : messages.endorse), action: this.props.onEndorseToggle });
|
menu.push({ text: intl.formatMessage(account.getIn(['relationship', 'endorsed']) ? messages.unendorse : messages.endorse), action: this.props.onEndorseToggle });
|
||||||
menu.push(null);
|
menu.push(null);
|
||||||
}
|
}
|
||||||
|
@ -7,7 +7,6 @@ import { debounce } from 'lodash';
|
|||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import LoadingIndicator from '../../components/loading_indicator';
|
import LoadingIndicator from '../../components/loading_indicator';
|
||||||
import Column from '../ui/components/column';
|
import Column from '../ui/components/column';
|
||||||
import ColumnBackButtonSlim from '../../components/column_back_button_slim';
|
|
||||||
import AccountContainer from '../../containers/account_container';
|
import AccountContainer from '../../containers/account_container';
|
||||||
import { fetchBlocks, expandBlocks } from '../../actions/blocks';
|
import { fetchBlocks, expandBlocks } from '../../actions/blocks';
|
||||||
import ScrollableList from '../../components/scrollable_list';
|
import ScrollableList from '../../components/scrollable_list';
|
||||||
@ -55,8 +54,7 @@ class Blocks extends ImmutablePureComponent {
|
|||||||
const emptyMessage = <FormattedMessage id='empty_column.blocks' defaultMessage="You haven't blocked any users yet." />;
|
const emptyMessage = <FormattedMessage id='empty_column.blocks' defaultMessage="You haven't blocked any users yet." />;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Column icon='ban' heading={intl.formatMessage(messages.heading)}>
|
<Column icon='ban' heading={intl.formatMessage(messages.heading)} backBtnSlim>
|
||||||
<ColumnBackButtonSlim />
|
|
||||||
<ScrollableList
|
<ScrollableList
|
||||||
scrollKey='blocks'
|
scrollKey='blocks'
|
||||||
onLoadMore={this.handleLoadMore}
|
onLoadMore={this.handleLoadMore}
|
||||||
|
@ -7,7 +7,6 @@ import ImmutablePropTypes from 'react-immutable-proptypes';
|
|||||||
import { debounce } from 'lodash';
|
import { debounce } from 'lodash';
|
||||||
import LoadingIndicator from '../../components/loading_indicator';
|
import LoadingIndicator from '../../components/loading_indicator';
|
||||||
import Column from '../ui/components/column';
|
import Column from '../ui/components/column';
|
||||||
import ColumnBackButtonSlim from '../../components/column_back_button_slim';
|
|
||||||
import DomainContainer from '../../containers/domain_container';
|
import DomainContainer from '../../containers/domain_container';
|
||||||
import { fetchDomainBlocks, expandDomainBlocks } from '../../actions/domain_blocks';
|
import { fetchDomainBlocks, expandDomainBlocks } from '../../actions/domain_blocks';
|
||||||
import ScrollableList from '../../components/scrollable_list';
|
import ScrollableList from '../../components/scrollable_list';
|
||||||
@ -56,8 +55,7 @@ class Blocks extends ImmutablePureComponent {
|
|||||||
const emptyMessage = <FormattedMessage id='empty_column.domain_blocks' defaultMessage='There are no hidden domains yet.' />;
|
const emptyMessage = <FormattedMessage id='empty_column.domain_blocks' defaultMessage='There are no hidden domains yet.' />;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Column icon='minus-circle' heading={intl.formatMessage(messages.heading)}>
|
<Column icon='minus-circle' heading={intl.formatMessage(messages.heading)} backBtnSlim>
|
||||||
<ColumnBackButtonSlim />
|
|
||||||
<ScrollableList
|
<ScrollableList
|
||||||
scrollKey='domain_blocks'
|
scrollKey='domain_blocks'
|
||||||
onLoadMore={this.handleLoadMore}
|
onLoadMore={this.handleLoadMore}
|
||||||
|
@ -8,7 +8,6 @@ import { fetchFavourites } from '../../actions/interactions';
|
|||||||
import { FormattedMessage } from 'react-intl';
|
import { FormattedMessage } from 'react-intl';
|
||||||
import AccountContainer from '../../containers/account_container';
|
import AccountContainer from '../../containers/account_container';
|
||||||
import Column from '../ui/components/column';
|
import Column from '../ui/components/column';
|
||||||
import ColumnBackButton from '../../components/column_back_button';
|
|
||||||
import ScrollableList from '../../components/scrollable_list';
|
import ScrollableList from '../../components/scrollable_list';
|
||||||
|
|
||||||
const mapStateToProps = (state, props) => ({
|
const mapStateToProps = (state, props) => ({
|
||||||
@ -49,8 +48,6 @@ class Favourites extends ImmutablePureComponent {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Column>
|
<Column>
|
||||||
<ColumnBackButton />
|
|
||||||
|
|
||||||
<ScrollableList
|
<ScrollableList
|
||||||
scrollKey='favourites'
|
scrollKey='favourites'
|
||||||
emptyMessage={emptyMessage}
|
emptyMessage={emptyMessage}
|
||||||
|
@ -7,7 +7,6 @@ import ImmutablePropTypes from 'react-immutable-proptypes';
|
|||||||
import { debounce } from 'lodash';
|
import { debounce } from 'lodash';
|
||||||
import LoadingIndicator from '../../components/loading_indicator';
|
import LoadingIndicator from '../../components/loading_indicator';
|
||||||
import Column from '../ui/components/column';
|
import Column from '../ui/components/column';
|
||||||
import ColumnBackButtonSlim from '../../components/column_back_button_slim';
|
|
||||||
import AccountAuthorizeContainer from './containers/account_authorize_container';
|
import AccountAuthorizeContainer from './containers/account_authorize_container';
|
||||||
import { fetchFollowRequests, expandFollowRequests } from '../../actions/accounts';
|
import { fetchFollowRequests, expandFollowRequests } from '../../actions/accounts';
|
||||||
import ScrollableList from '../../components/scrollable_list';
|
import ScrollableList from '../../components/scrollable_list';
|
||||||
@ -55,8 +54,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." />;
|
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 (
|
return (
|
||||||
<Column icon='user-plus' heading={intl.formatMessage(messages.heading)}>
|
<Column icon='user-plus' heading={intl.formatMessage(messages.heading)} backBtnSlim>
|
||||||
<ColumnBackButtonSlim />
|
|
||||||
<ScrollableList
|
<ScrollableList
|
||||||
scrollKey='follow_requests'
|
scrollKey='follow_requests'
|
||||||
onLoadMore={this.handleLoadMore}
|
onLoadMore={this.handleLoadMore}
|
||||||
|
@ -104,9 +104,11 @@ class Following extends ImmutablePureComponent {
|
|||||||
|
|
||||||
if (unavailable) {
|
if (unavailable) {
|
||||||
return (
|
return (
|
||||||
|
<Column>
|
||||||
<div className='empty-column-indicator'>
|
<div className='empty-column-indicator'>
|
||||||
<FormattedMessage id='empty_column.account_unavailable' defaultMessage='Profile unavailable' />
|
<FormattedMessage id='empty_column.account_unavailable' defaultMessage='Profile unavailable' />
|
||||||
</div>
|
</div>
|
||||||
|
</Column>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -4,7 +4,6 @@ import PropTypes from 'prop-types';
|
|||||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||||
import LoadingIndicator from '../../../components/loading_indicator';
|
import LoadingIndicator from '../../../components/loading_indicator';
|
||||||
import Column from '../../ui/components/column';
|
import Column from '../../ui/components/column';
|
||||||
import ColumnBackButtonSlim from '../../../components/column_back_button_slim';
|
|
||||||
import { fetchGroups } from '../../../actions/groups';
|
import { fetchGroups } from '../../../actions/groups';
|
||||||
import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
|
import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
|
||||||
import ImmutablePureComponent from 'react-immutable-pure-component';
|
import ImmutablePureComponent from 'react-immutable-pure-component';
|
||||||
@ -60,9 +59,7 @@ class Groups extends ImmutablePureComponent {
|
|||||||
const emptyMessage = <FormattedMessage id='empty_column.groups' defaultMessage="No groups." />;
|
const emptyMessage = <FormattedMessage id='empty_column.groups' defaultMessage="No groups." />;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Column icon='list-ul' heading={intl.formatMessage(messages.heading)}>
|
<Column icon='list-ul' heading={intl.formatMessage(messages.heading)} backBtnSlim>
|
||||||
<ColumnBackButtonSlim />
|
|
||||||
|
|
||||||
<NewGroupForm />
|
<NewGroupForm />
|
||||||
|
|
||||||
<ColumnSubheading text={intl.formatMessage(messages.subheading)} />
|
<ColumnSubheading text={intl.formatMessage(messages.subheading)} />
|
||||||
|
@ -4,7 +4,6 @@ import PropTypes from 'prop-types';
|
|||||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||||
import StatusListContainer from '../../ui/containers/status_list_container';
|
import StatusListContainer from '../../ui/containers/status_list_container';
|
||||||
import Column from '../../../components/column';
|
import Column from '../../../components/column';
|
||||||
import ColumnBackButton from '../../../components/column_back_button';
|
|
||||||
import ColumnHeader from '../../../components/column_header';
|
import ColumnHeader from '../../../components/column_header';
|
||||||
import { FormattedMessage, injectIntl } from 'react-intl';
|
import { FormattedMessage, injectIntl } from 'react-intl';
|
||||||
import { connectGroupStream } from '../../../actions/streaming';
|
import { connectGroupStream } from '../../../actions/streaming';
|
||||||
@ -73,7 +72,6 @@ class GroupTimeline extends React.PureComponent {
|
|||||||
} else if (group === false) {
|
} else if (group === false) {
|
||||||
return (
|
return (
|
||||||
<Column>
|
<Column>
|
||||||
<ColumnBackButton />
|
|
||||||
<MissingIndicator />
|
<MissingIndicator />
|
||||||
</Column>
|
</Column>
|
||||||
);
|
);
|
||||||
|
@ -8,6 +8,7 @@ import { expandHashtagTimeline, clearTimeline } from '../../actions/timelines';
|
|||||||
import { FormattedMessage } from 'react-intl';
|
import { FormattedMessage } from 'react-intl';
|
||||||
import { connectHashtagStream } from '../../actions/streaming';
|
import { connectHashtagStream } from '../../actions/streaming';
|
||||||
import { isEqual } from 'lodash';
|
import { isEqual } from 'lodash';
|
||||||
|
import ColumnBackButton from '../../components/column_back_button';
|
||||||
|
|
||||||
const mapStateToProps = (state, props) => ({
|
const mapStateToProps = (state, props) => ({
|
||||||
hasUnread: state.getIn(['timelines', `hashtag:${props.params.id}`, 'unread']) > 0,
|
hasUnread: state.getIn(['timelines', `hashtag:${props.params.id}`, 'unread']) > 0,
|
||||||
@ -107,6 +108,7 @@ class HashtagTimeline extends React.PureComponent {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Column label={`#${id}`}>
|
<Column label={`#${id}`}>
|
||||||
|
<ColumnBackButton />
|
||||||
<ColumnHeader icon='hashtag' active={hasUnread} title={this.title()} />
|
<ColumnHeader icon='hashtag' active={hasUnread} title={this.title()} />
|
||||||
<StatusListContainer
|
<StatusListContainer
|
||||||
scrollKey='hashtag_timeline'
|
scrollKey='hashtag_timeline'
|
||||||
|
@ -70,10 +70,7 @@ class HomeTimeline extends React.PureComponent {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Column label={intl.formatMessage(messages.title)}>
|
<Column label={intl.formatMessage(messages.title)}>
|
||||||
<HomeColumnHeader
|
<HomeColumnHeader activeItem='home' active={hasUnread}>
|
||||||
activeItem='home'
|
|
||||||
active={hasUnread}
|
|
||||||
>
|
|
||||||
<ColumnSettingsContainer />
|
<ColumnSettingsContainer />
|
||||||
</HomeColumnHeader>
|
</HomeColumnHeader>
|
||||||
<StatusListContainer
|
<StatusListContainer
|
||||||
|
@ -3,12 +3,14 @@ import PropTypes from 'prop-types';
|
|||||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import ImmutablePureComponent from 'react-immutable-pure-component';
|
import ImmutablePureComponent from 'react-immutable-pure-component';
|
||||||
import { injectIntl } from 'react-intl';
|
import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
|
||||||
import { setupListAdder, resetListAdder } from '../../actions/lists';
|
import { setupListAdder, resetListAdder } from '../../actions/lists';
|
||||||
import { createSelector } from 'reselect';
|
import { createSelector } from 'reselect';
|
||||||
import List from './components/list';
|
import List from './components/list';
|
||||||
import Account from './components/account';
|
import Account from './components/account';
|
||||||
|
import IconButton from 'gabsocial/components/icon_button';
|
||||||
import NewListForm from '../lists/components/new_list_form';
|
import NewListForm from '../lists/components/new_list_form';
|
||||||
|
import ColumnSubheading from '../ui/components/column_subheading';
|
||||||
// hack
|
// hack
|
||||||
|
|
||||||
const getOrderedLists = createSelector([state => state.get('lists')], lists => {
|
const getOrderedLists = createSelector([state => state.get('lists')], lists => {
|
||||||
@ -19,8 +21,9 @@ const getOrderedLists = createSelector([state => state.get('lists')], lists => {
|
|||||||
return lists.toList().filter(item => !!item).sort((a, b) => a.get('title').localeCompare(b.get('title')));
|
return lists.toList().filter(item => !!item).sort((a, b) => a.get('title').localeCompare(b.get('title')));
|
||||||
});
|
});
|
||||||
|
|
||||||
const mapStateToProps = state => ({
|
const mapStateToProps = (state, {accountId}) => ({
|
||||||
listIds: getOrderedLists(state).map(list=>list.get('id')),
|
listIds: getOrderedLists(state).map(list=>list.get('id')),
|
||||||
|
account: state.getIn(['accounts', accountId]),
|
||||||
});
|
});
|
||||||
|
|
||||||
const mapDispatchToProps = dispatch => ({
|
const mapDispatchToProps = dispatch => ({
|
||||||
@ -28,6 +31,12 @@ const mapDispatchToProps = dispatch => ({
|
|||||||
onReset: () => dispatch(resetListAdder()),
|
onReset: () => dispatch(resetListAdder()),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const messages = defineMessages({
|
||||||
|
close: { id: 'lightbox.close', defaultMessage: 'Close' },
|
||||||
|
subheading: { id: 'lists.subheading', defaultMessage: 'Your lists' },
|
||||||
|
add: { id: 'lists.new.create', defaultMessage: 'Add List' },
|
||||||
|
});
|
||||||
|
|
||||||
export default @connect(mapStateToProps, mapDispatchToProps)
|
export default @connect(mapStateToProps, mapDispatchToProps)
|
||||||
@injectIntl
|
@injectIntl
|
||||||
class ListAdder extends ImmutablePureComponent {
|
class ListAdder extends ImmutablePureComponent {
|
||||||
@ -39,6 +48,7 @@ class ListAdder extends ImmutablePureComponent {
|
|||||||
onInitialize: PropTypes.func.isRequired,
|
onInitialize: PropTypes.func.isRequired,
|
||||||
onReset: PropTypes.func.isRequired,
|
onReset: PropTypes.func.isRequired,
|
||||||
listIds: ImmutablePropTypes.list.isRequired,
|
listIds: ImmutablePropTypes.list.isRequired,
|
||||||
|
account: ImmutablePropTypes.map,
|
||||||
};
|
};
|
||||||
|
|
||||||
componentDidMount () {
|
componentDidMount () {
|
||||||
@ -51,22 +61,43 @@ class ListAdder extends ImmutablePureComponent {
|
|||||||
onReset();
|
onReset();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
onClickClose = () => {
|
||||||
|
this.props.onClose('LIST_ADDER');
|
||||||
|
};
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
const { accountId, listIds } = this.props;
|
const { accountId, listIds, intl, onClose, account } = this.props;
|
||||||
|
|
||||||
|
const displayNameHtml = account ? { __html: account.get('display_name_html') } : '';
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className='modal-root__modal list-adder'>
|
<div className='modal-root__modal compose-modal'>
|
||||||
|
<div className='compose-modal__header'>
|
||||||
|
<h3 className='compose-modal__header__title'>
|
||||||
|
<FormattedMessage id='list_adder.header_title' defaultMessage='Add or Remove from Lists' />
|
||||||
|
</h3>
|
||||||
|
<IconButton className='compose-modal__close' title={intl.formatMessage(messages.close)} icon='times' onClick={this.onClickClose} size={20} />
|
||||||
|
</div>
|
||||||
|
<div className='compose-modal__content'>
|
||||||
|
<div className='list-adder'>
|
||||||
<div className='list-adder__account'>
|
<div className='list-adder__account'>
|
||||||
<Account accountId={accountId} />
|
<Account accountId={accountId} />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<br/>
|
||||||
|
|
||||||
|
<ColumnSubheading text={intl.formatMessage(messages.add)} />
|
||||||
<NewListForm />
|
<NewListForm />
|
||||||
|
|
||||||
|
<br/>
|
||||||
|
|
||||||
|
<ColumnSubheading text={intl.formatMessage(messages.subheading)} />
|
||||||
<div className='list-adder__lists'>
|
<div className='list-adder__lists'>
|
||||||
{listIds.map(ListId => <List key={ListId} listId={ListId} />)}
|
{listIds.map(ListId => <List key={ListId} listId={ListId} />)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2,11 +2,12 @@ import React from 'react';
|
|||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { changeListEditorTitle, submitListEditor } from '../../../actions/lists';
|
import { changeListEditorTitle, submitListEditor } from '../../../actions/lists';
|
||||||
import IconButton from '../../../components/icon_button';
|
import Button from '../../../components/button';
|
||||||
import { defineMessages, injectIntl } from 'react-intl';
|
import { defineMessages, injectIntl } from 'react-intl';
|
||||||
|
|
||||||
const messages = defineMessages({
|
const messages = defineMessages({
|
||||||
title: { id: 'lists.edit.submit', defaultMessage: 'Change title' },
|
title: { id: 'lists.edit.submit', defaultMessage: 'Change title' },
|
||||||
|
save: { id: 'lists.new.save_title', defaultMessage: 'Save Title' },
|
||||||
});
|
});
|
||||||
|
|
||||||
const mapStateToProps = state => ({
|
const mapStateToProps = state => ({
|
||||||
@ -48,21 +49,24 @@ class ListForm extends React.PureComponent {
|
|||||||
const { value, disabled, intl } = this.props;
|
const { value, disabled, intl } = this.props;
|
||||||
|
|
||||||
const title = intl.formatMessage(messages.title);
|
const title = intl.formatMessage(messages.title);
|
||||||
|
const save = intl.formatMessage(messages.save);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<form className='column-inline-form' onSubmit={this.handleSubmit}>
|
<form className='column-inline-form' onSubmit={this.handleSubmit}>
|
||||||
<input
|
<input
|
||||||
className='setting-text'
|
className='setting-text new-list-form__input'
|
||||||
value={value}
|
value={value}
|
||||||
onChange={this.handleChange}
|
onChange={this.handleChange}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<IconButton
|
{ !disabled &&
|
||||||
disabled={disabled}
|
<Button
|
||||||
icon='check'
|
className='new-list-form__btn'
|
||||||
title={title}
|
|
||||||
onClick={this.handleClick}
|
onClick={this.handleClick}
|
||||||
/>
|
>
|
||||||
|
{save}
|
||||||
|
</Button>
|
||||||
|
}
|
||||||
</form>
|
</form>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -5,9 +5,11 @@ import { defineMessages, injectIntl } from 'react-intl';
|
|||||||
import { fetchListSuggestions, clearListSuggestions, changeListSuggestions } from '../../../actions/lists';
|
import { fetchListSuggestions, clearListSuggestions, changeListSuggestions } from '../../../actions/lists';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import Icon from 'gabsocial/components/icon';
|
import Icon from 'gabsocial/components/icon';
|
||||||
|
import Button from 'gabsocial/components/button';
|
||||||
|
|
||||||
const messages = defineMessages({
|
const messages = defineMessages({
|
||||||
search: { id: 'lists.search', defaultMessage: 'Search among people you follow' },
|
search: { id: 'lists.search', defaultMessage: 'Search among people you follow' },
|
||||||
|
searchTitle: { id: 'tabs_bar.search', defaultMessage: 'Search' },
|
||||||
});
|
});
|
||||||
|
|
||||||
const mapStateToProps = state => ({
|
const mapStateToProps = state => ({
|
||||||
@ -42,6 +44,10 @@ class Search extends React.PureComponent {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
handleSubmit = () => {
|
||||||
|
this.props.onSubmit(this.props.value);
|
||||||
|
}
|
||||||
|
|
||||||
handleClear = () => {
|
handleClear = () => {
|
||||||
this.props.onClear();
|
this.props.onClear();
|
||||||
}
|
}
|
||||||
@ -69,6 +75,7 @@ class Search extends React.PureComponent {
|
|||||||
<Icon id='search' className={classNames({ active: !hasValue })} />
|
<Icon id='search' className={classNames({ active: !hasValue })} />
|
||||||
<Icon id='times-circle' aria-label={intl.formatMessage(messages.search)} className={classNames({ active: hasValue })} />
|
<Icon id='times-circle' aria-label={intl.formatMessage(messages.search)} className={classNames({ active: hasValue })} />
|
||||||
</div>
|
</div>
|
||||||
|
<Button onClick={this.handleSubmit}>{intl.formatMessage(messages.searchTitle)}</Button>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -3,13 +3,13 @@ import PropTypes from 'prop-types';
|
|||||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import ImmutablePureComponent from 'react-immutable-pure-component';
|
import ImmutablePureComponent from 'react-immutable-pure-component';
|
||||||
import { injectIntl } from 'react-intl';
|
import { injectIntl, defineMessages } from 'react-intl';
|
||||||
import { setupListEditor, clearListSuggestions, resetListEditor } from '../../actions/lists';
|
import { setupListEditor, clearListSuggestions, resetListEditor } from '../../actions/lists';
|
||||||
import Account from './components/account';
|
import Account from './components/account';
|
||||||
import Search from './components/search';
|
import Search from './components/search';
|
||||||
import EditListForm from './components/edit_list_form';
|
import EditListForm from './components/edit_list_form';
|
||||||
import Motion from '../ui/util/optional_motion';
|
import ColumnSubheading from '../ui/components/column_subheading';
|
||||||
import spring from 'react-motion/lib/spring';
|
import IconButton from 'gabsocial/components/icon_button';
|
||||||
|
|
||||||
const mapStateToProps = state => ({
|
const mapStateToProps = state => ({
|
||||||
accountIds: state.getIn(['listEditor', 'accounts', 'items']),
|
accountIds: state.getIn(['listEditor', 'accounts', 'items']),
|
||||||
@ -22,6 +22,14 @@ const mapDispatchToProps = dispatch => ({
|
|||||||
onReset: () => dispatch(resetListEditor()),
|
onReset: () => dispatch(resetListEditor()),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const messages = defineMessages({
|
||||||
|
close: { id: 'lightbox.close', defaultMessage: 'Close' },
|
||||||
|
changeTitle: { id: 'lists.edit.submit', defaultMessage: 'Change title' },
|
||||||
|
addToList: { id: 'lists.account.add', defaultMessage: 'Add to list' },
|
||||||
|
removeFromList: { id: 'lists.account.remove', defaultMessage: 'Remove from list' },
|
||||||
|
editList: { id: 'lists.edit', defaultMessage: 'Edit list' },
|
||||||
|
});
|
||||||
|
|
||||||
export default @connect(mapStateToProps, mapDispatchToProps)
|
export default @connect(mapStateToProps, mapDispatchToProps)
|
||||||
@injectIntl
|
@injectIntl
|
||||||
class ListEditor extends ImmutablePureComponent {
|
class ListEditor extends ImmutablePureComponent {
|
||||||
@ -47,30 +55,45 @@ class ListEditor extends ImmutablePureComponent {
|
|||||||
onReset();
|
onReset();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
onClickClose = () => {
|
||||||
|
this.props.onClose('LIST_ADDER');
|
||||||
|
};
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
const { accountIds, searchAccountIds, onClear } = this.props;
|
const { accountIds, searchAccountIds, onClear, intl } = this.props;
|
||||||
const showSearch = searchAccountIds.size > 0;
|
const showSearch = searchAccountIds.size > 0;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className='modal-root__modal list-editor'>
|
<div className='modal-root__modal compose-modal'>
|
||||||
|
<div className='compose-modal__header'>
|
||||||
|
<h3 className='compose-modal__header__title'>
|
||||||
|
{intl.formatMessage(messages.editList)}
|
||||||
|
</h3>
|
||||||
|
<IconButton className='compose-modal__close' title={intl.formatMessage(messages.close)} icon='times' onClick={this.onClickClose} size={20} />
|
||||||
|
</div>
|
||||||
|
<div className='compose-modal__content'>
|
||||||
|
<div className='list-editor'>
|
||||||
|
<ColumnSubheading text={intl.formatMessage(messages.changeTitle)} />
|
||||||
<EditListForm />
|
<EditListForm />
|
||||||
|
<br/>
|
||||||
|
|
||||||
<Search />
|
{
|
||||||
|
accountIds.size > 0 &&
|
||||||
<div className='drawer__pager'>
|
<div>
|
||||||
<div className='drawer__inner list-editor__accounts'>
|
<ColumnSubheading text={intl.formatMessage(messages.removeFromList)} />
|
||||||
|
<div className='list-editor__accounts'>
|
||||||
{accountIds.map(accountId => <Account key={accountId} accountId={accountId} added />)}
|
{accountIds.map(accountId => <Account key={accountId} accountId={accountId} added />)}
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
|
||||||
{showSearch && <div role='button' tabIndex='-1' className='drawer__backdrop' onClick={onClear} />}
|
<br/>
|
||||||
|
<ColumnSubheading text={intl.formatMessage(messages.addToList)} />
|
||||||
<Motion defaultStyle={{ x: -100 }} style={{ x: spring(showSearch ? 0 : -100, { stiffness: 210, damping: 20 }) }}>
|
<Search />
|
||||||
{({ x }) => (
|
<div className='list-editor__accounts'>
|
||||||
<div className='drawer__inner backdrop' style={{ transform: x === 0 ? null : `translateX(${x}%)`, visibility: x === -100 ? 'hidden' : 'visible' }}>
|
|
||||||
{searchAccountIds.map(accountId => <Account key={accountId} accountId={accountId} />)}
|
{searchAccountIds.map(accountId => <Account key={accountId} accountId={accountId} />)}
|
||||||
</div>
|
</div>
|
||||||
)}
|
</div>
|
||||||
</Motion>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
@ -4,8 +4,6 @@ import PropTypes from 'prop-types';
|
|||||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||||
import StatusListContainer from '../ui/containers/status_list_container';
|
import StatusListContainer from '../ui/containers/status_list_container';
|
||||||
import Column from '../../components/column';
|
import Column from '../../components/column';
|
||||||
import ColumnBackButton from '../../components/column_back_button';
|
|
||||||
import ColumnHeader from '../../components/column_header';
|
|
||||||
import { FormattedMessage, defineMessages, injectIntl } from 'react-intl';
|
import { FormattedMessage, defineMessages, injectIntl } from 'react-intl';
|
||||||
import { connectListStream } from '../../actions/streaming';
|
import { connectListStream } from '../../actions/streaming';
|
||||||
import { expandListTimeline } from '../../actions/timelines';
|
import { expandListTimeline } from '../../actions/timelines';
|
||||||
@ -14,6 +12,9 @@ import { openModal } from '../../actions/modal';
|
|||||||
import MissingIndicator from '../../components/missing_indicator';
|
import MissingIndicator from '../../components/missing_indicator';
|
||||||
import LoadingIndicator from '../../components/loading_indicator';
|
import LoadingIndicator from '../../components/loading_indicator';
|
||||||
import Icon from 'gabsocial/components/icon';
|
import Icon from 'gabsocial/components/icon';
|
||||||
|
import HomeColumnHeader from '../../components/home_column_header';
|
||||||
|
import { Link } from 'react-router-dom';
|
||||||
|
import Button from 'gabsocial/components/button';
|
||||||
|
|
||||||
const messages = defineMessages({
|
const messages = defineMessages({
|
||||||
deleteMessage: { id: 'confirmations.delete_list.message', defaultMessage: 'Are you sure you want to permanently delete this list?' },
|
deleteMessage: { id: 'confirmations.delete_list.message', defaultMessage: 'Are you sure you want to permanently delete this list?' },
|
||||||
@ -97,15 +98,22 @@ class ListTimeline extends React.PureComponent {
|
|||||||
} else if (list === false) {
|
} else if (list === false) {
|
||||||
return (
|
return (
|
||||||
<Column>
|
<Column>
|
||||||
<ColumnBackButton />
|
|
||||||
<MissingIndicator />
|
<MissingIndicator />
|
||||||
</Column>
|
</Column>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const emptyMessage = (
|
||||||
|
<div>
|
||||||
|
<FormattedMessage id='empty_column.list' defaultMessage='There is nothing in this list yet. When members of this list post new statuses, they will appear here.' />
|
||||||
|
<br/><br/>
|
||||||
|
<Button onClick={this.handleEditClick}><FormattedMessage id='list.click_to_add' defaultMessage='Click here to add people'/></Button>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Column label={title}>
|
<Column label={title}>
|
||||||
<ColumnHeader icon='list-ul' active={hasUnread} title={title} >
|
<HomeColumnHeader activeItem='lists' activeSubItem={id} active={hasUnread}>
|
||||||
<div className='column-header__links'>
|
<div className='column-header__links'>
|
||||||
<button className='text-btn column-header__setting-btn' tabIndex='0' onClick={this.handleEditClick}>
|
<button className='text-btn column-header__setting-btn' tabIndex='0' onClick={this.handleEditClick}>
|
||||||
<Icon id='pencil' /> <FormattedMessage id='lists.edit' defaultMessage='Edit list' />
|
<Icon id='pencil' /> <FormattedMessage id='lists.edit' defaultMessage='Edit list' />
|
||||||
@ -114,16 +122,21 @@ class ListTimeline extends React.PureComponent {
|
|||||||
<button className='text-btn column-header__setting-btn' tabIndex='0' onClick={this.handleDeleteClick}>
|
<button className='text-btn column-header__setting-btn' tabIndex='0' onClick={this.handleDeleteClick}>
|
||||||
<Icon id='trash' /> <FormattedMessage id='lists.delete' defaultMessage='Delete list' />
|
<Icon id='trash' /> <FormattedMessage id='lists.delete' defaultMessage='Delete list' />
|
||||||
</button>
|
</button>
|
||||||
</div>
|
|
||||||
|
|
||||||
<hr />
|
<hr/>
|
||||||
</ColumnHeader>
|
|
||||||
|
<Link to='/lists' className='text-btn column-header__setting-btn column-header__setting-btn--link'>
|
||||||
|
<FormattedMessage id='lists.view_all' defaultMessage='View all lists' />
|
||||||
|
<Icon id='arrow-right' />
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
</HomeColumnHeader>
|
||||||
|
|
||||||
<StatusListContainer
|
<StatusListContainer
|
||||||
scrollKey='list_timeline'
|
scrollKey='list_timeline'
|
||||||
timelineId={`list:${id}`}
|
timelineId={`list:${id}`}
|
||||||
onLoadMore={this.handleLoadMore}
|
onLoadMore={this.handleLoadMore}
|
||||||
emptyMessage={<FormattedMessage id='empty_column.list' defaultMessage='There is nothing in this list yet. When members of this list post new statuses, they will appear here.' />}
|
emptyMessage={emptyMessage}
|
||||||
/>
|
/>
|
||||||
</Column>
|
</Column>
|
||||||
);
|
);
|
||||||
|
@ -2,12 +2,13 @@ import React from 'react';
|
|||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { changeListEditorTitle, submitListEditor } from '../../../actions/lists';
|
import { changeListEditorTitle, submitListEditor } from '../../../actions/lists';
|
||||||
import IconButton from '../../../components/icon_button';
|
import Button from '../../../components/button';
|
||||||
import { defineMessages, injectIntl } from 'react-intl';
|
import { defineMessages, injectIntl } from 'react-intl';
|
||||||
|
|
||||||
const messages = defineMessages({
|
const messages = defineMessages({
|
||||||
label: { id: 'lists.new.title_placeholder', defaultMessage: 'New list title' },
|
label: { id: 'lists.new.title_placeholder', defaultMessage: 'New list title' },
|
||||||
title: { id: 'lists.new.create', defaultMessage: 'Add list' },
|
title: { id: 'lists.new.create', defaultMessage: 'Add list' },
|
||||||
|
create: { id: 'lists.new.create_title', defaultMessage: 'Create' },
|
||||||
});
|
});
|
||||||
|
|
||||||
const mapStateToProps = state => ({
|
const mapStateToProps = state => ({
|
||||||
@ -50,6 +51,7 @@ class NewListForm extends React.PureComponent {
|
|||||||
|
|
||||||
const label = intl.formatMessage(messages.label);
|
const label = intl.formatMessage(messages.label);
|
||||||
const title = intl.formatMessage(messages.title);
|
const title = intl.formatMessage(messages.title);
|
||||||
|
const create = intl.formatMessage(messages.create);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<form className='column-inline-form' onSubmit={this.handleSubmit}>
|
<form className='column-inline-form' onSubmit={this.handleSubmit}>
|
||||||
@ -57,7 +59,7 @@ class NewListForm extends React.PureComponent {
|
|||||||
<span style={{ display: 'none' }}>{label}</span>
|
<span style={{ display: 'none' }}>{label}</span>
|
||||||
|
|
||||||
<input
|
<input
|
||||||
className='setting-text'
|
className='setting-text new-list-form__input'
|
||||||
value={value}
|
value={value}
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
onChange={this.handleChange}
|
onChange={this.handleChange}
|
||||||
@ -65,12 +67,13 @@ class NewListForm extends React.PureComponent {
|
|||||||
/>
|
/>
|
||||||
</label>
|
</label>
|
||||||
|
|
||||||
<IconButton
|
<Button
|
||||||
|
className='new-list-form__btn'
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
icon='plus'
|
|
||||||
title={title}
|
|
||||||
onClick={this.handleClick}
|
onClick={this.handleClick}
|
||||||
/>
|
>
|
||||||
|
{create}
|
||||||
|
</Button>
|
||||||
</form>
|
</form>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -4,7 +4,6 @@ import PropTypes from 'prop-types';
|
|||||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||||
import LoadingIndicator from '../../components/loading_indicator';
|
import LoadingIndicator from '../../components/loading_indicator';
|
||||||
import Column from '../ui/components/column';
|
import Column from '../ui/components/column';
|
||||||
import ColumnBackButtonSlim from '../../components/column_back_button_slim';
|
|
||||||
import { fetchLists } from '../../actions/lists';
|
import { fetchLists } from '../../actions/lists';
|
||||||
import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
|
import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
|
||||||
import ImmutablePureComponent from 'react-immutable-pure-component';
|
import ImmutablePureComponent from 'react-immutable-pure-component';
|
||||||
@ -17,6 +16,7 @@ import ScrollableList from '../../components/scrollable_list';
|
|||||||
const messages = defineMessages({
|
const messages = defineMessages({
|
||||||
heading: { id: 'column.lists', defaultMessage: 'Lists' },
|
heading: { id: 'column.lists', defaultMessage: 'Lists' },
|
||||||
subheading: { id: 'lists.subheading', defaultMessage: 'Your lists' },
|
subheading: { id: 'lists.subheading', defaultMessage: 'Your lists' },
|
||||||
|
add: { id: 'lists.new.create', defaultMessage: 'Add List' },
|
||||||
});
|
});
|
||||||
|
|
||||||
const getOrderedLists = createSelector([state => state.get('lists')], lists => {
|
const getOrderedLists = createSelector([state => state.get('lists')], lists => {
|
||||||
@ -60,11 +60,11 @@ class Lists extends ImmutablePureComponent {
|
|||||||
const emptyMessage = <FormattedMessage id='empty_column.lists' defaultMessage="You don't have any lists yet. When you create one, it will show up here." />;
|
const emptyMessage = <FormattedMessage id='empty_column.lists' defaultMessage="You don't have any lists yet. When you create one, it will show up here." />;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Column icon='list-ul' heading={intl.formatMessage(messages.heading)}>
|
<Column icon='list-ul' heading={intl.formatMessage(messages.heading)} backBtnSlim>
|
||||||
<ColumnBackButtonSlim />
|
<br/>
|
||||||
|
<ColumnSubheading text={intl.formatMessage(messages.add)} />
|
||||||
<NewListForm />
|
<NewListForm />
|
||||||
|
<br/>
|
||||||
<ColumnSubheading text={intl.formatMessage(messages.subheading)} />
|
<ColumnSubheading text={intl.formatMessage(messages.subheading)} />
|
||||||
<ScrollableList
|
<ScrollableList
|
||||||
scrollKey='lists'
|
scrollKey='lists'
|
||||||
|
@ -7,7 +7,6 @@ import ImmutablePropTypes from 'react-immutable-proptypes';
|
|||||||
import { debounce } from 'lodash';
|
import { debounce } from 'lodash';
|
||||||
import LoadingIndicator from '../../components/loading_indicator';
|
import LoadingIndicator from '../../components/loading_indicator';
|
||||||
import Column from '../ui/components/column';
|
import Column from '../ui/components/column';
|
||||||
import ColumnBackButtonSlim from '../../components/column_back_button_slim';
|
|
||||||
import AccountContainer from '../../containers/account_container';
|
import AccountContainer from '../../containers/account_container';
|
||||||
import { fetchMutes, expandMutes } from '../../actions/mutes';
|
import { fetchMutes, expandMutes } from '../../actions/mutes';
|
||||||
import ScrollableList from '../../components/scrollable_list';
|
import ScrollableList from '../../components/scrollable_list';
|
||||||
@ -55,8 +54,7 @@ class Mutes extends ImmutablePureComponent {
|
|||||||
const emptyMessage = <FormattedMessage id='empty_column.mutes' defaultMessage="You haven't muted any users yet." />;
|
const emptyMessage = <FormattedMessage id='empty_column.mutes' defaultMessage="You haven't muted any users yet." />;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Column icon='volume-off' heading={intl.formatMessage(messages.heading)}>
|
<Column icon='volume-off' heading={intl.formatMessage(messages.heading)} backBtnSlim>
|
||||||
<ColumnBackButtonSlim />
|
|
||||||
<ScrollableList
|
<ScrollableList
|
||||||
scrollKey='mutes'
|
scrollKey='mutes'
|
||||||
onLoadMore={this.handleLoadMore}
|
onLoadMore={this.handleLoadMore}
|
||||||
|
@ -10,7 +10,6 @@ import { fetchStatus } from '../../actions/statuses';
|
|||||||
import { FormattedMessage } from 'react-intl';
|
import { FormattedMessage } from 'react-intl';
|
||||||
import AccountContainer from '../../containers/account_container';
|
import AccountContainer from '../../containers/account_container';
|
||||||
import Column from '../ui/components/column';
|
import Column from '../ui/components/column';
|
||||||
import ColumnBackButton from '../../components/column_back_button';
|
|
||||||
import ScrollableList from '../../components/scrollable_list';
|
import ScrollableList from '../../components/scrollable_list';
|
||||||
import { makeGetStatus } from '../../selectors';
|
import { makeGetStatus } from '../../selectors';
|
||||||
|
|
||||||
@ -72,8 +71,6 @@ class Reblogs extends ImmutablePureComponent {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Column>
|
<Column>
|
||||||
<ColumnBackButton />
|
|
||||||
|
|
||||||
<ScrollableList
|
<ScrollableList
|
||||||
scrollKey='reblogs'
|
scrollKey='reblogs'
|
||||||
emptyMessage={emptyMessage}
|
emptyMessage={emptyMessage}
|
||||||
|
@ -4,7 +4,6 @@ import { defineMessages, injectIntl } from 'react-intl';
|
|||||||
|
|
||||||
import Column from './column';
|
import Column from './column';
|
||||||
import ColumnHeader from './column_header';
|
import ColumnHeader from './column_header';
|
||||||
import ColumnBackButtonSlim from '../../../components/column_back_button_slim';
|
|
||||||
import IconButton from '../../../components/icon_button';
|
import IconButton from '../../../components/icon_button';
|
||||||
|
|
||||||
const messages = defineMessages({
|
const messages = defineMessages({
|
||||||
@ -30,7 +29,6 @@ class BundleColumnError extends React.PureComponent {
|
|||||||
return (
|
return (
|
||||||
<Column>
|
<Column>
|
||||||
<ColumnHeader icon='exclamation-circle' type={formatMessage(messages.title)} />
|
<ColumnHeader icon='exclamation-circle' type={formatMessage(messages.title)} />
|
||||||
<ColumnBackButtonSlim />
|
|
||||||
<div className='error-column'>
|
<div className='error-column'>
|
||||||
<IconButton title={formatMessage(messages.retry)} icon='refresh' onClick={this.handleRetry} size={64} />
|
<IconButton title={formatMessage(messages.retry)} icon='refresh' onClick={this.handleRetry} size={64} />
|
||||||
{formatMessage(messages.body)}
|
{formatMessage(messages.body)}
|
||||||
|
@ -2,6 +2,8 @@ import React from 'react';
|
|||||||
import ColumnHeader from './column_header';
|
import ColumnHeader from './column_header';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { isMobile } from '../../../is_mobile';
|
import { isMobile } from '../../../is_mobile';
|
||||||
|
import ColumnBackButton from '../../../components/column_back_button';
|
||||||
|
import ColumnBackButtonSlim from '../../../components/column_back_button_slim';
|
||||||
|
|
||||||
export default class Column extends React.PureComponent {
|
export default class Column extends React.PureComponent {
|
||||||
|
|
||||||
@ -11,10 +13,11 @@ export default class Column extends React.PureComponent {
|
|||||||
children: PropTypes.node,
|
children: PropTypes.node,
|
||||||
active: PropTypes.bool,
|
active: PropTypes.bool,
|
||||||
hideHeadingOnMobile: PropTypes.bool,
|
hideHeadingOnMobile: PropTypes.bool,
|
||||||
|
backBtnSlim: PropTypes.bool,
|
||||||
};
|
};
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
const { heading, icon, children, active, hideHeadingOnMobile } = this.props;
|
const { heading, icon, children, active, hideHeadingOnMobile, backBtnSlim } = this.props;
|
||||||
|
|
||||||
const showHeading = heading && (!hideHeadingOnMobile || (hideHeadingOnMobile && !isMobile(window.innerWidth)));
|
const showHeading = heading && (!hideHeadingOnMobile || (hideHeadingOnMobile && !isMobile(window.innerWidth)));
|
||||||
|
|
||||||
@ -22,9 +25,13 @@ export default class Column extends React.PureComponent {
|
|||||||
const header = showHeading && (
|
const header = showHeading && (
|
||||||
<ColumnHeader icon={icon} active={active} type={heading} columnHeaderId={columnHeaderId} />
|
<ColumnHeader icon={icon} active={active} type={heading} columnHeaderId={columnHeaderId} />
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const backBtn = backBtnSlim ? (<ColumnBackButtonSlim/>) : (<ColumnBackButton/>);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div role='region' aria-labelledby={columnHeaderId} className='column'>
|
<div role='region' aria-labelledby={columnHeaderId} className='column'>
|
||||||
{header}
|
{header}
|
||||||
|
{backBtn}
|
||||||
{children}
|
{children}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
@ -53,6 +53,8 @@ import {
|
|||||||
Explore,
|
Explore,
|
||||||
Groups,
|
Groups,
|
||||||
GroupTimeline,
|
GroupTimeline,
|
||||||
|
ListTimeline,
|
||||||
|
Lists,
|
||||||
} from './util/async-components';
|
} from './util/async-components';
|
||||||
import { me, meUsername } from '../../initial_state';
|
import { me, meUsername } from '../../initial_state';
|
||||||
import { previewState as previewMediaState } from './components/media_modal';
|
import { previewState as previewMediaState } from './components/media_modal';
|
||||||
@ -109,8 +111,8 @@ const LAYOUT = {
|
|||||||
},
|
},
|
||||||
DEFAULT: {
|
DEFAULT: {
|
||||||
LEFT: [
|
LEFT: [
|
||||||
<WhoToFollowPanel />,
|
<WhoToFollowPanel key='0' />,
|
||||||
<LinkFooter />,
|
<LinkFooter key='1' />,
|
||||||
],
|
],
|
||||||
RIGHT: [
|
RIGHT: [
|
||||||
// <TrendsPanel />,
|
// <TrendsPanel />,
|
||||||
@ -120,9 +122,9 @@ const LAYOUT = {
|
|||||||
TOP: null,
|
TOP: null,
|
||||||
LEFT: null,
|
LEFT: null,
|
||||||
RIGHT: [
|
RIGHT: [
|
||||||
<WhoToFollowPanel />,
|
<WhoToFollowPanel key='0' />,
|
||||||
// <TrendsPanel />,
|
// <TrendsPanel />,
|
||||||
<LinkFooter />,
|
<LinkFooter key='1' />,
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
@ -177,8 +179,8 @@ class SwitchingColumnsArea extends React.PureComponent {
|
|||||||
|
|
||||||
<WrappedRoute path='/tags/:id' component={HashtagTimeline} content={children} />
|
<WrappedRoute path='/tags/:id' component={HashtagTimeline} content={children} />
|
||||||
|
|
||||||
<Redirect from='/lists' to='/home' />
|
<WrappedRoute path='/lists' layout={LAYOUT.DEFAULT} component={Lists} content={children} />
|
||||||
<Redirect from='/list' to='/home' />
|
<WrappedRoute path='/list/:id' page={HomePage} component={ListTimeline} content={children} />
|
||||||
|
|
||||||
<WrappedRoute path='/notifications' layout={LAYOUT.DEFAULT} component={Notifications} content={children} />
|
<WrappedRoute path='/notifications' layout={LAYOUT.DEFAULT} component={Notifications} content={children} />
|
||||||
|
|
||||||
|
@ -2495,11 +2495,14 @@ a.status-card.compact:hover {
|
|||||||
background: transparent;
|
background: transparent;
|
||||||
font: inherit;
|
font: inherit;
|
||||||
text-align: left;
|
text-align: left;
|
||||||
text-overflow: ellipsis;
|
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
overflow: hidden;
|
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
|
|
||||||
|
&--sub {
|
||||||
|
font-size: 14px;
|
||||||
|
padding: 6px 10px;
|
||||||
|
}
|
||||||
|
|
||||||
&.grouped {
|
&.grouped {
|
||||||
margin: 6px;
|
margin: 6px;
|
||||||
}
|
}
|
||||||
@ -2597,6 +2600,13 @@ a.status-card.compact:hover {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.column-header__setting-btn {
|
.column-header__setting-btn {
|
||||||
|
&--link {
|
||||||
|
text-decoration: none;
|
||||||
|
|
||||||
|
.fa {
|
||||||
|
margin-left: 10px;
|
||||||
|
}
|
||||||
|
}
|
||||||
&:hover {
|
&:hover {
|
||||||
color: $darker-text-color;
|
color: $darker-text-color;
|
||||||
text-decoration: underline;
|
text-decoration: underline;
|
||||||
@ -2615,6 +2625,12 @@ a.status-card.compact:hover {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.column-header__expansion {
|
||||||
|
overflow-x: scroll;
|
||||||
|
overflow-y: hidden;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
.text-btn {
|
.text-btn {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
@ -2961,6 +2977,7 @@ a.status-card.compact:hover {
|
|||||||
flex: 1 1 auto;
|
flex: 1 1 auto;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
|
min-height: 160px;
|
||||||
|
|
||||||
@supports(display: grid) { // hack to fix Chrome <57
|
@supports(display: grid) { // hack to fix Chrome <57
|
||||||
contain: strict;
|
contain: strict;
|
||||||
@ -4364,12 +4381,11 @@ noscript {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.list-editor {
|
.list-editor {
|
||||||
background: $ui-base-color;
|
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
border-radius: 8px;
|
width: 100%;
|
||||||
box-shadow: 2px 4px 15px rgba($base-shadow-color, 0.4);
|
|
||||||
width: 380px;
|
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
height: 100%;
|
||||||
|
overflow-y: scroll;
|
||||||
|
|
||||||
@media screen and (max-width: 420px) {
|
@media screen and (max-width: 420px) {
|
||||||
width: 90%;
|
width: 90%;
|
||||||
@ -4384,10 +4400,6 @@ noscript {
|
|||||||
border-radius: 8px 8px 0 0;
|
border-radius: 8px 8px 0 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.drawer__pager {
|
|
||||||
height: 50vh;
|
|
||||||
}
|
|
||||||
|
|
||||||
.drawer__inner {
|
.drawer__inner {
|
||||||
border-radius: 0 0 8px 8px;
|
border-radius: 0 0 8px 8px;
|
||||||
|
|
||||||
@ -4399,7 +4411,9 @@ noscript {
|
|||||||
}
|
}
|
||||||
|
|
||||||
&__accounts {
|
&__accounts {
|
||||||
|
background: lighten($ui-base-color, 13%);
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
|
max-height: 200px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.account__display-name {
|
.account__display-name {
|
||||||
@ -4413,17 +4427,31 @@ noscript {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.search {
|
.search {
|
||||||
margin-bottom: 0;
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
margin: 10px 0;
|
||||||
|
|
||||||
|
> label {
|
||||||
|
flex: 1 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
> .search__icon .fa {
|
||||||
|
right: 102px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
> .button {
|
||||||
|
width: 80px;
|
||||||
|
margin-left: 10px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.list-adder {
|
.list-adder {
|
||||||
background: $ui-base-color;
|
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
border-radius: 8px;
|
width: 100%;
|
||||||
box-shadow: 2px 4px 15px rgba($base-shadow-color, 0.4);
|
|
||||||
width: 380px;
|
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
height: 100%;
|
||||||
|
overflow-y: scroll;
|
||||||
|
|
||||||
@media screen and (max-width: 420px) {
|
@media screen and (max-width: 420px) {
|
||||||
width: 90%;
|
width: 90%;
|
||||||
@ -4431,22 +4459,24 @@ noscript {
|
|||||||
|
|
||||||
&__account {
|
&__account {
|
||||||
background: lighten($ui-base-color, 13%);
|
background: lighten($ui-base-color, 13%);
|
||||||
|
border-radius: 4px;
|
||||||
}
|
}
|
||||||
|
|
||||||
&__lists {
|
&__lists {
|
||||||
background: lighten($ui-base-color, 13%);
|
background: lighten($ui-base-color, 13%);
|
||||||
height: 50vh;
|
|
||||||
border-radius: 0 0 8px 8px;
|
|
||||||
overflow-y: auto;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.list {
|
.list {
|
||||||
padding: 10px;
|
padding: 4px;
|
||||||
border-bottom: 1px solid lighten($ui-base-color, 8%);
|
border-bottom: 1px solid lighten($ui-base-color, 8%);
|
||||||
}
|
}
|
||||||
|
|
||||||
.list__wrapper {
|
.list__wrapper {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|
||||||
|
.account__relationship {
|
||||||
|
padding: 8px 5px 0 5px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.list__display-name {
|
.list__display-name {
|
||||||
@ -4458,6 +4488,18 @@ noscript {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.new-list-form,
|
||||||
|
.edit-list-form {
|
||||||
|
&__btn {
|
||||||
|
margin-left: 6px;
|
||||||
|
width: 112px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__input {
|
||||||
|
height: 36px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.focal-point-modal {
|
.focal-point-modal {
|
||||||
max-width: 80vw;
|
max-width: 80vw;
|
||||||
max-height: 80vh;
|
max-height: 80vh;
|
||||||
@ -4827,6 +4869,7 @@ noscript {
|
|||||||
border-radius: 6px;
|
border-radius: 6px;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
width: 600px;
|
width: 600px;
|
||||||
|
margin: 10px 0;
|
||||||
|
|
||||||
&__header {
|
&__header {
|
||||||
display: block;
|
display: block;
|
||||||
@ -4868,8 +4911,9 @@ noscript {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@media screen and (max-width:895px) {
|
@media screen and (max-width:895px) {
|
||||||
height: 100vh;
|
margin: 0;
|
||||||
width: 100vw;
|
height: 98vh;
|
||||||
|
width: 98vw;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user