Merge branch 'groups-updates' of https://code.gab.com/gab/social/gab-social into develop
This commit is contained in:
@@ -68,6 +68,7 @@ class ComposeForm extends ImmutablePureComponent {
|
||||
anyMedia: PropTypes.bool,
|
||||
shouldCondense: PropTypes.bool,
|
||||
autoFocus: PropTypes.bool,
|
||||
group: ImmutablePropTypes.map,
|
||||
isModalOpen: PropTypes.bool,
|
||||
};
|
||||
|
||||
@@ -119,7 +120,7 @@ class ComposeForm extends ImmutablePureComponent {
|
||||
return;
|
||||
}
|
||||
|
||||
this.props.onSubmit(this.context.router ? this.context.router.history : null);
|
||||
this.props.onSubmit(this.context.router ? this.context.router.history : null, this.props.group);
|
||||
}
|
||||
|
||||
onSuggestionsClearRequested = () => {
|
||||
|
||||
@@ -34,8 +34,8 @@ const mapDispatchToProps = (dispatch) => ({
|
||||
dispatch(changeCompose(text));
|
||||
},
|
||||
|
||||
onSubmit (router) {
|
||||
dispatch(submitCompose(router));
|
||||
onSubmit (router, group) {
|
||||
dispatch(submitCompose(router, group));
|
||||
},
|
||||
|
||||
onClearSuggestions () {
|
||||
|
||||
@@ -1,78 +1,111 @@
|
||||
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 { changeValue, submit, reset } from '../../../actions/group_editor';
|
||||
import Icon from '../../../components/icon';
|
||||
import { defineMessages, injectIntl } from 'react-intl';
|
||||
import classNames from 'classnames';
|
||||
|
||||
const messages = defineMessages({
|
||||
label: { id: 'groups.new.title_placeholder', defaultMessage: 'New group title' },
|
||||
title: { id: 'groups.new.create', defaultMessage: 'Add group' },
|
||||
title: { id: 'groups.form.title', defaultMessage: 'Enter a new group title' },
|
||||
description: { id: 'groups.form.description', defaultMessage: 'Enter the group description' },
|
||||
coverImage: { id: 'groups.form.coverImage', defaultMessage: 'Upload a banner image' },
|
||||
coverImageChange: { id: 'groups.form.coverImageChange', defaultMessage: 'Banner image selected' },
|
||||
create: { id: 'groups.form.create', defaultMessage: 'Create group' },
|
||||
});
|
||||
|
||||
const mapStateToProps = state => ({
|
||||
value: state.getIn(['groupEditor', 'title']),
|
||||
disabled: state.getIn(['groupEditor', 'isSubmitting']),
|
||||
title: state.getIn(['group_editor', 'title']),
|
||||
description: state.getIn(['group_editor', 'description']),
|
||||
coverImage: state.getIn(['group_editor', 'coverImage']),
|
||||
disabled: state.getIn(['group_editor', 'isSubmitting']),
|
||||
});
|
||||
|
||||
const mapDispatchToProps = dispatch => ({
|
||||
onChange: value => dispatch(changeListEditorTitle(value)),
|
||||
onSubmit: () => dispatch(submitListEditor(true)),
|
||||
onTitleChange: value => dispatch(changeValue('title', value)),
|
||||
onDescriptionChange: value => dispatch(changeValue('description', value)),
|
||||
onCoverImageChange: value => dispatch(changeValue('coverImage', value)),
|
||||
onSubmit: routerHistory => dispatch(submit(routerHistory)),
|
||||
reset: () => dispatch(reset()),
|
||||
});
|
||||
|
||||
export default @connect(mapStateToProps, mapDispatchToProps)
|
||||
@injectIntl
|
||||
class Create extends React.PureComponent {
|
||||
|
||||
static propTypes = {
|
||||
value: PropTypes.string.isRequired,
|
||||
disabled: PropTypes.bool,
|
||||
intl: PropTypes.object.isRequired,
|
||||
onChange: PropTypes.func.isRequired,
|
||||
onSubmit: PropTypes.func.isRequired,
|
||||
};
|
||||
static contextTypes = {
|
||||
router: PropTypes.object
|
||||
}
|
||||
|
||||
handleChange = e => {
|
||||
this.props.onChange(e.target.value);
|
||||
}
|
||||
static propTypes = {
|
||||
title: PropTypes.string.isRequired,
|
||||
description: PropTypes.string.isRequired,
|
||||
coverImage: PropTypes.object,
|
||||
disabled: PropTypes.bool,
|
||||
intl: PropTypes.object.isRequired,
|
||||
onTitleChange: PropTypes.func.isRequired,
|
||||
onSubmit: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
handleSubmit = e => {
|
||||
e.preventDefault();
|
||||
this.props.onSubmit();
|
||||
}
|
||||
componentWillMount() {
|
||||
this.props.reset();
|
||||
}
|
||||
|
||||
handleClick = () => {
|
||||
this.props.onSubmit();
|
||||
}
|
||||
handleTitleChange = e => {
|
||||
this.props.onTitleChange(e.target.value);
|
||||
}
|
||||
|
||||
render () {
|
||||
const { value, disabled, intl } = this.props;
|
||||
handleDescriptionChange = e => {
|
||||
this.props.onDescriptionChange(e.target.value);
|
||||
}
|
||||
|
||||
const label = intl.formatMessage(messages.label);
|
||||
const title = intl.formatMessage(messages.title);
|
||||
handleCoverImageChange = e => {
|
||||
this.props.onCoverImageChange(e.target.files[0]);
|
||||
}
|
||||
|
||||
return (
|
||||
<form className='column-inline-form' onSubmit={this.handleSubmit}>
|
||||
<label>
|
||||
<span style={{ display: 'none' }}>{label}</span>
|
||||
handleSubmit = e => {
|
||||
e.preventDefault();
|
||||
this.props.onSubmit(this.context.router.history);
|
||||
}
|
||||
|
||||
<input
|
||||
className='setting-text'
|
||||
value={value}
|
||||
disabled={disabled}
|
||||
onChange={this.handleChange}
|
||||
placeholder={label}
|
||||
/>
|
||||
</label>
|
||||
render () {
|
||||
const { title, description, coverImage, disabled, intl } = this.props;
|
||||
|
||||
<IconButton
|
||||
disabled={disabled}
|
||||
icon='plus'
|
||||
title={title}
|
||||
onClick={this.handleClick}
|
||||
/>
|
||||
</form>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<form className='group-form' onSubmit={this.handleSubmit}>
|
||||
<div>
|
||||
<input
|
||||
className='group-form__input'
|
||||
value={title}
|
||||
disabled={disabled}
|
||||
onChange={this.handleTitleChange}
|
||||
placeholder={intl.formatMessage(messages.title)}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<input
|
||||
className='group-form__input'
|
||||
value={description}
|
||||
disabled={disabled}
|
||||
onChange={this.handleDescriptionChange}
|
||||
placeholder={intl.formatMessage(messages.description)}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label htmlFor='group_cover_image' className={classNames('group-form__file-label', { 'group-form__file-label--selected': coverImage !== null })}>
|
||||
<Icon id='camera' /> {intl.formatMessage(coverImage === null ? messages.coverImage : messages.coverImageChange)}
|
||||
</label>
|
||||
<input
|
||||
type='file'
|
||||
className='group-form__file'
|
||||
id='group_cover_image'
|
||||
disabled={disabled}
|
||||
onChange={this.handleCoverImageChange}
|
||||
/>
|
||||
<button className='standard-small'>{intl.formatMessage(messages.create)}</button>
|
||||
</div>
|
||||
</form>
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
146
app/javascript/gabsocial/features/groups/edit/index.js
Normal file
146
app/javascript/gabsocial/features/groups/edit/index.js
Normal file
@@ -0,0 +1,146 @@
|
||||
import React from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import PropTypes from 'prop-types';
|
||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||
import { changeValue, submit, setUp } from '../../../actions/group_editor';
|
||||
import Icon from '../../../components/icon';
|
||||
import { defineMessages, injectIntl } from 'react-intl';
|
||||
import LoadingIndicator from '../../../components/loading_indicator';
|
||||
import Column from '../../../components/column';
|
||||
import classNames from 'classnames';
|
||||
|
||||
const messages = defineMessages({
|
||||
title: { id: 'groups.form.title', defaultMessage: 'Title' },
|
||||
description: { id: 'groups.form.description', defaultMessage: 'Description' },
|
||||
coverImage: { id: 'groups.form.coverImage', defaultMessage: 'Upload new banner image (optional)' },
|
||||
coverImageChange: { id: 'groups.form.coverImageChange', defaultMessage: 'Banner image selected' },
|
||||
update: { id: 'groups.form.update', defaultMessage: 'Update group' },
|
||||
});
|
||||
|
||||
const mapStateToProps = (state, props) => ({
|
||||
group: state.getIn(['groups', props.params.id]),
|
||||
title: state.getIn(['group_editor', 'title']),
|
||||
description: state.getIn(['group_editor', 'description']),
|
||||
coverImage: state.getIn(['group_editor', 'coverImage']),
|
||||
disabled: state.getIn(['group_editor', 'isSubmitting']),
|
||||
});
|
||||
|
||||
const mapDispatchToProps = dispatch => ({
|
||||
onTitleChange: value => dispatch(changeValue('title', value)),
|
||||
onDescriptionChange: value => dispatch(changeValue('description', value)),
|
||||
onCoverImageChange: value => dispatch(changeValue('coverImage', value)),
|
||||
onSubmit: routerHistory => dispatch(submit(routerHistory)),
|
||||
setUp: group => dispatch(setUp(group)),
|
||||
});
|
||||
|
||||
export default @connect(mapStateToProps, mapDispatchToProps)
|
||||
@injectIntl
|
||||
class Edit extends React.PureComponent {
|
||||
|
||||
static contextTypes = {
|
||||
router: PropTypes.object
|
||||
}
|
||||
|
||||
static propTypes = {
|
||||
group: ImmutablePropTypes.map,
|
||||
title: PropTypes.string.isRequired,
|
||||
description: PropTypes.string.isRequired,
|
||||
coverImage: PropTypes.object,
|
||||
disabled: PropTypes.bool,
|
||||
intl: PropTypes.object.isRequired,
|
||||
onTitleChange: PropTypes.func.isRequired,
|
||||
onSubmit: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
componentWillMount(nextProps) {
|
||||
if (this.props.group) {
|
||||
this.props.setUp(this.props.group);
|
||||
}
|
||||
}
|
||||
|
||||
componentWillReceiveProps(nextProps) {
|
||||
if (!this.props.group && nextProps.group) {
|
||||
this.props.setUp(nextProps.group);
|
||||
}
|
||||
}
|
||||
|
||||
handleTitleChange = e => {
|
||||
this.props.onTitleChange(e.target.value);
|
||||
}
|
||||
|
||||
handleDescriptionChange = e => {
|
||||
this.props.onDescriptionChange(e.target.value);
|
||||
}
|
||||
|
||||
handleCoverImageChange = e => {
|
||||
this.props.onCoverImageChange(e.target.files[0]);
|
||||
}
|
||||
|
||||
handleSubmit = e => {
|
||||
e.preventDefault();
|
||||
this.props.onSubmit(this.context.router.history);
|
||||
}
|
||||
|
||||
handleClick = () => {
|
||||
this.props.onSubmit(this.context.router.history);
|
||||
}
|
||||
|
||||
render () {
|
||||
const { group, title, description, coverImage, disabled, intl } = this.props;
|
||||
|
||||
if (typeof group === 'undefined') {
|
||||
return (
|
||||
<Column>
|
||||
<LoadingIndicator />
|
||||
</Column>
|
||||
);
|
||||
} else if (group === false) {
|
||||
return (
|
||||
<Column>
|
||||
<MissingIndicator />
|
||||
</Column>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<form className='group-form' onSubmit={this.handleSubmit}>
|
||||
<div>
|
||||
<input
|
||||
className='group-form__input'
|
||||
value={title}
|
||||
disabled={disabled}
|
||||
onChange={this.handleTitleChange}
|
||||
placeholder={intl.formatMessage(messages.title)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<input
|
||||
className='group-form__input'
|
||||
value={description}
|
||||
disabled={disabled}
|
||||
onChange={this.handleDescriptionChange}
|
||||
placeholder={intl.formatMessage(messages.description)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label htmlFor='group_cover_image' className={classNames('group-form__file-label', { 'group-form__file-label--selected': coverImage !== null })}>
|
||||
<Icon id='camera' /> {intl.formatMessage(coverImage === null ? messages.coverImage : messages.coverImageChange)}
|
||||
</label>
|
||||
|
||||
<input
|
||||
type='file'
|
||||
className='group-form__file'
|
||||
id='group_cover_image'
|
||||
disabled={disabled}
|
||||
onChange={this.handleCoverImageChange}
|
||||
/>
|
||||
|
||||
<button>{intl.formatMessage(messages.update)}</button>
|
||||
</div>
|
||||
</form>
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
54
app/javascript/gabsocial/features/groups/index/card.js
Normal file
54
app/javascript/gabsocial/features/groups/index/card.js
Normal file
@@ -0,0 +1,54 @@
|
||||
import React from 'react';
|
||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||
import ImmutablePureComponent from 'react-immutable-pure-component';
|
||||
import { defineMessages, injectIntl } from 'react-intl';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { shortNumberFormat } from '../../../utils/numbers';
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
const messages = defineMessages({
|
||||
members: { id: 'groups.card.members', defaultMessage: 'Members' },
|
||||
view: { id: 'groups.card.view', defaultMessage: 'View' },
|
||||
join: { id: 'groups.card.join', defaultMessage: 'Join' },
|
||||
role_member: { id: 'groups.card.roles.member', defaultMessage: 'You\'re a member' },
|
||||
role_admin: { id: 'groups.card.roles.admin', defaultMessage: 'You\'re an admin' },
|
||||
});
|
||||
|
||||
const mapStateToProps = (state, { id }) => ({
|
||||
group: state.getIn(['groups', id]),
|
||||
relationships: state.getIn(['group_relationships', id]),
|
||||
});
|
||||
|
||||
export default @connect(mapStateToProps)
|
||||
@injectIntl
|
||||
class GroupCard extends ImmutablePureComponent {
|
||||
static propTypes = {
|
||||
group: ImmutablePropTypes.map,
|
||||
relationships: ImmutablePropTypes.map,
|
||||
}
|
||||
|
||||
getRole() {
|
||||
const { intl, relationships } = this.props;
|
||||
|
||||
if (!relationships) return null;
|
||||
if (relationships.get('admin')) return intl.formatMessage(messages.role_admin);
|
||||
if (relationships.get('member')) return intl.formatMessage(messages.role_member);
|
||||
}
|
||||
|
||||
render() {
|
||||
const { intl, group } = this.props;
|
||||
const coverImageUrl = group.get('cover_image_url');
|
||||
const role = this.getRole();
|
||||
|
||||
return (
|
||||
<Link to={`/groups/${group.get('id')}`} className="group-card">
|
||||
<div className="group-card__header">{coverImageUrl && <img alt="" src={coverImageUrl} />}</div>
|
||||
<div className="group-card__content">
|
||||
<div className="group-card__title">{group.get('title')}</div>
|
||||
<div className="group-card__meta"><strong>{shortNumberFormat(group.get('member_count'))}</strong> {intl.formatMessage(messages.members)}{role && <span> · {role}</span>}</div>
|
||||
<div className="group-card__description">{group.get('description')}</div>
|
||||
</div>
|
||||
</Link>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -2,77 +2,88 @@ import React from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import PropTypes from 'prop-types';
|
||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||
import LoadingIndicator from '../../../components/loading_indicator';
|
||||
import Column from '../../ui/components/column';
|
||||
import { fetchGroups } from '../../../actions/groups';
|
||||
import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
|
||||
import { defineMessages, injectIntl } from 'react-intl';
|
||||
import ImmutablePureComponent from 'react-immutable-pure-component';
|
||||
import ColumnLink from '../../ui/components/column_link';
|
||||
import ColumnSubheading from '../../ui/components/column_subheading';
|
||||
import NewGroupForm from '../create';
|
||||
import { createSelector } from 'reselect';
|
||||
import ScrollableList from '../../../components/scrollable_list';
|
||||
import { Link } from 'react-router-dom';
|
||||
import classNames from 'classnames';
|
||||
import GroupCard from './card';
|
||||
import GroupCreate from '../create';
|
||||
|
||||
const messages = defineMessages({
|
||||
heading: { id: 'column.groups', defaultMessage: 'Groups' },
|
||||
subheading: { id: 'groups.subheading', defaultMessage: 'Your groups' },
|
||||
heading: { id: 'column.groups', defaultMessage: 'Groups' },
|
||||
create: { id: 'groups.create', defaultMessage: 'Create group' },
|
||||
tab_featured: { id: 'groups.tab_featured', defaultMessage: 'Featured' },
|
||||
tab_member: { id: 'groups.tab_member', defaultMessage: 'Groups you\'re in' },
|
||||
tab_admin: { id: 'groups.tab_admin', defaultMessage: 'Groups you manage' },
|
||||
});
|
||||
|
||||
const getOrderedGroups = createSelector([state => state.get('groups')], groups => {
|
||||
if (!groups) {
|
||||
return groups;
|
||||
}
|
||||
|
||||
return groups.toList().filter(item => !!item);
|
||||
});
|
||||
|
||||
const mapStateToProps = state => ({
|
||||
groups: getOrderedGroups(state),
|
||||
const mapStateToProps = (state, { activeTab }) => ({
|
||||
groupIds: state.getIn(['group_lists', activeTab]),
|
||||
});
|
||||
|
||||
export default @connect(mapStateToProps)
|
||||
@injectIntl
|
||||
class Groups extends ImmutablePureComponent {
|
||||
static propTypes = {
|
||||
params: PropTypes.object.isRequired,
|
||||
activeTab: PropTypes.string.isRequired,
|
||||
showCreateForm: PropTypes.bool,
|
||||
dispatch: PropTypes.func.isRequired,
|
||||
groups: ImmutablePropTypes.map,
|
||||
groupIds: ImmutablePropTypes.list,
|
||||
intl: PropTypes.object.isRequired,
|
||||
};
|
||||
|
||||
static propTypes = {
|
||||
params: PropTypes.object.isRequired,
|
||||
dispatch: PropTypes.func.isRequired,
|
||||
groups: ImmutablePropTypes.list,
|
||||
intl: PropTypes.object.isRequired,
|
||||
};
|
||||
componentWillMount () {
|
||||
this.props.dispatch(fetchGroups(this.props.activeTab));
|
||||
}
|
||||
|
||||
componentWillMount () {
|
||||
this.props.dispatch(fetchGroups());
|
||||
}
|
||||
componentDidUpdate(oldProps) {
|
||||
if (this.props.activeTab && this.props.activeTab !== oldProps.activeTab) {
|
||||
this.props.dispatch(fetchGroups(this.props.activeTab));
|
||||
}
|
||||
}
|
||||
|
||||
render () {
|
||||
const { intl, groups } = this.props;
|
||||
renderHeader() {
|
||||
const { intl, activeTab } = this.props;
|
||||
|
||||
if (!groups) {
|
||||
return (
|
||||
<Column>
|
||||
<LoadingIndicator />
|
||||
</Column>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<div className="group-column-header">
|
||||
<div className="group-column-header__cta"><Link to="/groups/create" className="button standard-small">{intl.formatMessage(messages.create)}</Link></div>
|
||||
<div className="group-column-header__title">{intl.formatMessage(messages.heading)}</div>
|
||||
|
||||
const emptyMessage = <FormattedMessage id='empty_column.groups' defaultMessage="No groups." />;
|
||||
<div className="column-header__wrapper">
|
||||
<h1 className="column-header">
|
||||
<Link to='/groups' className={classNames('btn grouped', {'active': 'featured' === activeTab})}>
|
||||
{intl.formatMessage(messages.tab_featured)}
|
||||
</Link>
|
||||
|
||||
return (
|
||||
<Column icon='list-ul' heading={intl.formatMessage(messages.heading)} backBtnSlim>
|
||||
<NewGroupForm />
|
||||
<Link to='/groups/browse/member' className={classNames('btn grouped', {'active': 'member' === activeTab})}>
|
||||
{intl.formatMessage(messages.tab_member)}
|
||||
</Link>
|
||||
|
||||
<ColumnSubheading text={intl.formatMessage(messages.subheading)} />
|
||||
<ScrollableList
|
||||
scrollKey='lists'
|
||||
emptyMessage={emptyMessage}
|
||||
>
|
||||
{groups.map(group =>
|
||||
<ColumnLink key={group.get('id')} to={`/groups/${group.get('id')}`} icon='list-ul' text={group.get('title')} />
|
||||
)}
|
||||
</ScrollableList>
|
||||
</Column>
|
||||
);
|
||||
}
|
||||
<Link to='/groups/browse/admin' className={classNames('btn grouped', {'active': 'admin' === activeTab})}>
|
||||
{intl.formatMessage(messages.tab_admin)}
|
||||
</Link>
|
||||
</h1>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
render () {
|
||||
const { groupIds, showCreateForm } = this.props;
|
||||
|
||||
return (
|
||||
<div>
|
||||
{!showCreateForm && this.renderHeader()}
|
||||
{showCreateForm && <GroupCreate /> }
|
||||
|
||||
<div className="group-card-list">
|
||||
{groupIds.map(id => <GroupCard key={id} id={id} />)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
73
app/javascript/gabsocial/features/groups/members/index.js
Normal file
73
app/javascript/gabsocial/features/groups/members/index.js
Normal file
@@ -0,0 +1,73 @@
|
||||
import React from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import ImmutablePureComponent from 'react-immutable-pure-component';
|
||||
import PropTypes from 'prop-types';
|
||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||
import { debounce } from 'lodash';
|
||||
import LoadingIndicator from '../../../components/loading_indicator';
|
||||
import {
|
||||
fetchMembers,
|
||||
expandMembers,
|
||||
} from '../../../actions/groups';
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
import AccountContainer from '../../../containers/account_container';
|
||||
import Column from '../../ui/components/column';
|
||||
import ScrollableList from '../../../components/scrollable_list';
|
||||
|
||||
const mapStateToProps = (state, { params: { id } }) => ({
|
||||
group: state.getIn(['groups', id]),
|
||||
accountIds: state.getIn(['user_lists', 'groups', id, 'items']),
|
||||
hasMore: !!state.getIn(['user_lists', 'groups', id, 'next']),
|
||||
});
|
||||
|
||||
export default @connect(mapStateToProps)
|
||||
class GroupMembers extends ImmutablePureComponent {
|
||||
|
||||
static propTypes = {
|
||||
params: PropTypes.object.isRequired,
|
||||
dispatch: PropTypes.func.isRequired,
|
||||
accountIds: ImmutablePropTypes.list,
|
||||
hasMore: PropTypes.bool,
|
||||
};
|
||||
|
||||
componentWillMount () {
|
||||
const { params: { id } } = this.props;
|
||||
|
||||
this.props.dispatch(fetchMembers(id));
|
||||
}
|
||||
|
||||
componentWillReceiveProps (nextProps) {
|
||||
if (nextProps.params.id !== this.props.params.id) {
|
||||
this.props.dispatch(fetchMembers(nextProps.params.id));
|
||||
}
|
||||
}
|
||||
|
||||
handleLoadMore = debounce(() => {
|
||||
this.props.dispatch(expandMembers(this.props.params.id));
|
||||
}, 300, { leading: true });
|
||||
|
||||
render () {
|
||||
const { accountIds, hasMore, group } = this.props;
|
||||
|
||||
if (!group || !accountIds) {
|
||||
return (
|
||||
<Column>
|
||||
<LoadingIndicator />
|
||||
</Column>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Column>
|
||||
<ScrollableList
|
||||
scrollKey='members'
|
||||
hasMore={hasMore}
|
||||
onLoadMore={this.handleLoadMore}
|
||||
emptyMessage={<FormattedMessage id='group.members.empty' defaultMessage='This group does not has any members.' />}
|
||||
>
|
||||
{accountIds.map(id => <AccountContainer key={id} id={id} withNote={false} />)}
|
||||
</ScrollableList>
|
||||
</Column>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
import React from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import ImmutablePureComponent from 'react-immutable-pure-component';
|
||||
import PropTypes from 'prop-types';
|
||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||
import { debounce } from 'lodash';
|
||||
import LoadingIndicator from '../../../components/loading_indicator';
|
||||
import {
|
||||
fetchRemovedAccounts,
|
||||
expandRemovedAccounts,
|
||||
removeRemovedAccount,
|
||||
} from '../../../actions/groups';
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
import AccountContainer from '../../../containers/account_container';
|
||||
import Column from '../../ui/components/column';
|
||||
import ScrollableList from '../../../components/scrollable_list';
|
||||
import { defineMessages, injectIntl } from 'react-intl';
|
||||
|
||||
const messages = defineMessages({
|
||||
remove: { id: 'groups.removed_accounts', defaultMessage: 'Allow joining' },
|
||||
});
|
||||
|
||||
const mapStateToProps = (state, { params: { id } }) => ({
|
||||
group: state.getIn(['groups', id]),
|
||||
accountIds: state.getIn(['user_lists', 'groups_removed_accounts', id, 'items']),
|
||||
hasMore: !!state.getIn(['user_lists', 'groups_removed_accounts', id, 'next']),
|
||||
});
|
||||
|
||||
export default @connect(mapStateToProps)
|
||||
@injectIntl
|
||||
class GroupRemovedAccounts extends ImmutablePureComponent {
|
||||
|
||||
static propTypes = {
|
||||
params: PropTypes.object.isRequired,
|
||||
dispatch: PropTypes.func.isRequired,
|
||||
accountIds: ImmutablePropTypes.list,
|
||||
hasMore: PropTypes.bool,
|
||||
};
|
||||
|
||||
componentWillMount () {
|
||||
const { params: { id } } = this.props;
|
||||
|
||||
this.props.dispatch(fetchRemovedAccounts(id));
|
||||
}
|
||||
|
||||
componentWillReceiveProps (nextProps) {
|
||||
if (nextProps.params.id !== this.props.params.id) {
|
||||
this.props.dispatch(fetchRemovedAccounts(nextProps.params.id));
|
||||
}
|
||||
}
|
||||
|
||||
handleLoadMore = debounce(() => {
|
||||
this.props.dispatch(expandRemovedAccounts(this.props.params.id));
|
||||
}, 300, { leading: true });
|
||||
|
||||
render () {
|
||||
const { accountIds, hasMore, group, intl } = this.props;
|
||||
|
||||
if (!group || !accountIds) {
|
||||
return (
|
||||
<Column>
|
||||
<LoadingIndicator />
|
||||
</Column>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Column>
|
||||
<ScrollableList
|
||||
scrollKey='removed_accounts'
|
||||
hasMore={hasMore}
|
||||
onLoadMore={this.handleLoadMore}
|
||||
emptyMessage={<FormattedMessage id='group.removed_accounts.empty' defaultMessage='This group does not has any removed accounts.' />}
|
||||
>
|
||||
{accountIds.map(id => <AccountContainer
|
||||
key={id}
|
||||
id={id}
|
||||
actionIcon="remove"
|
||||
onActionClick={() => this.props.dispatch(removeRemovedAccount(group.get('id'), id))}
|
||||
actionTitle={intl.formatMessage(messages.remove)}
|
||||
/>)}
|
||||
</ScrollableList>
|
||||
</Column>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
import React from 'react';
|
||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||
import ImmutablePureComponent from 'react-immutable-pure-component';
|
||||
import { defineMessages, injectIntl } from 'react-intl';
|
||||
import { connect } from 'react-redux';
|
||||
import Item from './item';
|
||||
import Icon from 'gabsocial/components/icon';
|
||||
import { Link } from 'react-router-dom';
|
||||
|
||||
const messages = defineMessages({
|
||||
title: { id: 'groups.sidebar-panel.title', defaultMessage: 'Groups you\'re in' },
|
||||
show_all: { id: 'groups.sidebar-panel.show_all', defaultMessage: 'Show all' },
|
||||
});
|
||||
|
||||
const mapStateToProps = (state, { id }) => ({
|
||||
groupIds: state.getIn(['group_lists', 'member']),
|
||||
});
|
||||
|
||||
export default @connect(mapStateToProps)
|
||||
@injectIntl
|
||||
class GroupSidebarPanel extends ImmutablePureComponent {
|
||||
static propTypes = {
|
||||
groupIds: ImmutablePropTypes.list,
|
||||
}
|
||||
|
||||
render() {
|
||||
const { intl, groupIds } = this.props;
|
||||
const count = groupIds.count();
|
||||
|
||||
// Only when there are groups to show
|
||||
if (count === 0) return null;
|
||||
|
||||
return (
|
||||
<div className='wtf-panel group-sidebar-panel'>
|
||||
<div className='wtf-panel-header'>
|
||||
<Icon id='users' className='wtf-panel-header__icon' />
|
||||
<span className='wtf-panel-header__label'>{intl.formatMessage(messages.title)}</span>
|
||||
</div>
|
||||
|
||||
<div className='wtf-panel__content'>
|
||||
<div className="group-sidebar-panel__items">
|
||||
{groupIds.slice(0, 10).map(groupId => <Item key={groupId} id={groupId} />)}
|
||||
{count > 10 && <Link className="group-sidebar-panel__items__show-all" to='/groups/browse/member'>{intl.formatMessage(messages.show_all)}</Link>}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
import React from 'react';
|
||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||
import ImmutablePureComponent from 'react-immutable-pure-component';
|
||||
import { defineMessages, injectIntl } from 'react-intl';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { shortNumberFormat } from '../../../utils/numbers';
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
const messages = defineMessages({
|
||||
new_statuses: { id: 'groups.sidebar-panel.item.view', defaultMessage: 'new gabs' },
|
||||
no_recent_activity: { id: 'groups.sidebar-panel.item.no_recent_activity', defaultMessage: 'No recent activity' },
|
||||
});
|
||||
|
||||
const mapStateToProps = (state, { id }) => ({
|
||||
group: state.getIn(['groups', id]),
|
||||
relationships: state.getIn(['group_relationships', id]),
|
||||
});
|
||||
|
||||
export default @connect(mapStateToProps)
|
||||
@injectIntl
|
||||
class Item extends ImmutablePureComponent {
|
||||
static propTypes = {
|
||||
group: ImmutablePropTypes.map,
|
||||
relationships: ImmutablePropTypes.map,
|
||||
}
|
||||
|
||||
render() {
|
||||
const { intl, group, relationships } = this.props;
|
||||
|
||||
// Wait for relationships
|
||||
if (!relationships) return null;
|
||||
|
||||
const unreadCount = relationships.get('unread_count');
|
||||
|
||||
return (
|
||||
<Link to={`/groups/${group.get('id')}`} className="group-sidebar-panel__item">
|
||||
<div className="group-sidebar-panel__item__title">{group.get('title')}</div>
|
||||
<div className="group-sidebar-panel__item__meta">
|
||||
{unreadCount > 0 && <span className="group-sidebar-panel__item__meta__unread">{shortNumberFormat(unreadCount)} {intl.formatMessage(messages.new_statuses)}</span>}
|
||||
{unreadCount === 0 && <span>{intl.formatMessage(messages.no_recent_activity)}</span>}
|
||||
</div>
|
||||
</Link>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,44 +1,77 @@
|
||||
import React from 'react';
|
||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||
import PropTypes from 'prop-types';
|
||||
import InnerHeader from './inner_header';
|
||||
import Button from 'gabsocial/components/button';
|
||||
import ImmutablePureComponent from 'react-immutable-pure-component';
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
import { defineMessages, injectIntl } from 'react-intl';
|
||||
import { NavLink } from 'react-router-dom';
|
||||
import DropdownMenuContainer from '../../../../containers/dropdown_menu_container';
|
||||
|
||||
export default class Header extends ImmutablePureComponent {
|
||||
const messages = defineMessages({
|
||||
join: { id: 'groups.join', defaultMessage: 'Join group' },
|
||||
leave: { id: 'groups.leave', defaultMessage: 'Leave group' },
|
||||
removed_accounts: { id: 'groups.removed_accounts', defaultMessage: 'Removed Accounts' },
|
||||
edit: { id: 'groups.edit', defaultMessage: 'Edit' }
|
||||
});
|
||||
|
||||
static propTypes = {
|
||||
group: ImmutablePropTypes.map,
|
||||
relationships: ImmutablePropTypes.map,
|
||||
toggleMembership: PropTypes.func.isRequired,
|
||||
};
|
||||
export default @injectIntl
|
||||
class Header extends ImmutablePureComponent {
|
||||
static propTypes = {
|
||||
group: ImmutablePropTypes.map,
|
||||
relationships: ImmutablePropTypes.map,
|
||||
toggleMembership: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
static contextTypes = {
|
||||
router: PropTypes.object,
|
||||
};
|
||||
static contextTypes = {
|
||||
router: PropTypes.object,
|
||||
};
|
||||
|
||||
render () {
|
||||
const { group, relationships, toggleMembership } = this.props;
|
||||
getActionButton() {
|
||||
const { group, relationships, toggleMembership, intl } = this.props;
|
||||
const toggle = () => toggleMembership(group, relationships);
|
||||
|
||||
if (group === null) {
|
||||
return null;
|
||||
}
|
||||
if (!relationships) {
|
||||
return '';
|
||||
} else if (!relationships.get('member')) {
|
||||
return <Button className='logo-button' text={intl.formatMessage(messages.join)} onClick={toggle} />;
|
||||
} else if (relationships.get('member')) {
|
||||
return <Button className='logo-button' text={intl.formatMessage(messages.leave, { name: group.get('title') })} onClick={toggle} />;
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div className='account-timeline__header'>
|
||||
<InnerHeader
|
||||
group={group}
|
||||
relationships={relationships}
|
||||
toggleMembership={toggleMembership}
|
||||
/>
|
||||
getAdminMenu() {
|
||||
const { group, intl } = this.props;
|
||||
|
||||
<div className='account__section-headline'>
|
||||
<NavLink exact to={`/groups/${group.get('id')}`}><FormattedMessage id='groups.posts' defaultMessage='Posts' /></NavLink>
|
||||
<NavLink exact to={`/groups/${group.get('id')}/accounts`}><FormattedMessage id='group.accounts' defaultMessage='Members' /></NavLink>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
const menu = [
|
||||
{ text: intl.formatMessage(messages.edit), to: `/groups/${group.get('id')}/edit` },
|
||||
{ text: intl.formatMessage(messages.removed_accounts), to: `/groups/${group.get('id')}/removed_accounts` },
|
||||
];
|
||||
|
||||
return <DropdownMenuContainer items={menu} icon='ellipsis-v' size={24} direction='right' />;
|
||||
}
|
||||
|
||||
render () {
|
||||
const { group, relationships } = this.props;
|
||||
|
||||
if (!group || !relationships) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className='group__header-container'>
|
||||
<div className="group__header">
|
||||
<div className='group__cover'>
|
||||
<img src={group.get('cover_image_url')} alt='' className='parallax' />
|
||||
</div>
|
||||
|
||||
<div className='group__tabs'>
|
||||
<NavLink exact className='group__tabs__tab' activeClassName='group__tabs__tab--active' to={`/groups/${group.get('id')}`}>Posts</NavLink>
|
||||
<NavLink exact className='group__tabs__tab' activeClassName='group__tabs__tab--active' to={`/groups/${group.get('id')}/members`}>Members</NavLink>
|
||||
{this.getActionButton()}
|
||||
{relationships.get('admin') && this.getAdminMenu()}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,96 +0,0 @@
|
||||
'use strict';
|
||||
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||
import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
|
||||
import Button from 'gabsocial/components/button';
|
||||
import ImmutablePureComponent from 'react-immutable-pure-component';
|
||||
import Icon from 'gabsocial/components/icon';
|
||||
import DropdownMenuContainer from 'gabsocial/containers/dropdown_menu_container';
|
||||
|
||||
const messages = defineMessages({
|
||||
join: { id: 'groups.join', defaultMessage: 'Join' },
|
||||
leave: { id: 'groups.leave', defaultMessage: 'Leave' },
|
||||
});
|
||||
|
||||
export default @injectIntl
|
||||
class InnerHeader extends ImmutablePureComponent {
|
||||
|
||||
static propTypes = {
|
||||
group: ImmutablePropTypes.map,
|
||||
relationships: ImmutablePropTypes.map,
|
||||
toggleMembership: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
isStatusesPageActive = (match, location) => {
|
||||
if (!match) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return !location.pathname.match(/\/(accounts)\/?$/);
|
||||
}
|
||||
|
||||
render () {
|
||||
const { group, relationships, intl } = this.props;
|
||||
|
||||
if (!group || !relationships) {
|
||||
return null;
|
||||
}
|
||||
|
||||
let info = [];
|
||||
let actionBtn = '';
|
||||
let lockedIcon = '';
|
||||
let menu = [];
|
||||
|
||||
if (relationships.get('admin')) {
|
||||
info.push(<span key='admin'><FormattedMessage id='group.admin' defaultMessage='You are an admin' /></span>);
|
||||
}
|
||||
|
||||
if (!relationships) { // Wait until the relationship is loaded
|
||||
actionBtn = '';
|
||||
} else if (!relationships.get('member')) {
|
||||
actionBtn = <Button className='logo-button' text={intl.formatMessage(messages.join)} onClick={() => this.props.toggleMembership(group, relationships)} />;
|
||||
} else if (relationships.get('member')) {
|
||||
actionBtn = <Button className='logo-button' text={intl.formatMessage(messages.leave, { name: group.get('title') })} onClick={() => this.props.toggleMembership(group, relationships)} />;
|
||||
}
|
||||
|
||||
if (group.get('archived')) {
|
||||
lockedIcon = <Icon id='lock' title={intl.formatMessage(messages.group_archived)} />;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className='account__header'>
|
||||
<div className='account__header__image'>
|
||||
<div className='account__header__info'>
|
||||
<img src={group.get('cover_image_url')} alt='' className='parallax' />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className='account__header__bar'>
|
||||
<div className='account__header__tabs'>
|
||||
<div className='account__header__tabs__buttons'>
|
||||
{actionBtn}
|
||||
|
||||
<DropdownMenuContainer items={menu} icon='ellipsis-v' size={24} direction='right' />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className='account__header__tabs__name'>
|
||||
<h1>
|
||||
<span>{group.get('title')} {info}</span>
|
||||
<small>{lockedIcon}</small>
|
||||
</h1>
|
||||
</div>
|
||||
|
||||
<div className='account__header__extra'>
|
||||
<div className='account__header__bio'>
|
||||
{group.get('description').length > 0 && <div className='account__header__content'>{group.get('description')}</div>}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
import React from 'react';
|
||||
import ImmutablePureComponent from 'react-immutable-pure-component';
|
||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||
import { injectIntl, defineMessages } from 'react-intl';
|
||||
|
||||
const messages = defineMessages({
|
||||
group_archived: { id: 'group.detail.archived_group', defaultMessage: 'Archived group' },
|
||||
group_admin: { id: 'groups.detail.role_admin', defaultMessage: 'You\'re an admin' }
|
||||
});
|
||||
|
||||
export default @injectIntl
|
||||
class GroupPanel extends ImmutablePureComponent {
|
||||
static propTypes = {
|
||||
group: ImmutablePropTypes.map,
|
||||
relationships: ImmutablePropTypes.map,
|
||||
}
|
||||
|
||||
render() {
|
||||
const { group, relationships, intl } = this.props;
|
||||
|
||||
return (
|
||||
<div className="group__panel">
|
||||
<h1 className="group__panel__title">
|
||||
{group.get('title')}
|
||||
{group.get('archived') && <Icon id='lock' title={intl.formatMessage(messages.group_archived)} />}
|
||||
</h1>
|
||||
|
||||
{relationships.get('admin') && <span className="group__panel__label">{intl.formatMessage(messages.group_admin)}</span>}
|
||||
|
||||
<div className="group__panel__description">{group.get('description')}</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -4,99 +4,101 @@ 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 ColumnHeader from '../../../components/column_header';
|
||||
import { FormattedMessage, injectIntl } from 'react-intl';
|
||||
import { connectGroupStream } from '../../../actions/streaming';
|
||||
import { expandGroupTimeline } from '../../../actions/timelines';
|
||||
import { fetchGroup } from '../../../actions/groups';
|
||||
import MissingIndicator from '../../../components/missing_indicator';
|
||||
import LoadingIndicator from '../../../components/loading_indicator';
|
||||
import HeaderContainer from './containers/header_container';
|
||||
import ComposeFormContainer from '../../../../gabsocial/features/compose/containers/compose_form_container';
|
||||
import { me } from 'gabsocial/initial_state';
|
||||
import Avatar from '../../../components/avatar';
|
||||
|
||||
const mapStateToProps = (state, props) => ({
|
||||
group: state.getIn(['groups', props.params.id]),
|
||||
hasUnread: state.getIn(['timelines', `group:${props.params.id}`, 'unread']) > 0,
|
||||
account: state.getIn(['accounts', me]),
|
||||
group: state.getIn(['groups', props.params.id]),
|
||||
relationships: state.getIn(['group_relationships', props.params.id]),
|
||||
hasUnread: state.getIn(['timelines', `group:${props.params.id}`, 'unread']) > 0,
|
||||
});
|
||||
|
||||
export default @connect(mapStateToProps)
|
||||
@injectIntl
|
||||
class GroupTimeline extends React.PureComponent {
|
||||
static contextTypes = {
|
||||
router: PropTypes.object,
|
||||
};
|
||||
|
||||
static contextTypes = {
|
||||
router: PropTypes.object,
|
||||
};
|
||||
static propTypes = {
|
||||
params: PropTypes.object.isRequired,
|
||||
dispatch: PropTypes.func.isRequired,
|
||||
columnId: PropTypes.string,
|
||||
hasUnread: PropTypes.bool,
|
||||
group: PropTypes.oneOfType([ImmutablePropTypes.map, PropTypes.bool]),
|
||||
relationships: ImmutablePropTypes.map,
|
||||
account: ImmutablePropTypes.map,
|
||||
intl: PropTypes.object.isRequired,
|
||||
};
|
||||
|
||||
static propTypes = {
|
||||
params: PropTypes.object.isRequired,
|
||||
dispatch: PropTypes.func.isRequired,
|
||||
hasUnread: PropTypes.bool,
|
||||
group: PropTypes.oneOfType([ImmutablePropTypes.map, PropTypes.bool]),
|
||||
intl: PropTypes.object.isRequired,
|
||||
};
|
||||
componentDidMount () {
|
||||
const { dispatch } = this.props;
|
||||
const { id } = this.props.params;
|
||||
|
||||
componentDidMount () {
|
||||
const { dispatch } = this.props;
|
||||
const { id } = this.props.params;
|
||||
dispatch(expandGroupTimeline(id));
|
||||
|
||||
dispatch(fetchGroup(id));
|
||||
dispatch(expandGroupTimeline(id));
|
||||
this.disconnect = dispatch(connectGroupStream(id));
|
||||
}
|
||||
|
||||
this.disconnect = dispatch(connectGroupStream(id));
|
||||
}
|
||||
componentWillUnmount () {
|
||||
if (this.disconnect) {
|
||||
this.disconnect();
|
||||
this.disconnect = null;
|
||||
}
|
||||
}
|
||||
|
||||
componentWillUnmount () {
|
||||
if (this.disconnect) {
|
||||
this.disconnect();
|
||||
this.disconnect = null;
|
||||
}
|
||||
}
|
||||
handleLoadMore = maxId => {
|
||||
const { id } = this.props.params;
|
||||
this.props.dispatch(expandGroupTimeline(id, { maxId }));
|
||||
}
|
||||
|
||||
handleLoadMore = maxId => {
|
||||
const { id } = this.props.params;
|
||||
this.props.dispatch(expandGroupTimeline(id, { maxId }));
|
||||
}
|
||||
render () {
|
||||
const { columnId, group, relationships, account } = this.props;
|
||||
const { id } = this.props.params;
|
||||
|
||||
render () {
|
||||
const { hasUnread, group } = this.props;
|
||||
const { id } = this.props.params;
|
||||
const title = group ? group.get('title') : id;
|
||||
if (typeof group === 'undefined' || !relationships) {
|
||||
return (
|
||||
<Column>
|
||||
<LoadingIndicator />
|
||||
</Column>
|
||||
);
|
||||
} else if (group === false) {
|
||||
return (
|
||||
<Column>
|
||||
<MissingIndicator />
|
||||
</Column>
|
||||
);
|
||||
}
|
||||
|
||||
if (typeof group === 'undefined') {
|
||||
return (
|
||||
<Column>
|
||||
<div>
|
||||
<LoadingIndicator />
|
||||
</div>
|
||||
</Column>
|
||||
);
|
||||
} else if (group === false) {
|
||||
return (
|
||||
<Column>
|
||||
<MissingIndicator />
|
||||
</Column>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Column label={title}>
|
||||
<ColumnHeader icon='list-ul' active={hasUnread} title={title}>
|
||||
<div className='column-header__links'>
|
||||
{/* Leave might be here */}
|
||||
</div>
|
||||
|
||||
<hr />
|
||||
</ColumnHeader>
|
||||
|
||||
<StatusListContainer
|
||||
prepend={<HeaderContainer groupId={id} />}
|
||||
alwaysPrepend
|
||||
scrollKey='group_timeline'
|
||||
timelineId={`group:${id}`}
|
||||
onLoadMore={this.handleLoadMore}
|
||||
emptyMessage={<FormattedMessage id='empty_column.group' defaultMessage='There is nothing in this group yet. When members of this group post new statuses, they will appear here.' />}
|
||||
/>
|
||||
</Column>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<div>
|
||||
{relationships.get('member') && (
|
||||
<div className='timeline-compose-block'>
|
||||
<div className='timeline-compose-block__avatar'>
|
||||
<Avatar account={account} size={46} />
|
||||
</div>
|
||||
<ComposeFormContainer group={group} shouldCondense={true} autoFocus={false}/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className='group__feed'>
|
||||
<StatusListContainer
|
||||
alwaysPrepend
|
||||
scrollKey={`group_timeline-${columnId}`}
|
||||
timelineId={`group:${id}`}
|
||||
onLoadMore={this.handleLoadMore}
|
||||
withGroupAdmin={relationships && relationships.get('admin')}
|
||||
emptyMessage={<FormattedMessage id='empty_column.group' defaultMessage='There is nothing in this group yet. When members of this group post new statuses, they will appear here.' />}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,9 +24,9 @@ export const privateLinks = [
|
||||
<NotificationsCounterIcon />
|
||||
<FormattedMessage id='tabs_bar.notifications' defaultMessage='Notifications' />
|
||||
</NavLink>,
|
||||
// <NavLink className='tabs-bar__link groups' to='/groups' data-preview-title-id='column.groups' >
|
||||
// <FormattedMessage id='tabs_bar.groups' defaultMessage='Groups' />
|
||||
// </NavLink>,
|
||||
<NavLink className='tabs-bar__link groups' to='/groups' data-preview-title-id='column.groups' >
|
||||
<FormattedMessage id='tabs_bar.groups' defaultMessage='Groups' />
|
||||
</NavLink>,
|
||||
<NavLink className='tabs-bar__link optional' to='/search' data-preview-title-id='tabs_bar.search' >
|
||||
<FormattedMessage id='tabs_bar.search' defaultMessage='Search' />
|
||||
</NavLink>,
|
||||
|
||||
@@ -25,8 +25,11 @@ import TabsBar from './components/tabs_bar';
|
||||
import WhoToFollowPanel from './components/who_to_follow_panel';
|
||||
import LinkFooter from './components/link_footer';
|
||||
import ProfilePage from 'gabsocial/pages/profile_page';
|
||||
import GroupsPage from 'gabsocial/pages/groups_page';
|
||||
import GroupPage from 'gabsocial/pages/group_page';
|
||||
import SearchPage from 'gabsocial/pages/search_page';
|
||||
import HomePage from 'gabsocial/pages/home_page';
|
||||
import GroupSidebarPanel from '../groups/sidebar_panel';
|
||||
|
||||
import {
|
||||
Status,
|
||||
@@ -55,6 +58,10 @@ import {
|
||||
GroupTimeline,
|
||||
ListTimeline,
|
||||
Lists,
|
||||
GroupMembers,
|
||||
GroupRemovedAccounts,
|
||||
GroupCreate,
|
||||
GroupEdit,
|
||||
} from './util/async-components';
|
||||
import { me, meUsername } from '../../initial_state';
|
||||
import { previewState as previewMediaState } from './components/media_modal';
|
||||
@@ -63,6 +70,7 @@ import { previewState as previewVideoState } from './components/video_modal';
|
||||
// Dummy import, to make sure that <Status /> ends up in the application bundle.
|
||||
// Without this it ends up in ~8 very commonly used bundles.
|
||||
import '../../components/status';
|
||||
import { fetchGroups } from '../../actions/groups';
|
||||
|
||||
const messages = defineMessages({
|
||||
beforeUnload: { id: 'ui.beforeunload', defaultMessage: 'Your draft will be lost if you leave Gab Social.' },
|
||||
@@ -116,12 +124,14 @@ const LAYOUT = {
|
||||
],
|
||||
RIGHT: [
|
||||
// <TrendsPanel />,
|
||||
<GroupSidebarPanel />
|
||||
],
|
||||
},
|
||||
STATUS: {
|
||||
TOP: null,
|
||||
LEFT: null,
|
||||
RIGHT: [
|
||||
<GroupSidebarPanel />,
|
||||
<WhoToFollowPanel key='0' />,
|
||||
// <TrendsPanel />,
|
||||
<LinkFooter key='1' />,
|
||||
@@ -174,8 +184,14 @@ class SwitchingColumnsArea extends React.PureComponent {
|
||||
<WrappedRoute path='/home' exact page={HomePage} component={HomeTimeline} content={children} />
|
||||
<WrappedRoute path='/timeline/all' exact page={HomePage} component={CommunityTimeline} content={children} />
|
||||
|
||||
<WrappedRoute path='/groups' component={Groups} content={children} />
|
||||
<WrappedRoute path='/groups/:id' component={GroupTimeline} content={children} />
|
||||
<WrappedRoute path='/groups' exact page={GroupsPage} component={Groups} content={children} componentParams={{ activeTab: 'featured' }} />
|
||||
<WrappedRoute path='/groups/create' page={GroupsPage} component={Groups} content={children} componentParams={{ showCreateForm: true, activeTab: 'featured' }} />
|
||||
<WrappedRoute path='/groups/browse/member' page={GroupsPage} component={Groups} content={children} componentParams={{ activeTab: 'member' }} />
|
||||
<WrappedRoute path='/groups/browse/admin' page={GroupsPage} component={Groups} content={children} componentParams={{ activeTab: 'admin' }} />
|
||||
<WrappedRoute path='/groups/:id/members' page={GroupPage} component={GroupMembers} content={children} />
|
||||
<WrappedRoute path='/groups/:id/removed_accounts' page={GroupPage} component={GroupRemovedAccounts} content={children} />
|
||||
<WrappedRoute path='/groups/:id/edit' page={GroupPage} component={GroupEdit} content={children} />
|
||||
<WrappedRoute path='/groups/:id' page={GroupPage} component={GroupTimeline} content={children} />
|
||||
|
||||
<WrappedRoute path='/tags/:id' component={HashtagTimeline} content={children} />
|
||||
|
||||
@@ -360,6 +376,7 @@ class UI extends React.PureComponent {
|
||||
if (me) {
|
||||
this.props.dispatch(expandHomeTimeline());
|
||||
this.props.dispatch(expandNotifications());
|
||||
this.props.dispatch(fetchGroups('member'));
|
||||
|
||||
setTimeout(() => this.props.dispatch(fetchFilters()), 500);
|
||||
}
|
||||
|
||||
@@ -34,6 +34,22 @@ export function GroupTimeline () {
|
||||
return import(/* webpackChunkName: "features/groups/timeline" */'../../groups/timeline');
|
||||
}
|
||||
|
||||
export function GroupMembers () {
|
||||
return import(/* webpackChunkName: "features/groups/timeline" */'../../groups/members');
|
||||
}
|
||||
|
||||
export function GroupRemovedAccounts () {
|
||||
return import(/* webpackChunkName: "features/groups/timeline" */'../../groups/removed_accounts');
|
||||
}
|
||||
|
||||
export function GroupCreate () {
|
||||
return import(/* webpackChunkName: "features/groups/timeline" */'../../groups/create');
|
||||
}
|
||||
|
||||
export function GroupEdit () {
|
||||
return import(/* webpackChunkName: "features/groups/timeline" */'../../groups/edit');
|
||||
}
|
||||
|
||||
export function Groups () {
|
||||
return import(/* webpackChunkName: "features/groups/index" */'../../groups/index');
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user