From 65bab5e7865f895b3a060b3f9f9d53d7b70ca67a Mon Sep 17 00:00:00 2001 From: Soapbox Bot Date: Thu, 20 Apr 2023 21:04:44 +0000 Subject: [PATCH 01/56] Update Node.js to v20 --- .gitlab-ci.yml | 2 +- Dockerfile | 2 +- Dockerfile.dev | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 9516ec2d6..97892e166 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -1,4 +1,4 @@ -image: node:18 +image: node:20 variables: NODE_ENV: test diff --git a/Dockerfile b/Dockerfile index bfb7c2e48..b02bf86e1 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM node:18 as build +FROM node:20 as build WORKDIR /app COPY package.json . COPY yarn.lock . diff --git a/Dockerfile.dev b/Dockerfile.dev index 8d1655db0..1e6056945 100644 --- a/Dockerfile.dev +++ b/Dockerfile.dev @@ -1,4 +1,4 @@ -FROM node:18 +FROM node:20 RUN apt-get update &&\ apt-get install -y inotify-tools &&\ From d8a3f51e4a95c791e9b17790d36b1377433f5916 Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Thu, 27 Apr 2023 14:11:45 -0500 Subject: [PATCH 02/56] useGroupMembershipRequests: dismiss instead of decrement --- app/soapbox/hooks/api/groups/useGroupMembershipRequests.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/soapbox/hooks/api/groups/useGroupMembershipRequests.ts b/app/soapbox/hooks/api/groups/useGroupMembershipRequests.ts index 78a59ced6..171510338 100644 --- a/app/soapbox/hooks/api/groups/useGroupMembershipRequests.ts +++ b/app/soapbox/hooks/api/groups/useGroupMembershipRequests.ts @@ -1,5 +1,5 @@ import { Entities } from 'soapbox/entity-store/entities'; -import { useEntities, useIncrementEntity } from 'soapbox/entity-store/hooks'; +import { useDismissEntity, useEntities } from 'soapbox/entity-store/hooks'; import { useApi } from 'soapbox/hooks/useApi'; import { accountSchema } from 'soapbox/schemas'; import { GroupRoles } from 'soapbox/schemas/group-member'; @@ -23,13 +23,13 @@ function useGroupMembershipRequests(groupId: string) { }, ); - const { incrementEntity: authorize } = useIncrementEntity(path, -1, async (accountId: string) => { + const { dismissEntity: authorize } = useDismissEntity(path, async (accountId: string) => { const response = await api.post(`/api/v1/groups/${groupId}/membership_requests/${accountId}/authorize`); invalidate(); return response; }); - const { incrementEntity: reject } = useIncrementEntity(path, -1, async (accountId: string) => { + const { dismissEntity: reject } = useDismissEntity(path, async (accountId: string) => { const response = await api.post(`/api/v1/groups/${groupId}/membership_requests/${accountId}/reject`); invalidate(); return response; From e0af6c4b2e78ec010973d279fc620e6299e5741a Mon Sep 17 00:00:00 2001 From: Chewbacca Date: Mon, 1 May 2023 14:58:40 -0400 Subject: [PATCH 03/56] Move Group hooks to api folder --- .../{hooks/api => api/hooks/accounts}/useAccount.ts | 2 +- .../api => api/hooks/accounts}/useRelationships.ts | 3 +-- .../api => api/hooks}/groups/useBlockGroupMember.ts | 0 .../hooks}/groups/useCancelMembershipRequest.ts | 0 .../{hooks/api => api/hooks}/groups/useCreateGroup.ts | 0 .../{hooks/api => api/hooks}/groups/useDeleteGroup.ts | 0 .../api => api/hooks}/groups/useDemoteGroupMember.ts | 0 .../{hooks/api => api/hooks}/groups/useGroupLookup.ts | 0 .../{hooks/api => api/hooks}/groups/useGroupMedia.ts | 0 .../{hooks/api => api/hooks/groups}/useGroupMembers.ts | 2 +- .../hooks}/groups/useGroupMembershipRequests.ts | 0 .../{hooks/api => api/hooks}/groups/useGroupSearch.ts | 4 +--- .../{hooks/api => api/hooks}/groups/useGroupTag.ts | 0 .../{hooks/api => api/hooks}/groups/useGroupTags.ts | 0 .../api => api/hooks}/groups/useGroupValidation.ts | 0 app/soapbox/{hooks/api => api/hooks}/groups/useGroups.ts | 3 +-- .../{hooks/api => api/hooks}/groups/useGroupsFromTag.ts | 4 +--- .../{hooks/api => api/hooks}/groups/useJoinGroup.ts | 0 .../{hooks/api => api/hooks}/groups/useLeaveGroup.ts | 0 .../{hooks/api => api/hooks/groups}/usePopularGroups.ts | 7 ++++--- .../{hooks/api => api/hooks}/groups/usePopularTags.ts | 6 ++---- .../api => api/hooks}/groups/usePromoteGroupMember.ts | 0 .../api => api/hooks/groups}/useSuggestedGroups.ts | 7 +++---- .../{hooks/api => api/hooks}/groups/useUpdateGroup.ts | 0 .../{hooks/api => api/hooks}/groups/useUpdateGroupTag.ts | 0 app/soapbox/{hooks/api => api/hooks}/index.ts | 9 +++++++-- app/soapbox/components/hoc/group-lookup-hoc.tsx | 2 +- .../features/group/components/group-action-button.tsx | 2 +- .../features/group/components/group-member-list-item.tsx | 2 +- .../features/group/components/group-options-button.tsx | 2 +- .../features/group/components/group-tag-list-item.tsx | 2 +- app/soapbox/features/group/edit-group.tsx | 2 +- app/soapbox/features/group/group-blocked-members.tsx | 2 +- app/soapbox/features/group/group-gallery.tsx | 2 +- app/soapbox/features/group/group-members.tsx | 5 ++--- app/soapbox/features/group/group-membership-requests.tsx | 3 +-- app/soapbox/features/group/group-tag-timeline.tsx | 2 +- app/soapbox/features/group/group-tags.tsx | 2 +- app/soapbox/features/group/group-timeline.tsx | 2 +- app/soapbox/features/group/manage-group.tsx | 2 +- .../groups/components/discover/popular-groups.tsx | 2 +- .../features/groups/components/discover/popular-tags.tsx | 2 +- .../groups/components/discover/search/results.tsx | 2 +- .../groups/components/discover/search/search.tsx | 2 +- .../groups/components/discover/suggested-groups.tsx | 2 +- app/soapbox/features/groups/index.tsx | 2 +- app/soapbox/features/groups/popular.tsx | 2 +- app/soapbox/features/groups/suggested.tsx | 2 +- app/soapbox/features/groups/tag.tsx | 2 +- app/soapbox/features/groups/tags.tsx | 2 +- .../modals/manage-group-modal/create-group-modal.tsx | 2 +- .../modals/manage-group-modal/steps/details-step.tsx | 2 +- .../modals/manage-group-modal/steps/privacy-step.tsx | 2 +- .../features/ui/components/panels/my-groups-panel.tsx | 2 +- .../ui/components/panels/suggested-groups-panel.tsx | 2 +- app/soapbox/hooks/useGroupsPath.ts | 3 ++- app/soapbox/pages/group-page.tsx | 6 +++--- 57 files changed, 57 insertions(+), 61 deletions(-) rename app/soapbox/{hooks/api => api/hooks/accounts}/useAccount.ts (94%) rename app/soapbox/{hooks/api => api/hooks/accounts}/useRelationships.ts (92%) rename app/soapbox/{hooks/api => api/hooks}/groups/useBlockGroupMember.ts (100%) rename app/soapbox/{hooks/api => api/hooks}/groups/useCancelMembershipRequest.ts (100%) rename app/soapbox/{hooks/api => api/hooks}/groups/useCreateGroup.ts (100%) rename app/soapbox/{hooks/api => api/hooks}/groups/useDeleteGroup.ts (100%) rename app/soapbox/{hooks/api => api/hooks}/groups/useDemoteGroupMember.ts (100%) rename app/soapbox/{hooks/api => api/hooks}/groups/useGroupLookup.ts (100%) rename app/soapbox/{hooks/api => api/hooks}/groups/useGroupMedia.ts (100%) rename app/soapbox/{hooks/api => api/hooks/groups}/useGroupMembers.ts (93%) rename app/soapbox/{hooks/api => api/hooks}/groups/useGroupMembershipRequests.ts (100%) rename app/soapbox/{hooks/api => api/hooks}/groups/useGroupSearch.ts (91%) rename app/soapbox/{hooks/api => api/hooks}/groups/useGroupTag.ts (100%) rename app/soapbox/{hooks/api => api/hooks}/groups/useGroupTags.ts (100%) rename app/soapbox/{hooks/api => api/hooks}/groups/useGroupValidation.ts (100%) rename app/soapbox/{hooks/api => api/hooks}/groups/useGroups.ts (97%) rename app/soapbox/{hooks/api => api/hooks}/groups/useGroupsFromTag.ts (90%) rename app/soapbox/{hooks/api => api/hooks}/groups/useJoinGroup.ts (100%) rename app/soapbox/{hooks/api => api/hooks}/groups/useLeaveGroup.ts (100%) rename app/soapbox/{hooks/api => api/hooks/groups}/usePopularGroups.ts (82%) rename app/soapbox/{hooks/api => api/hooks}/groups/usePopularTags.ts (77%) rename app/soapbox/{hooks/api => api/hooks}/groups/usePromoteGroupMember.ts (100%) rename app/soapbox/{hooks/api => api/hooks/groups}/useSuggestedGroups.ts (78%) rename app/soapbox/{hooks/api => api/hooks}/groups/useUpdateGroup.ts (100%) rename app/soapbox/{hooks/api => api/hooks}/groups/useUpdateGroupTag.ts (100%) rename app/soapbox/{hooks/api => api/hooks}/index.ts (77%) diff --git a/app/soapbox/hooks/api/useAccount.ts b/app/soapbox/api/hooks/accounts/useAccount.ts similarity index 94% rename from app/soapbox/hooks/api/useAccount.ts rename to app/soapbox/api/hooks/accounts/useAccount.ts index a222d8de4..2442ad642 100644 --- a/app/soapbox/hooks/api/useAccount.ts +++ b/app/soapbox/api/hooks/accounts/useAccount.ts @@ -1,8 +1,8 @@ import { Entities } from 'soapbox/entity-store/entities'; import { useEntity } from 'soapbox/entity-store/hooks'; +import { useApi } from 'soapbox/hooks/useApi'; import { type Account, accountSchema } from 'soapbox/schemas'; -import { useApi } from '../useApi'; import { useRelationships } from './useRelationships'; diff --git a/app/soapbox/hooks/api/useRelationships.ts b/app/soapbox/api/hooks/accounts/useRelationships.ts similarity index 92% rename from app/soapbox/hooks/api/useRelationships.ts rename to app/soapbox/api/hooks/accounts/useRelationships.ts index fde5f1017..2103e2438 100644 --- a/app/soapbox/hooks/api/useRelationships.ts +++ b/app/soapbox/api/hooks/accounts/useRelationships.ts @@ -1,9 +1,8 @@ import { Entities } from 'soapbox/entity-store/entities'; import { useEntities } from 'soapbox/entity-store/hooks'; +import { useApi } from 'soapbox/hooks/useApi'; import { type Relationship, relationshipSchema } from 'soapbox/schemas'; -import { useApi } from '../useApi'; - function useRelationships(ids: string[]) { const api = useApi(); diff --git a/app/soapbox/hooks/api/groups/useBlockGroupMember.ts b/app/soapbox/api/hooks/groups/useBlockGroupMember.ts similarity index 100% rename from app/soapbox/hooks/api/groups/useBlockGroupMember.ts rename to app/soapbox/api/hooks/groups/useBlockGroupMember.ts diff --git a/app/soapbox/hooks/api/groups/useCancelMembershipRequest.ts b/app/soapbox/api/hooks/groups/useCancelMembershipRequest.ts similarity index 100% rename from app/soapbox/hooks/api/groups/useCancelMembershipRequest.ts rename to app/soapbox/api/hooks/groups/useCancelMembershipRequest.ts diff --git a/app/soapbox/hooks/api/groups/useCreateGroup.ts b/app/soapbox/api/hooks/groups/useCreateGroup.ts similarity index 100% rename from app/soapbox/hooks/api/groups/useCreateGroup.ts rename to app/soapbox/api/hooks/groups/useCreateGroup.ts diff --git a/app/soapbox/hooks/api/groups/useDeleteGroup.ts b/app/soapbox/api/hooks/groups/useDeleteGroup.ts similarity index 100% rename from app/soapbox/hooks/api/groups/useDeleteGroup.ts rename to app/soapbox/api/hooks/groups/useDeleteGroup.ts diff --git a/app/soapbox/hooks/api/groups/useDemoteGroupMember.ts b/app/soapbox/api/hooks/groups/useDemoteGroupMember.ts similarity index 100% rename from app/soapbox/hooks/api/groups/useDemoteGroupMember.ts rename to app/soapbox/api/hooks/groups/useDemoteGroupMember.ts diff --git a/app/soapbox/hooks/api/groups/useGroupLookup.ts b/app/soapbox/api/hooks/groups/useGroupLookup.ts similarity index 100% rename from app/soapbox/hooks/api/groups/useGroupLookup.ts rename to app/soapbox/api/hooks/groups/useGroupLookup.ts diff --git a/app/soapbox/hooks/api/groups/useGroupMedia.ts b/app/soapbox/api/hooks/groups/useGroupMedia.ts similarity index 100% rename from app/soapbox/hooks/api/groups/useGroupMedia.ts rename to app/soapbox/api/hooks/groups/useGroupMedia.ts diff --git a/app/soapbox/hooks/api/useGroupMembers.ts b/app/soapbox/api/hooks/groups/useGroupMembers.ts similarity index 93% rename from app/soapbox/hooks/api/useGroupMembers.ts rename to app/soapbox/api/hooks/groups/useGroupMembers.ts index 3a1c99af5..a9b03e7f2 100644 --- a/app/soapbox/hooks/api/useGroupMembers.ts +++ b/app/soapbox/api/hooks/groups/useGroupMembers.ts @@ -3,7 +3,7 @@ import { useEntities } from 'soapbox/entity-store/hooks'; import { GroupMember, groupMemberSchema } from 'soapbox/schemas'; import { GroupRoles } from 'soapbox/schemas/group-member'; -import { useApi } from '../useApi'; +import { useApi } from '../../../hooks/useApi'; function useGroupMembers(groupId: string, role: GroupRoles) { const api = useApi(); diff --git a/app/soapbox/hooks/api/groups/useGroupMembershipRequests.ts b/app/soapbox/api/hooks/groups/useGroupMembershipRequests.ts similarity index 100% rename from app/soapbox/hooks/api/groups/useGroupMembershipRequests.ts rename to app/soapbox/api/hooks/groups/useGroupMembershipRequests.ts diff --git a/app/soapbox/hooks/api/groups/useGroupSearch.ts b/app/soapbox/api/hooks/groups/useGroupSearch.ts similarity index 91% rename from app/soapbox/hooks/api/groups/useGroupSearch.ts rename to app/soapbox/api/hooks/groups/useGroupSearch.ts index 17c10e90f..508310911 100644 --- a/app/soapbox/hooks/api/groups/useGroupSearch.ts +++ b/app/soapbox/api/hooks/groups/useGroupSearch.ts @@ -1,10 +1,8 @@ import { Entities } from 'soapbox/entity-store/entities'; import { useEntities } from 'soapbox/entity-store/hooks'; +import { useApi, useFeatures } from 'soapbox/hooks'; import { groupSchema } from 'soapbox/schemas'; -import { useApi } from '../../useApi'; -import { useFeatures } from '../../useFeatures'; - import { useGroupRelationships } from './useGroups'; import type { Group } from 'soapbox/schemas'; diff --git a/app/soapbox/hooks/api/groups/useGroupTag.ts b/app/soapbox/api/hooks/groups/useGroupTag.ts similarity index 100% rename from app/soapbox/hooks/api/groups/useGroupTag.ts rename to app/soapbox/api/hooks/groups/useGroupTag.ts diff --git a/app/soapbox/hooks/api/groups/useGroupTags.ts b/app/soapbox/api/hooks/groups/useGroupTags.ts similarity index 100% rename from app/soapbox/hooks/api/groups/useGroupTags.ts rename to app/soapbox/api/hooks/groups/useGroupTags.ts diff --git a/app/soapbox/hooks/api/groups/useGroupValidation.ts b/app/soapbox/api/hooks/groups/useGroupValidation.ts similarity index 100% rename from app/soapbox/hooks/api/groups/useGroupValidation.ts rename to app/soapbox/api/hooks/groups/useGroupValidation.ts diff --git a/app/soapbox/hooks/api/groups/useGroups.ts b/app/soapbox/api/hooks/groups/useGroups.ts similarity index 97% rename from app/soapbox/hooks/api/groups/useGroups.ts rename to app/soapbox/api/hooks/groups/useGroups.ts index 9db3001ce..42904c142 100644 --- a/app/soapbox/hooks/api/groups/useGroups.ts +++ b/app/soapbox/api/hooks/groups/useGroups.ts @@ -5,11 +5,10 @@ import { fetchGroupRelationshipsSuccess } from 'soapbox/actions/groups'; import { Entities } from 'soapbox/entity-store/entities'; import { useEntities, useEntity } from 'soapbox/entity-store/hooks'; import { useApi, useAppDispatch } from 'soapbox/hooks'; +import { useFeatures } from 'soapbox/hooks/useFeatures'; import { groupSchema, Group } from 'soapbox/schemas/group'; import { groupRelationshipSchema, GroupRelationship } from 'soapbox/schemas/group-relationship'; -import { useFeatures } from '../../useFeatures'; - function useGroups(q: string = '') { const api = useApi(); const features = useFeatures(); diff --git a/app/soapbox/hooks/api/groups/useGroupsFromTag.ts b/app/soapbox/api/hooks/groups/useGroupsFromTag.ts similarity index 90% rename from app/soapbox/hooks/api/groups/useGroupsFromTag.ts rename to app/soapbox/api/hooks/groups/useGroupsFromTag.ts index a6b8540dc..f579c719e 100644 --- a/app/soapbox/hooks/api/groups/useGroupsFromTag.ts +++ b/app/soapbox/api/hooks/groups/useGroupsFromTag.ts @@ -1,10 +1,8 @@ import { Entities } from 'soapbox/entity-store/entities'; import { useEntities } from 'soapbox/entity-store/hooks'; +import { useApi, useFeatures } from 'soapbox/hooks'; import { groupSchema } from 'soapbox/schemas'; -import { useApi } from '../../useApi'; -import { useFeatures } from '../../useFeatures'; - import { useGroupRelationships } from './useGroups'; import type { Group } from 'soapbox/schemas'; diff --git a/app/soapbox/hooks/api/groups/useJoinGroup.ts b/app/soapbox/api/hooks/groups/useJoinGroup.ts similarity index 100% rename from app/soapbox/hooks/api/groups/useJoinGroup.ts rename to app/soapbox/api/hooks/groups/useJoinGroup.ts diff --git a/app/soapbox/hooks/api/groups/useLeaveGroup.ts b/app/soapbox/api/hooks/groups/useLeaveGroup.ts similarity index 100% rename from app/soapbox/hooks/api/groups/useLeaveGroup.ts rename to app/soapbox/api/hooks/groups/useLeaveGroup.ts diff --git a/app/soapbox/hooks/api/usePopularGroups.ts b/app/soapbox/api/hooks/groups/usePopularGroups.ts similarity index 82% rename from app/soapbox/hooks/api/usePopularGroups.ts rename to app/soapbox/api/hooks/groups/usePopularGroups.ts index 385322500..ead72a79b 100644 --- a/app/soapbox/hooks/api/usePopularGroups.ts +++ b/app/soapbox/api/hooks/groups/usePopularGroups.ts @@ -2,9 +2,10 @@ import { Entities } from 'soapbox/entity-store/entities'; import { useEntities } from 'soapbox/entity-store/hooks'; import { Group, groupSchema } from 'soapbox/schemas'; -import { useGroupRelationships } from '../api/groups/useGroups'; -import { useApi } from '../useApi'; -import { useFeatures } from '../useFeatures'; +import { useApi } from '../../../hooks/useApi'; +import { useFeatures } from '../../../hooks/useFeatures'; + +import { useGroupRelationships } from './useGroups'; function usePopularGroups() { const api = useApi(); diff --git a/app/soapbox/hooks/api/groups/usePopularTags.ts b/app/soapbox/api/hooks/groups/usePopularTags.ts similarity index 77% rename from app/soapbox/hooks/api/groups/usePopularTags.ts rename to app/soapbox/api/hooks/groups/usePopularTags.ts index 0bd272a2d..e0ec2c550 100644 --- a/app/soapbox/hooks/api/groups/usePopularTags.ts +++ b/app/soapbox/api/hooks/groups/usePopularTags.ts @@ -1,9 +1,7 @@ import { Entities } from 'soapbox/entity-store/entities'; import { useEntities } from 'soapbox/entity-store/hooks'; -import { GroupTag, groupTagSchema } from 'soapbox/schemas'; - -import { useApi } from '../../useApi'; -import { useFeatures } from '../../useFeatures'; +import { useApi, useFeatures } from 'soapbox/hooks'; +import { type GroupTag, groupTagSchema } from 'soapbox/schemas'; function usePopularTags() { const api = useApi(); diff --git a/app/soapbox/hooks/api/groups/usePromoteGroupMember.ts b/app/soapbox/api/hooks/groups/usePromoteGroupMember.ts similarity index 100% rename from app/soapbox/hooks/api/groups/usePromoteGroupMember.ts rename to app/soapbox/api/hooks/groups/usePromoteGroupMember.ts diff --git a/app/soapbox/hooks/api/useSuggestedGroups.ts b/app/soapbox/api/hooks/groups/useSuggestedGroups.ts similarity index 78% rename from app/soapbox/hooks/api/useSuggestedGroups.ts rename to app/soapbox/api/hooks/groups/useSuggestedGroups.ts index 49f60c2b1..78b1ddb79 100644 --- a/app/soapbox/hooks/api/useSuggestedGroups.ts +++ b/app/soapbox/api/hooks/groups/useSuggestedGroups.ts @@ -1,10 +1,9 @@ import { Entities } from 'soapbox/entity-store/entities'; import { useEntities } from 'soapbox/entity-store/hooks'; -import { Group, groupSchema } from 'soapbox/schemas'; +import { useApi, useFeatures } from 'soapbox/hooks'; +import { type Group, groupSchema } from 'soapbox/schemas'; -import { useGroupRelationships } from '../api/groups/useGroups'; -import { useApi } from '../useApi'; -import { useFeatures } from '../useFeatures'; +import { useGroupRelationships } from './useGroups'; function useSuggestedGroups() { const api = useApi(); diff --git a/app/soapbox/hooks/api/groups/useUpdateGroup.ts b/app/soapbox/api/hooks/groups/useUpdateGroup.ts similarity index 100% rename from app/soapbox/hooks/api/groups/useUpdateGroup.ts rename to app/soapbox/api/hooks/groups/useUpdateGroup.ts diff --git a/app/soapbox/hooks/api/groups/useUpdateGroupTag.ts b/app/soapbox/api/hooks/groups/useUpdateGroupTag.ts similarity index 100% rename from app/soapbox/hooks/api/groups/useUpdateGroupTag.ts rename to app/soapbox/api/hooks/groups/useUpdateGroupTag.ts diff --git a/app/soapbox/hooks/api/index.ts b/app/soapbox/api/hooks/index.ts similarity index 77% rename from app/soapbox/hooks/api/index.ts rename to app/soapbox/api/hooks/index.ts index c8e1f67c3..eba4f0d84 100644 --- a/app/soapbox/hooks/api/index.ts +++ b/app/soapbox/api/hooks/index.ts @@ -1,7 +1,8 @@ + /** * Accounts */ -export { useAccount } from './useAccount'; +export { useAccount } from './accounts/useAccount'; /** * Groups @@ -12,7 +13,9 @@ export { useCreateGroup, type CreateGroupParams } from './groups/useCreateGroup' export { useDeleteGroup } from './groups/useDeleteGroup'; export { useDemoteGroupMember } from './groups/useDemoteGroupMember'; export { useGroup, useGroups } from './groups/useGroups'; +export { useGroupLookup } from './groups/useGroupLookup'; export { useGroupMedia } from './groups/useGroupMedia'; +export { useGroupMembers } from './groups/useGroupMembers'; export { useGroupMembershipRequests } from './groups/useGroupMembershipRequests'; export { useGroupSearch } from './groups/useGroupSearch'; export { useGroupTag } from './groups/useGroupTag'; @@ -21,12 +24,14 @@ export { useGroupValidation } from './groups/useGroupValidation'; export { useGroupsFromTag } from './groups/useGroupsFromTag'; export { useJoinGroup } from './groups/useJoinGroup'; export { useLeaveGroup } from './groups/useLeaveGroup'; +export { usePopularGroups } from './groups/usePopularGroups'; export { usePopularTags } from './groups/usePopularTags'; export { usePromoteGroupMember } from './groups/usePromoteGroupMember'; +export { useSuggestedGroups } from './groups/useSuggestedGroups'; export { useUpdateGroup } from './groups/useUpdateGroup'; export { useUpdateGroupTag } from './groups/useUpdateGroupTag'; /** * Relationships */ -export { useRelationships } from './useRelationships'; +export { useRelationships } from './accounts/useRelationships'; \ No newline at end of file diff --git a/app/soapbox/components/hoc/group-lookup-hoc.tsx b/app/soapbox/components/hoc/group-lookup-hoc.tsx index a7c76eed6..07c2d475b 100644 --- a/app/soapbox/components/hoc/group-lookup-hoc.tsx +++ b/app/soapbox/components/hoc/group-lookup-hoc.tsx @@ -1,7 +1,7 @@ import React from 'react'; +import { useGroupLookup } from 'soapbox/api/hooks'; import ColumnLoading from 'soapbox/features/ui/components/column-loading'; -import { useGroupLookup } from 'soapbox/hooks/api/groups/useGroupLookup'; import { Layout } from '../ui'; diff --git a/app/soapbox/features/group/components/group-action-button.tsx b/app/soapbox/features/group/components/group-action-button.tsx index e9a47a962..005b9e245 100644 --- a/app/soapbox/features/group/components/group-action-button.tsx +++ b/app/soapbox/features/group/components/group-action-button.tsx @@ -3,11 +3,11 @@ import { defineMessages, FormattedMessage, useIntl } from 'react-intl'; import { fetchGroupRelationshipsSuccess } from 'soapbox/actions/groups'; import { openModal } from 'soapbox/actions/modals'; +import { useCancelMembershipRequest, useJoinGroup, useLeaveGroup } from 'soapbox/api/hooks'; import { Button } from 'soapbox/components/ui'; import { importEntities } from 'soapbox/entity-store/actions'; import { Entities } from 'soapbox/entity-store/entities'; import { useAppDispatch, useOwnAccount } from 'soapbox/hooks'; -import { useCancelMembershipRequest, useJoinGroup, useLeaveGroup } from 'soapbox/hooks/api'; import { queryClient } from 'soapbox/queries/client'; import { GroupKeys } from 'soapbox/queries/groups'; import { GroupRoles } from 'soapbox/schemas/group-member'; diff --git a/app/soapbox/features/group/components/group-member-list-item.tsx b/app/soapbox/features/group/components/group-member-list-item.tsx index c88f4e58d..d2226879a 100644 --- a/app/soapbox/features/group/components/group-member-list-item.tsx +++ b/app/soapbox/features/group/components/group-member-list-item.tsx @@ -4,6 +4,7 @@ import { defineMessages, useIntl } from 'react-intl'; import { groupKick } from 'soapbox/actions/groups'; import { openModal } from 'soapbox/actions/modals'; +import { useAccount, useBlockGroupMember, useDemoteGroupMember, usePromoteGroupMember } from 'soapbox/api/hooks'; import Account from 'soapbox/components/account'; import DropdownMenu from 'soapbox/components/dropdown-menu/dropdown-menu'; import { HStack } from 'soapbox/components/ui'; @@ -11,7 +12,6 @@ import { deleteEntities } from 'soapbox/entity-store/actions'; import { Entities } from 'soapbox/entity-store/entities'; import PlaceholderAccount from 'soapbox/features/placeholder/components/placeholder-account'; import { useAppDispatch, useFeatures } from 'soapbox/hooks'; -import { useAccount, useBlockGroupMember, useDemoteGroupMember, usePromoteGroupMember } from 'soapbox/hooks/api'; import { GroupRoles } from 'soapbox/schemas/group-member'; import toast from 'soapbox/toast'; diff --git a/app/soapbox/features/group/components/group-options-button.tsx b/app/soapbox/features/group/components/group-options-button.tsx index 7f24a3668..597a751d7 100644 --- a/app/soapbox/features/group/components/group-options-button.tsx +++ b/app/soapbox/features/group/components/group-options-button.tsx @@ -3,10 +3,10 @@ import { defineMessages, useIntl } from 'react-intl'; import { openModal } from 'soapbox/actions/modals'; import { initReport, ReportableEntities } from 'soapbox/actions/reports'; +import { useLeaveGroup } from 'soapbox/api/hooks'; import DropdownMenu, { Menu } from 'soapbox/components/dropdown-menu'; import { IconButton } from 'soapbox/components/ui'; import { useAppDispatch, useOwnAccount } from 'soapbox/hooks'; -import { useLeaveGroup } from 'soapbox/hooks/api'; import { GroupRoles } from 'soapbox/schemas/group-member'; import toast from 'soapbox/toast'; diff --git a/app/soapbox/features/group/components/group-tag-list-item.tsx b/app/soapbox/features/group/components/group-tag-list-item.tsx index eab096747..7908bb13d 100644 --- a/app/soapbox/features/group/components/group-tag-list-item.tsx +++ b/app/soapbox/features/group/components/group-tag-list-item.tsx @@ -2,11 +2,11 @@ import React from 'react'; import { defineMessages, useIntl } from 'react-intl'; import { Link } from 'react-router-dom'; +import { useUpdateGroupTag } from 'soapbox/api/hooks'; import { HStack, IconButton, Stack, Text, Tooltip } from 'soapbox/components/ui'; import { importEntities } from 'soapbox/entity-store/actions'; import { Entities } from 'soapbox/entity-store/entities'; import { useAppDispatch } from 'soapbox/hooks'; -import { useUpdateGroupTag } from 'soapbox/hooks/api'; import { GroupRoles } from 'soapbox/schemas/group-member'; import toast from 'soapbox/toast'; import { shortNumberFormat } from 'soapbox/utils/numbers'; diff --git a/app/soapbox/features/group/edit-group.tsx b/app/soapbox/features/group/edit-group.tsx index 184df76d5..c0e972604 100644 --- a/app/soapbox/features/group/edit-group.tsx +++ b/app/soapbox/features/group/edit-group.tsx @@ -1,9 +1,9 @@ import React, { useEffect, useState } from 'react'; import { defineMessages, FormattedMessage, useIntl } from 'react-intl'; +import { useGroup, useUpdateGroup } from 'soapbox/api/hooks'; import { Button, Column, Form, FormActions, FormGroup, Icon, Input, Spinner, Textarea } from 'soapbox/components/ui'; import { useAppSelector, useInstance } from 'soapbox/hooks'; -import { useGroup, useUpdateGroup } from 'soapbox/hooks/api'; import { useImageField, useTextField } from 'soapbox/hooks/forms'; import toast from 'soapbox/toast'; import { isDefaultAvatar, isDefaultHeader } from 'soapbox/utils/accounts'; diff --git a/app/soapbox/features/group/group-blocked-members.tsx b/app/soapbox/features/group/group-blocked-members.tsx index 988b90b1b..f45e8259f 100644 --- a/app/soapbox/features/group/group-blocked-members.tsx +++ b/app/soapbox/features/group/group-blocked-members.tsx @@ -2,11 +2,11 @@ import React, { useCallback, useEffect } from 'react'; import { FormattedMessage, defineMessages, useIntl } from 'react-intl'; import { fetchGroupBlocks, groupUnblock } from 'soapbox/actions/groups'; +import { useGroup } from 'soapbox/api/hooks'; import Account from 'soapbox/components/account'; import ScrollableList from 'soapbox/components/scrollable-list'; import { Button, Column, HStack, Spinner } from 'soapbox/components/ui'; import { useAppDispatch, useAppSelector } from 'soapbox/hooks'; -import { useGroup } from 'soapbox/hooks/api'; import { makeGetAccount } from 'soapbox/selectors'; import toast from 'soapbox/toast'; diff --git a/app/soapbox/features/group/group-gallery.tsx b/app/soapbox/features/group/group-gallery.tsx index 70a77680d..f0b2b95c7 100644 --- a/app/soapbox/features/group/group-gallery.tsx +++ b/app/soapbox/features/group/group-gallery.tsx @@ -2,11 +2,11 @@ import React from 'react'; import { FormattedMessage } from 'react-intl'; import { openModal } from 'soapbox/actions/modals'; +import { useGroup, useGroupMedia } from 'soapbox/api/hooks'; import LoadMore from 'soapbox/components/load-more'; import MissingIndicator from 'soapbox/components/missing-indicator'; import { Column, Spinner } from 'soapbox/components/ui'; import { useAppDispatch } from 'soapbox/hooks'; -import { useGroup, useGroupMedia } from 'soapbox/hooks/api'; import MediaItem from '../account-gallery/components/media-item'; diff --git a/app/soapbox/features/group/group-members.tsx b/app/soapbox/features/group/group-members.tsx index 36ccd05b7..1c2a892f3 100644 --- a/app/soapbox/features/group/group-members.tsx +++ b/app/soapbox/features/group/group-members.tsx @@ -1,12 +1,10 @@ import clsx from 'clsx'; import React, { useMemo } from 'react'; +import { useGroup, useGroupMembers, useGroupMembershipRequests } from 'soapbox/api/hooks'; import { PendingItemsRow } from 'soapbox/components/pending-items-row'; import ScrollableList from 'soapbox/components/scrollable-list'; import { useFeatures } from 'soapbox/hooks'; -import { useGroup } from 'soapbox/hooks/api'; -import { useGroupMembershipRequests } from 'soapbox/hooks/api/groups/useGroupMembershipRequests'; -import { useGroupMembers } from 'soapbox/hooks/api/useGroupMembers'; import { GroupRoles } from 'soapbox/schemas/group-member'; import PlaceholderAccount from '../placeholder/components/placeholder-account'; @@ -15,6 +13,7 @@ import GroupMemberListItem from './components/group-member-list-item'; import type { Group } from 'soapbox/types/entities'; + interface IGroupMembers { params: { groupId: string } } diff --git a/app/soapbox/features/group/group-membership-requests.tsx b/app/soapbox/features/group/group-membership-requests.tsx index c83d45b06..e50092887 100644 --- a/app/soapbox/features/group/group-membership-requests.tsx +++ b/app/soapbox/features/group/group-membership-requests.tsx @@ -1,12 +1,11 @@ import React, { useEffect } from 'react'; import { FormattedMessage, defineMessages, useIntl } from 'react-intl'; +import { useGroup, useGroupMembers, useGroupMembershipRequests } from 'soapbox/api/hooks'; import Account from 'soapbox/components/account'; import { AuthorizeRejectButtons } from 'soapbox/components/authorize-reject-buttons'; import ScrollableList from 'soapbox/components/scrollable-list'; import { Column, HStack, Spinner } from 'soapbox/components/ui'; -import { useGroup, useGroupMembershipRequests } from 'soapbox/hooks/api'; -import { useGroupMembers } from 'soapbox/hooks/api/useGroupMembers'; import { GroupRoles } from 'soapbox/schemas/group-member'; import toast from 'soapbox/toast'; diff --git a/app/soapbox/features/group/group-tag-timeline.tsx b/app/soapbox/features/group/group-tag-timeline.tsx index f2ddee382..e79092e7a 100644 --- a/app/soapbox/features/group/group-tag-timeline.tsx +++ b/app/soapbox/features/group/group-tag-timeline.tsx @@ -2,9 +2,9 @@ import React, { useEffect } from 'react'; import { FormattedMessage } from 'react-intl'; import { expandGroupTimelineFromTag } from 'soapbox/actions/timelines'; +import { useGroup, useGroupTag } from 'soapbox/api/hooks'; import { Column, Icon, Stack, Text } from 'soapbox/components/ui'; import { useAppDispatch } from 'soapbox/hooks'; -import { useGroup, useGroupTag } from 'soapbox/hooks/api'; import Timeline from '../ui/components/timeline'; diff --git a/app/soapbox/features/group/group-tags.tsx b/app/soapbox/features/group/group-tags.tsx index 516fb94df..710a4fdb5 100644 --- a/app/soapbox/features/group/group-tags.tsx +++ b/app/soapbox/features/group/group-tags.tsx @@ -1,9 +1,9 @@ import React from 'react'; import { FormattedMessage } from 'react-intl'; +import { useGroupTags } from 'soapbox/api/hooks'; import ScrollableList from 'soapbox/components/scrollable-list'; import { Icon, Stack, Text } from 'soapbox/components/ui'; -import { useGroupTags } from 'soapbox/hooks/api'; import { useGroup } from 'soapbox/queries/groups'; import PlaceholderAccount from '../placeholder/components/placeholder-account'; diff --git a/app/soapbox/features/group/group-timeline.tsx b/app/soapbox/features/group/group-timeline.tsx index d189fdb68..a920a862c 100644 --- a/app/soapbox/features/group/group-timeline.tsx +++ b/app/soapbox/features/group/group-timeline.tsx @@ -6,10 +6,10 @@ import { Link } from 'react-router-dom'; import { groupCompose, setGroupTimelineVisible, uploadCompose } from 'soapbox/actions/compose'; import { connectGroupStream } from 'soapbox/actions/streaming'; import { expandGroupTimeline } from 'soapbox/actions/timelines'; +import { useGroup } from 'soapbox/api/hooks'; import { Avatar, HStack, Icon, Stack, Text, Toggle } from 'soapbox/components/ui'; import ComposeForm from 'soapbox/features/compose/components/compose-form'; import { useAppDispatch, useAppSelector, useDraggedFiles, useOwnAccount } from 'soapbox/hooks'; -import { useGroup } from 'soapbox/hooks/api'; import Timeline from '../ui/components/timeline'; diff --git a/app/soapbox/features/group/manage-group.tsx b/app/soapbox/features/group/manage-group.tsx index 5c3ee5c24..648593318 100644 --- a/app/soapbox/features/group/manage-group.tsx +++ b/app/soapbox/features/group/manage-group.tsx @@ -3,10 +3,10 @@ import { defineMessages, useIntl } from 'react-intl'; import { useHistory } from 'react-router-dom'; import { openModal } from 'soapbox/actions/modals'; +import { useDeleteGroup, useGroup } from 'soapbox/api/hooks'; import List, { ListItem } from 'soapbox/components/list'; import { CardBody, CardHeader, CardTitle, Column, Spinner, Text } from 'soapbox/components/ui'; import { useAppDispatch, useBackend, useGroupsPath } from 'soapbox/hooks'; -import { useDeleteGroup, useGroup } from 'soapbox/hooks/api'; import { GroupRoles } from 'soapbox/schemas/group-member'; import toast from 'soapbox/toast'; import { TRUTHSOCIAL } from 'soapbox/utils/features'; diff --git a/app/soapbox/features/groups/components/discover/popular-groups.tsx b/app/soapbox/features/groups/components/discover/popular-groups.tsx index 894542eb8..83426f553 100644 --- a/app/soapbox/features/groups/components/discover/popular-groups.tsx +++ b/app/soapbox/features/groups/components/discover/popular-groups.tsx @@ -1,10 +1,10 @@ import React, { useState } from 'react'; import { FormattedMessage } from 'react-intl'; +import { usePopularGroups } from 'soapbox/api/hooks'; import Link from 'soapbox/components/link'; import { Carousel, HStack, Stack, Text } from 'soapbox/components/ui'; import PlaceholderGroupDiscover from 'soapbox/features/placeholder/components/placeholder-group-discover'; -import { usePopularGroups } from 'soapbox/hooks/api/usePopularGroups'; import GroupGridItem from './group-grid-item'; diff --git a/app/soapbox/features/groups/components/discover/popular-tags.tsx b/app/soapbox/features/groups/components/discover/popular-tags.tsx index 4cfed0bee..75ff36628 100644 --- a/app/soapbox/features/groups/components/discover/popular-tags.tsx +++ b/app/soapbox/features/groups/components/discover/popular-tags.tsx @@ -1,9 +1,9 @@ import React from 'react'; import { FormattedMessage } from 'react-intl'; +import { usePopularTags } from 'soapbox/api/hooks'; import Link from 'soapbox/components/link'; import { HStack, Stack, Text } from 'soapbox/components/ui'; -import { usePopularTags } from 'soapbox/hooks/api'; import TagListItem from './tag-list-item'; diff --git a/app/soapbox/features/groups/components/discover/search/results.tsx b/app/soapbox/features/groups/components/discover/search/results.tsx index 2543f6ebc..3ae6b2179 100644 --- a/app/soapbox/features/groups/components/discover/search/results.tsx +++ b/app/soapbox/features/groups/components/discover/search/results.tsx @@ -3,8 +3,8 @@ import React, { useCallback, useState } from 'react'; import { FormattedMessage } from 'react-intl'; import { Components, Virtuoso, VirtuosoGrid } from 'react-virtuoso'; +import { useGroupSearch } from 'soapbox/api/hooks'; import { HStack, Stack, Text } from 'soapbox/components/ui'; -import { useGroupSearch } from 'soapbox/hooks/api'; import GroupGridItem from '../group-grid-item'; import GroupListItem from '../group-list-item'; diff --git a/app/soapbox/features/groups/components/discover/search/search.tsx b/app/soapbox/features/groups/components/discover/search/search.tsx index 647f10fb5..0e2d7f00e 100644 --- a/app/soapbox/features/groups/components/discover/search/search.tsx +++ b/app/soapbox/features/groups/components/discover/search/search.tsx @@ -1,10 +1,10 @@ import React, { useEffect } from 'react'; import { FormattedMessage } from 'react-intl'; +import { useGroupSearch } from 'soapbox/api/hooks'; import { Stack } from 'soapbox/components/ui'; import PlaceholderGroupSearch from 'soapbox/features/placeholder/components/placeholder-group-search'; import { useDebounce, useOwnAccount } from 'soapbox/hooks'; -import { useGroupSearch } from 'soapbox/hooks/api'; import { saveGroupSearch } from 'soapbox/utils/groups'; import Blankslate from './blankslate'; diff --git a/app/soapbox/features/groups/components/discover/suggested-groups.tsx b/app/soapbox/features/groups/components/discover/suggested-groups.tsx index e0e26e874..5d73cc3f0 100644 --- a/app/soapbox/features/groups/components/discover/suggested-groups.tsx +++ b/app/soapbox/features/groups/components/discover/suggested-groups.tsx @@ -1,10 +1,10 @@ import React, { useState } from 'react'; import { FormattedMessage } from 'react-intl'; +import { useSuggestedGroups } from 'soapbox/api/hooks'; import Link from 'soapbox/components/link'; import { Carousel, HStack, Stack, Text } from 'soapbox/components/ui'; import PlaceholderGroupDiscover from 'soapbox/features/placeholder/components/placeholder-group-discover'; -import { useSuggestedGroups } from 'soapbox/hooks/api/useSuggestedGroups'; import GroupGridItem from './group-grid-item'; diff --git a/app/soapbox/features/groups/index.tsx b/app/soapbox/features/groups/index.tsx index 7528989f6..7b1c51c55 100644 --- a/app/soapbox/features/groups/index.tsx +++ b/app/soapbox/features/groups/index.tsx @@ -3,11 +3,11 @@ import { defineMessages, FormattedMessage, useIntl } from 'react-intl'; import { Link } from 'react-router-dom'; import { openModal } from 'soapbox/actions/modals'; +import { useGroups } from 'soapbox/api/hooks'; import GroupCard from 'soapbox/components/group-card'; import ScrollableList from 'soapbox/components/scrollable-list'; import { Button, Input, Stack, Text } from 'soapbox/components/ui'; import { useAppDispatch, useAppSelector, useDebounce, useFeatures } from 'soapbox/hooks'; -import { useGroups } from 'soapbox/hooks/api'; import { PERMISSION_CREATE_GROUPS, hasPermission } from 'soapbox/utils/permissions'; import PlaceholderGroupCard from '../placeholder/components/placeholder-group-card'; diff --git a/app/soapbox/features/groups/popular.tsx b/app/soapbox/features/groups/popular.tsx index c2c11148c..2f417dd8f 100644 --- a/app/soapbox/features/groups/popular.tsx +++ b/app/soapbox/features/groups/popular.tsx @@ -3,8 +3,8 @@ import React, { useCallback, useState } from 'react'; import { defineMessages, useIntl } from 'react-intl'; import { Components, Virtuoso, VirtuosoGrid } from 'react-virtuoso'; +import { usePopularGroups } from 'soapbox/api/hooks'; import { Column } from 'soapbox/components/ui'; -import { usePopularGroups } from 'soapbox/hooks/api/usePopularGroups'; import GroupGridItem from './components/discover/group-grid-item'; import GroupListItem from './components/discover/group-list-item'; diff --git a/app/soapbox/features/groups/suggested.tsx b/app/soapbox/features/groups/suggested.tsx index 4d1843598..89833a9a8 100644 --- a/app/soapbox/features/groups/suggested.tsx +++ b/app/soapbox/features/groups/suggested.tsx @@ -3,8 +3,8 @@ import React, { useCallback, useState } from 'react'; import { defineMessages, useIntl } from 'react-intl'; import { Components, Virtuoso, VirtuosoGrid } from 'react-virtuoso'; +import { useSuggestedGroups } from 'soapbox/api/hooks'; import { Column } from 'soapbox/components/ui'; -import { useSuggestedGroups } from 'soapbox/hooks/api/useSuggestedGroups'; import GroupGridItem from './components/discover/group-grid-item'; import GroupListItem from './components/discover/group-list-item'; diff --git a/app/soapbox/features/groups/tag.tsx b/app/soapbox/features/groups/tag.tsx index 8b18053d3..ccc54bbb3 100644 --- a/app/soapbox/features/groups/tag.tsx +++ b/app/soapbox/features/groups/tag.tsx @@ -2,8 +2,8 @@ import clsx from 'clsx'; import React, { useCallback, useState } from 'react'; import { Components, Virtuoso, VirtuosoGrid } from 'react-virtuoso'; +import { useGroupTag, useGroupsFromTag } from 'soapbox/api/hooks'; import { Column, HStack, Icon } from 'soapbox/components/ui'; -import { useGroupTag, useGroupsFromTag } from 'soapbox/hooks/api'; import GroupGridItem from './components/discover/group-grid-item'; import GroupListItem from './components/discover/group-list-item'; diff --git a/app/soapbox/features/groups/tags.tsx b/app/soapbox/features/groups/tags.tsx index 1484665e4..aa37a514b 100644 --- a/app/soapbox/features/groups/tags.tsx +++ b/app/soapbox/features/groups/tags.tsx @@ -3,8 +3,8 @@ import React from 'react'; import { FormattedMessage, defineMessages, useIntl } from 'react-intl'; import { Virtuoso } from 'react-virtuoso'; +import { usePopularTags } from 'soapbox/api/hooks'; import { Column, Text } from 'soapbox/components/ui'; -import { usePopularTags } from 'soapbox/hooks/api'; import TagListItem from './components/discover/tag-list-item'; diff --git a/app/soapbox/features/ui/components/modals/manage-group-modal/create-group-modal.tsx b/app/soapbox/features/ui/components/modals/manage-group-modal/create-group-modal.tsx index f09232b42..d87126c74 100644 --- a/app/soapbox/features/ui/components/modals/manage-group-modal/create-group-modal.tsx +++ b/app/soapbox/features/ui/components/modals/manage-group-modal/create-group-modal.tsx @@ -2,9 +2,9 @@ import { AxiosError } from 'axios'; import React, { useMemo, useState } from 'react'; import { defineMessages, FormattedMessage, useIntl } from 'react-intl'; +import { useCreateGroup, useGroupValidation, type CreateGroupParams } from 'soapbox/api/hooks'; import { Modal, Stack } from 'soapbox/components/ui'; import { useDebounce } from 'soapbox/hooks'; -import { useCreateGroup, useGroupValidation, type CreateGroupParams } from 'soapbox/hooks/api'; import { type Group } from 'soapbox/schemas'; import toast from 'soapbox/toast'; diff --git a/app/soapbox/features/ui/components/modals/manage-group-modal/steps/details-step.tsx b/app/soapbox/features/ui/components/modals/manage-group-modal/steps/details-step.tsx index d5e59e575..02dbf3279 100644 --- a/app/soapbox/features/ui/components/modals/manage-group-modal/steps/details-step.tsx +++ b/app/soapbox/features/ui/components/modals/manage-group-modal/steps/details-step.tsx @@ -1,12 +1,12 @@ import React from 'react'; import { defineMessages, FormattedMessage, useIntl } from 'react-intl'; +import { CreateGroupParams, useGroupValidation } from 'soapbox/api/hooks'; import { Form, FormGroup, Input, Textarea } from 'soapbox/components/ui'; import AvatarPicker from 'soapbox/features/group/components/group-avatar-picker'; import HeaderPicker from 'soapbox/features/group/components/group-header-picker'; import GroupTagsField from 'soapbox/features/group/components/group-tags-field'; import { useAppSelector, useDebounce, useInstance } from 'soapbox/hooks'; -import { CreateGroupParams, useGroupValidation } from 'soapbox/hooks/api'; import { usePreview } from 'soapbox/hooks/forms'; import resizeImage from 'soapbox/utils/resize-image'; diff --git a/app/soapbox/features/ui/components/modals/manage-group-modal/steps/privacy-step.tsx b/app/soapbox/features/ui/components/modals/manage-group-modal/steps/privacy-step.tsx index 4e42d0b58..951b1ea67 100644 --- a/app/soapbox/features/ui/components/modals/manage-group-modal/steps/privacy-step.tsx +++ b/app/soapbox/features/ui/components/modals/manage-group-modal/steps/privacy-step.tsx @@ -1,9 +1,9 @@ import React from 'react'; import { FormattedMessage } from 'react-intl'; +import { type CreateGroupParams } from 'soapbox/api/hooks'; import List, { ListItem } from 'soapbox/components/list'; import { Form, FormGroup, Stack, Text } from 'soapbox/components/ui'; -import { type CreateGroupParams } from 'soapbox/hooks/api'; interface IPrivacyStep { params: CreateGroupParams diff --git a/app/soapbox/features/ui/components/panels/my-groups-panel.tsx b/app/soapbox/features/ui/components/panels/my-groups-panel.tsx index e6434b1dd..c732f5ae7 100644 --- a/app/soapbox/features/ui/components/panels/my-groups-panel.tsx +++ b/app/soapbox/features/ui/components/panels/my-groups-panel.tsx @@ -1,9 +1,9 @@ import React from 'react'; +import { useGroups } from 'soapbox/api/hooks'; import { Widget } from 'soapbox/components/ui'; import GroupListItem from 'soapbox/features/groups/components/discover/group-list-item'; import PlaceholderGroupSearch from 'soapbox/features/placeholder/components/placeholder-group-search'; -import { useGroups } from 'soapbox/hooks/api'; const MyGroupsPanel = () => { const { groups, isFetching, isFetched, isError } = useGroups(); diff --git a/app/soapbox/features/ui/components/panels/suggested-groups-panel.tsx b/app/soapbox/features/ui/components/panels/suggested-groups-panel.tsx index 395a53f9c..5ef131047 100644 --- a/app/soapbox/features/ui/components/panels/suggested-groups-panel.tsx +++ b/app/soapbox/features/ui/components/panels/suggested-groups-panel.tsx @@ -1,9 +1,9 @@ import React from 'react'; +import { useSuggestedGroups } from 'soapbox/api/hooks'; import { Widget } from 'soapbox/components/ui'; import GroupListItem from 'soapbox/features/groups/components/discover/group-list-item'; import PlaceholderGroupSearch from 'soapbox/features/placeholder/components/placeholder-group-search'; -import { useSuggestedGroups } from 'soapbox/hooks/api/useSuggestedGroups'; const SuggestedGroupsPanel = () => { const { groups, isFetching, isFetched, isError } = useSuggestedGroups(); diff --git a/app/soapbox/hooks/useGroupsPath.ts b/app/soapbox/hooks/useGroupsPath.ts index a4c1efa19..7a101e463 100644 --- a/app/soapbox/hooks/useGroupsPath.ts +++ b/app/soapbox/hooks/useGroupsPath.ts @@ -1,4 +1,5 @@ -import { useGroups } from './api'; +import { useGroups } from 'soapbox/api/hooks'; + import { useFeatures } from './useFeatures'; /** diff --git a/app/soapbox/pages/group-page.tsx b/app/soapbox/pages/group-page.tsx index 9e11c34a9..e2d6e5755 100644 --- a/app/soapbox/pages/group-page.tsx +++ b/app/soapbox/pages/group-page.tsx @@ -2,6 +2,7 @@ import React, { useMemo } from 'react'; import { FormattedMessage, defineMessages, useIntl } from 'react-intl'; import { useRouteMatch } from 'react-router-dom'; +import { useGroup, useGroupMembershipRequests } from 'soapbox/api/hooks'; import GroupLookupHoc from 'soapbox/components/hoc/group-lookup-hoc'; import { Column, Icon, Layout, Stack, Text } from 'soapbox/components/ui'; import GroupHeader from 'soapbox/features/group/components/group-header'; @@ -14,12 +15,11 @@ import { SuggestedGroupsPanel, } from 'soapbox/features/ui/util/async-components'; import { useFeatures, useOwnAccount } from 'soapbox/hooks'; -import { useGroup } from 'soapbox/hooks/api'; -import { useGroupMembershipRequests } from 'soapbox/hooks/api/groups/useGroupMembershipRequests'; -import { Group } from 'soapbox/schemas'; import { Tabs } from '../components/ui'; +import type { Group } from 'soapbox/schemas'; + const messages = defineMessages({ all: { id: 'group.tabs.all', defaultMessage: 'All' }, members: { id: 'group.tabs.members', defaultMessage: 'Members' }, From 0f1e7fb2a7b451e1d45612294ad68b738b43597a Mon Sep 17 00:00:00 2001 From: Chewbacca Date: Tue, 2 May 2023 13:29:29 -0400 Subject: [PATCH 04/56] Support HTML version of note in Group Creation modal --- .../modals/manage-group-modal/steps/confirmation-step.tsx | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/app/soapbox/features/ui/components/modals/manage-group-modal/steps/confirmation-step.tsx b/app/soapbox/features/ui/components/modals/manage-group-modal/steps/confirmation-step.tsx index 0b7a38d0e..4c388751f 100644 --- a/app/soapbox/features/ui/components/modals/manage-group-modal/steps/confirmation-step.tsx +++ b/app/soapbox/features/ui/components/modals/manage-group-modal/steps/confirmation-step.tsx @@ -54,7 +54,11 @@ const ConfirmationStep: React.FC = ({ group }) => { {group.display_name} - {group.note} + From 4dd4ba212dcfbea6dac6a2b405e35dbe5c86b70a Mon Sep 17 00:00:00 2001 From: Chewbacca Date: Tue, 2 May 2023 13:33:42 -0400 Subject: [PATCH 05/56] Remove pencil from Compose button --- app/soapbox/features/ui/components/compose-button.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/app/soapbox/features/ui/components/compose-button.tsx b/app/soapbox/features/ui/components/compose-button.tsx index a2944f699..d36cb26b3 100644 --- a/app/soapbox/features/ui/components/compose-button.tsx +++ b/app/soapbox/features/ui/components/compose-button.tsx @@ -25,7 +25,6 @@ const HomeComposeButton = () => { return ( )} - {poll.pleroma.get('non_anonymous') && ( + {poll.pleroma?.non_anonymous && ( <> diff --git a/app/soapbox/components/polls/poll-option.tsx b/app/soapbox/components/polls/poll-option.tsx index 792a3a066..b4c37e11d 100644 --- a/app/soapbox/components/polls/poll-option.tsx +++ b/app/soapbox/components/polls/poll-option.tsx @@ -112,10 +112,13 @@ const PollOption: React.FC = (props): JSX.Element | null => { const pollVotesCount = poll.voters_count || poll.votes_count; const percent = pollVotesCount === 0 ? 0 : (option.votes_count / pollVotesCount) * 100; - const leading = poll.options.filterNot(other => other.title === option.title).every(other => option.votes_count >= other.votes_count); const voted = poll.own_votes?.includes(index); const message = intl.formatMessage(messages.votes, { votes: option.votes_count }); + const leading = poll.options + .filter(other => other.title !== option.title) + .every(other => option.votes_count >= other.votes_count); + return (
{showResults ? ( diff --git a/app/soapbox/normalizers/__tests__/poll.test.ts b/app/soapbox/normalizers/__tests__/poll.test.ts deleted file mode 100644 index d5226e938..000000000 --- a/app/soapbox/normalizers/__tests__/poll.test.ts +++ /dev/null @@ -1,47 +0,0 @@ -import { Record as ImmutableRecord } from 'immutable'; - -import { normalizePoll } from '../poll'; - -describe('normalizePoll()', () => { - it('adds base fields', () => { - const poll = { options: [{ title: 'Apples' }] }; - const result = normalizePoll(poll); - - const expected = { - options: [{ title: 'Apples', votes_count: 0 }], - emojis: [], - expired: false, - multiple: false, - voters_count: 0, - votes_count: 0, - own_votes: null, - voted: false, - }; - - expect(ImmutableRecord.isRecord(result)).toBe(true); - expect(ImmutableRecord.isRecord(result.options.get(0))).toBe(true); - expect(result.toJS()).toMatchObject(expected); - }); - - it('normalizes a Pleroma logged-out poll', () => { - const { poll } = require('soapbox/__fixtures__/pleroma-status-with-poll.json'); - const result = normalizePoll(poll); - - // Adds logged-in fields - expect(result.voted).toBe(false); - expect(result.own_votes).toBe(null); - }); - - it('normalizes poll with emojis', () => { - const { poll } = require('soapbox/__fixtures__/pleroma-status-with-poll-with-emojis.json'); - const result = normalizePoll(poll); - - // Emojifies poll options - expect(result.options.get(1)?.title_emojified) - .toContain('emojione'); - - // Parses emojis as Immutable.Record's - expect(ImmutableRecord.isRecord(result.emojis.get(0))).toBe(true); - expect(result.emojis.get(1)?.shortcode).toEqual('soapbox'); - }); -}); diff --git a/app/soapbox/normalizers/__tests__/status.test.ts b/app/soapbox/normalizers/__tests__/status.test.ts index 5c66a4b9b..0024a212c 100644 --- a/app/soapbox/normalizers/__tests__/status.test.ts +++ b/app/soapbox/normalizers/__tests__/status.test.ts @@ -146,12 +146,16 @@ describe('normalizeStatus()', () => { }); it('normalizes poll and poll options', () => { - const status = { poll: { options: [{ title: 'Apples' }] } }; + const status = { poll: { id: '1', options: [{ title: 'Apples' }, { title: 'Oranges' }] } }; const result = normalizeStatus(status); const poll = result.poll as Poll; const expected = { - options: [{ title: 'Apples', votes_count: 0 }], + id: '1', + options: [ + { title: 'Apples', votes_count: 0 }, + { title: 'Oranges', votes_count: 0 }, + ], emojis: [], expired: false, multiple: false, @@ -161,9 +165,7 @@ describe('normalizeStatus()', () => { voted: false, }; - expect(ImmutableRecord.isRecord(poll)).toBe(true); - expect(ImmutableRecord.isRecord(poll.options.get(0))).toBe(true); - expect(poll.toJS()).toMatchObject(expected); + expect(poll).toMatchObject(expected); }); it('normalizes a Pleroma logged-out poll', () => { @@ -182,12 +184,10 @@ describe('normalizeStatus()', () => { const poll = result.poll as Poll; // Emojifies poll options - expect(poll.options.get(1)?.title_emojified) + expect(poll.options[1].title_emojified) .toContain('emojione'); - // Parses emojis as Immutable.Record's - expect(ImmutableRecord.isRecord(poll.emojis.get(0))).toBe(true); - expect(poll.emojis.get(1)?.shortcode).toEqual('soapbox'); + expect(poll.emojis[1].shortcode).toEqual('soapbox'); }); it('normalizes a card', () => { diff --git a/app/soapbox/normalizers/index.ts b/app/soapbox/normalizers/index.ts index e7100fa9d..12bb77d0c 100644 --- a/app/soapbox/normalizers/index.ts +++ b/app/soapbox/normalizers/index.ts @@ -18,7 +18,6 @@ export { ListRecord, normalizeList } from './list'; export { LocationRecord, normalizeLocation } from './location'; export { MentionRecord, normalizeMention } from './mention'; export { NotificationRecord, normalizeNotification } from './notification'; -export { PollRecord, PollOptionRecord, normalizePoll } from './poll'; export { StatusRecord, normalizeStatus } from './status'; export { StatusEditRecord, normalizeStatusEdit } from './status-edit'; export { TagRecord, normalizeTag } from './tag'; diff --git a/app/soapbox/normalizers/poll.ts b/app/soapbox/normalizers/poll.ts deleted file mode 100644 index 726278a57..000000000 --- a/app/soapbox/normalizers/poll.ts +++ /dev/null @@ -1,102 +0,0 @@ -/** - * Poll normalizer: - * Converts API polls into our internal format. - * @see {@link https://docs.joinmastodon.org/entities/poll/} - */ -import escapeTextContentForBrowser from 'escape-html'; -import { - Map as ImmutableMap, - List as ImmutableList, - Record as ImmutableRecord, - fromJS, -} from 'immutable'; - -import emojify from 'soapbox/features/emoji'; -import { normalizeEmoji } from 'soapbox/normalizers/emoji'; -import { makeEmojiMap } from 'soapbox/utils/normalizers'; - -import type { Emoji, PollOption } from 'soapbox/types/entities'; - -// https://docs.joinmastodon.org/entities/poll/ -export const PollRecord = ImmutableRecord({ - emojis: ImmutableList(), - expired: false, - expires_at: '', - id: '', - multiple: false, - options: ImmutableList(), - voters_count: 0, - votes_count: 0, - own_votes: null as ImmutableList | null, - voted: false, - pleroma: ImmutableMap(), -}); - -// Sub-entity of Poll -export const PollOptionRecord = ImmutableRecord({ - title: '', - votes_count: 0, - - // Internal fields - title_emojified: '', -}); - -// Normalize emojis -const normalizeEmojis = (entity: ImmutableMap) => { - return entity.update('emojis', ImmutableList(), emojis => { - return emojis.map(normalizeEmoji); - }); -}; - -const normalizePollOption = (option: ImmutableMap | string, emojis: ImmutableList> = ImmutableList()) => { - const emojiMap = makeEmojiMap(emojis); - - if (typeof option === 'string') { - const titleEmojified = emojify(escapeTextContentForBrowser(option), emojiMap); - - return PollOptionRecord({ - title: option, - title_emojified: titleEmojified, - }); - } - - const titleEmojified = emojify(escapeTextContentForBrowser(option.get('title')), emojiMap); - - return PollOptionRecord( - option.set('title_emojified', titleEmojified), - ); -}; - -// Normalize poll options -const normalizePollOptions = (poll: ImmutableMap) => { - const emojis = poll.get('emojis'); - - return poll.update('options', (options: ImmutableList>) => { - return options.map(option => normalizePollOption(option, emojis)); - }); -}; - -// Normalize own_votes to `null` if empty (like Mastodon) -const normalizePollOwnVotes = (poll: ImmutableMap) => { - return poll.update('own_votes', ownVotes => { - return ownVotes?.size > 0 ? ownVotes : null; - }); -}; - -// Whether the user voted in the poll -const normalizePollVoted = (poll: ImmutableMap) => { - return poll.update('voted', voted => { - return typeof voted === 'boolean' ? voted : poll.get('own_votes')?.size > 0; - }); -}; - -export const normalizePoll = (poll: Record) => { - return PollRecord( - ImmutableMap(fromJS(poll)).withMutations((poll: ImmutableMap) => { - normalizeEmojis(poll); - normalizePollOptions(poll); - normalizePollOwnVotes(poll); - normalizePollVoted(poll); - }), - ); -}; diff --git a/app/soapbox/normalizers/status-edit.ts b/app/soapbox/normalizers/status-edit.ts index 6f5d8d53a..f569ecce3 100644 --- a/app/soapbox/normalizers/status-edit.ts +++ b/app/soapbox/normalizers/status-edit.ts @@ -12,7 +12,7 @@ import { import emojify from 'soapbox/features/emoji'; import { normalizeAttachment } from 'soapbox/normalizers/attachment'; import { normalizeEmoji } from 'soapbox/normalizers/emoji'; -import { normalizePoll } from 'soapbox/normalizers/poll'; +import { pollSchema } from 'soapbox/schemas'; import { stripCompatibilityFeatures } from 'soapbox/utils/html'; import { makeEmojiMap } from 'soapbox/utils/normalizers'; @@ -50,9 +50,10 @@ const normalizeEmojis = (entity: ImmutableMap) => { // Normalize the poll in the status, if applicable const normalizeStatusPoll = (statusEdit: ImmutableMap) => { - if (statusEdit.hasIn(['poll', 'options'])) { - return statusEdit.update('poll', ImmutableMap(), normalizePoll); - } else { + try { + const poll = pollSchema.parse(statusEdit.get('poll').toJS()); + return statusEdit.set('poll', poll); + } catch (_e) { return statusEdit.set('poll', null); } }; diff --git a/app/soapbox/normalizers/status.ts b/app/soapbox/normalizers/status.ts index 64a00b316..17a23b19e 100644 --- a/app/soapbox/normalizers/status.ts +++ b/app/soapbox/normalizers/status.ts @@ -13,8 +13,7 @@ import { import { normalizeAttachment } from 'soapbox/normalizers/attachment'; import { normalizeEmoji } from 'soapbox/normalizers/emoji'; import { normalizeMention } from 'soapbox/normalizers/mention'; -import { normalizePoll } from 'soapbox/normalizers/poll'; -import { cardSchema } from 'soapbox/schemas/card'; +import { cardSchema, pollSchema } from 'soapbox/schemas'; import type { ReducerAccount } from 'soapbox/reducers/accounts'; import type { Account, Attachment, Card, Emoji, Group, Mention, Poll, EmbeddedEntity } from 'soapbox/types/entities'; @@ -109,9 +108,10 @@ const normalizeEmojis = (entity: ImmutableMap) => { // Normalize the poll in the status, if applicable const normalizeStatusPoll = (status: ImmutableMap) => { - if (status.hasIn(['poll', 'options'])) { - return status.update('poll', ImmutableMap(), normalizePoll); - } else { + try { + const poll = pollSchema.parse(status.get('poll').toJS()); + return status.set('poll', poll); + } catch (_e) { return status.set('poll', null); } }; diff --git a/app/soapbox/reducers/__tests__/polls.test.ts b/app/soapbox/reducers/__tests__/polls.test.ts index b9ceb07f7..74627fb68 100644 --- a/app/soapbox/reducers/__tests__/polls.test.ts +++ b/app/soapbox/reducers/__tests__/polls.test.ts @@ -11,14 +11,17 @@ describe('polls reducer', () => { describe('POLLS_IMPORT', () => { it('normalizes the poll', () => { - const polls = [{ id: '3', options: [{ title: 'Apples' }] }]; + const polls = [{ id: '3', options: [{ title: 'Apples' }, { title: 'Oranges' }] }]; const action = { type: POLLS_IMPORT, polls }; const result = reducer(undefined, action); const expected = { '3': { - options: [{ title: 'Apples', votes_count: 0 }], + options: [ + { title: 'Apples', votes_count: 0 }, + { title: 'Oranges', votes_count: 0 }, + ], emojis: [], expired: false, multiple: false, diff --git a/app/soapbox/reducers/statuses.ts b/app/soapbox/reducers/statuses.ts index 3ae9c308f..d8c91b6a3 100644 --- a/app/soapbox/reducers/statuses.ts +++ b/app/soapbox/reducers/statuses.ts @@ -74,11 +74,11 @@ const minifyStatus = (status: StatusRecord): ReducerStatus => { }; // Gets titles of poll options from status -const getPollOptionTitles = ({ poll }: StatusRecord): ImmutableList => { +const getPollOptionTitles = ({ poll }: StatusRecord): readonly string[] => { if (poll && typeof poll === 'object') { return poll.options.map(({ title }) => title); } else { - return ImmutableList(); + return []; } }; diff --git a/app/soapbox/schemas/__tests__/poll.test.ts b/app/soapbox/schemas/__tests__/poll.test.ts new file mode 100644 index 000000000..fe39315dd --- /dev/null +++ b/app/soapbox/schemas/__tests__/poll.test.ts @@ -0,0 +1,44 @@ +import { pollSchema } from '../poll'; + +describe('normalizePoll()', () => { + it('adds base fields', () => { + const poll = { id: '1', options: [{ title: 'Apples' }, { title: 'Oranges' }] }; + const result = pollSchema.parse(poll); + + const expected = { + options: [ + { title: 'Apples', votes_count: 0 }, + { title: 'Oranges', votes_count: 0 }, + ], + emojis: [], + expired: false, + multiple: false, + voters_count: 0, + votes_count: 0, + own_votes: null, + voted: false, + }; + + expect(result).toMatchObject(expected); + }); + + it('normalizes a Pleroma logged-out poll', () => { + const { poll } = require('soapbox/__fixtures__/pleroma-status-with-poll.json'); + const result = pollSchema.parse(poll); + + // Adds logged-in fields + expect(result.voted).toBe(false); + expect(result.own_votes).toBe(null); + }); + + it('normalizes poll with emojis', () => { + const { poll } = require('soapbox/__fixtures__/pleroma-status-with-poll-with-emojis.json'); + const result = pollSchema.parse(poll); + + // Emojifies poll options + expect(result.options[1]?.title_emojified) + .toContain('emojione'); + + expect(result.emojis[1]?.shortcode).toEqual('soapbox'); + }); +}); diff --git a/app/soapbox/schemas/account.ts b/app/soapbox/schemas/account.ts index a6fb6b4c3..9381edbbd 100644 --- a/app/soapbox/schemas/account.ts +++ b/app/soapbox/schemas/account.ts @@ -43,8 +43,8 @@ const accountSchema = z.object({ pleroma: z.any(), // TODO source: z.any(), // TODO statuses_count: z.number().catch(0), - uri: z.string().catch(''), - url: z.string().catch(''), + uri: z.string().url().catch(''), + url: z.string().url().catch(''), username: z.string().catch(''), verified: z.boolean().default(false), website: z.string().catch(''), diff --git a/app/soapbox/schemas/index.ts b/app/soapbox/schemas/index.ts index 29b1c2edd..655bffc7f 100644 --- a/app/soapbox/schemas/index.ts +++ b/app/soapbox/schemas/index.ts @@ -6,6 +6,7 @@ export { groupSchema, type Group } from './group'; export { groupMemberSchema, type GroupMember } from './group-member'; export { groupRelationshipSchema, type GroupRelationship } from './group-relationship'; export { groupTagSchema, type GroupTag } from './group-tag'; +export { pollSchema, type Poll, type PollOption } from './poll'; export { relationshipSchema, type Relationship } from './relationship'; // Soapbox diff --git a/app/soapbox/schemas/poll.ts b/app/soapbox/schemas/poll.ts new file mode 100644 index 000000000..73d27753a --- /dev/null +++ b/app/soapbox/schemas/poll.ts @@ -0,0 +1,50 @@ +import escapeTextContentForBrowser from 'escape-html'; +import { z } from 'zod'; + +import emojify from 'soapbox/features/emoji'; + +import { customEmojiSchema } from './custom-emoji'; +import { filteredArray, makeCustomEmojiMap } from './utils'; + +const pollOptionSchema = z.object({ + title: z.string().catch(''), + votes_count: z.number().catch(0), +}); + +const pollSchema = z.object({ + emojis: filteredArray(customEmojiSchema), + expired: z.boolean().catch(false), + expires_at: z.string().datetime().catch(new Date().toUTCString()), + id: z.string(), + multiple: z.boolean().catch(false), + options: z.array(pollOptionSchema).min(2), + voters_count: z.number().catch(0), + votes_count: z.number().catch(0), + own_votes: z.array(z.number()).nonempty().nullable().catch(null), + voted: z.boolean().catch(false), + pleroma: z.object({ + non_anonymous: z.boolean().catch(false), + }).optional().catch(undefined), +}).transform((poll) => { + const emojiMap = makeCustomEmojiMap(poll.emojis); + + const emojifiedOptions = poll.options.map((option) => ({ + ...option, + title_emojified: emojify(escapeTextContentForBrowser(option.title), emojiMap), + })); + + // If the user has votes, they have certainly voted. + if (poll.own_votes?.length) { + poll.voted = true; + } + + return { + ...poll, + options: emojifiedOptions, + }; +}); + +type Poll = z.infer; +type PollOption = Poll['options'][number]; + +export { pollSchema, type Poll, type PollOption }; \ No newline at end of file diff --git a/app/soapbox/schemas/soapbox/ad.ts b/app/soapbox/schemas/soapbox/ad.ts index 343b519b2..40dc05fb3 100644 --- a/app/soapbox/schemas/soapbox/ad.ts +++ b/app/soapbox/schemas/soapbox/ad.ts @@ -5,7 +5,7 @@ import { cardSchema } from '../card'; const adSchema = z.object({ card: cardSchema, impression: z.string().optional().catch(undefined), - expires_at: z.string().optional().catch(undefined), + expires_at: z.string().datetime().optional().catch(undefined), reason: z.string().optional().catch(undefined), }); diff --git a/app/soapbox/types/entities.ts b/app/soapbox/types/entities.ts index e99aa3acf..712a89e23 100644 --- a/app/soapbox/types/entities.ts +++ b/app/soapbox/types/entities.ts @@ -18,8 +18,6 @@ import { LocationRecord, MentionRecord, NotificationRecord, - PollRecord, - PollOptionRecord, StatusEditRecord, StatusRecord, TagRecord, @@ -47,8 +45,6 @@ type List = ReturnType; type Location = ReturnType; type Mention = ReturnType; type Notification = ReturnType; -type Poll = ReturnType; -type PollOption = ReturnType; type StatusEdit = ReturnType; type Tag = ReturnType; @@ -89,8 +85,6 @@ export { Location, Mention, Notification, - Poll, - PollOption, Status, StatusEdit, Tag, @@ -106,5 +100,7 @@ export type { Group, GroupMember, GroupRelationship, + Poll, + PollOption, Relationship, } from 'soapbox/schemas'; \ No newline at end of file From ebaff75e388c8167c143002fa041249b936102a6 Mon Sep 17 00:00:00 2001 From: Chewbacca Date: Wed, 3 May 2023 14:03:13 -0400 Subject: [PATCH 42/56] Improve Group UX --- .../api/hooks/groups/useGroupMembershipRequests.ts | 3 ++- .../components/authorize-reject-buttons.tsx | 2 +- .../features/group/group-membership-requests.tsx | 14 ++++++++------ app/soapbox/features/group/manage-group.tsx | 10 +++++----- .../features/ui/components/group-media-panel.tsx | 4 ++-- .../manage-group-modal/steps/confirmation-step.tsx | 2 +- app/soapbox/locales/en.json | 10 +++++----- 7 files changed, 24 insertions(+), 21 deletions(-) diff --git a/app/soapbox/api/hooks/groups/useGroupMembershipRequests.ts b/app/soapbox/api/hooks/groups/useGroupMembershipRequests.ts index 16ff924c7..a6e068091 100644 --- a/app/soapbox/api/hooks/groups/useGroupMembershipRequests.ts +++ b/app/soapbox/api/hooks/groups/useGroupMembershipRequests.ts @@ -14,7 +14,7 @@ function useGroupMembershipRequests(groupId: string) { const { entity: relationship } = useGroupRelationship(groupId); - const { entities, invalidate, ...rest } = useEntities( + const { entities, invalidate, fetchEntities, ...rest } = useEntities( path, () => api.get(`/api/v1/groups/${groupId}/membership_requests`), { @@ -37,6 +37,7 @@ function useGroupMembershipRequests(groupId: string) { return { accounts: entities, + refetch: fetchEntities, authorize, reject, ...rest, diff --git a/app/soapbox/components/authorize-reject-buttons.tsx b/app/soapbox/components/authorize-reject-buttons.tsx index a5fddc61a..5dfb37a31 100644 --- a/app/soapbox/components/authorize-reject-buttons.tsx +++ b/app/soapbox/components/authorize-reject-buttons.tsx @@ -51,7 +51,7 @@ const AuthorizeRejectButtons: React.FC = ({ onAuthorize await action(); setState(past); } catch (e) { - console.error(e); + if (e) console.error(e); } }; if (typeof countdown === 'number') { diff --git a/app/soapbox/features/group/group-membership-requests.tsx b/app/soapbox/features/group/group-membership-requests.tsx index e50092887..eebe387bb 100644 --- a/app/soapbox/features/group/group-membership-requests.tsx +++ b/app/soapbox/features/group/group-membership-requests.tsx @@ -58,7 +58,7 @@ const GroupMembershipRequests: React.FC = ({ params }) const { group } = useGroup(id); - const { accounts, authorize, reject, isLoading } = useGroupMembershipRequests(id); + const { accounts, authorize, reject, refetch, isLoading } = useGroupMembershipRequests(id); const { invalidate } = useGroupMembers(id, GroupRoles.USER); useEffect(() => { @@ -80,11 +80,13 @@ const GroupMembershipRequests: React.FC = ({ params }) } async function handleAuthorize(account: AccountEntity) { - try { - await authorize(account.id); - } catch (_e) { - toast.error(intl.formatMessage(messages.authorizeFail, { name: account.username })); - } + return authorize(account.id) + .then(() => Promise.resolve()) + .catch(() => { + refetch(); + toast.error(intl.formatMessage(messages.authorizeFail, { name: account.username })); + return Promise.reject(); + }); } async function handleReject(account: AccountEntity) { diff --git a/app/soapbox/features/group/manage-group.tsx b/app/soapbox/features/group/manage-group.tsx index 648593318..d57b8792a 100644 --- a/app/soapbox/features/group/manage-group.tsx +++ b/app/soapbox/features/group/manage-group.tsx @@ -16,16 +16,16 @@ import ColumnForbidden from '../ui/components/column-forbidden'; type RouteParams = { groupId: string }; const messages = defineMessages({ - heading: { id: 'column.manage_group', defaultMessage: 'Manage group' }, - editGroup: { id: 'manage_group.edit_group', defaultMessage: 'Edit group' }, + heading: { id: 'column.manage_group', defaultMessage: 'Manage Group' }, + editGroup: { id: 'manage_group.edit_group', defaultMessage: 'Edit Group' }, pendingRequests: { id: 'manage_group.pending_requests', defaultMessage: 'Pending Requests' }, blockedMembers: { id: 'manage_group.blocked_members', defaultMessage: 'Banned Members' }, - deleteGroup: { id: 'manage_group.delete_group', defaultMessage: 'Delete group' }, + deleteGroup: { id: 'manage_group.delete_group', defaultMessage: 'Delete Group' }, deleteConfirm: { id: 'confirmations.delete_group.confirm', defaultMessage: 'Delete' }, - deleteHeading: { id: 'confirmations.delete_group.heading', defaultMessage: 'Delete group' }, + deleteHeading: { id: 'confirmations.delete_group.heading', defaultMessage: 'Delete Group' }, deleteMessage: { id: 'confirmations.delete_group.message', defaultMessage: 'Are you sure you want to delete this group? This is a permanent action that cannot be undone.' }, members: { id: 'group.tabs.members', defaultMessage: 'Members' }, - other: { id: 'settings.other', defaultMessage: 'Other options' }, + other: { id: 'settings.other', defaultMessage: 'Other Options' }, deleteSuccess: { id: 'group.delete.success', defaultMessage: 'Group successfully deleted' }, }); diff --git a/app/soapbox/features/ui/components/group-media-panel.tsx b/app/soapbox/features/ui/components/group-media-panel.tsx index 1d9428f33..9f52af5a2 100644 --- a/app/soapbox/features/ui/components/group-media-panel.tsx +++ b/app/soapbox/features/ui/components/group-media-panel.tsx @@ -40,7 +40,7 @@ const GroupMediaPanel: React.FC = ({ group }) => { useEffect(() => { setLoading(true); - if (group && (isMember || !isPrivate)) { + if (group && !group.deleted_at && (isMember || !isPrivate)) { dispatch(expandGroupMediaTimeline(group.id)) // @ts-ignore .then(() => setLoading(false)) @@ -72,7 +72,7 @@ const GroupMediaPanel: React.FC = ({ group }) => { } }; - if (isPrivate && !isMember) { + if ((isPrivate && !isMember) || group?.deleted_at) { return null; } diff --git a/app/soapbox/features/ui/components/modals/manage-group-modal/steps/confirmation-step.tsx b/app/soapbox/features/ui/components/modals/manage-group-modal/steps/confirmation-step.tsx index 4c388751f..11244c3c5 100644 --- a/app/soapbox/features/ui/components/modals/manage-group-modal/steps/confirmation-step.tsx +++ b/app/soapbox/features/ui/components/modals/manage-group-modal/steps/confirmation-step.tsx @@ -19,7 +19,7 @@ const ConfirmationStep: React.FC = ({ group }) => { const intl = useIntl(); const handleCopyLink = () => { - copy(`${window.location.origin}/group/${group?.slug}`, () => { + copy(group?.url as string, () => { toast.success(intl.formatMessage(messages.copied)); }); }; diff --git a/app/soapbox/locales/en.json b/app/soapbox/locales/en.json index 96efd8530..91fad97e0 100644 --- a/app/soapbox/locales/en.json +++ b/app/soapbox/locales/en.json @@ -358,7 +358,7 @@ "column.import_data": "Import data", "column.info": "Server information", "column.lists": "Lists", - "column.manage_group": "Manage group", + "column.manage_group": "Manage Group", "column.mentions": "Mentions", "column.mfa": "Multi-Factor Authentication", "column.mfa_cancel": "Cancel", @@ -491,7 +491,7 @@ "confirmations.delete_from_group.heading": "Delete from group", "confirmations.delete_from_group.message": "Are you sure you want to delete @{name}'s post?", "confirmations.delete_group.confirm": "Delete", - "confirmations.delete_group.heading": "Delete group", + "confirmations.delete_group.heading": "Delete Group", "confirmations.delete_group.message": "Are you sure you want to delete this group? This is a permanent action that cannot be undone.", "confirmations.delete_list.confirm": "Delete", "confirmations.delete_list.heading": "Delete list", @@ -970,9 +970,9 @@ "manage_group.confirmation.share": "Share this group", "manage_group.confirmation.title": "You’re all set!", "manage_group.create": "Create Group", - "manage_group.delete_group": "Delete group", + "manage_group.delete_group": "Delete Group", "manage_group.done": "Done", - "manage_group.edit_group": "Edit group", + "manage_group.edit_group": "Edit Group", "manage_group.fields.cannot_change_hint": "This cannot be changed after the group is created.", "manage_group.fields.description_label": "Description", "manage_group.fields.description_placeholder": "Description", @@ -1354,7 +1354,7 @@ "settings.delete_account": "Delete Account", "settings.edit_profile": "Edit Profile", "settings.messages.label": "Allow users to start a new chat with you", - "settings.other": "Other options", + "settings.other": "Other Options", "settings.preferences": "Preferences", "settings.profile": "Profile", "settings.save.success": "Your preferences have been saved!", From f48edfba456652318451cf46e7fd7e515458d55f Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Wed, 3 May 2023 13:40:30 -0500 Subject: [PATCH 43/56] Add tagSchema --- app/soapbox/schemas/index.ts | 1 + app/soapbox/schemas/tag.ts | 18 ++++++++++++++++++ 2 files changed, 19 insertions(+) create mode 100644 app/soapbox/schemas/tag.ts diff --git a/app/soapbox/schemas/index.ts b/app/soapbox/schemas/index.ts index 655bffc7f..25f5f3d45 100644 --- a/app/soapbox/schemas/index.ts +++ b/app/soapbox/schemas/index.ts @@ -8,6 +8,7 @@ export { groupRelationshipSchema, type GroupRelationship } from './group-relatio export { groupTagSchema, type GroupTag } from './group-tag'; export { pollSchema, type Poll, type PollOption } from './poll'; export { relationshipSchema, type Relationship } from './relationship'; +export { tagSchema, type Tag } from './tag'; // Soapbox export { adSchema, type Ad } from './soapbox/ad'; \ No newline at end of file diff --git a/app/soapbox/schemas/tag.ts b/app/soapbox/schemas/tag.ts new file mode 100644 index 000000000..5f74a31c7 --- /dev/null +++ b/app/soapbox/schemas/tag.ts @@ -0,0 +1,18 @@ +import { z } from 'zod'; + +const historySchema = z.object({ + accounts: z.coerce.number(), + uses: z.coerce.number(), +}); + +/** // https://docs.joinmastodon.org/entities/tag */ +const tagSchema = z.object({ + name: z.string().min(1), + url: z.string().url().catch(''), + history: z.array(historySchema).nullable().catch(null), + following: z.boolean().catch(false), +}); + +type Tag = z.infer; + +export { tagSchema, type Tag }; \ No newline at end of file From 08b2f0446d49f4b6bd9e3a550cc6b5e0f8739f41 Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Wed, 3 May 2023 14:29:57 -0500 Subject: [PATCH 44/56] DetailedStatus: remove timestamp from account --- app/soapbox/features/status/components/detailed-status.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/app/soapbox/features/status/components/detailed-status.tsx b/app/soapbox/features/status/components/detailed-status.tsx index 2959d285d..15f2e86e8 100644 --- a/app/soapbox/features/status/components/detailed-status.tsx +++ b/app/soapbox/features/status/components/detailed-status.tsx @@ -125,7 +125,6 @@ const DetailedStatus: React.FC = ({ Date: Wed, 3 May 2023 14:37:41 -0500 Subject: [PATCH 45/56] InteractionCounter: use neutral colors --- .../features/status/components/status-interaction-bar.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/soapbox/features/status/components/status-interaction-bar.tsx b/app/soapbox/features/status/components/status-interaction-bar.tsx index 87b9a234c..0dc6b1c08 100644 --- a/app/soapbox/features/status/components/status-interaction-bar.tsx +++ b/app/soapbox/features/status/components/status-interaction-bar.tsx @@ -230,7 +230,7 @@ const InteractionCounter: React.FC = ({ count, onClick, chi } > - + {shortNumberFormat(count)} From 4f081abb7add7f1a9d8efb83b3af23db400af6eb Mon Sep 17 00:00:00 2001 From: Chewbacca Date: Thu, 4 May 2023 08:14:43 -0400 Subject: [PATCH 46/56] d --- .../group/group-membership-requests.tsx | 17 +++++++++-------- app/soapbox/locales/en.json | 3 +-- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/app/soapbox/features/group/group-membership-requests.tsx b/app/soapbox/features/group/group-membership-requests.tsx index eebe387bb..2ea708c3d 100644 --- a/app/soapbox/features/group/group-membership-requests.tsx +++ b/app/soapbox/features/group/group-membership-requests.tsx @@ -17,8 +17,7 @@ type RouteParams = { groupId: string }; const messages = defineMessages({ heading: { id: 'column.group_pending_requests', defaultMessage: 'Pending requests' }, - authorizeFail: { id: 'group.group_mod_authorize.fail', defaultMessage: 'Failed to approve @{name}' }, - rejectFail: { id: 'group.group_mod_reject.fail', defaultMessage: 'Failed to reject @{name}' }, + authorizeRejectFail: { id: 'group.membership_requests.fail', defaultMessage: 'Group owner or admin has already taken action on this request.' }, }); interface IMembershipRequest { @@ -84,17 +83,19 @@ const GroupMembershipRequests: React.FC = ({ params }) .then(() => Promise.resolve()) .catch(() => { refetch(); - toast.error(intl.formatMessage(messages.authorizeFail, { name: account.username })); + toast.error(intl.formatMessage(messages.authorizeRejectFail)); return Promise.reject(); }); } async function handleReject(account: AccountEntity) { - try { - await reject(account.id); - } catch (_e) { - toast.error(intl.formatMessage(messages.rejectFail, { name: account.username })); - } + return reject(account.id) + .then(() => Promise.resolve()) + .catch(() => { + refetch(); + toast.error(intl.formatMessage(messages.authorizeRejectFail)); + return Promise.reject(); + }); } return ( diff --git a/app/soapbox/locales/en.json b/app/soapbox/locales/en.json index 91fad97e0..2c81f307f 100644 --- a/app/soapbox/locales/en.json +++ b/app/soapbox/locales/en.json @@ -773,14 +773,12 @@ "group.delete.success": "Group successfully deleted", "group.deleted.message": "This group has been deleted.", "group.demote.user.success": "@{name} is now a member", - "group.group_mod_authorize.fail": "Failed to approve @{name}", "group.group_mod_block": "Ban from group", "group.group_mod_block.success": "@{name} is banned", "group.group_mod_demote": "Remove {role} role", "group.group_mod_kick": "Kick @{name} from group", "group.group_mod_kick.success": "Kicked @{name} from group", "group.group_mod_promote_mod": "Assign {role} role", - "group.group_mod_reject.fail": "Failed to reject @{name}", "group.group_mod_unblock": "Unban", "group.group_mod_unblock.success": "Unbanned @{name} from group", "group.header.alt": "Group header", @@ -794,6 +792,7 @@ "group.manage": "Manage Group", "group.member.admin.limit.summary": "You can assign up to {count} admins for the group at this time.", "group.member.admin.limit.title": "Admin limit reached", + "group.membership_requests.fail": "Group owner or admin has already taken action on this request.", "group.popover.action": "View Group", "group.popover.summary": "You must be a member of the group in order to reply to this status.", "group.popover.title": "Membership required", From 8ebaca0b44a8613da2ac9e4349b74591141b5805 Mon Sep 17 00:00:00 2001 From: Chewbacca Date: Thu, 4 May 2023 09:48:39 -0400 Subject: [PATCH 47/56] Remove group search inside global search --- app/soapbox/actions/search.ts | 10 +---- app/soapbox/containers/group-container.tsx | 24 ------------ .../compose/components/search-results.tsx | 39 +------------------ 3 files changed, 2 insertions(+), 71 deletions(-) delete mode 100644 app/soapbox/containers/group-container.tsx diff --git a/app/soapbox/actions/search.ts b/app/soapbox/actions/search.ts index 6d64b6534..a2f165ac0 100644 --- a/app/soapbox/actions/search.ts +++ b/app/soapbox/actions/search.ts @@ -1,7 +1,7 @@ import api from '../api'; import { fetchRelationships } from './accounts'; -import { importFetchedAccounts, importFetchedGroups, importFetchedStatuses } from './importer'; +import { importFetchedAccounts, importFetchedStatuses } from './importer'; import type { AxiosError } from 'axios'; import type { SearchFilter } from 'soapbox/reducers/search'; @@ -83,10 +83,6 @@ const submitSearch = (filter?: SearchFilter) => dispatch(importFetchedStatuses(response.data.statuses)); } - if (response.data.groups) { - dispatch(importFetchedGroups(response.data.groups)); - } - dispatch(fetchSearchSuccess(response.data, value, type)); dispatch(fetchRelationships(response.data.accounts.map((item: APIEntity) => item.id))); }).catch(error => { @@ -143,10 +139,6 @@ const expandSearch = (type: SearchFilter) => (dispatch: AppDispatch, getState: ( dispatch(importFetchedStatuses(data.statuses)); } - if (data.groups) { - dispatch(importFetchedGroups(data.groups)); - } - dispatch(expandSearchSuccess(data, value, type)); dispatch(fetchRelationships(data.accounts.map((item: APIEntity) => item.id))); }).catch(error => { diff --git a/app/soapbox/containers/group-container.tsx b/app/soapbox/containers/group-container.tsx deleted file mode 100644 index f1254b2ca..000000000 --- a/app/soapbox/containers/group-container.tsx +++ /dev/null @@ -1,24 +0,0 @@ -import React, { useCallback } from 'react'; - -import GroupCard from 'soapbox/components/group-card'; -import { useAppSelector } from 'soapbox/hooks'; -import { makeGetGroup } from 'soapbox/selectors'; - -interface IGroupContainer { - id: string -} - -const GroupContainer: React.FC = (props) => { - const { id, ...rest } = props; - - const getGroup = useCallback(makeGetGroup(), []); - const group = useAppSelector(state => getGroup(state, id)); - - if (group) { - return ; - } else { - return null; - } -}; - -export default GroupContainer; diff --git a/app/soapbox/features/compose/components/search-results.tsx b/app/soapbox/features/compose/components/search-results.tsx index 0962f7429..fe7a66d22 100644 --- a/app/soapbox/features/compose/components/search-results.tsx +++ b/app/soapbox/features/compose/components/search-results.tsx @@ -9,13 +9,11 @@ import IconButton from 'soapbox/components/icon-button'; import ScrollableList from 'soapbox/components/scrollable-list'; import { HStack, Tabs, Text } from 'soapbox/components/ui'; import AccountContainer from 'soapbox/containers/account-container'; -import GroupContainer from 'soapbox/containers/group-container'; import StatusContainer from 'soapbox/containers/status-container'; import PlaceholderAccount from 'soapbox/features/placeholder/components/placeholder-account'; -import PlaceholderGroupCard from 'soapbox/features/placeholder/components/placeholder-group-card'; import PlaceholderHashtag from 'soapbox/features/placeholder/components/placeholder-hashtag'; import PlaceholderStatus from 'soapbox/features/placeholder/components/placeholder-status'; -import { useAppDispatch, useAppSelector, useFeatures } from 'soapbox/hooks'; +import { useAppDispatch, useAppSelector } from 'soapbox/hooks'; import type { OrderedSet as ImmutableOrderedSet } from 'immutable'; import type { VirtuosoHandle } from 'react-virtuoso'; @@ -24,7 +22,6 @@ import type { SearchFilter } from 'soapbox/reducers/search'; const messages = defineMessages({ accounts: { id: 'search_results.accounts', defaultMessage: 'People' }, statuses: { id: 'search_results.statuses', defaultMessage: 'Posts' }, - groups: { id: 'search_results.groups', defaultMessage: 'Groups' }, hashtags: { id: 'search_results.hashtags', defaultMessage: 'Hashtags' }, }); @@ -33,7 +30,6 @@ const SearchResults = () => { const intl = useIntl(); const dispatch = useAppDispatch(); - const features = useFeatures(); const value = useAppSelector((state) => state.search.submittedValue); const results = useAppSelector((state) => state.search.results); @@ -66,14 +62,6 @@ const SearchResults = () => { }, ); - if (features.groups) items.push( - { - text: intl.formatMessage(messages.groups), - action: () => selectFilter('groups'), - name: 'groups', - }, - ); - items.push( { text: intl.formatMessage(messages.hashtags), @@ -186,31 +174,6 @@ const SearchResults = () => { } } - if (selectedFilter === 'groups') { - hasMore = results.groupsHasMore; - loaded = results.groupsLoaded; - placeholderComponent = PlaceholderGroupCard; - - if (results.groups && results.groups.size > 0) { - searchResults = results.groups.map((groupId: string) => ( - - )); - resultsIds = results.groups; - } else if (!submitted && trendingStatuses && !trendingStatuses.isEmpty()) { - searchResults = null; - } else if (loaded) { - noResultsMessage = ( -
- -
- ); - } - } - if (selectedFilter === 'hashtags') { hasMore = results.hashtagsHasMore; loaded = results.hashtagsLoaded; From f1b98e16027821657908b41678b6a35436bc9d86 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?marcin=20miko=C5=82ajczak?= Date: Fri, 5 May 2023 13:18:39 +0200 Subject: [PATCH 48/56] Hide relationship in BirthdayPanel MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: marcin mikołajczak --- app/soapbox/components/birthday-panel.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/app/soapbox/components/birthday-panel.tsx b/app/soapbox/components/birthday-panel.tsx index 059b8678b..bcfe5d073 100644 --- a/app/soapbox/components/birthday-panel.tsx +++ b/app/soapbox/components/birthday-panel.tsx @@ -58,6 +58,7 @@ const BirthdayPanel = ({ limit }: IBirthdayPanel) => { key={accountId} // @ts-ignore: TS thinks `id` is passed to , but it isn't id={accountId} + withRelationship={false} /> ))} From ad6f237a9d8ee5fe547a8daa42a91aa84d349012 Mon Sep 17 00:00:00 2001 From: Chewbacca Date: Fri, 5 May 2023 08:34:23 -0400 Subject: [PATCH 49/56] Add back 'uses' attribute for specific use-case --- app/soapbox/features/group/components/group-tag-list-item.tsx | 2 +- app/soapbox/schemas/group-tag.ts | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/app/soapbox/features/group/components/group-tag-list-item.tsx b/app/soapbox/features/group/components/group-tag-list-item.tsx index 7908bb13d..c076a445e 100644 --- a/app/soapbox/features/group/components/group-tag-list-item.tsx +++ b/app/soapbox/features/group/components/group-tag-list-item.tsx @@ -137,7 +137,7 @@ const GroupTagListItem = (props: IGroupMemberListItem) => { {intl.formatMessage(messages.total)}: {' '} - {shortNumberFormat(tag.groups)} + {shortNumberFormat(tag.uses)}
diff --git a/app/soapbox/schemas/group-tag.ts b/app/soapbox/schemas/group-tag.ts index f49162cfd..0d07003ff 100644 --- a/app/soapbox/schemas/group-tag.ts +++ b/app/soapbox/schemas/group-tag.ts @@ -5,6 +5,7 @@ const groupTagSchema = z.object({ name: z.string(), groups: z.number().optional(), url: z.string().optional(), + uses: z.number().optional(), pinned: z.boolean().optional().catch(false), visible: z.boolean().optional().default(true), }); From db91a00aca04f4b27b52309fb4c95852a43735e2 Mon Sep 17 00:00:00 2001 From: Chewbacca Date: Fri, 5 May 2023 08:35:11 -0400 Subject: [PATCH 50/56] Add more Jest tests for various Group components --- .../__tests__/group-header.test.tsx | 46 +++ .../__tests__/group-member-list-item.test.tsx | 320 ++++++++++++++++++ .../__tests__/group-tag-list-item.test.tsx | 123 +++++++ .../group/components/group-header.tsx | 14 +- .../components/group-member-list-item.tsx | 7 +- .../group/components/group-tag-list-item.tsx | 8 +- app/soapbox/jest/factory.ts | 28 +- 7 files changed, 537 insertions(+), 9 deletions(-) create mode 100644 app/soapbox/features/group/components/__tests__/group-header.test.tsx create mode 100644 app/soapbox/features/group/components/__tests__/group-member-list-item.test.tsx create mode 100644 app/soapbox/features/group/components/__tests__/group-tag-list-item.test.tsx diff --git a/app/soapbox/features/group/components/__tests__/group-header.test.tsx b/app/soapbox/features/group/components/__tests__/group-header.test.tsx new file mode 100644 index 000000000..03f171e14 --- /dev/null +++ b/app/soapbox/features/group/components/__tests__/group-header.test.tsx @@ -0,0 +1,46 @@ +import React from 'react'; + +import { buildGroup } from 'soapbox/jest/factory'; +import { render, screen } from 'soapbox/jest/test-helpers'; +import { Group } from 'soapbox/types/entities'; + +import GroupHeader from '../group-header'; + +let group: Group; + +describe('', () => { + describe('without a group', () => { + it('should render the blankslate', () => { + render(); + expect(screen.getByTestId('group-header-missing')).toBeInTheDocument(); + }); + }); + + describe('when the Group has been deleted', () => { + it('only shows name, header, and avatar', () => { + group = buildGroup({ display_name: 'my group', deleted_at: new Date().toISOString() }); + render(); + + expect(screen.queryAllByTestId('group-header-missing')).toHaveLength(0); + expect(screen.queryAllByTestId('group-actions')).toHaveLength(0); + expect(screen.queryAllByTestId('group-meta')).toHaveLength(0); + expect(screen.getByTestId('group-header-image')).toBeInTheDocument(); + expect(screen.getByTestId('group-avatar')).toBeInTheDocument(); + expect(screen.getByTestId('group-name')).toBeInTheDocument(); + }); + }); + + describe('with a valid Group', () => { + it('only shows all fields', () => { + group = buildGroup({ display_name: 'my group', deleted_at: null }); + render(); + + expect(screen.queryAllByTestId('group-header-missing')).toHaveLength(0); + expect(screen.getByTestId('group-actions')).toBeInTheDocument(); + expect(screen.getByTestId('group-meta')).toBeInTheDocument(); + expect(screen.getByTestId('group-header-image')).toBeInTheDocument(); + expect(screen.getByTestId('group-avatar')).toBeInTheDocument(); + expect(screen.getByTestId('group-name')).toBeInTheDocument(); + }); + }); +}); \ No newline at end of file diff --git a/app/soapbox/features/group/components/__tests__/group-member-list-item.test.tsx b/app/soapbox/features/group/components/__tests__/group-member-list-item.test.tsx new file mode 100644 index 000000000..abecc3287 --- /dev/null +++ b/app/soapbox/features/group/components/__tests__/group-member-list-item.test.tsx @@ -0,0 +1,320 @@ +import userEvent from '@testing-library/user-event'; +import React from 'react'; + +import { __stub } from 'soapbox/api'; +import { buildGroup, buildGroupMember, buildGroupRelationship } from 'soapbox/jest/factory'; +import { render, screen, waitFor } from 'soapbox/jest/test-helpers'; +import { GroupRoles } from 'soapbox/schemas/group-member'; + +import GroupMemberListItem from '../group-member-list-item'; + +describe('', () => { + describe('account rendering', () => { + const accountId = '4'; + const groupMember = buildGroupMember({}, { + id: accountId, + display_name: 'tiger woods', + }); + + beforeEach(() => { + __stub((mock) => { + mock.onGet(`/api/v1/accounts/${accountId}`).reply(200, groupMember.account); + }); + }); + + it('should render the users avatar', async () => { + const group = buildGroup({ + relationship: buildGroupRelationship(), + }); + + render(); + + await waitFor(() => { + expect(screen.getByTestId('group-member-list-item')).toHaveTextContent(groupMember.account.display_name); + }); + }); + }); + + describe('role badge', () => { + const accountId = '4'; + const group = buildGroup(); + + describe('when the user is an Owner', () => { + const groupMember = buildGroupMember({ role: GroupRoles.OWNER }, { + id: accountId, + display_name: 'tiger woods', + }); + + beforeEach(() => { + __stub((mock) => { + mock.onGet(`/api/v1/accounts/${accountId}`).reply(200, groupMember.account); + }); + }); + + it('should render the correct badge', async () => { + render(); + + await waitFor(() => { + expect(screen.getByTestId('role-badge')).toHaveTextContent('owner'); + }); + }); + }); + + describe('when the user is an Admin', () => { + const groupMember = buildGroupMember({ role: GroupRoles.ADMIN }, { + id: accountId, + display_name: 'tiger woods', + }); + + beforeEach(() => { + __stub((mock) => { + mock.onGet(`/api/v1/accounts/${accountId}`).reply(200, groupMember.account); + }); + }); + + it('should render the correct badge', async () => { + render(); + + await waitFor(() => { + expect(screen.getByTestId('role-badge')).toHaveTextContent('admin'); + }); + }); + }); + + describe('when the user is an User', () => { + const groupMember = buildGroupMember({ role: GroupRoles.USER }, { + id: accountId, + display_name: 'tiger woods', + }); + + beforeEach(() => { + __stub((mock) => { + mock.onGet(`/api/v1/accounts/${accountId}`).reply(200, groupMember.account); + }); + }); + + it('should render no correct badge', async () => { + render(); + + await waitFor(() => { + expect(screen.queryAllByTestId('role-badge')).toHaveLength(0); + }); + }); + }); + }); + + describe('as a Group owner', () => { + const group = buildGroup({ + relationship: buildGroupRelationship({ + role: GroupRoles.OWNER, + member: true, + }), + }); + + describe('when the user has role of "user"', () => { + const accountId = '4'; + const groupMember = buildGroupMember({}, { + id: accountId, + display_name: 'tiger woods', + username: 'tiger', + }); + + beforeEach(() => { + __stub((mock) => { + mock.onGet(`/api/v1/accounts/${accountId}`).reply(200, groupMember.account); + }); + }); + + describe('when "canPromoteToAdmin is true', () => { + it('should render dropdown with correct Owner actions', async () => { + const user = userEvent.setup(); + + render(); + + await waitFor(async() => { + await user.click(screen.getByTestId('icon-button')); + }); + + const dropdownMenu = screen.getByTestId('dropdown-menu'); + expect(dropdownMenu).toHaveTextContent('Assign admin role'); + expect(dropdownMenu).toHaveTextContent('Kick @tiger from group'); + expect(dropdownMenu).toHaveTextContent('Ban from group'); + }); + }); + + describe('when "canPromoteToAdmin is false', () => { + it('should prevent promoting user to Admin', async () => { + const user = userEvent.setup(); + + render(); + + await waitFor(async() => { + await user.click(screen.getByTestId('icon-button')); + await user.click(screen.getByTitle('Assign admin role')); + }); + + expect(screen.getByTestId('toast')).toHaveTextContent('Admin limit reached'); + }); + }); + }); + + describe('when the user has role of "admin"', () => { + const accountId = '4'; + const groupMember = buildGroupMember( + { + role: GroupRoles.ADMIN, + }, + { + id: accountId, + display_name: 'tiger woods', + username: 'tiger', + }, + ); + + beforeEach(() => { + __stub((mock) => { + mock.onGet(`/api/v1/accounts/${accountId}`).reply(200, groupMember.account); + }); + }); + + it('should render dropdown with correct Owner actions', async () => { + const user = userEvent.setup(); + + render(); + + await waitFor(async() => { + await user.click(screen.getByTestId('icon-button')); + }); + + const dropdownMenu = screen.getByTestId('dropdown-menu'); + expect(dropdownMenu).toHaveTextContent('Remove admin role'); + expect(dropdownMenu).toHaveTextContent('Kick @tiger from group'); + expect(dropdownMenu).toHaveTextContent('Ban from group'); + }); + }); + }); + + describe('as a Group admin', () => { + const group = buildGroup({ + relationship: buildGroupRelationship({ + role: GroupRoles.ADMIN, + member: true, + }), + }); + + describe('when the user has role of "user"', () => { + const accountId = '4'; + const groupMember = buildGroupMember({}, { + id: accountId, + display_name: 'tiger woods', + username: 'tiger', + }); + + beforeEach(() => { + __stub((mock) => { + mock.onGet(`/api/v1/accounts/${accountId}`).reply(200, groupMember.account); + }); + }); + + it('should render dropdown with correct Admin actions', async () => { + const user = userEvent.setup(); + + render(); + + await waitFor(async() => { + await user.click(screen.getByTestId('icon-button')); + }); + + const dropdownMenu = screen.getByTestId('dropdown-menu'); + expect(dropdownMenu).not.toHaveTextContent('Assign admin role'); + expect(dropdownMenu).toHaveTextContent('Kick @tiger from group'); + expect(dropdownMenu).toHaveTextContent('Ban from group'); + }); + }); + + describe('when the user has role of "admin"', () => { + const accountId = '4'; + const groupMember = buildGroupMember( + { + role: GroupRoles.ADMIN, + }, + { + id: accountId, + display_name: 'tiger woods', + username: 'tiger', + }, + ); + + beforeEach(() => { + __stub((mock) => { + mock.onGet(`/api/v1/accounts/${accountId}`).reply(200, groupMember.account); + }); + }); + + it('should not render the dropdown', async () => { + render(); + + await waitFor(async() => { + expect(screen.queryAllByTestId('icon-button')).toHaveLength(0); + }); + }); + }); + + describe('when the user has role of "owner"', () => { + const accountId = '4'; + const groupMember = buildGroupMember( + { + role: GroupRoles.OWNER, + }, + { + id: accountId, + display_name: 'tiger woods', + username: 'tiger', + }, + ); + + beforeEach(() => { + __stub((mock) => { + mock.onGet(`/api/v1/accounts/${accountId}`).reply(200, groupMember.account); + }); + }); + + it('should not render the dropdown', async () => { + render(); + + await waitFor(async() => { + expect(screen.queryAllByTestId('icon-button')).toHaveLength(0); + }); + }); + }); + }); + + describe('as a Group user', () => { + const group = buildGroup({ + relationship: buildGroupRelationship({ + role: GroupRoles.USER, + member: true, + }), + }); + const accountId = '4'; + const groupMember = buildGroupMember({}, { + id: accountId, + display_name: 'tiger woods', + username: 'tiger', + }); + + beforeEach(() => { + __stub((mock) => { + mock.onGet(`/api/v1/accounts/${accountId}`).reply(200, groupMember.account); + }); + }); + + it('should not render the dropdown', async () => { + render(); + + await waitFor(async() => { + expect(screen.queryAllByTestId('icon-button')).toHaveLength(0); + }); + }); + }); +}); \ No newline at end of file diff --git a/app/soapbox/features/group/components/__tests__/group-tag-list-item.test.tsx b/app/soapbox/features/group/components/__tests__/group-tag-list-item.test.tsx new file mode 100644 index 000000000..4418fff86 --- /dev/null +++ b/app/soapbox/features/group/components/__tests__/group-tag-list-item.test.tsx @@ -0,0 +1,123 @@ +import React from 'react'; + +import { buildGroup, buildGroupTag, buildGroupRelationship } from 'soapbox/jest/factory'; +import { render, screen } from 'soapbox/jest/test-helpers'; +import { GroupRoles } from 'soapbox/schemas/group-member'; + +import GroupTagListItem from '../group-tag-list-item'; + +describe('', () => { + describe('tag name', () => { + const name = 'hello'; + + it('should render the tag name', () => { + const group = buildGroup(); + const tag = buildGroupTag({ name }); + render(); + + expect(screen.getByTestId('group-tag-list-item')).toHaveTextContent(`#${name}`); + }); + + describe('when the tag is "visible"', () => { + const group = buildGroup(); + const tag = buildGroupTag({ name, visible: true }); + + it('renders the default name', () => { + render(); + expect(screen.getByTestId('group-tag-name')).toHaveClass('text-gray-900'); + }); + }); + + describe('when the tag is not "visible" and user is Owner', () => { + const group = buildGroup({ + relationship: buildGroupRelationship({ + role: GroupRoles.OWNER, + member: true, + }), + }); + const tag = buildGroupTag({ + name, + visible: false, + }); + + it('renders the subtle name', () => { + render(); + expect(screen.getByTestId('group-tag-name')).toHaveClass('text-gray-400'); + }); + }); + + describe('when the tag is not "visible" and user is Admin or User', () => { + const group = buildGroup({ + relationship: buildGroupRelationship({ + role: GroupRoles.ADMIN, + member: true, + }), + }); + const tag = buildGroupTag({ + name, + visible: false, + }); + + it('renders the subtle name', () => { + render(); + expect(screen.getByTestId('group-tag-name')).toHaveClass('text-gray-900'); + }); + }); + }); + + describe('pinning', () => { + describe('as an owner', () => { + const group = buildGroup({ + relationship: buildGroupRelationship({ + role: GroupRoles.OWNER, + member: true, + }), + }); + + describe('when the tag is visible', () => { + const tag = buildGroupTag({ visible: true }); + + it('renders the pin icon', () => { + render(); + expect(screen.getByTestId('pin-icon')).toBeInTheDocument(); + }); + }); + + describe('when the tag is not visible', () => { + const tag = buildGroupTag({ visible: false }); + + it('does not render the pin icon', () => { + render(); + expect(screen.queryAllByTestId('pin-icon')).toHaveLength(0); + }); + }); + + describe('as a non-owner', () => { + const group = buildGroup({ + relationship: buildGroupRelationship({ + role: GroupRoles.ADMIN, + member: true, + }), + }); + + describe('when the tag is visible', () => { + const tag = buildGroupTag({ visible: true }); + + it('does not render the pin icon', () => { + render(); + expect(screen.queryAllByTestId('pin-icon')).toHaveLength(0); + }); + }); + + describe('when the tag is not visible', () => { + const tag = buildGroupTag({ visible: false }); + + it('does not render the pin icon', () => { + render(); + expect(screen.queryAllByTestId('pin-icon')).toHaveLength(0); + }); + }); + }); + }); + }); +}); \ No newline at end of file diff --git a/app/soapbox/features/group/components/group-header.tsx b/app/soapbox/features/group/components/group-header.tsx index 9626906d5..ad22e1e13 100644 --- a/app/soapbox/features/group/components/group-header.tsx +++ b/app/soapbox/features/group/components/group-header.tsx @@ -34,7 +34,7 @@ const GroupHeader: React.FC = ({ group }) => { if (!group) { return ( -
+
@@ -107,7 +107,10 @@ const GroupHeader: React.FC = ({ group }) => { } return ( -
+
{isHeaderMissing ? ( ) : header} @@ -120,7 +123,7 @@ const GroupHeader: React.FC = ({ group }) => {
{renderHeader()} -
+
= ({ group }) => { size='xl' weight='bold' dangerouslySetInnerHTML={{ __html: group.display_name_html }} + data-testid='group-name' /> {!isDeleted && ( <> - + @@ -154,7 +158,7 @@ const GroupHeader: React.FC = ({ group }) => { /> - + diff --git a/app/soapbox/features/group/components/group-member-list-item.tsx b/app/soapbox/features/group/components/group-member-list-item.tsx index d2226879a..f9b18735d 100644 --- a/app/soapbox/features/group/components/group-member-list-item.tsx +++ b/app/soapbox/features/group/components/group-member-list-item.tsx @@ -180,7 +180,11 @@ const GroupMemberListItem = (props: IGroupMemberListItem) => { } return ( - +
@@ -188,6 +192,7 @@ const GroupMemberListItem = (props: IGroupMemberListItem) => { {(isMemberOwner || isMemberAdmin) ? ( { require('@tabler/icons/pin.svg') } iconClassName='h-5 w-5 text-primary-500 dark:text-accent-blue' + data-testid='pin-icon' /> ); @@ -123,13 +124,18 @@ const GroupTagListItem = (props: IGroupMemberListItem) => { }; return ( - + #{tag.name} diff --git a/app/soapbox/jest/factory.ts b/app/soapbox/jest/factory.ts index 07f4bc7d2..35ea063e0 100644 --- a/app/soapbox/jest/factory.ts +++ b/app/soapbox/jest/factory.ts @@ -1,23 +1,34 @@ import { v4 as uuidv4 } from 'uuid'; import { + accountSchema, adSchema, cardSchema, - groupSchema, + groupMemberSchema, groupRelationshipSchema, + groupSchema, groupTagSchema, relationshipSchema, + type Account, type Ad, type Card, type Group, + type GroupMember, type GroupRelationship, type GroupTag, type Relationship, } from 'soapbox/schemas'; +import { GroupRoles } from 'soapbox/schemas/group-member'; // TODO: there's probably a better way to create these factory functions. // This looks promising but didn't work on my first attempt: https://github.com/anatine/zod-plugins/tree/main/packages/zod-mock +function buildAccount(props: Partial = {}): Account { + return accountSchema.parse(Object.assign({ + id: uuidv4(), + }, props)); +} + function buildCard(props: Partial = {}): Card { return cardSchema.parse(Object.assign({ url: 'https://soapbox.test', @@ -39,6 +50,18 @@ function buildGroupRelationship(props: Partial = {}): GroupRe function buildGroupTag(props: Partial = {}): GroupTag { return groupTagSchema.parse(Object.assign({ id: uuidv4(), + name: uuidv4(), + }, props)); +} + +function buildGroupMember( + props: Partial = {}, + accountProps: Partial = {}, +): GroupMember { + return groupMemberSchema.parse(Object.assign({ + id: uuidv4(), + account: buildAccount(accountProps), + role: GroupRoles.USER, }, props)); } @@ -55,10 +78,11 @@ function buildRelationship(props: Partial = {}): Relationship { } export { + buildAd, buildCard, buildGroup, + buildGroupMember, buildGroupRelationship, buildGroupTag, - buildAd, buildRelationship, }; \ No newline at end of file From 9b011275b86dcd3a6d19459553cf18f06badf9c0 Mon Sep 17 00:00:00 2001 From: Chewbacca Date: Fri, 5 May 2023 08:35:21 -0400 Subject: [PATCH 51/56] Switch order of Group Page tabs --- app/soapbox/pages/group-page.tsx | 24 +++++++++++++----------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/app/soapbox/pages/group-page.tsx b/app/soapbox/pages/group-page.tsx index e2d6e5755..41d561604 100644 --- a/app/soapbox/pages/group-page.tsx +++ b/app/soapbox/pages/group-page.tsx @@ -123,17 +123,19 @@ const GroupPage: React.FC = ({ params, children }) => { }); } - items.push({ - text: intl.formatMessage(messages.members), - to: `/group/${group?.slug}/members`, - name: '/group/:groupSlug/members', - count: pending.length, - }, - { - text: intl.formatMessage(messages.media), - to: `/group/${group?.slug}/media`, - name: '/group/:groupSlug/media', - }); + items.push( + { + text: intl.formatMessage(messages.media), + to: `/group/${group?.slug}/media`, + name: '/group/:groupSlug/media', + }, + { + text: intl.formatMessage(messages.members), + to: `/group/${group?.slug}/members`, + name: '/group/:groupSlug/members', + count: pending.length, + }, + ); return items; }, [features.groupsTags, pending.length]); From 5b61aa39a7b23a7a48b648ccc366b7532a7a14c7 Mon Sep 17 00:00:00 2001 From: Chewbacca Date: Fri, 5 May 2023 08:41:25 -0400 Subject: [PATCH 52/56] Account for 409 response codes --- .../group/group-membership-requests.tsx | 24 +++++++++++++++---- app/soapbox/locales/en.json | 3 ++- 2 files changed, 21 insertions(+), 6 deletions(-) diff --git a/app/soapbox/features/group/group-membership-requests.tsx b/app/soapbox/features/group/group-membership-requests.tsx index 2ea708c3d..79700e1a7 100644 --- a/app/soapbox/features/group/group-membership-requests.tsx +++ b/app/soapbox/features/group/group-membership-requests.tsx @@ -1,3 +1,4 @@ +import { AxiosError } from 'axios'; import React, { useEffect } from 'react'; import { FormattedMessage, defineMessages, useIntl } from 'react-intl'; @@ -17,7 +18,8 @@ type RouteParams = { groupId: string }; const messages = defineMessages({ heading: { id: 'column.group_pending_requests', defaultMessage: 'Pending requests' }, - authorizeRejectFail: { id: 'group.membership_requests.fail', defaultMessage: 'Group owner or admin has already taken action on this request.' }, + authorizeFail: { id: 'group.group_mod_authorize.fail', defaultMessage: 'Failed to approve @{name}' }, + rejectFail: { id: 'group.group_mod_reject.fail', defaultMessage: 'Failed to reject @{name}' }, }); interface IMembershipRequest { @@ -81,9 +83,15 @@ const GroupMembershipRequests: React.FC = ({ params }) async function handleAuthorize(account: AccountEntity) { return authorize(account.id) .then(() => Promise.resolve()) - .catch(() => { + .catch((error: AxiosError) => { refetch(); - toast.error(intl.formatMessage(messages.authorizeRejectFail)); + + let message = intl.formatMessage(messages.authorizeFail, { name: account.username }); + if (error.response?.status === 409) { + message = (error.response?.data as any).error; + } + toast.error(message); + return Promise.reject(); }); } @@ -91,9 +99,15 @@ const GroupMembershipRequests: React.FC = ({ params }) async function handleReject(account: AccountEntity) { return reject(account.id) .then(() => Promise.resolve()) - .catch(() => { + .catch((error: AxiosError) => { refetch(); - toast.error(intl.formatMessage(messages.authorizeRejectFail)); + + let message = intl.formatMessage(messages.rejectFail, { name: account.username }); + if (error.response?.status === 409) { + message = (error.response?.data as any).error; + } + toast.error(message); + return Promise.reject(); }); } diff --git a/app/soapbox/locales/en.json b/app/soapbox/locales/en.json index 2c81f307f..91fad97e0 100644 --- a/app/soapbox/locales/en.json +++ b/app/soapbox/locales/en.json @@ -773,12 +773,14 @@ "group.delete.success": "Group successfully deleted", "group.deleted.message": "This group has been deleted.", "group.demote.user.success": "@{name} is now a member", + "group.group_mod_authorize.fail": "Failed to approve @{name}", "group.group_mod_block": "Ban from group", "group.group_mod_block.success": "@{name} is banned", "group.group_mod_demote": "Remove {role} role", "group.group_mod_kick": "Kick @{name} from group", "group.group_mod_kick.success": "Kicked @{name} from group", "group.group_mod_promote_mod": "Assign {role} role", + "group.group_mod_reject.fail": "Failed to reject @{name}", "group.group_mod_unblock": "Unban", "group.group_mod_unblock.success": "Unbanned @{name} from group", "group.header.alt": "Group header", @@ -792,7 +794,6 @@ "group.manage": "Manage Group", "group.member.admin.limit.summary": "You can assign up to {count} admins for the group at this time.", "group.member.admin.limit.title": "Admin limit reached", - "group.membership_requests.fail": "Group owner or admin has already taken action on this request.", "group.popover.action": "View Group", "group.popover.summary": "You must be a member of the group in order to reply to this status.", "group.popover.title": "Membership required", From 2e7e6b3df6e3d7e07514e7f6b1b4e5a333949741 Mon Sep 17 00:00:00 2001 From: Chewbacca Date: Fri, 5 May 2023 08:44:23 -0400 Subject: [PATCH 53/56] I18n --- app/soapbox/locales/en.json | 2 -- 1 file changed, 2 deletions(-) diff --git a/app/soapbox/locales/en.json b/app/soapbox/locales/en.json index 96efd8530..64116bc10 100644 --- a/app/soapbox/locales/en.json +++ b/app/soapbox/locales/en.json @@ -691,7 +691,6 @@ "empty_column.remote": "There is nothing here! Manually follow users from {instance} to fill it up.", "empty_column.scheduled_statuses": "You don't have any scheduled statuses yet. When you add one, it will show up here.", "empty_column.search.accounts": "There are no people results for \"{term}\"", - "empty_column.search.groups": "There are no groups results for \"{term}\"", "empty_column.search.hashtags": "There are no hashtags results for \"{term}\"", "empty_column.search.statuses": "There are no posts results for \"{term}\"", "empty_column.test": "The test timeline is empty.", @@ -1322,7 +1321,6 @@ "search.placeholder": "Search", "search_results.accounts": "People", "search_results.filter_message": "You are searching for posts from @{acct}.", - "search_results.groups": "Groups", "search_results.hashtags": "Hashtags", "search_results.statuses": "Posts", "security.codes.fail": "Failed to fetch backup codes", From 06d8370fe63b171fd5efbf6a62187f7e5d977d1c Mon Sep 17 00:00:00 2001 From: Ahmad Dakhlallah Date: Fri, 5 May 2023 17:51:33 +0300 Subject: [PATCH 54/56] Enhance RTL detection by ignoring links in post --- app/soapbox/rtl.ts | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/app/soapbox/rtl.ts b/app/soapbox/rtl.ts index 4c3599cb6..f069bf5c4 100644 --- a/app/soapbox/rtl.ts +++ b/app/soapbox/rtl.ts @@ -19,7 +19,12 @@ export function isRtl(text: string): boolean { if (text.length === 0) { return false; } - + // Remove http(s), (s)ftp, ws(s), blob and smtp(s) links + text = text.replace(/(?:https?|ftp|sftp|ws|wss|blob|smtp|smtps):\/\/[\n\S]+/g, ''); + // Remove email address links + text = text.replace(/(mailto:)([^\s@]+@[^\s@]+\.[^\s@]+)/g, ''); + // Remove Phone numbe links + text = text.replace(/(tel:)([+\d\s()-]+)/g, ''); text = text.replace(/(?:^|[^\/\w])@([a-z0-9_]+(@[a-z0-9\.\-]+)?)/ig, ''); text = text.replace(/(?:^|[^\/\w])#([\S]+)/ig, ''); text = text.replace(/\s+/g, ''); @@ -27,6 +32,7 @@ export function isRtl(text: string): boolean { const matches = text.match(rtlChars); if (!matches) { + return false; } From 7a0ce1aab4e951960517148f70e3647a7e42fd60 Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Fri, 5 May 2023 16:26:58 +0000 Subject: [PATCH 55/56] Sound better now! --- app/soapbox/rtl.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/soapbox/rtl.ts b/app/soapbox/rtl.ts index f069bf5c4..16a6100ef 100644 --- a/app/soapbox/rtl.ts +++ b/app/soapbox/rtl.ts @@ -20,7 +20,7 @@ export function isRtl(text: string): boolean { return false; } // Remove http(s), (s)ftp, ws(s), blob and smtp(s) links - text = text.replace(/(?:https?|ftp|sftp|ws|wss|blob|smtp|smtps):\/\/[\n\S]+/g, ''); + text = text.replace(/(?:https?|ftp|sftp|ws|wss|blob|smtp|smtps):\/\/[\S]+/g, ''); // Remove email address links text = text.replace(/(mailto:)([^\s@]+@[^\s@]+\.[^\s@]+)/g, ''); // Remove Phone numbe links From 185ef4e3c6ce3edcb8055b4f4259489cff29501f Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Sat, 6 May 2023 22:55:36 -0500 Subject: [PATCH 56/56] StatusReplyMentions: enforce a 200px max width --- app/soapbox/components/status-reply-mentions.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/soapbox/components/status-reply-mentions.tsx b/app/soapbox/components/status-reply-mentions.tsx index 61f2f2969..e03d0c7f7 100644 --- a/app/soapbox/components/status-reply-mentions.tsx +++ b/app/soapbox/components/status-reply-mentions.tsx @@ -54,7 +54,7 @@ const StatusReplyMentions: React.FC = ({ status, hoverable e.stopPropagation()} > @{isPubkey(account.username) ? account.username.slice(0, 8) : account.username}