Merge branch 'tag-improvements' into 'develop'

Tag improvements

See merge request soapbox-pub/soapbox!2458
This commit is contained in:
Chewbacca 2023-04-24 12:59:13 +00:00
commit b6a5c56859
10 changed files with 78 additions and 46 deletions

View file

@ -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,

View file

@ -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()

View file

@ -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>
); );
}; };

View file

@ -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>

View file

@ -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}
> >

View file

@ -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,
}} }}

View file

@ -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,
}} }}

View file

@ -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,
}} }}

View file

@ -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,
}} }}

View file

@ -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} />}