Merge branch 'develop' of https://code.gab.com/gab/social/gab-social into develop

This commit is contained in:
Rob Colbert 2019-07-21 22:56:50 -04:00
commit daee22dcbb
25 changed files with 320 additions and 127 deletions

View File

@ -1,20 +1,39 @@
'use strict';
import React from 'react';
import { connect } from 'react-redux';
import PropTypes from 'prop-types';
import ImmutablePropTypes from 'react-immutable-proptypes';
import classNames from 'classnames';
import { FormattedMessage, injectIntl, defineMessages } from 'react-intl';
import { Link } from 'react-router-dom';
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({
show: { id: 'column_header.show_settings', defaultMessage: 'Show settings' },
hide: { id: 'column_header.hide_settings', defaultMessage: 'Hide settings' },
homeTitle: { id: 'home_column_header.home', defaultMessage: 'Home' },
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 {
static contextTypes = {
@ -23,16 +42,24 @@ class ColumnHeader extends React.PureComponent {
static propTypes = {
intl: PropTypes.object.isRequired,
dispatch: PropTypes.func.isRequired,
active: PropTypes.bool,
children: PropTypes.node,
activeItem: PropTypes.string,
activeSubItem: PropTypes.string,
lists: ImmutablePropTypes.list,
};
state = {
collapsed: true,
animating: false,
expandedFor: null, //lists, groups, etc.
};
componentDidMount() {
this.props.dispatch(fetchLists());
}
handleToggleClick = (e) => {
e.stopPropagation();
this.setState({ collapsed: !this.state.collapsed, animating: true });
@ -42,9 +69,15 @@ class ColumnHeader extends React.PureComponent {
this.setState({ animating: false });
}
expandLists = () => {
this.setState({
expandedFor: 'lists',
});
}
render () {
const { active, children, intl: { formatMessage }, activeItem } = this.props;
const { collapsed, animating } = this.state;
const { active, children, intl: { formatMessage }, activeItem, activeSubItem, lists } = this.props;
const { collapsed, animating, expandedFor } = this.state;
const wrapperClassName = classNames('column-header__wrapper', {
'active': active,
@ -63,6 +96,10 @@ class ColumnHeader extends React.PureComponent {
'active': !collapsed,
});
const expansionClassName = classNames('column-header column-header__expansion', {
'open': expandedFor,
});
let extraContent, collapseButton;
if (children) {
@ -79,6 +116,23 @@ class ColumnHeader extends React.PureComponent {
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 (
<div className={wrapperClassName}>
<h1 className={buttonClassName}>
@ -92,11 +146,31 @@ class ColumnHeader extends React.PureComponent {
{formatMessage(messages.allTitle)}
</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'>
{collapseButton}
</div>
</h1>
{
expandedContent &&
<h1 className={expansionClassName}>
{expandedContent}
</h1>
}
<div className={collapsibleClassName} tabIndex={collapsed ? -1 : null} onTransitionEnd={this.handleTransitionEnd}>
<div className='column-header__collapsible-inner'>
{(!collapsed || animating) && collapsedContent}
@ -105,5 +179,6 @@ class ColumnHeader extends React.PureComponent {
</div>
);
}
}
export default injectIntl(connect(mapStateToProps)(ColumnHeader));

View File

@ -82,7 +82,7 @@ export default class ScrollableList extends PureComponent {
componentDidMount () {
this.window = window;
this.documentElement = document.documentElement;
this.documentElement = document.scrollingElement || document.documentElement;
this.attachScrollListener();
this.attachIntersectionObserver();
@ -124,10 +124,11 @@ export default class ScrollableList extends PureComponent {
handleScroll = throttle(() => {
if (this.window) {
const { scrollTop, scrollHeight, clientHeight } = this.documentElement;
const offset = scrollHeight - scrollTop - clientHeight;
const { scrollTop, scrollHeight } = this.documentElement;
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();
}
@ -174,6 +175,7 @@ export default class ScrollableList extends PureComponent {
componentWillUnmount () {
this.clearMouseIdleTimer();
this.detachScrollListener();
this.detachIntersectionObserver();
}

View File

@ -43,6 +43,7 @@ const messages = defineMessages({
endorse: { id: 'account.endorse', defaultMessage: '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}' },
add_or_remove_from_list: { id: 'account.add_or_remove_from_list', defaultMessage: 'Add or Remove from lists' },
});
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.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(null);
}

View File

@ -7,7 +7,6 @@ import { debounce } from 'lodash';
import PropTypes from 'prop-types';
import LoadingIndicator from '../../components/loading_indicator';
import Column from '../ui/components/column';
import ColumnBackButtonSlim from '../../components/column_back_button_slim';
import AccountContainer from '../../containers/account_container';
import { fetchBlocks, expandBlocks } from '../../actions/blocks';
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." />;
return (
<Column icon='ban' heading={intl.formatMessage(messages.heading)}>
<ColumnBackButtonSlim />
<Column icon='ban' heading={intl.formatMessage(messages.heading)} backBtnSlim>
<ScrollableList
scrollKey='blocks'
onLoadMore={this.handleLoadMore}

View File

@ -7,7 +7,6 @@ import ImmutablePropTypes from 'react-immutable-proptypes';
import { debounce } from 'lodash';
import LoadingIndicator from '../../components/loading_indicator';
import Column from '../ui/components/column';
import ColumnBackButtonSlim from '../../components/column_back_button_slim';
import DomainContainer from '../../containers/domain_container';
import { fetchDomainBlocks, expandDomainBlocks } from '../../actions/domain_blocks';
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.' />;
return (
<Column icon='minus-circle' heading={intl.formatMessage(messages.heading)}>
<ColumnBackButtonSlim />
<Column icon='minus-circle' heading={intl.formatMessage(messages.heading)} backBtnSlim>
<ScrollableList
scrollKey='domain_blocks'
onLoadMore={this.handleLoadMore}

View File

@ -8,7 +8,6 @@ import { fetchFavourites } from '../../actions/interactions';
import { 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';
const mapStateToProps = (state, props) => ({
@ -49,8 +48,6 @@ class Favourites extends ImmutablePureComponent {
return (
<Column>
<ColumnBackButton />
<ScrollableList
scrollKey='favourites'
emptyMessage={emptyMessage}

View File

@ -7,7 +7,6 @@ import ImmutablePropTypes from 'react-immutable-proptypes';
import { debounce } from 'lodash';
import LoadingIndicator from '../../components/loading_indicator';
import Column from '../ui/components/column';
import ColumnBackButtonSlim from '../../components/column_back_button_slim';
import AccountAuthorizeContainer from './containers/account_authorize_container';
import { fetchFollowRequests, expandFollowRequests } from '../../actions/accounts';
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." />;
return (
<Column icon='user-plus' heading={intl.formatMessage(messages.heading)}>
<ColumnBackButtonSlim />
<Column icon='user-plus' heading={intl.formatMessage(messages.heading)} backBtnSlim>
<ScrollableList
scrollKey='follow_requests'
onLoadMore={this.handleLoadMore}

View File

@ -104,9 +104,11 @@ class Following extends ImmutablePureComponent {
if (unavailable) {
return (
<div className='empty-column-indicator'>
<FormattedMessage id='empty_column.account_unavailable' defaultMessage='Profile unavailable' />
</div>
<Column>
<div className='empty-column-indicator'>
<FormattedMessage id='empty_column.account_unavailable' defaultMessage='Profile unavailable' />
</div>
</Column>
);
}

View File

@ -4,7 +4,6 @@ import PropTypes from 'prop-types';
import ImmutablePropTypes from 'react-immutable-proptypes';
import LoadingIndicator from '../../../components/loading_indicator';
import Column from '../../ui/components/column';
import ColumnBackButtonSlim from '../../../components/column_back_button_slim';
import { fetchGroups } from '../../../actions/groups';
import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
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." />;
return (
<Column icon='list-ul' heading={intl.formatMessage(messages.heading)}>
<ColumnBackButtonSlim />
<Column icon='list-ul' heading={intl.formatMessage(messages.heading)} backBtnSlim>
<NewGroupForm />
<ColumnSubheading text={intl.formatMessage(messages.subheading)} />

View File

@ -4,7 +4,6 @@ import PropTypes from 'prop-types';
import ImmutablePropTypes from 'react-immutable-proptypes';
import StatusListContainer from '../../ui/containers/status_list_container';
import Column from '../../../components/column';
import ColumnBackButton from '../../../components/column_back_button';
import ColumnHeader from '../../../components/column_header';
import { FormattedMessage, injectIntl } from 'react-intl';
import { connectGroupStream } from '../../../actions/streaming';
@ -73,7 +72,6 @@ class GroupTimeline extends React.PureComponent {
} else if (group === false) {
return (
<Column>
<ColumnBackButton />
<MissingIndicator />
</Column>
);

View File

@ -8,6 +8,7 @@ import { expandHashtagTimeline, clearTimeline } from '../../actions/timelines';
import { FormattedMessage } from 'react-intl';
import { connectHashtagStream } from '../../actions/streaming';
import { isEqual } from 'lodash';
import ColumnBackButton from '../../components/column_back_button';
const mapStateToProps = (state, props) => ({
hasUnread: state.getIn(['timelines', `hashtag:${props.params.id}`, 'unread']) > 0,
@ -107,6 +108,7 @@ class HashtagTimeline extends React.PureComponent {
return (
<Column label={`#${id}`}>
<ColumnBackButton />
<ColumnHeader icon='hashtag' active={hasUnread} title={this.title()} />
<StatusListContainer
scrollKey='hashtag_timeline'

View File

@ -70,10 +70,7 @@ class HomeTimeline extends React.PureComponent {
return (
<Column label={intl.formatMessage(messages.title)}>
<HomeColumnHeader
activeItem='home'
active={hasUnread}
>
<HomeColumnHeader activeItem='home' active={hasUnread}>
<ColumnSettingsContainer />
</HomeColumnHeader>
<StatusListContainer

View File

@ -3,12 +3,14 @@ import PropTypes from 'prop-types';
import ImmutablePropTypes from 'react-immutable-proptypes';
import { connect } from 'react-redux';
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 { createSelector } from 'reselect';
import List from './components/list';
import Account from './components/account';
import IconButton from 'gabsocial/components/icon_button';
import NewListForm from '../lists/components/new_list_form';
import ColumnSubheading from '../ui/components/column_subheading';
// hack
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')));
});
const mapStateToProps = state => ({
const mapStateToProps = (state, {accountId}) => ({
listIds: getOrderedLists(state).map(list=>list.get('id')),
account: state.getIn(['accounts', accountId]),
});
const mapDispatchToProps = dispatch => ({
@ -28,6 +31,12 @@ const mapDispatchToProps = dispatch => ({
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)
@injectIntl
class ListAdder extends ImmutablePureComponent {
@ -39,6 +48,7 @@ class ListAdder extends ImmutablePureComponent {
onInitialize: PropTypes.func.isRequired,
onReset: PropTypes.func.isRequired,
listIds: ImmutablePropTypes.list.isRequired,
account: ImmutablePropTypes.map,
};
componentDidMount () {
@ -51,20 +61,41 @@ class ListAdder extends ImmutablePureComponent {
onReset();
}
onClickClose = () => {
this.props.onClose('LIST_ADDER');
};
render () {
const { accountId, listIds } = this.props;
const { accountId, listIds, intl, onClose, account } = this.props;
const displayNameHtml = account ? { __html: account.get('display_name_html') } : '';
return (
<div className='modal-root__modal list-adder'>
<div className='list-adder__account'>
<Account accountId={accountId} />
<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'>
<Account accountId={accountId} />
</div>
<NewListForm />
<br/>
<ColumnSubheading text={intl.formatMessage(messages.add)} />
<NewListForm />
<div className='list-adder__lists'>
{listIds.map(ListId => <List key={ListId} listId={ListId} />)}
<br/>
<ColumnSubheading text={intl.formatMessage(messages.subheading)} />
<div className='list-adder__lists'>
{listIds.map(ListId => <List key={ListId} listId={ListId} />)}
</div>
</div>
</div>
</div>
);

View File

@ -2,11 +2,12 @@ import React from 'react';
import { connect } from 'react-redux';
import PropTypes from 'prop-types';
import { changeListEditorTitle, submitListEditor } from '../../../actions/lists';
import IconButton from '../../../components/icon_button';
import Button from '../../../components/button';
import { defineMessages, injectIntl } from 'react-intl';
const messages = defineMessages({
title: { id: 'lists.edit.submit', defaultMessage: 'Change title' },
save: { id: 'lists.new.save_title', defaultMessage: 'Save Title' },
});
const mapStateToProps = state => ({
@ -48,21 +49,24 @@ class ListForm extends React.PureComponent {
const { value, disabled, intl } = this.props;
const title = intl.formatMessage(messages.title);
const save = intl.formatMessage(messages.save);
return (
<form className='column-inline-form' onSubmit={this.handleSubmit}>
<input
className='setting-text'
className='setting-text new-list-form__input'
value={value}
onChange={this.handleChange}
/>
<IconButton
disabled={disabled}
icon='check'
title={title}
onClick={this.handleClick}
/>
{ !disabled &&
<Button
className='new-list-form__btn'
onClick={this.handleClick}
>
{save}
</Button>
}
</form>
);
}

View File

@ -5,9 +5,11 @@ import { defineMessages, injectIntl } from 'react-intl';
import { fetchListSuggestions, clearListSuggestions, changeListSuggestions } from '../../../actions/lists';
import classNames from 'classnames';
import Icon from 'gabsocial/components/icon';
import Button from 'gabsocial/components/button';
const messages = defineMessages({
search: { id: 'lists.search', defaultMessage: 'Search among people you follow' },
searchTitle: { id: 'tabs_bar.search', defaultMessage: 'Search' },
});
const mapStateToProps = state => ({
@ -42,6 +44,10 @@ class Search extends React.PureComponent {
}
}
handleSubmit = () => {
this.props.onSubmit(this.props.value);
}
handleClear = () => {
this.props.onClear();
}
@ -69,6 +75,7 @@ class Search extends React.PureComponent {
<Icon id='search' className={classNames({ active: !hasValue })} />
<Icon id='times-circle' aria-label={intl.formatMessage(messages.search)} className={classNames({ active: hasValue })} />
</div>
<Button onClick={this.handleSubmit}>{intl.formatMessage(messages.searchTitle)}</Button>
</div>
);
}

View File

@ -3,13 +3,13 @@ import PropTypes from 'prop-types';
import ImmutablePropTypes from 'react-immutable-proptypes';
import { connect } from 'react-redux';
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 Account from './components/account';
import Search from './components/search';
import EditListForm from './components/edit_list_form';
import Motion from '../ui/util/optional_motion';
import spring from 'react-motion/lib/spring';
import ColumnSubheading from '../ui/components/column_subheading';
import IconButton from 'gabsocial/components/icon_button';
const mapStateToProps = state => ({
accountIds: state.getIn(['listEditor', 'accounts', 'items']),
@ -22,6 +22,14 @@ const mapDispatchToProps = dispatch => ({
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)
@injectIntl
class ListEditor extends ImmutablePureComponent {
@ -47,30 +55,45 @@ class ListEditor extends ImmutablePureComponent {
onReset();
}
onClickClose = () => {
this.props.onClose('LIST_ADDER');
};
render () {
const { accountIds, searchAccountIds, onClear } = this.props;
const { accountIds, searchAccountIds, onClear, intl } = this.props;
const showSearch = searchAccountIds.size > 0;
return (
<div className='modal-root__modal list-editor'>
<EditListForm />
<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 />
<br/>
<Search />
<div className='drawer__pager'>
<div className='drawer__inner list-editor__accounts'>
{accountIds.map(accountId => <Account key={accountId} accountId={accountId} added />)}
</div>
{showSearch && <div role='button' tabIndex='-1' className='drawer__backdrop' onClick={onClear} />}
<Motion defaultStyle={{ x: -100 }} style={{ x: spring(showSearch ? 0 : -100, { stiffness: 210, damping: 20 }) }}>
{({ x }) => (
<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} />)}
{
accountIds.size > 0 &&
<div>
<ColumnSubheading text={intl.formatMessage(messages.removeFromList)} />
<div className='list-editor__accounts'>
{accountIds.map(accountId => <Account key={accountId} accountId={accountId} added />)}
</div>
</div>
)}
</Motion>
}
<br/>
<ColumnSubheading text={intl.formatMessage(messages.addToList)} />
<Search />
<div className='list-editor__accounts'>
{searchAccountIds.map(accountId => <Account key={accountId} accountId={accountId} />)}
</div>
</div>
</div>
</div>
);

View File

@ -4,8 +4,6 @@ import PropTypes from 'prop-types';
import ImmutablePropTypes from 'react-immutable-proptypes';
import StatusListContainer from '../ui/containers/status_list_container';
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 { connectListStream } from '../../actions/streaming';
import { expandListTimeline } from '../../actions/timelines';
@ -14,6 +12,9 @@ import { openModal } from '../../actions/modal';
import MissingIndicator from '../../components/missing_indicator';
import LoadingIndicator from '../../components/loading_indicator';
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({
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) {
return (
<Column>
<ColumnBackButton />
<MissingIndicator />
</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 (
<Column label={title}>
<ColumnHeader icon='list-ul' active={hasUnread} title={title} >
<HomeColumnHeader activeItem='lists' activeSubItem={id} active={hasUnread}>
<div className='column-header__links'>
<button className='text-btn column-header__setting-btn' tabIndex='0' onClick={this.handleEditClick}>
<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}>
<Icon id='trash' /> <FormattedMessage id='lists.delete' defaultMessage='Delete list' />
</button>
</div>
<hr />
</ColumnHeader>
<hr/>
<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
scrollKey='list_timeline'
timelineId={`list:${id}`}
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>
);

View File

@ -2,12 +2,13 @@ import React from 'react';
import { connect } from 'react-redux';
import PropTypes from 'prop-types';
import { changeListEditorTitle, submitListEditor } from '../../../actions/lists';
import IconButton from '../../../components/icon_button';
import Button from '../../../components/button';
import { defineMessages, injectIntl } from 'react-intl';
const messages = defineMessages({
label: { id: 'lists.new.title_placeholder', defaultMessage: 'New list title' },
title: { id: 'lists.new.create', defaultMessage: 'Add list' },
create: { id: 'lists.new.create_title', defaultMessage: 'Create' },
});
const mapStateToProps = state => ({
@ -50,6 +51,7 @@ class NewListForm extends React.PureComponent {
const label = intl.formatMessage(messages.label);
const title = intl.formatMessage(messages.title);
const create = intl.formatMessage(messages.create);
return (
<form className='column-inline-form' onSubmit={this.handleSubmit}>
@ -57,7 +59,7 @@ class NewListForm extends React.PureComponent {
<span style={{ display: 'none' }}>{label}</span>
<input
className='setting-text'
className='setting-text new-list-form__input'
value={value}
disabled={disabled}
onChange={this.handleChange}
@ -65,12 +67,13 @@ class NewListForm extends React.PureComponent {
/>
</label>
<IconButton
<Button
className='new-list-form__btn'
disabled={disabled}
icon='plus'
title={title}
onClick={this.handleClick}
/>
>
{create}
</Button>
</form>
);
}

View File

@ -4,7 +4,6 @@ import PropTypes from 'prop-types';
import ImmutablePropTypes from 'react-immutable-proptypes';
import LoadingIndicator from '../../components/loading_indicator';
import Column from '../ui/components/column';
import ColumnBackButtonSlim from '../../components/column_back_button_slim';
import { fetchLists } from '../../actions/lists';
import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
import ImmutablePureComponent from 'react-immutable-pure-component';
@ -17,6 +16,7 @@ import ScrollableList from '../../components/scrollable_list';
const messages = defineMessages({
heading: { id: 'column.lists', defaultMessage: 'Lists' },
subheading: { id: 'lists.subheading', defaultMessage: 'Your lists' },
add: { id: 'lists.new.create', defaultMessage: 'Add List' },
});
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." />;
return (
<Column icon='list-ul' heading={intl.formatMessage(messages.heading)}>
<ColumnBackButtonSlim />
<Column icon='list-ul' heading={intl.formatMessage(messages.heading)} backBtnSlim>
<br/>
<ColumnSubheading text={intl.formatMessage(messages.add)} />
<NewListForm />
<br/>
<ColumnSubheading text={intl.formatMessage(messages.subheading)} />
<ScrollableList
scrollKey='lists'

View File

@ -7,7 +7,6 @@ import ImmutablePropTypes from 'react-immutable-proptypes';
import { debounce } from 'lodash';
import LoadingIndicator from '../../components/loading_indicator';
import Column from '../ui/components/column';
import ColumnBackButtonSlim from '../../components/column_back_button_slim';
import AccountContainer from '../../containers/account_container';
import { fetchMutes, expandMutes } from '../../actions/mutes';
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." />;
return (
<Column icon='volume-off' heading={intl.formatMessage(messages.heading)}>
<ColumnBackButtonSlim />
<Column icon='volume-off' heading={intl.formatMessage(messages.heading)} backBtnSlim>
<ScrollableList
scrollKey='mutes'
onLoadMore={this.handleLoadMore}

View File

@ -10,7 +10,6 @@ import { fetchStatus } from '../../actions/statuses';
import { 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 { makeGetStatus } from '../../selectors';
@ -72,8 +71,6 @@ class Reblogs extends ImmutablePureComponent {
return (
<Column>
<ColumnBackButton />
<ScrollableList
scrollKey='reblogs'
emptyMessage={emptyMessage}

View File

@ -4,7 +4,6 @@ import { defineMessages, injectIntl } from 'react-intl';
import Column from './column';
import ColumnHeader from './column_header';
import ColumnBackButtonSlim from '../../../components/column_back_button_slim';
import IconButton from '../../../components/icon_button';
const messages = defineMessages({
@ -30,7 +29,6 @@ class BundleColumnError extends React.PureComponent {
return (
<Column>
<ColumnHeader icon='exclamation-circle' type={formatMessage(messages.title)} />
<ColumnBackButtonSlim />
<div className='error-column'>
<IconButton title={formatMessage(messages.retry)} icon='refresh' onClick={this.handleRetry} size={64} />
{formatMessage(messages.body)}

View File

@ -2,6 +2,8 @@ import React from 'react';
import ColumnHeader from './column_header';
import PropTypes from 'prop-types';
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 {
@ -11,10 +13,11 @@ export default class Column extends React.PureComponent {
children: PropTypes.node,
active: PropTypes.bool,
hideHeadingOnMobile: PropTypes.bool,
backBtnSlim: PropTypes.bool,
};
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)));
@ -22,9 +25,13 @@ export default class Column extends React.PureComponent {
const header = showHeading && (
<ColumnHeader icon={icon} active={active} type={heading} columnHeaderId={columnHeaderId} />
);
const backBtn = backBtnSlim ? (<ColumnBackButtonSlim/>) : (<ColumnBackButton/>);
return (
<div role='region' aria-labelledby={columnHeaderId} className='column'>
{header}
{backBtn}
{children}
</div>
);

View File

@ -53,6 +53,8 @@ import {
Explore,
Groups,
GroupTimeline,
ListTimeline,
Lists,
} from './util/async-components';
import { me, meUsername } from '../../initial_state';
import { previewState as previewMediaState } from './components/media_modal';
@ -109,8 +111,8 @@ const LAYOUT = {
},
DEFAULT: {
LEFT: [
<WhoToFollowPanel />,
<LinkFooter />,
<WhoToFollowPanel key='0' />,
<LinkFooter key='1' />,
],
RIGHT: [
// <TrendsPanel />,
@ -120,9 +122,9 @@ const LAYOUT = {
TOP: null,
LEFT: null,
RIGHT: [
<WhoToFollowPanel />,
<WhoToFollowPanel key='0' />,
// <TrendsPanel />,
<LinkFooter />,
<LinkFooter key='1' />,
],
},
};
@ -177,8 +179,8 @@ class SwitchingColumnsArea extends React.PureComponent {
<WrappedRoute path='/tags/:id' component={HashtagTimeline} content={children} />
<Redirect from='/lists' to='/home' />
<Redirect from='/list' to='/home' />
<WrappedRoute path='/lists' layout={LAYOUT.DEFAULT} component={Lists} content={children} />
<WrappedRoute path='/list/:id' page={HomePage} component={ListTimeline} content={children} />
<WrappedRoute path='/notifications' layout={LAYOUT.DEFAULT} component={Notifications} content={children} />

View File

@ -1477,7 +1477,7 @@ a.account__display-name {
width: 100%;
margin: 0 auto;
overflow: hidden;
.column,
.drawer {
width: 100%;
@ -2495,11 +2495,14 @@ a.status-card.compact:hover {
background: transparent;
font: inherit;
text-align: left;
text-overflow: ellipsis;
text-decoration: none;
overflow: hidden;
white-space: nowrap;
&--sub {
font-size: 14px;
padding: 6px 10px;
}
&.grouped {
margin: 6px;
}
@ -2597,6 +2600,13 @@ a.status-card.compact:hover {
}
.column-header__setting-btn {
&--link {
text-decoration: none;
.fa {
margin-left: 10px;
}
}
&:hover {
color: $darker-text-color;
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 {
display: inline-block;
padding: 0;
@ -2961,6 +2977,7 @@ a.status-card.compact:hover {
flex: 1 1 auto;
align-items: center;
justify-content: center;
min-height: 160px;
@supports(display: grid) { // hack to fix Chrome <57
contain: strict;
@ -4364,12 +4381,11 @@ noscript {
}
.list-editor {
background: $ui-base-color;
flex-direction: column;
border-radius: 8px;
box-shadow: 2px 4px 15px rgba($base-shadow-color, 0.4);
width: 380px;
width: 100%;
overflow: hidden;
height: 100%;
overflow-y: scroll;
@media screen and (max-width: 420px) {
width: 90%;
@ -4384,10 +4400,6 @@ noscript {
border-radius: 8px 8px 0 0;
}
.drawer__pager {
height: 50vh;
}
.drawer__inner {
border-radius: 0 0 8px 8px;
@ -4399,7 +4411,9 @@ noscript {
}
&__accounts {
background: lighten($ui-base-color, 13%);
overflow-y: auto;
max-height: 200px;
}
.account__display-name {
@ -4413,17 +4427,31 @@ noscript {
}
.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 {
background: $ui-base-color;
flex-direction: column;
border-radius: 8px;
box-shadow: 2px 4px 15px rgba($base-shadow-color, 0.4);
width: 380px;
width: 100%;
overflow: hidden;
height: 100%;
overflow-y: scroll;
@media screen and (max-width: 420px) {
width: 90%;
@ -4431,22 +4459,24 @@ noscript {
&__account {
background: lighten($ui-base-color, 13%);
border-radius: 4px;
}
&__lists {
background: lighten($ui-base-color, 13%);
height: 50vh;
border-radius: 0 0 8px 8px;
overflow-y: auto;
}
.list {
padding: 10px;
padding: 4px;
border-bottom: 1px solid lighten($ui-base-color, 8%);
}
.list__wrapper {
display: flex;
.account__relationship {
padding: 8px 5px 0 5px;
}
}
.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 {
max-width: 80vw;
max-height: 80vh;
@ -4827,6 +4869,7 @@ noscript {
border-radius: 6px;
flex-direction: column;
width: 600px;
margin: 10px 0;
&__header {
display: block;
@ -4868,8 +4911,9 @@ noscript {
}
@media screen and (max-width:895px) {
height: 100vh;
width: 100vw;
margin: 0;
height: 98vh;
width: 98vw;
}
}