Merge branch 'tag-improvements' into 'develop'
Tag improvements See merge request soapbox-pub/soapbox!2458
This commit is contained in:
commit
b6a5c56859
10 changed files with 78 additions and 46 deletions
|
@ -248,6 +248,9 @@ const expandListTimeline = (id: string, { maxId }: Record<string, any> = {}, don
|
||||||
const expandGroupTimeline = (id: string, { maxId }: Record<string, any> = {}, done = noOp) =>
|
const expandGroupTimeline = (id: string, { maxId }: Record<string, any> = {}, done = noOp) =>
|
||||||
expandTimeline(`group:${id}`, `/api/v1/timelines/group/${id}`, { max_id: maxId }, done);
|
expandTimeline(`group:${id}`, `/api/v1/timelines/group/${id}`, { max_id: maxId }, done);
|
||||||
|
|
||||||
|
const expandGroupTimelineFromTag = (id: string, tagName: string, { maxId }: Record<string, any> = {}, done = noOp) =>
|
||||||
|
expandTimeline(`group:tags:${id}:${tagName}`, `/api/v1/timelines/group/${id}/tags/${tagName}`, { max_id: maxId }, done);
|
||||||
|
|
||||||
const expandGroupMediaTimeline = (id: string | number, { maxId }: Record<string, any> = {}) =>
|
const expandGroupMediaTimeline = (id: string | number, { maxId }: Record<string, any> = {}) =>
|
||||||
expandTimeline(`group:${id}:media`, `/api/v1/timelines/group/${id}`, { max_id: maxId, only_media: true, limit: 40, with_muted: true });
|
expandTimeline(`group:${id}:media`, `/api/v1/timelines/group/${id}`, { max_id: maxId, only_media: true, limit: 40, with_muted: true });
|
||||||
|
|
||||||
|
@ -350,6 +353,7 @@ export {
|
||||||
expandAccountMediaTimeline,
|
expandAccountMediaTimeline,
|
||||||
expandListTimeline,
|
expandListTimeline,
|
||||||
expandGroupTimeline,
|
expandGroupTimeline,
|
||||||
|
expandGroupTimelineFromTag,
|
||||||
expandGroupMediaTimeline,
|
expandGroupMediaTimeline,
|
||||||
expandHashtagTimeline,
|
expandHashtagTimeline,
|
||||||
expandTimelineRequest,
|
expandTimelineRequest,
|
||||||
|
|
|
@ -39,8 +39,6 @@ const GroupTagListItem = (props: IGroupMemberListItem) => {
|
||||||
const { updateGroupTag } = useUpdateGroupTag(group.id, tag.id);
|
const { updateGroupTag } = useUpdateGroupTag(group.id, tag.id);
|
||||||
|
|
||||||
const isOwner = group.relationship?.role === GroupRoles.OWNER;
|
const isOwner = group.relationship?.role === GroupRoles.OWNER;
|
||||||
const isAdmin = group.relationship?.role === GroupRoles.ADMIN;
|
|
||||||
const canEdit = isOwner || isAdmin;
|
|
||||||
|
|
||||||
const toggleVisibility = () => {
|
const toggleVisibility = () => {
|
||||||
updateGroupTag({
|
updateGroupTag({
|
||||||
|
@ -123,16 +121,16 @@ const GroupTagListItem = (props: IGroupMemberListItem) => {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<HStack alignItems='center' justifyContent='between'>
|
<HStack alignItems='center' justifyContent='between'>
|
||||||
<Link to={`/groups/${group.id}/tag/${tag.id}`} className='group grow'>
|
<Link to={`/group/${group.slug}/tag/${tag.id}`} className='group grow'>
|
||||||
<Stack>
|
<Stack>
|
||||||
<Text
|
<Text
|
||||||
weight='bold'
|
weight='bold'
|
||||||
theme={(tag.visible || !canEdit) ? 'default' : 'subtle'}
|
theme={(tag.visible || !isOwner) ? 'default' : 'subtle'}
|
||||||
className='group-hover:underline'
|
className='group-hover:underline'
|
||||||
>
|
>
|
||||||
#{tag.name}
|
#{tag.name}
|
||||||
</Text>
|
</Text>
|
||||||
<Text size='sm' theme={(tag.visible || !canEdit) ? 'muted' : 'subtle'}>
|
<Text size='sm' theme={(tag.visible || !isOwner) ? 'muted' : 'subtle'}>
|
||||||
{intl.formatMessage(messages.total)}:
|
{intl.formatMessage(messages.total)}:
|
||||||
{' '}
|
{' '}
|
||||||
<Text size='sm' theme='inherit' weight='semibold' tag='span'>
|
<Text size='sm' theme='inherit' weight='semibold' tag='span'>
|
||||||
|
@ -142,7 +140,7 @@ const GroupTagListItem = (props: IGroupMemberListItem) => {
|
||||||
</Stack>
|
</Stack>
|
||||||
</Link>
|
</Link>
|
||||||
|
|
||||||
{canEdit ? (
|
{isOwner ? (
|
||||||
<HStack alignItems='center' space={2}>
|
<HStack alignItems='center' space={2}>
|
||||||
{tag.visible ? (
|
{tag.visible ? (
|
||||||
renderPinIcon()
|
renderPinIcon()
|
||||||
|
|
|
@ -1,28 +1,66 @@
|
||||||
import React from 'react';
|
import React, { useEffect } from 'react';
|
||||||
|
import { FormattedMessage } from 'react-intl';
|
||||||
|
|
||||||
import { Column } from 'soapbox/components/ui';
|
import { expandGroupTimelineFromTag } from 'soapbox/actions/timelines';
|
||||||
|
import { Column, Icon, Stack, Text } from 'soapbox/components/ui';
|
||||||
|
import { useAppDispatch } from 'soapbox/hooks';
|
||||||
import { useGroup, useGroupTag } from 'soapbox/hooks/api';
|
import { useGroup, useGroupTag } from 'soapbox/hooks/api';
|
||||||
|
|
||||||
type RouteParams = { id: string, groupId: string };
|
import Timeline from '../ui/components/timeline';
|
||||||
|
|
||||||
|
type RouteParams = { tagId: string, groupId: string };
|
||||||
|
|
||||||
interface IGroupTimeline {
|
interface IGroupTimeline {
|
||||||
params: RouteParams
|
params: RouteParams
|
||||||
}
|
}
|
||||||
|
|
||||||
const GroupTagTimeline: React.FC<IGroupTimeline> = (props) => {
|
const GroupTagTimeline: React.FC<IGroupTimeline> = (props) => {
|
||||||
|
const dispatch = useAppDispatch();
|
||||||
const groupId = props.params.groupId;
|
const groupId = props.params.groupId;
|
||||||
const tagId = props.params.id;
|
const tagId = props.params.tagId;
|
||||||
|
|
||||||
const { group } = useGroup(groupId);
|
const { group } = useGroup(groupId);
|
||||||
const { tag } = useGroupTag(tagId);
|
const { tag, isLoading } = useGroupTag(tagId);
|
||||||
|
|
||||||
if (!group) {
|
const handleLoadMore = (maxId: string) => {
|
||||||
|
dispatch(expandGroupTimelineFromTag(groupId, tag?.name as string, { maxId }));
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (tag?.name) {
|
||||||
|
dispatch(expandGroupTimelineFromTag(groupId, tag?.name));
|
||||||
|
}
|
||||||
|
}, [groupId, tag]);
|
||||||
|
|
||||||
|
|
||||||
|
if (isLoading || !tag || !group) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Column label={`#${tag}`}>
|
<Column label={`#${tag.name}`}>
|
||||||
{/* TODO */}
|
<Timeline
|
||||||
|
scrollKey='group_timeline'
|
||||||
|
timelineId={`group:tags:${groupId}:${tag.name}`}
|
||||||
|
onLoadMore={handleLoadMore}
|
||||||
|
divideType='border'
|
||||||
|
showGroup={false}
|
||||||
|
emptyMessageCard={false}
|
||||||
|
emptyMessage={
|
||||||
|
<Stack space={4} className='py-6' justifyContent='center' alignItems='center'>
|
||||||
|
<div className='rounded-full bg-gray-200 p-4 dark:bg-gray-800'>
|
||||||
|
<Icon
|
||||||
|
src={require('@tabler/icons/message-2.svg')}
|
||||||
|
className='h-6 w-6 text-gray-600'
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Text theme='muted'>
|
||||||
|
<FormattedMessage id='empty_column.group' defaultMessage='There are no posts in this group yet.' />
|
||||||
|
</Text>
|
||||||
|
</Stack>
|
||||||
|
}
|
||||||
|
/>
|
||||||
</Column>
|
</Column>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
@ -20,7 +20,7 @@ const GroupGridItem = forwardRef((props: IGroup, ref: React.ForwardedRef<HTMLDiv
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
key={group.id}
|
key={group.id}
|
||||||
className='relative flex shrink-0 flex-col space-y-2 px-0.5'
|
className='relative flex shrink-0 flex-col space-y-2 px-1'
|
||||||
style={{
|
style={{
|
||||||
width,
|
width,
|
||||||
}}
|
}}
|
||||||
|
@ -39,7 +39,11 @@ const GroupGridItem = forwardRef((props: IGroup, ref: React.ForwardedRef<HTMLDiv
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<Stack justifyContent='end' className='z-10 p-4 text-white' space={3}>
|
<div
|
||||||
|
className='absolute inset-x-0 bottom-0 flex justify-center rounded-b-lg bg-gradient-to-t from-gray-900 to-transparent pb-8 pt-12 transition-opacity duration-500'
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Stack justifyContent='end' className='p-4 text-white' space={3}>
|
||||||
<GroupAvatar
|
<GroupAvatar
|
||||||
group={group}
|
group={group}
|
||||||
size={44}
|
size={44}
|
||||||
|
@ -60,10 +64,6 @@ const GroupGridItem = forwardRef((props: IGroup, ref: React.ForwardedRef<HTMLDiv
|
||||||
</HStack>
|
</HStack>
|
||||||
</Stack>
|
</Stack>
|
||||||
</Stack>
|
</Stack>
|
||||||
|
|
||||||
<div
|
|
||||||
className='absolute inset-x-0 bottom-0 z-0 flex justify-center rounded-b-lg bg-gradient-to-t from-gray-900 to-transparent pb-8 pt-12 transition-opacity duration-500'
|
|
||||||
/>
|
|
||||||
</Stack>
|
</Stack>
|
||||||
</Link>
|
</Link>
|
||||||
|
|
||||||
|
|
|
@ -53,7 +53,7 @@ const PopularGroups = () => {
|
||||||
{isFetching ? (
|
{isFetching ? (
|
||||||
new Array(4).fill(0).map((_, idx) => (
|
new Array(4).fill(0).map((_, idx) => (
|
||||||
<div
|
<div
|
||||||
className='relative flex shrink-0 flex-col space-y-2 px-0.5'
|
className='relative flex shrink-0 flex-col space-y-2 px-1'
|
||||||
style={{ width: width || 'auto' }}
|
style={{ width: width || 'auto' }}
|
||||||
key={idx}
|
key={idx}
|
||||||
>
|
>
|
||||||
|
|
|
@ -46,10 +46,8 @@ export default (props: Props) => {
|
||||||
</div>
|
</div>
|
||||||
), []);
|
), []);
|
||||||
|
|
||||||
const renderGroupGrid = useCallback((group: Group, index: number) => (
|
const renderGroupGrid = useCallback((group: Group) => (
|
||||||
<div className='pb-4'>
|
<GroupGridItem group={group} />
|
||||||
<GroupGridItem group={group} />
|
|
||||||
</div>
|
|
||||||
), []);
|
), []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -79,10 +77,10 @@ export default (props: Props) => {
|
||||||
<VirtuosoGrid
|
<VirtuosoGrid
|
||||||
useWindowScroll
|
useWindowScroll
|
||||||
data={groups}
|
data={groups}
|
||||||
itemContent={(index, group) => renderGroupGrid(group, index)}
|
itemContent={(_index, group) => renderGroupGrid(group)}
|
||||||
components={{
|
components={{
|
||||||
Item: (props) => (
|
Item: (props) => (
|
||||||
<div {...props} className='w-1/2 flex-none' />
|
<div {...props} className='w-1/2 flex-none pb-4 [&:nth-last-of-type(-n+2)]:pb-0' />
|
||||||
),
|
),
|
||||||
List: GridList,
|
List: GridList,
|
||||||
}}
|
}}
|
||||||
|
|
|
@ -46,10 +46,8 @@ const Popular: React.FC = () => {
|
||||||
</div>
|
</div>
|
||||||
), []);
|
), []);
|
||||||
|
|
||||||
const renderGroupGrid = useCallback((group: Group, index: number) => (
|
const renderGroupGrid = useCallback((group: Group) => (
|
||||||
<div className='pb-4'>
|
<GroupGridItem group={group} />
|
||||||
<GroupGridItem group={group} />
|
|
||||||
</div>
|
|
||||||
), []);
|
), []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -73,10 +71,10 @@ const Popular: React.FC = () => {
|
||||||
<VirtuosoGrid
|
<VirtuosoGrid
|
||||||
useWindowScroll
|
useWindowScroll
|
||||||
data={groups}
|
data={groups}
|
||||||
itemContent={(index, group) => renderGroupGrid(group, index)}
|
itemContent={(_index, group) => renderGroupGrid(group)}
|
||||||
components={{
|
components={{
|
||||||
Item: (props) => (
|
Item: (props) => (
|
||||||
<div {...props} className='w-1/2 flex-none' />
|
<div {...props} className='w-1/2 flex-none pb-4 [&:nth-last-of-type(-n+2)]:pb-0' />
|
||||||
),
|
),
|
||||||
List: GridList,
|
List: GridList,
|
||||||
}}
|
}}
|
||||||
|
|
|
@ -46,10 +46,8 @@ const Suggested: React.FC = () => {
|
||||||
</div>
|
</div>
|
||||||
), []);
|
), []);
|
||||||
|
|
||||||
const renderGroupGrid = useCallback((group: Group, index: number) => (
|
const renderGroupGrid = useCallback((group: Group) => (
|
||||||
<div className='pb-4'>
|
<GroupGridItem group={group} />
|
||||||
<GroupGridItem group={group} />
|
|
||||||
</div>
|
|
||||||
), []);
|
), []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -73,10 +71,10 @@ const Suggested: React.FC = () => {
|
||||||
<VirtuosoGrid
|
<VirtuosoGrid
|
||||||
useWindowScroll
|
useWindowScroll
|
||||||
data={groups}
|
data={groups}
|
||||||
itemContent={(index, group) => renderGroupGrid(group, index)}
|
itemContent={(_index, group) => renderGroupGrid(group)}
|
||||||
components={{
|
components={{
|
||||||
Item: (props) => (
|
Item: (props) => (
|
||||||
<div {...props} className='w-1/2 flex-none' />
|
<div {...props} className='w-1/2 flex-none pb-4 [&:nth-last-of-type(-n+2)]:pb-0' />
|
||||||
),
|
),
|
||||||
List: GridList,
|
List: GridList,
|
||||||
}}
|
}}
|
||||||
|
|
|
@ -50,10 +50,8 @@ const Tag: React.FC<ITag> = (props) => {
|
||||||
</div>
|
</div>
|
||||||
), []);
|
), []);
|
||||||
|
|
||||||
const renderGroupGrid = useCallback((group: Group, index: number) => (
|
const renderGroupGrid = useCallback((group: Group) => (
|
||||||
<div className='pb-4'>
|
<GroupGridItem group={group} />
|
||||||
<GroupGridItem group={group} />
|
|
||||||
</div>
|
|
||||||
), []);
|
), []);
|
||||||
|
|
||||||
if (isLoading || !tag) {
|
if (isLoading || !tag) {
|
||||||
|
@ -100,10 +98,10 @@ const Tag: React.FC<ITag> = (props) => {
|
||||||
<VirtuosoGrid
|
<VirtuosoGrid
|
||||||
useWindowScroll
|
useWindowScroll
|
||||||
data={groups}
|
data={groups}
|
||||||
itemContent={(index, group) => renderGroupGrid(group, index)}
|
itemContent={(_index, group) => renderGroupGrid(group)}
|
||||||
components={{
|
components={{
|
||||||
Item: (props) => (
|
Item: (props) => (
|
||||||
<div {...props} className='w-1/2 flex-none' />
|
<div {...props} className='w-1/2 flex-none pb-4 [&:nth-last-of-type(-n+2)]:pb-0' />
|
||||||
),
|
),
|
||||||
List: GridList,
|
List: GridList,
|
||||||
}}
|
}}
|
||||||
|
|
|
@ -327,7 +327,7 @@ const SwitchingColumnsArea: React.FC<ISwitchingColumnsArea> = ({ children }) =>
|
||||||
{features.groups && <WrappedRoute path='/groups/:groupId/posts/:statusId' exact page={StatusPage} component={Status} content={children} />}
|
{features.groups && <WrappedRoute path='/groups/:groupId/posts/:statusId' exact page={StatusPage} component={Status} content={children} />}
|
||||||
|
|
||||||
{features.groupsTags && <WrappedRoute path='/group/:groupSlug/tags' exact page={GroupPage} component={GroupTagsSlug} content={children} />}
|
{features.groupsTags && <WrappedRoute path='/group/:groupSlug/tags' exact page={GroupPage} component={GroupTagsSlug} content={children} />}
|
||||||
{features.groupsTags && <WrappedRoute path='/group/:groupSlug/tag/:id' exact page={GroupsPendingPage} component={GroupTagTimelineSlug} content={children} />}
|
{features.groupsTags && <WrappedRoute path='/group/:groupSlug/tag/:tagId' exact page={GroupsPendingPage} component={GroupTagTimelineSlug} content={children} />}
|
||||||
{features.groups && <WrappedRoute path='/group/:groupSlug' exact page={GroupPage} component={GroupTimelineSlug} content={children} />}
|
{features.groups && <WrappedRoute path='/group/:groupSlug' exact page={GroupPage} component={GroupTimelineSlug} content={children} />}
|
||||||
{features.groups && <WrappedRoute path='/group/:groupSlug/members' exact page={GroupPage} component={GroupMembersSlug} content={children} />}
|
{features.groups && <WrappedRoute path='/group/:groupSlug/members' exact page={GroupPage} component={GroupMembersSlug} content={children} />}
|
||||||
{features.groups && <WrappedRoute path='/group/:groupSlug/media' publicRoute={!authenticatedProfile} component={GroupGallerySlug} page={GroupPage} content={children} />}
|
{features.groups && <WrappedRoute path='/group/:groupSlug/media' publicRoute={!authenticatedProfile} component={GroupGallerySlug} page={GroupPage} content={children} />}
|
||||||
|
|
Loading…
Reference in a new issue