diff --git a/app/javascript/gabsocial/actions/streaming.js b/app/javascript/gabsocial/actions/streaming.js index 2469970d..ea43aecd 100644 --- a/app/javascript/gabsocial/actions/streaming.js +++ b/app/javascript/gabsocial/actions/streaming.js @@ -55,7 +55,7 @@ export const connectProStream = () => connectTimelineStream('pro', 'pro'); export const connectHashtagStream = (id, tag, accept) => connectTimelineStream(`hashtag:${id}`, `hashtag&tag=${tag}`, null, accept); export const connectListStream = id => connectTimelineStream(`list:${id}`, `list&list=${id}`); export const connectGroupStream = id => connectTimelineStream(`group:${id}`, `group&group=${id}`); -export const connectGroupCollectionStream = (id) => connectTimelineStream(`group:collection:${id}`, `group&group:collection=${id}`); +export const connectGroupCollectionStream = (collectionType, sortBy) => connectTimelineStream(`group_collection:${collectionType}:${sortBy}`, `group_collection&group_collection=${collectionType}`); export const connectStatusUpdateStream = () => { return connectStream('statuscard', null, (dispatch, getState) => { diff --git a/app/javascript/gabsocial/actions/timelines.js b/app/javascript/gabsocial/actions/timelines.js index f1392fd0..798e0e54 100644 --- a/app/javascript/gabsocial/actions/timelines.js +++ b/app/javascript/gabsocial/actions/timelines.js @@ -172,8 +172,8 @@ export const expandAccountTimeline = (accountId, { maxId, withReplies, commentsO export const expandAccountFeaturedTimeline = accountId => expandTimeline(`account:${accountId}:pinned`, `/api/v1/accounts/${accountId}/statuses`, { pinned: true }); export const expandAccountMediaTimeline = (accountId, { maxId, limit, mediaType } = {}) => expandTimeline(`account:${accountId}:media`, `/api/v1/accounts/${accountId}/statuses`, { max_id: maxId, only_media: true, limit: limit || 20, media_type: mediaType }); export const expandListTimeline = (id, { maxId } = {}, done = noOp) => expandTimeline(`list:${id}`, `/api/v1/timelines/list/${id}`, { max_id: maxId }, done); -export const expandGroupTimeline = (id, { sortBy, maxId } = {}, done = noOp) => expandTimeline(`group:${id}`, `/api/v1/timelines/group/${id}`, { sort_by: sortBy, max_id: maxId }, done); -export const expandGroupCollectionTimeline = (collectionType, { sortBy, maxId } = {}, done = noOp) => expandTimeline(`group:collection:${collectionType}`, `/api/v1/timelines/group_collection/${collectionType}`, { sort_by: sortBy, max_id: maxId }, done); +export const expandGroupTimeline = (id, { sortBy, maxId, onlyMedia } = {}, done = noOp) => expandTimeline(`group:${id}`, `/api/v1/timelines/group/${id}`, { sort_by: sortBy, max_id: maxId, only_media: onlyMedia }, done); +export const expandGroupCollectionTimeline = (collectionType, { sortBy, maxId } = {}, done = noOp) => expandTimeline(`group_collection:${collectionType}`, `/api/v1/timelines/group_collection/${collectionType}`, { sort_by: sortBy, max_id: maxId }, done); export const expandHashtagTimeline = (hashtag, { maxId, tags } = {}, done = noOp) => { return expandTimeline(`hashtag:${hashtag}`, `/api/v1/timelines/tag/${hashtag}`, { max_id: maxId, diff --git a/app/javascript/gabsocial/components/popover/group_timeline_sort_options_popover.js b/app/javascript/gabsocial/components/popover/group_timeline_sort_options_popover.js index 6fe7d4a3..f40f480d 100644 --- a/app/javascript/gabsocial/components/popover/group_timeline_sort_options_popover.js +++ b/app/javascript/gabsocial/components/popover/group_timeline_sort_options_popover.js @@ -24,8 +24,8 @@ const mapStateToProps = (state) => ({ }) const mapDispatchToProps = (dispatch) => ({ - onSort(sort, options) { - dispatch(setGroupTimelineSort(sort, options)) + onSort(sort) { + dispatch(setGroupTimelineSort(sort)) dispatch(closePopover()) }, onClosePopover: () => dispatch(closePopover()), @@ -46,7 +46,7 @@ class GroupTimelineSortOptionsPopover extends PureComponent { } handleOnClick = (type) => { - this.props.onSort(type, this.props.options) + this.props.onSort(type) } handleOnClosePopover = () => { diff --git a/app/javascript/gabsocial/components/popover/group_timeline_sort_top_options_popover.js b/app/javascript/gabsocial/components/popover/group_timeline_sort_top_options_popover.js index 798f9e85..bfbadae6 100644 --- a/app/javascript/gabsocial/components/popover/group_timeline_sort_top_options_popover.js +++ b/app/javascript/gabsocial/components/popover/group_timeline_sort_top_options_popover.js @@ -24,8 +24,8 @@ const mapStateToProps = (state) => ({ }) const mapDispatchToProps = (dispatch) => ({ - onSort(sort, options) { - dispatch(setGroupTimelineTopSort(sort, options)) + onSort(sort) { + dispatch(setGroupTimelineTopSort(sort)) dispatch(closePopover()) }, onClosePopover: () => dispatch(closePopover()), @@ -42,11 +42,10 @@ class GroupTimelineSortTopOptionsPopover extends PureComponent { isXS: PropTypes.bool, onClosePopover: PropTypes.func.isRequired, onSort: PropTypes.func.isRequired, - options: PropTypes.object.isRequired, } handleOnClick = (type) => { - this.props.onSort(type, this.props.options) + this.props.onSort(type) } handleOnClosePopover = () => { diff --git a/app/javascript/gabsocial/features/group_collection_timeline.js b/app/javascript/gabsocial/features/group_collection_timeline.js index c771033d..459876e6 100644 --- a/app/javascript/gabsocial/features/group_collection_timeline.js +++ b/app/javascript/gabsocial/features/group_collection_timeline.js @@ -1,63 +1,145 @@ import { Fragment } from 'react' import { injectIntl, defineMessages } from 'react-intl' +import { List as ImmutableList } from 'immutable' import { me } from '../initial_state' -import { GROUP_TIMELINE_SORTING_TYPE_TOP } from '../constants' import { connectGroupCollectionStream } from '../actions/streaming' -import { expandGroupCollectionTimeline } from '../actions/timelines' +import { + clearTimeline, + expandGroupCollectionTimeline, +} from '../actions/timelines' +import { + setGroupTimelineSort, + setGroupTimelineTopSort, +} from '../actions/groups' +import { + GROUP_TIMELINE_SORTING_TYPE_TOP, + GROUP_TIMELINE_SORTING_TYPE_TOP_OPTION_WEEKLY, + GROUP_TIMELINE_SORTING_TYPE_NEWEST, +} from '../constants' +import getSortBy from '../utils/group_sort_by' +import Text from '../components/text' import StatusList from '../components/status_list' import GroupSortBlock from '../components/group_sort_block' +import GroupsCollection from './groups_collection' const messages = defineMessages({ empty: { id: 'empty_column.group_collection_timeline', defaultMessage: 'There are no gabs to display.' }, }) -const mapStateToProps = (state) => ({ - sortByValue: state.getIn(['group_lists', 'sortByValue']), - sortByTopValue: state.getIn(['group_lists', 'sortByTopValue']), +const mapStateToProps = (state, { collectionType }) => { + + let dontShowGroupSort = true + try { + dontShowGroupSort = !!me && collectionType === 'member' && state.getIn(['timelines', `group_collection:${collectionType}`, 'items'], ImmutableList()).count() === 0 + } catch (error) { + // + } + + return { + dontShowGroupSort, + sortByValue: state.getIn(['group_lists', 'sortByValue']), + sortByTopValue: state.getIn(['group_lists', 'sortByTopValue']), + } +} + +const mapDispatchToProps = (dispatch) => ({ + onConnectGroupCollectionStream(collectionType, sortBy) { + dispatch(connectGroupCollectionStream(collectionType, sortBy)) + }, + onClearTimeline(timeline) { + dispatch(clearTimeline(timeline)) + }, + onExpandGroupCollectionTimeline(collectionType, options) { + dispatch(expandGroupCollectionTimeline(collectionType, options)) + }, + setFeaturedTop() { + dispatch(setGroupTimelineSort(GROUP_TIMELINE_SORTING_TYPE_TOP)) + dispatch(setGroupTimelineTopSort(GROUP_TIMELINE_SORTING_TYPE_TOP_OPTION_WEEKLY)) + }, + setMemberNewest() { + dispatch(setGroupTimelineSort(GROUP_TIMELINE_SORTING_TYPE_NEWEST)) + }, }) export default @injectIntl -@connect(mapStateToProps) +@connect(mapStateToProps, mapDispatchToProps) class GroupCollectionTimeline extends PureComponent { static propTypes = { params: PropTypes.object.isRequired, - dispatch: PropTypes.func.isRequired, + onConnectGroupCollectionStream: PropTypes.func.isRequired, + onClearTimeline: PropTypes.func.isRequired, + onExpandGroupCollectionTimeline: PropTypes.func.isRequired, + setFeaturedTop: PropTypes.func.isRequired, + setMemberNewest: PropTypes.func.isRequired, intl: PropTypes.object.isRequired, + collectionType: PropTypes.string.isRequired, sortByValue: PropTypes.string.isRequired, sortByTopValue: PropTypes.string, + hasStatuses: PropTypes.bool.isRequired, + } + + state = { + //keep track of loads for if no user, + //only allow 2 loads before showing sign up msg + loadCount: 0, + } + + _unsubscribe() { + if (this.disconnect && !!me) { + this.disconnect() + this.disconnect = null + } + } + + _subscribe = () => { + if (!!me) { + const { + collectionType, + sortByValue, + sortByTopValue, + } = this.props + + if (collectionType === 'member' && sortByValue === 'new') { + const sortBy = getSortBy(sortByValue, sortByTopValue) + this.disconnect = this.props.onConnectGroupCollectionStream(collectionType, sortBy) + } + } } componentDidMount() { const { collectionType, - dispatch, sortByValue, sortByTopValue, } = this.props - const sortBy = sortByValue === GROUP_TIMELINE_SORTING_TYPE_TOP ? `${sortByValue}_${sortByTopValue}` : sortByValue - const options = { sortBy } - - dispatch(expandGroupCollectionTimeline(collectionType, options)) + if (this.props.collectionType === 'featured' && sortByValue !== GROUP_TIMELINE_SORTING_TYPE_TOP) { + this.props.setFeaturedTop() + } else if (!!me && this.props.collectionType === 'member' && sortByValue !== GROUP_TIMELINE_SORTING_TYPE_NEWEST) { + this.props.setMemberNewest() + } else { + const sortBy = getSortBy(sortByValue, sortByTopValue) + this.props.onExpandGroupCollectionTimeline(collectionType, { sortBy }) - if (!!me) { - this.disconnect = dispatch(connectGroupCollectionStream(collectionType)) + this._subscribe() } } componentDidUpdate(prevProps) { - if (prevProps.sortByValue !== this.props.sortByValue || prevProps.sortByTopValue !== this.props.sortByTopValue) { - this.handleLoadMore() + if (prevProps.sortByValue !== this.props.sortByValue || + prevProps.sortByTopValue !== this.props.sortByTopValue || + prevProps.collectionType !== this.props.collectionType) { + this._unsubscribe() + this._subscribe() + this.props.onClearTimeline(`group_collection:${prevProps.collectionType}`) + this.handleLoadMore() } } componentWillUnmount() { - if (this.disconnect && !!me) { - this.disconnect() - this.disconnect = null - } + this._unsubscribe() } handleLoadMore = (maxId) => { @@ -67,26 +149,39 @@ class GroupCollectionTimeline extends PureComponent { sortByTopValue, } = this.props - const sortBy = sortByValue === GROUP_TIMELINE_SORTING_TYPE_TOP ? `${sortByValue}_${sortByTopValue}` : sortByValue + const sortBy = getSortBy(sortByValue, sortByTopValue) const options = { sortBy, maxId } - this.props.dispatch(expandGroupCollectionTimeline(collectionType, options)) + this.props.onExpandGroupCollectionTimeline(collectionType, options) } render() { const { collectionType, intl, + dontShowGroupSort, } = this.props + const emptyMessage = !!me && collectionType === 'member' ? ( +
+ + Join some groups then come back here to view your group timeline + + +
+ ) : intl.formatMessage(messages.empty) + return ( - + { + !dontShowGroupSort && + + } ) diff --git a/app/javascript/gabsocial/features/group_timeline.js b/app/javascript/gabsocial/features/group_timeline.js index dd9e051f..c8d1ce36 100644 --- a/app/javascript/gabsocial/features/group_timeline.js +++ b/app/javascript/gabsocial/features/group_timeline.js @@ -3,9 +3,18 @@ import ImmutablePureComponent from 'react-immutable-pure-component' import ImmutablePropTypes from 'react-immutable-proptypes' import { injectIntl, defineMessages } from 'react-intl' import { me } from '../initial_state' -import { GROUP_TIMELINE_SORTING_TYPE_TOP } from '../constants' +import getSortBy from '../utils/group_sort_by' import { connectGroupStream } from '../actions/streaming' -import { expandGroupTimeline } from '../actions/timelines' +import { + clearTimeline, + expandGroupTimeline, +} from '../actions/timelines' +import { + setGroupTimelineSort, +} from '../actions/groups' +import { + GROUP_TIMELINE_SORTING_TYPE_NEWEST, +} from '../constants' import StatusList from '../components/status_list' import ColumnIndicator from '../components/column_indicator' import GroupSortBlock from '../components/group_sort_block' @@ -15,49 +24,82 @@ const messages = defineMessages({ }) const mapStateToProps = (state, props) => ({ + groupId: props.params.id, group: state.getIn(['groups', props.params.id]), sortByValue: state.getIn(['group_lists', 'sortByValue']), sortByTopValue: state.getIn(['group_lists', 'sortByTopValue']), }) +const mapDispatchToProps = (dispatch) => ({ + onConnectGroupStream(groupId) { + dispatch(connectGroupStream(groupId)) + }, + onClearTimeline(timelineId) { + dispatch(clearTimeline(timelineId)) + }, + onExpandGroupTimeline(groupId, options) { + dispatch(expandGroupTimeline(groupId, options)) + }, + setMemberNewest() { + dispatch(setGroupTimelineSort(GROUP_TIMELINE_SORTING_TYPE_NEWEST)) + }, +}) + export default -@connect(mapStateToProps) +@connect(mapStateToProps, mapDispatchToProps) @injectIntl class GroupTimeline extends ImmutablePureComponent { static propTypes = { params: PropTypes.object.isRequired, - dispatch: PropTypes.func.isRequired, group: PropTypes.oneOfType([ ImmutablePropTypes.map, PropTypes.bool, ]), + groupId: PropTypes.string, intl: PropTypes.object.isRequired, + onConnectGroupStream: PropTypes.func.isRequired, + onClearTimeline: PropTypes.func.isRequired, + onExpandGroupTimeline: PropTypes.func.isRequired, + setMemberNewest: PropTypes.func.isRequired, sortByValue: PropTypes.string.isRequired, sortByTopValue: PropTypes.string, + onlyMedia: PropTypes.bool, } + state = { + //keep track of loads for if no user, + //only allow 2 loads before showing sign up msg + loadCount: 0, + } + componentDidMount() { const { - dispatch, + groupId, sortByValue, sortByTopValue, + onlyMedia, } = this.props - const { id } = this.props.params - const sortBy = sortByValue === GROUP_TIMELINE_SORTING_TYPE_TOP ? `${sortByValue}_${sortByTopValue}` : sortByValue - const options = { sortBy } + if (sortByValue !== GROUP_TIMELINE_SORTING_TYPE_NEWEST) { + this.props.setMemberNewest() + } else { + const sortBy = getSortBy(sortByValue, sortByTopValue, onlyMedia) + this.props.onExpandGroupTimeline(groupId, { sortBy, onlyMedia }) - dispatch(expandGroupTimeline(id, options)) - - if (!!me) { - this.disconnect = dispatch(connectGroupStream(id)) + if (!!me) { + this.disconnect = this.props.onConnectGroupStream(groupId) + } } } componentDidUpdate(prevProps) { - if (prevProps.sortByValue !== this.props.sortByValue || prevProps.sortByTopValue !== this.props.sortByTopValue) { + if ((prevProps.sortByValue !== this.props.sortByValue || + prevProps.sortByTopValue !== this.props.sortByTopValue || + prevProps.onlyMedia !== this.props.onlyMedia) && + prevProps.groupId === this.props.groupId) { this.handleLoadMore() + this.props.onClearTimeline(`group:${this.props.groupId}`) } } @@ -70,21 +112,22 @@ class GroupTimeline extends ImmutablePureComponent { handleLoadMore = (maxId) => { const { - dispatch, + groupId, sortByValue, sortByTopValue, + onlyMedia, } = this.props - const { id } = this.props.params - const sortBy = sortByValue === GROUP_TIMELINE_SORTING_TYPE_TOP ? `${sortByValue}_${sortByTopValue}` : sortByValue - const options = { sortBy, maxId } - - dispatch(expandGroupTimeline(id, options)) + const sortBy = getSortBy(sortByValue, sortByTopValue) + this.props.onExpandGroupTimeline(groupId, { sortBy, maxId, onlyMedia }) } render() { - const { group, intl } = this.props - const { id } = this.props.params + const { + group, + groupId, + intl, + } = this.props if (typeof group === 'undefined') { return @@ -94,10 +137,10 @@ class GroupTimeline extends ImmutablePureComponent { return ( - + diff --git a/app/javascript/gabsocial/pages/groups_page.js b/app/javascript/gabsocial/pages/groups_page.js index b3a86c38..2776dc3d 100644 --- a/app/javascript/gabsocial/pages/groups_page.js +++ b/app/javascript/gabsocial/pages/groups_page.js @@ -1,9 +1,12 @@ +import { Fragment } from 'react' import { me } from '../initial_state' import { defineMessages, injectIntl } from 'react-intl' import PageTitle from '../features/ui/util/page_title' import LinkFooter from '../components/link_footer' +import Text from '../components/text' import GroupsPanel from '../components/panel/groups_panel' import DefaultLayout from '../layouts/default_layout' +import GroupsCollection from '../features/groups_collection' const messages = defineMessages({ groups: { id: 'groups', defaultMessage: 'Groups' }, @@ -24,6 +27,7 @@ export default class GroupsPage extends PureComponent { static propTypes = { + activeTab: PropTypes.string.isRequired, intl: PropTypes.object.isRequired, children: PropTypes.node.isRequired, isPro: PropTypes.bool, @@ -31,11 +35,14 @@ class GroupsPage extends PureComponent { render() { const { + activeTab, intl, children, isPro, } = this.props + const dontShowChildren = (activeTab === 'timeline' && !me) + const actions = isPro ? [ { icon: 'add', @@ -45,7 +52,7 @@ class GroupsPage extends PureComponent { const tabs = !!me ? [ { - title: intl.formatMessage(messages.myGroupsTimeline), + title: intl.formatMessage(dontShowChildren ? messages.myGroupsTimeline : messages.groups), to: '/groups', }, { @@ -70,6 +77,12 @@ class GroupsPage extends PureComponent { } const title = intl.formatMessage(messages.groups) + + const layout = [] + if (!!me) { + layout.push() + } + layout.push() return ( , - , - ]} + layout={layout} > - { children } + + { !dontShowChildren && children } + + { + dontShowChildren && + + } ) } diff --git a/app/models/group_categories.rb b/app/models/group_categories.rb index d1273f5f..91f18f7f 100644 --- a/app/models/group_categories.rb +++ b/app/models/group_categories.rb @@ -2,10 +2,10 @@ # # Table name: group_categories # -# id :bigint(8) not null, primary key -# created_at :datetime not null -# updated_at :datetime not null -# text :string not null +# id :bigint(8) not null, primary key +# created_at :datetime not null +# updated_at :datetime not null +# text :string default(""), not null # class GroupCategories < ApplicationRecord