diff --git a/src/__fixtures__/realDonaldTrump.json b/src/__fixtures__/realDonaldTrump.json deleted file mode 100644 index c9cf20076..000000000 --- a/src/__fixtures__/realDonaldTrump.json +++ /dev/null @@ -1,26 +0,0 @@ -{ - "id": "107780257626128497", - "username": "realDonaldTrump", - "acct": "realDonaldTrump", - "display_name": "Donald J. Trump", - "locked": false, - "bot": false, - "discoverable": null, - "group": false, - "created_at": "2022-02-11T00:00:00.000Z", - "note": "

45th President of the United States of America

", - "url": "https://truthsocial.com/@realDonaldTrump", - "avatar": "https://static-assets.truthsocial.com/tmtg:prime-truth-social-assets/accounts/avatars/107/780/257/626/128/497/original/573cf5cc8281e7e9.jpeg", - "avatar_static": "https://static-assets.truthsocial.com/tmtg:prime-truth-social-assets/accounts/avatars/107/780/257/626/128/497/original/573cf5cc8281e7e9.jpeg", - "header": "https://static-assets.truthsocial.com/tmtg:prime-truth-social-assets/accounts/headers/107/780/257/626/128/497/original/3c1acf607b065ded.jpeg", - "header_static": "https://static-assets.truthsocial.com/tmtg:prime-truth-social-assets/accounts/headers/107/780/257/626/128/497/original/3c1acf607b065ded.jpeg", - "followers_count": 51507, - "following_count": 1, - "statuses_count": 1, - "last_status_at": "2022-02-14", - "verified": true, - "location": "", - "website": "", - "emojis": [], - "fields": [] -} diff --git a/src/__fixtures__/rules.json b/src/__fixtures__/rules.json index c1c7a1f7d..b26807ce8 100644 --- a/src/__fixtures__/rules.json +++ b/src/__fixtures__/rules.json @@ -3,12 +3,10 @@ "id": "1", "text": "Illegal activity and behavior", "subtext": "Content that depicts illegal or criminal acts, threats of violence.", - "rule_type": "content" }, { "id": "2", "text": "Intellectual property infringement", "subtext": "Impersonating another account or business, infringing on intellectual property rights.", - "rule_type": "content" } ] diff --git a/src/__fixtures__/truthsocial-account.json b/src/__fixtures__/truthsocial-account.json deleted file mode 100644 index 6b451ca8b..000000000 --- a/src/__fixtures__/truthsocial-account.json +++ /dev/null @@ -1,26 +0,0 @@ -{ - "id": "107759994408336377", - "username": "alex", - "acct": "alex", - "display_name": "Alex G.", - "locked": false, - "bot": false, - "discoverable": null, - "group": false, - "created_at": "2022-02-08T00:00:00.000Z", - "note": "

Launching Truth Social

", - "url": "https://truthsocial.com/@alex", - "avatar": "https://static-assets.truthsocial.com/tmtg:prime-truth-social-assets/accounts/avatars/107/759/994/408/336/377/original/119cb0dd1fa615b7.png", - "avatar_static": "https://static-assets.truthsocial.com/tmtg:prime-truth-social-assets/accounts/avatars/107/759/994/408/336/377/original/119cb0dd1fa615b7.png", - "header": "https://static-assets.truthsocial.com/tmtg:prime-truth-social-assets/accounts/headers/107/759/994/408/336/377/original/31f62b0453ccf554.png", - "header_static": "https://static-assets.truthsocial.com/tmtg:prime-truth-social-assets/accounts/headers/107/759/994/408/336/377/original/31f62b0453ccf554.png", - "followers_count": 966, - "following_count": 39, - "statuses_count": 4, - "last_status_at": "2022-02-20", - "verified": true, - "location": "Texas", - "website": "https://soapbox.pub", - "emojis": [], - "fields": [] -} diff --git a/src/__fixtures__/truthsocial-status-in-moderation.json b/src/__fixtures__/truthsocial-status-in-moderation.json deleted file mode 100644 index 7613ebd93..000000000 --- a/src/__fixtures__/truthsocial-status-in-moderation.json +++ /dev/null @@ -1,85 +0,0 @@ -{ - "id": "108046224464672537", - "created_at": "2022-03-30T15:40:53.287Z", - "in_reply_to_id": null, - "in_reply_to_account_id": null, - "sensitive": false, - "spoiler_text": "", - "visibility": "self", - "language": null, - "uri": "https://truthsocial.com/users/alex/statuses/108046244464677537", - "url": "https://truthsocial.com/@alex/108046244464677537", - "replies_count": 0, - "reblogs_count": 0, - "favourites_count": 0, - "favourited": false, - "reblogged": false, - "muted": false, - "bookmarked": false, - "pinned": false, - "content": "

A federal agent inspects a 'lumber' truck after smelling alcohol during the prohibition period. Los Angeles, 1926 (during the Prohibition era).

", - "reblog": null, - "application": { - "name": "Soapbox FE", - "website": "https://soapbox.pub/" - }, - "account": { - "id": "107759994408336377", - "username": "alex", - "acct": "alex", - "display_name": "Alex Gleason", - "locked": false, - "bot": false, - "discoverable": null, - "group": false, - "created_at": "2022-02-08T00:00:00.000Z", - "note": "

Launching Truth Social

", - "url": "https://truthsocial.com/@alex", - "avatar": "https://static-assets.truthsocial.com/tmtg:prime-truth-social-assets/accounts/avatars/107/759/994/408/336/377/original/119cb0dd1fa615b7.png", - "avatar_static": "https://static-assets.truthsocial.com/tmtg:prime-truth-social-assets/accounts/avatars/107/759/994/408/336/377/original/119cb0dd1fa615b7.png", - "header": "https://static-assets.truthsocial.com/tmtg:prime-truth-social-assets/accounts/headers/107/759/994/408/336/377/original/31f62b0453ccf554.png", - "header_static": "https://static-assets.truthsocial.com/tmtg:prime-truth-social-assets/accounts/headers/107/759/994/408/336/377/original/31f62b0453ccf554.png", - "followers_count": 4713, - "following_count": 43, - "statuses_count": 7, - "last_status_at": "2022-03-30", - "verified": true, - "location": "Texas", - "website": "https://soapbox.pub/", - "emojis": [], - "fields": [] - }, - "media_attachments": [ - { - "id": "108635651287436632", - "type": "image", - "url": "https://static-assets-1.truthsocial.com/tmtg:prime-ts-assets/media_attachments/files/108/635/651/487/436/632/original/7873bda5a7ab45d3.jpeg", - "preview_url": "https://static-assets-1.truthsocial.com/tmtg:prime-ts-assets/media_attachments/files/108/635/651/487/436/632/small/7873bda5a7ab45d3.jpeg", - "external_video_id": null, - "remote_url": null, - "preview_remote_url": null, - "text_url": "https://truthsocial.com/media/_Kc-2w2Pe7knhYJV-CM", - "meta": { - "original": { - "width": 1080, - "height": 841, - "size": "1080x841", - "aspect": 1.2841854934601664 - }, - "small": { - "width": 907, - "height": 706, - "size": "907x706", - "aspect": 1.2847025495750708 - } - }, - "description": null, - "blurhash": "UIIY5?4n~q9FIUIUD%WB?bt7M{t7of%MofIU" - } - ], - "mentions": [], - "tags": [], - "emojis": [], - "card": null, - "poll": null -} \ No newline at end of file diff --git a/src/actions/compose.ts b/src/actions/compose.ts index 99f84298e..57327b157 100644 --- a/src/actions/compose.ts +++ b/src/actions/compose.ts @@ -50,7 +50,6 @@ const COMPOSE_UPLOAD_FAIL = 'COMPOSE_UPLOAD_FAIL' as const; const COMPOSE_UPLOAD_PROGRESS = 'COMPOSE_UPLOAD_PROGRESS' as const; const COMPOSE_UPLOAD_UNDO = 'COMPOSE_UPLOAD_UNDO' as const; const COMPOSE_GROUP_POST = 'COMPOSE_GROUP_POST' as const; -const COMPOSE_SET_GROUP_TIMELINE_VISIBLE = 'COMPOSE_SET_GROUP_TIMELINE_VISIBLE' as const; const COMPOSE_SUGGESTIONS_CLEAR = 'COMPOSE_SUGGESTIONS_CLEAR' as const; const COMPOSE_SUGGESTIONS_READY = 'COMPOSE_SUGGESTIONS_READY' as const; @@ -366,7 +365,6 @@ const submitCompose = (composeId: string, opts: SubmitComposeOpts = {}) => if (compose.privacy === 'group') { params.group_id = compose.group_id; - params.group_timeline_visible = compose.group_timeline_visible; // Truth Social } return dispatch(createStatus(params, idempotencyKey, statusId)).then(function(data) { @@ -506,12 +504,6 @@ const groupCompose = (composeId: string, groupId: string) => ({ group_id: groupId, }); -const setGroupTimelineVisible = (composeId: string, groupTimelineVisible: boolean) => ({ - type: COMPOSE_SET_GROUP_TIMELINE_VISIBLE, - id: composeId, - groupTimelineVisible, -}); - const clearComposeSuggestions = (composeId: string) => { if (cancelFetchComposeSuggestions) { cancelFetchComposeSuggestions(); @@ -882,7 +874,6 @@ type ComposeAction = | ReturnType | ReturnType | ReturnType - | ReturnType | ReturnType | ComposeSuggestionsReadyAction | ComposeSuggestionSelectAction @@ -954,7 +945,6 @@ export { COMPOSE_REMOVE_FROM_MENTIONS, COMPOSE_SET_STATUS, COMPOSE_EDITOR_STATE_SET, - COMPOSE_SET_GROUP_TIMELINE_VISIBLE, COMPOSE_CHANGE_MEDIA_ORDER, setComposeToStatus, changeCompose, @@ -984,7 +974,6 @@ export { undoUploadCompose, groupCompose, groupComposeModal, - setGroupTimelineVisible, clearComposeSuggestions, fetchComposeSuggestions, readyComposeSuggestionsEmojis, diff --git a/src/actions/notifications.ts b/src/actions/notifications.ts index c6ce8043a..fb70a717b 100644 --- a/src/actions/notifications.ts +++ b/src/actions/notifications.ts @@ -89,7 +89,6 @@ const updateNotificationsQueue = (notification: APIEntity, intlMessages: Record< (dispatch: AppDispatch, getState: () => RootState) => { if (!notification.type) return; // drop invalid notifications if (notification.type === 'pleroma:chat_mention') return; // Drop chat notifications, handle them per-chat - if (notification.type === 'chat') return; // Drop Truth Social chat notifications. const showAlert = getSettings(getState()).getIn(['notifications', 'alerts', notification.type]); const filters = getFilters(getState(), { contextType: 'notifications' }); diff --git a/src/actions/reports.ts b/src/actions/reports.ts index 662b24b3d..6911a97b2 100644 --- a/src/actions/reports.ts +++ b/src/actions/reports.ts @@ -22,8 +22,6 @@ const REPORT_RULE_CHANGE = 'REPORT_RULE_CHANGE'; enum ReportableEntities { ACCOUNT = 'ACCOUNT', - CHAT_MESSAGE = 'CHAT_MESSAGE', - GROUP = 'GROUP', STATUS = 'STATUS' } diff --git a/src/actions/security.ts b/src/actions/security.ts index 4448104b7..6c2121b0e 100644 --- a/src/actions/security.ts +++ b/src/actions/security.ts @@ -6,7 +6,6 @@ import toast from 'soapbox/toast'; import { getLoggedInAccount } from 'soapbox/utils/auth'; -import { parseVersion, TRUTHSOCIAL } from 'soapbox/utils/features'; import { normalizeUsername } from 'soapbox/utils/input'; import api from '../api'; @@ -86,8 +85,6 @@ const changePassword = (oldPassword: string, newPassword: string, confirmation: const resetPassword = (usernameOrEmail: string) => (dispatch: AppDispatch, getState: () => RootState) => { const input = normalizeUsername(usernameOrEmail); - const state = getState(); - const v = parseVersion(state.instance.version); dispatch({ type: RESET_PASSWORD_REQUEST }); @@ -96,12 +93,7 @@ const resetPassword = (usernameOrEmail: string) => ? { email: input } : { nickname: input, username: input }; - const endpoint = - v.software === TRUTHSOCIAL - ? '/api/v1/truth/password_reset/request' - : '/auth/password'; - - return api(getState).post(endpoint, params).then(() => { + return api(getState).post('/auth/password', params).then(() => { dispatch({ type: RESET_PASSWORD_SUCCESS }); }).catch(error => { dispatch({ type: RESET_PASSWORD_FAIL, error }); @@ -109,19 +101,6 @@ const resetPassword = (usernameOrEmail: string) => }); }; -const resetPasswordConfirm = (password: string, token: string) => - (dispatch: AppDispatch, getState: () => RootState) => { - const params = { password, reset_password_token: token }; - dispatch({ type: RESET_PASSWORD_CONFIRM_REQUEST }); - - return api(getState).post('/api/v1/truth/password_reset/confirm', params).then(() => { - dispatch({ type: RESET_PASSWORD_CONFIRM_SUCCESS }); - }).catch(error => { - dispatch({ type: RESET_PASSWORD_CONFIRM_FAIL, error }); - throw error; - }); - }; - const changeEmail = (email: string, password: string) => (dispatch: AppDispatch, getState: () => RootState) => { dispatch({ type: CHANGE_EMAIL_REQUEST, email }); @@ -137,10 +116,6 @@ const changeEmail = (email: string, password: string) => }); }; -const confirmChangedEmail = (token: string) => - (_dispatch: AppDispatch, getState: () => RootState) => - api(getState).get(`/api/v1/truth/email/confirm?confirmation_token=${token}`); - const deleteAccount = (password: string) => (dispatch: AppDispatch, getState: () => RootState) => { const account = getLoggedInAccount(getState()); @@ -203,9 +178,7 @@ export { revokeOAuthTokenById, changePassword, resetPassword, - resetPasswordConfirm, changeEmail, - confirmChangedEmail, deleteAccount, moveAccount, }; diff --git a/src/actions/statuses.ts b/src/actions/statuses.ts index ab6a855ee..e5ec19032 100644 --- a/src/actions/statuses.ts +++ b/src/actions/statuses.ts @@ -1,8 +1,7 @@ import { isLoggedIn } from 'soapbox/utils/auth'; -import { getFeatures } from 'soapbox/utils/features'; import { shouldHaveCard } from 'soapbox/utils/status'; -import api, { getNextLink } from '../api'; +import api from '../api'; import { setComposeToStatus } from './compose'; import { fetchGroupRelationships } from './groups'; @@ -195,62 +194,13 @@ const fetchContext = (id: string) => }); }; -const fetchNext = (statusId: string, next: string) => - async(dispatch: AppDispatch, getState: () => RootState) => { - const response = await api(getState).get(next); - dispatch(importFetchedStatuses(response.data)); - - dispatch({ - type: CONTEXT_FETCH_SUCCESS, - id: statusId, - ancestors: [], - descendants: response.data, - }); - - return { next: getNextLink(response) }; - }; - -const fetchAncestors = (id: string) => - async(dispatch: AppDispatch, getState: () => RootState) => { - const response = await api(getState).get(`/api/v1/statuses/${id}/context/ancestors`); - dispatch(importFetchedStatuses(response.data)); - return response; - }; - -const fetchDescendants = (id: string) => - async(dispatch: AppDispatch, getState: () => RootState) => { - const response = await api(getState).get(`/api/v1/statuses/${id}/context/descendants`); - dispatch(importFetchedStatuses(response.data)); - return response; - }; - const fetchStatusWithContext = (id: string) => async(dispatch: AppDispatch, getState: () => RootState) => { - const features = getFeatures(getState().instance); - - if (features.paginatedContext) { - await dispatch(fetchStatus(id)); - const responses = await Promise.all([ - dispatch(fetchAncestors(id)), - dispatch(fetchDescendants(id)), - ]); - - dispatch({ - type: CONTEXT_FETCH_SUCCESS, - id, - ancestors: responses[0].data, - descendants: responses[1].data, - }); - - const next = getNextLink(responses[1]); - return { next }; - } else { - await Promise.all([ - dispatch(fetchContext(id)), - dispatch(fetchStatus(id)), - ]); - return { next: undefined }; - } + await Promise.all([ + dispatch(fetchContext(id)), + dispatch(fetchStatus(id)), + ]); + return { next: undefined }; }; const muteStatus = (id: string) => @@ -381,9 +331,6 @@ export { deleteStatus, updateStatus, fetchContext, - fetchNext, - fetchAncestors, - fetchDescendants, fetchStatusWithContext, muteStatus, unmuteStatus, diff --git a/src/actions/streaming.ts b/src/actions/streaming.ts index 925832265..eb369fcb9 100644 --- a/src/actions/streaming.ts +++ b/src/actions/streaming.ts @@ -4,11 +4,9 @@ import { importEntities } from 'soapbox/entity-store/actions'; import { Entities } from 'soapbox/entity-store/entities'; import { selectEntity } from 'soapbox/entity-store/selectors'; import messages from 'soapbox/messages'; -import { ChatKeys, IChat, isLastMessage } from 'soapbox/queries/chats'; import { queryClient } from 'soapbox/queries/client'; import { announcementSchema, type Announcement, type Relationship } from 'soapbox/schemas'; -import { getUnreadChatsCount, updateChatListItem, updateChatMessage } from 'soapbox/utils/chats'; -import { removePageItem } from 'soapbox/utils/queries'; +import { getUnreadChatsCount, updateChatListItem } from 'soapbox/utils/chats'; import { play, soundCache } from 'soapbox/utils/sounds'; import { connectStream } from '../stream'; @@ -27,41 +25,10 @@ import { import type { IStatContext } from 'soapbox/contexts/stat-context'; import type { AppDispatch, RootState } from 'soapbox/store'; -import type { APIEntity, Chat } from 'soapbox/types/entities'; +import type { APIEntity } from 'soapbox/types/entities'; const STREAMING_CHAT_UPDATE = 'STREAMING_CHAT_UPDATE'; -const removeChatMessage = (payload: string) => { - const data = JSON.parse(payload); - const chatId = data.chat_id; - const chatMessageId = data.deleted_message_id; - - // If the user just deleted the "last_message", then let's invalidate - // the Chat Search query so the Chat List will show the new "last_message". - if (isLastMessage(chatMessageId)) { - queryClient.invalidateQueries({ - queryKey: ChatKeys.chatSearch(), - }); - } - - removePageItem(ChatKeys.chatMessages(chatId), chatMessageId, (o: any, n: any) => String(o.id) === String(n)); -}; - -// Update the specific Chat query data. -const updateChatQuery = (chat: IChat) => { - const cachedChat = queryClient.getQueryData(ChatKeys.chat(chat.id)); - if (!cachedChat) { - return; - } - - const newChat = { - ...cachedChat, - latest_read_message_by_account: chat.latest_read_message_by_account, - latest_read_message_created_at: chat.latest_read_message_created_at, - }; - queryClient.setQueryData(ChatKeys.chat(chat.id), newChat as any); -}; - const updateAnnouncementReactions = ({ announcement_id: id, name, count }: APIEntity) => { queryClient.setQueryData(['announcements'], (prevResult: Announcement[]) => prevResult.map(value => { @@ -149,7 +116,6 @@ const connectTimelineStream = ( dispatch(fetchFilters()); break; case 'pleroma:chat_update': - case 'chat_message.created': // TruthSocial dispatch((_dispatch: AppDispatch, getState: () => RootState) => { const chat = JSON.parse(data.payload); const me = getState().me; @@ -169,22 +135,6 @@ const connectTimelineStream = ( } }); break; - case 'chat_message.deleted': // TruthSocial - removeChatMessage(data.payload); - break; - case 'chat_message.read': // TruthSocial - dispatch((_dispatch: AppDispatch, getState: () => RootState) => { - const chat = JSON.parse(data.payload); - const me = getState().me; - const isFromOtherUser = chat.account.id !== me; - if (isFromOtherUser) { - updateChatQuery(JSON.parse(data.payload)); - } - }); - break; - case 'chat_message.reaction': // TruthSocial - updateChatMessage(JSON.parse(data.payload)); - break; case 'pleroma:follow_relationships_update': dispatch(updateFollowRelationships(JSON.parse(data.payload))); break; diff --git a/src/actions/timelines.ts b/src/actions/timelines.ts index 35f0f5638..6713e78b4 100644 --- a/src/actions/timelines.ts +++ b/src/actions/timelines.ts @@ -243,9 +243,6 @@ const expandGroupTimeline = (id: string, { maxId }: Record = {}, do const expandGroupFeaturedTimeline = (id: string) => expandTimeline(`group:${id}:pinned`, `/api/v1/timelines/group/${id}`, { pinned: true }); -const expandGroupTimelineFromTag = (id: string, tagName: string, { maxId }: Record = {}, done = noOp) => - expandTimeline(`group:tags:${id}:${tagName}`, `/api/v1/timelines/group/${id}/tags/${tagName}`, { max_id: maxId }, done); - const expandGroupMediaTimeline = (id: string | number, { maxId }: Record = {}) => expandTimeline(`group:${id}:media`, `/api/v1/timelines/group/${id}`, { max_id: maxId, only_media: true, limit: 40, with_muted: true }); @@ -345,7 +342,6 @@ export { expandListTimeline, expandGroupTimeline, expandGroupFeaturedTimeline, - expandGroupTimelineFromTag, expandGroupMediaTimeline, expandHashtagTimeline, expandTimelineRequest, diff --git a/src/api/hooks/groups/useGroupLookup.test.ts b/src/api/hooks/groups/useGroupLookup.test.ts deleted file mode 100644 index ce3903ae3..000000000 --- a/src/api/hooks/groups/useGroupLookup.test.ts +++ /dev/null @@ -1,42 +0,0 @@ -import { __stub } from 'soapbox/api'; -import { buildGroup } from 'soapbox/jest/factory'; -import { renderHook, rootState, waitFor } from 'soapbox/jest/test-helpers'; - -import { useGroupLookup } from './useGroupLookup'; - -const group = buildGroup({ id: '1', slug: 'soapbox' }); -const state = rootState.setIn(['instance', 'version'], '3.4.1 (compatible; TruthSocial 1.0.0)'); - -describe('useGroupLookup hook', () => { - describe('with a successful request', () => { - beforeEach(() => { - __stub((mock) => { - mock.onGet(`/api/v1/groups/lookup?name=${group.slug}`).reply(200, group); - }); - }); - - it('is successful', async () => { - const { result } = renderHook(() => useGroupLookup(group.slug), undefined, state); - - await waitFor(() => expect(result.current.isFetching).toBe(false)); - - expect(result.current.entity?.id).toBe(group.id); - }); - }); - - describe('with an unsuccessful query', () => { - beforeEach(() => { - __stub((mock) => { - mock.onGet(`/api/v1/groups/lookup?name=${group.slug}`).networkError(); - }); - }); - - it('is has error state', async() => { - const { result } = renderHook(() => useGroupLookup(group.slug), undefined, state); - - await waitFor(() => expect(result.current.isFetching).toBe(false)); - - expect(result.current.entity).toBeUndefined(); - }); - }); -}); \ No newline at end of file diff --git a/src/api/hooks/groups/useGroupLookup.ts b/src/api/hooks/groups/useGroupLookup.ts deleted file mode 100644 index 8e161af78..000000000 --- a/src/api/hooks/groups/useGroupLookup.ts +++ /dev/null @@ -1,39 +0,0 @@ -import { useEffect } from 'react'; -import { useHistory } from 'react-router-dom'; - -import { Entities } from 'soapbox/entity-store/entities'; -import { useEntityLookup } from 'soapbox/entity-store/hooks'; -import { useApi } from 'soapbox/hooks/useApi'; -import { useFeatures } from 'soapbox/hooks/useFeatures'; -import { groupSchema } from 'soapbox/schemas'; - -import { useGroupRelationship } from './useGroupRelationship'; - -function useGroupLookup(slug: string) { - const api = useApi(); - const features = useFeatures(); - const history = useHistory(); - - const { entity: group, isUnauthorized, ...result } = useEntityLookup( - Entities.GROUPS, - (group) => group.slug.toLowerCase() === slug.toLowerCase(), - () => api.get(`/api/v1/groups/lookup?name=${slug}`), - { schema: groupSchema, enabled: features.groups && !!slug }, - ); - - const { groupRelationship: relationship } = useGroupRelationship(group?.id); - - useEffect(() => { - if (isUnauthorized) { - history.push('/login'); - } - }, [isUnauthorized]); - - return { - ...result, - isUnauthorized, - entity: group ? { ...group, relationship: relationship || null } : undefined, - }; -} - -export { useGroupLookup }; \ No newline at end of file diff --git a/src/api/hooks/groups/useGroupMutes.ts b/src/api/hooks/groups/useGroupMutes.ts deleted file mode 100644 index 67eca0772..000000000 --- a/src/api/hooks/groups/useGroupMutes.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { Entities } from 'soapbox/entity-store/entities'; -import { useEntities } from 'soapbox/entity-store/hooks'; -import { useFeatures } from 'soapbox/hooks'; -import { useApi } from 'soapbox/hooks/useApi'; -import { groupSchema } from 'soapbox/schemas'; - -import type { Group } from 'soapbox/schemas'; - -function useGroupMutes() { - const api = useApi(); - const features = useFeatures(); - - const { entities, ...result } = useEntities( - [Entities.GROUP_MUTES], - () => api.get('/api/v1/groups/mutes'), - { schema: groupSchema, enabled: features.groupsMuting }, - ); - - return { - ...result, - mutes: entities, - }; -} - -export { useGroupMutes }; \ No newline at end of file diff --git a/src/api/hooks/groups/useGroupSearch.ts b/src/api/hooks/groups/useGroupSearch.ts deleted file mode 100644 index 295d63933..000000000 --- a/src/api/hooks/groups/useGroupSearch.ts +++ /dev/null @@ -1,40 +0,0 @@ -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 { useGroupRelationships } from './useGroupRelationships'; - -import type { Group } from 'soapbox/schemas'; - -function useGroupSearch(search: string) { - const api = useApi(); - const features = useFeatures(); - - const { entities, ...result } = useEntities( - [Entities.GROUPS, 'discover', 'search', search], - () => api.get('/api/v1/groups/search', { - params: { - q: search, - }, - }), - { enabled: features.groupsDiscovery && !!search, schema: groupSchema }, - ); - - const { relationships } = useGroupRelationships( - ['discover', 'search', search], - entities.map(entity => entity.id), - ); - - const groups = entities.map((group) => ({ - ...group, - relationship: relationships[group.id] || null, - })); - - return { - ...result, - groups, - }; -} - -export { useGroupSearch }; \ No newline at end of file diff --git a/src/api/hooks/groups/useGroupTag.ts b/src/api/hooks/groups/useGroupTag.ts deleted file mode 100644 index d0e63d74d..000000000 --- a/src/api/hooks/groups/useGroupTag.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { Entities } from 'soapbox/entity-store/entities'; -import { useEntity } from 'soapbox/entity-store/hooks'; -import { useApi } from 'soapbox/hooks'; -import { type GroupTag, groupTagSchema } from 'soapbox/schemas'; - -function useGroupTag(tagId: string) { - const api = useApi(); - - const { entity: tag, ...result } = useEntity( - [Entities.GROUP_TAGS, tagId], - () => api.get(`/api/v1/tags/${tagId }`), - { schema: groupTagSchema }, - ); - - return { - ...result, - tag, - }; -} - -export { useGroupTag }; \ No newline at end of file diff --git a/src/api/hooks/groups/useGroupTags.ts b/src/api/hooks/groups/useGroupTags.ts deleted file mode 100644 index b2c29aa3c..000000000 --- a/src/api/hooks/groups/useGroupTags.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { Entities } from 'soapbox/entity-store/entities'; -import { useEntities } from 'soapbox/entity-store/hooks'; -import { useApi } from 'soapbox/hooks/useApi'; -import { groupTagSchema } from 'soapbox/schemas'; - -import type { GroupTag } from 'soapbox/schemas'; - -function useGroupTags(groupId: string) { - const api = useApi(); - - const { entities, ...result } = useEntities( - [Entities.GROUP_TAGS, groupId], - () => api.get(`/api/v1/truth/trends/groups/${groupId}/tags`), - { schema: groupTagSchema }, - ); - - return { - ...result, - tags: entities, - }; -} - -export { useGroupTags }; \ No newline at end of file diff --git a/src/api/hooks/groups/useGroupValidation.ts b/src/api/hooks/groups/useGroupValidation.ts deleted file mode 100644 index 470be22a5..000000000 --- a/src/api/hooks/groups/useGroupValidation.ts +++ /dev/null @@ -1,49 +0,0 @@ -import { useQuery } from '@tanstack/react-query'; - -import { useApi } from 'soapbox/hooks/useApi'; -import { useFeatures } from 'soapbox/hooks/useFeatures'; - -type Validation = { - error: string; - message: string; -} - -const ValidationKeys = { - validation: (name: string) => ['group', 'validation', name] as const, -}; - -function useGroupValidation(name: string = '') { - const api = useApi(); - const features = useFeatures(); - - const getValidation = async() => { - const { data } = await api.get('/api/v1/groups/validate', { - params: { name }, - }) - .catch((error) => { - if (error.response.status === 422) { - return { data: error.response.data }; - } - - throw error; - }); - - return data; - }; - - const queryInfo = useQuery({ - queryKey: ValidationKeys.validation(name), - queryFn: getValidation, - enabled: features.groupsValidation && !!name, - }); - - return { - ...queryInfo, - data: { - ...queryInfo.data, - isValid: !queryInfo.data?.error, - }, - }; -} - -export { useGroupValidation }; \ No newline at end of file diff --git a/src/api/hooks/groups/useGroups.ts b/src/api/hooks/groups/useGroups.ts index f5450bd73..a5384e136 100644 --- a/src/api/hooks/groups/useGroups.ts +++ b/src/api/hooks/groups/useGroups.ts @@ -6,17 +6,17 @@ import { groupSchema, type Group } from 'soapbox/schemas/group'; import { useGroupRelationships } from './useGroupRelationships'; -function useGroups(q: string = '') { +function useGroups() { const api = useApi(); const features = useFeatures(); const { entities, ...result } = useEntities( - [Entities.GROUPS, 'search', q], - () => api.get('/api/v1/groups', { params: { q } }), + [Entities.GROUPS, 'search', ''], + () => api.get('/api/v1/groups'), { enabled: features.groups, schema: groupSchema }, ); const { relationships } = useGroupRelationships( - ['search', q], + ['search', ''], entities.map(entity => entity.id), ); diff --git a/src/api/hooks/groups/useGroupsFromTag.ts b/src/api/hooks/groups/useGroupsFromTag.ts deleted file mode 100644 index 6c2b88bb1..000000000 --- a/src/api/hooks/groups/useGroupsFromTag.ts +++ /dev/null @@ -1,38 +0,0 @@ -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 { useGroupRelationships } from './useGroupRelationships'; - -import type { Group } from 'soapbox/schemas'; - -function useGroupsFromTag(tagId: string) { - const api = useApi(); - const features = useFeatures(); - - const { entities, ...result } = useEntities( - [Entities.GROUPS, 'tags', tagId], - () => api.get(`/api/v1/tags/${tagId}/groups`), - { - schema: groupSchema, - enabled: features.groupsDiscovery, - }, - ); - const { relationships } = useGroupRelationships( - ['tags', tagId], - entities.map(entity => entity.id), - ); - - const groups = entities.map((group) => ({ - ...group, - relationship: relationships[group.id] || null, - })); - - return { - ...result, - groups, - }; -} - -export { useGroupsFromTag }; \ No newline at end of file diff --git a/src/api/hooks/groups/useMuteGroup.ts b/src/api/hooks/groups/useMuteGroup.ts deleted file mode 100644 index e31c7f4d1..000000000 --- a/src/api/hooks/groups/useMuteGroup.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { Entities } from 'soapbox/entity-store/entities'; -import { useEntityActions } from 'soapbox/entity-store/hooks'; -import { type Group, groupRelationshipSchema } from 'soapbox/schemas'; - -function useMuteGroup(group?: Group) { - const { createEntity, isSubmitting } = useEntityActions( - [Entities.GROUP_RELATIONSHIPS, group?.id as string], - { post: `/api/v1/groups/${group?.id}/mute` }, - { schema: groupRelationshipSchema }, - ); - - return { - mutate: createEntity, - isSubmitting, - }; -} - -export { useMuteGroup }; \ No newline at end of file diff --git a/src/api/hooks/groups/usePendingGroups.test.ts b/src/api/hooks/groups/usePendingGroups.test.ts deleted file mode 100644 index 33190ae0b..000000000 --- a/src/api/hooks/groups/usePendingGroups.test.ts +++ /dev/null @@ -1,64 +0,0 @@ -import { __stub } from 'soapbox/api'; -import { Entities } from 'soapbox/entity-store/entities'; -import { buildAccount, buildGroup } from 'soapbox/jest/factory'; -import { renderHook, waitFor } from 'soapbox/jest/test-helpers'; -import { instanceSchema } from 'soapbox/schemas'; - -import { usePendingGroups } from './usePendingGroups'; - -const id = '1'; -const group = buildGroup({ id, display_name: 'soapbox' }); -const store = { - instance: instanceSchema.parse({ - version: '3.4.1 (compatible; TruthSocial 1.0.0+unreleased)', - }), - me: '1', - entities: { - [Entities.ACCOUNTS]: { - store: { - [id]: buildAccount({ - id, - acct: 'tiger', - display_name: 'Tiger', - avatar: 'test.jpg', - verified: true, - }), - }, - lists: {}, - }, - }, -}; - -describe('usePendingGroups hook', () => { - describe('with a successful request', () => { - beforeEach(() => { - __stub((mock) => { - mock.onGet('/api/v1/groups').reply(200, [group]); - }); - }); - - it('is successful', async () => { - const { result } = renderHook(usePendingGroups, undefined, store); - - await waitFor(() => expect(result.current.isFetching).toBe(false)); - - expect(result.current.groups).toHaveLength(1); - }); - }); - - describe('with an unsuccessful query', () => { - beforeEach(() => { - __stub((mock) => { - mock.onGet('/api/v1/groups').networkError(); - }); - }); - - it('is has error state', async() => { - const { result } = renderHook(usePendingGroups, undefined, store); - - await waitFor(() => expect(result.current.isFetching).toBe(false)); - - expect(result.current.groups).toHaveLength(0); - }); - }); -}); \ No newline at end of file diff --git a/src/api/hooks/groups/usePendingGroups.ts b/src/api/hooks/groups/usePendingGroups.ts deleted file mode 100644 index f4ea16a43..000000000 --- a/src/api/hooks/groups/usePendingGroups.ts +++ /dev/null @@ -1,30 +0,0 @@ -import { Entities } from 'soapbox/entity-store/entities'; -import { useEntities } from 'soapbox/entity-store/hooks'; -import { useApi, useFeatures, useOwnAccount } from 'soapbox/hooks'; -import { Group, groupSchema } from 'soapbox/schemas'; - -function usePendingGroups() { - const api = useApi(); - const { account } = useOwnAccount(); - const features = useFeatures(); - - const { entities, ...result } = useEntities( - [Entities.GROUPS, account?.id!, 'pending'], - () => api.get('/api/v1/groups', { - params: { - pending: true, - }, - }), - { - schema: groupSchema, - enabled: !!account && features.groupsPending, - }, - ); - - return { - ...result, - groups: entities, - }; -} - -export { usePendingGroups }; \ No newline at end of file diff --git a/src/api/hooks/groups/usePopularGroups.ts b/src/api/hooks/groups/usePopularGroups.ts deleted file mode 100644 index 69b04e32f..000000000 --- a/src/api/hooks/groups/usePopularGroups.ts +++ /dev/null @@ -1,36 +0,0 @@ -import { Entities } from 'soapbox/entity-store/entities'; -import { useEntities } from 'soapbox/entity-store/hooks'; -import { Group, groupSchema } from 'soapbox/schemas'; - -import { useApi } from '../../../hooks/useApi'; -import { useFeatures } from '../../../hooks/useFeatures'; - -import { useGroupRelationships } from './useGroupRelationships'; - -function usePopularGroups() { - const api = useApi(); - const features = useFeatures(); - - const { entities, ...result } = useEntities( - [Entities.GROUPS, 'popular'], - () => api.get('/api/v1/truth/trends/groups'), - { - schema: groupSchema, - enabled: features.groupsDiscovery, - }, - ); - - const { relationships } = useGroupRelationships(['popular'], entities.map(entity => entity.id)); - - const groups = entities.map((group) => ({ - ...group, - relationship: relationships[group.id] || null, - })); - - return { - ...result, - groups, - }; -} - -export { usePopularGroups }; \ No newline at end of file diff --git a/src/api/hooks/groups/usePopularTags.ts b/src/api/hooks/groups/usePopularTags.ts deleted file mode 100644 index e0ec2c550..000000000 --- a/src/api/hooks/groups/usePopularTags.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { Entities } from 'soapbox/entity-store/entities'; -import { useEntities } from 'soapbox/entity-store/hooks'; -import { useApi, useFeatures } from 'soapbox/hooks'; -import { type GroupTag, groupTagSchema } from 'soapbox/schemas'; - -function usePopularTags() { - const api = useApi(); - const features = useFeatures(); - - const { entities, ...result } = useEntities( - [Entities.GROUP_TAGS], - () => api.get('/api/v1/groups/tags'), - { - schema: groupTagSchema, - enabled: features.groupsDiscovery, - }, - ); - - return { - ...result, - tags: entities, - }; -} - -export { usePopularTags }; \ No newline at end of file diff --git a/src/api/hooks/groups/useSuggestedGroups.ts b/src/api/hooks/groups/useSuggestedGroups.ts deleted file mode 100644 index 69fb71065..000000000 --- a/src/api/hooks/groups/useSuggestedGroups.ts +++ /dev/null @@ -1,34 +0,0 @@ -import { Entities } from 'soapbox/entity-store/entities'; -import { useEntities } from 'soapbox/entity-store/hooks'; -import { useApi, useFeatures } from 'soapbox/hooks'; -import { type Group, groupSchema } from 'soapbox/schemas'; - -import { useGroupRelationships } from './useGroupRelationships'; - -function useSuggestedGroups() { - const api = useApi(); - const features = useFeatures(); - - const { entities, ...result } = useEntities( - [Entities.GROUPS, 'suggested'], - () => api.get('/api/v1/truth/suggestions/groups'), - { - schema: groupSchema, - enabled: features.groupsDiscovery, - }, - ); - - const { relationships } = useGroupRelationships(['suggested'], entities.map(entity => entity.id)); - - const groups = entities.map((group) => ({ - ...group, - relationship: relationships[group.id] || null, - })); - - return { - ...result, - groups, - }; -} - -export { useSuggestedGroups }; \ No newline at end of file diff --git a/src/api/hooks/groups/useUnmuteGroup.ts b/src/api/hooks/groups/useUnmuteGroup.ts deleted file mode 100644 index 6c8768d25..000000000 --- a/src/api/hooks/groups/useUnmuteGroup.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { Entities } from 'soapbox/entity-store/entities'; -import { useEntityActions } from 'soapbox/entity-store/hooks'; -import { type Group, groupRelationshipSchema } from 'soapbox/schemas'; - -function useUnmuteGroup(group?: Group) { - const { createEntity, isSubmitting } = useEntityActions( - [Entities.GROUP_RELATIONSHIPS, group?.id as string], - { post: `/api/v1/groups/${group?.id}/unmute` }, - { schema: groupRelationshipSchema }, - ); - - return { - mutate: createEntity, - isSubmitting, - }; -} - -export { useUnmuteGroup }; \ No newline at end of file diff --git a/src/api/hooks/groups/useUpdateGroup.ts b/src/api/hooks/groups/useUpdateGroup.ts index 129849514..02f61d224 100644 --- a/src/api/hooks/groups/useUpdateGroup.ts +++ b/src/api/hooks/groups/useUpdateGroup.ts @@ -10,7 +10,6 @@ interface UpdateGroupParams { header?: File | ''; group_visibility?: string; discoverable?: boolean; - tags?: string[]; } function useUpdateGroup(groupId: string) { diff --git a/src/api/hooks/groups/useUpdateGroupTag.ts b/src/api/hooks/groups/useUpdateGroupTag.ts deleted file mode 100644 index 1c68c714d..000000000 --- a/src/api/hooks/groups/useUpdateGroupTag.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { Entities } from 'soapbox/entity-store/entities'; -import { useEntityActions } from 'soapbox/entity-store/hooks'; - -import type { GroupTag } from 'soapbox/schemas'; - -function useUpdateGroupTag(groupId: string, tagId: string) { - const { updateEntity, ...rest } = useEntityActions( - [Entities.GROUP_TAGS, groupId, tagId], - { patch: `/api/v1/groups/${groupId}/tags/${tagId}` }, - ); - - return { - updateGroupTag: updateEntity, - ...rest, - }; -} - -export { useUpdateGroupTag }; \ No newline at end of file diff --git a/src/api/hooks/index.ts b/src/api/hooks/index.ts index f085f3c4b..bccb87cb2 100644 --- a/src/api/hooks/index.ts +++ b/src/api/hooks/index.ts @@ -19,30 +19,16 @@ export { useCreateGroup, type CreateGroupParams } from './groups/useCreateGroup' export { useDeleteGroup } from './groups/useDeleteGroup'; export { useDemoteGroupMember } from './groups/useDemoteGroupMember'; export { useGroup } from './groups/useGroup'; -export { useGroupLookup } from './groups/useGroupLookup'; export { useGroupMedia } from './groups/useGroupMedia'; export { useGroupMembers } from './groups/useGroupMembers'; export { useGroupMembershipRequests } from './groups/useGroupMembershipRequests'; -export { useGroupMutes } from './groups/useGroupMutes'; export { useGroupRelationship } from './groups/useGroupRelationship'; export { useGroupRelationships } from './groups/useGroupRelationships'; -export { useGroupSearch } from './groups/useGroupSearch'; -export { useGroupTag } from './groups/useGroupTag'; -export { useGroupTags } from './groups/useGroupTags'; -export { useGroupValidation } from './groups/useGroupValidation'; export { useGroups } from './groups/useGroups'; -export { useGroupsFromTag } from './groups/useGroupsFromTag'; export { useJoinGroup } from './groups/useJoinGroup'; -export { useMuteGroup } from './groups/useMuteGroup'; export { useLeaveGroup } from './groups/useLeaveGroup'; -export { usePendingGroups } from './groups/usePendingGroups'; -export { usePopularGroups } from './groups/usePopularGroups'; -export { usePopularTags } from './groups/usePopularTags'; export { usePromoteGroupMember } from './groups/usePromoteGroupMember'; -export { useSuggestedGroups } from './groups/useSuggestedGroups'; -export { useUnmuteGroup } from './groups/useUnmuteGroup'; export { useUpdateGroup } from './groups/useUpdateGroup'; -export { useUpdateGroupTag } from './groups/useUpdateGroupTag'; // Statuses export { useBookmarkFolders } from './statuses/useBookmarkFolders'; diff --git a/src/components/groups/popover/group-popover.tsx b/src/components/groups/popover/group-popover.tsx index 44d1d5244..4e079f430 100644 --- a/src/components/groups/popover/group-popover.tsx +++ b/src/components/groups/popover/group-popover.tsx @@ -30,7 +30,7 @@ const GroupPopover = (props: IGroupPopoverContainer) => { const path = history.location.pathname; const shouldHideAction = matchPath(path, { - path: ['/group/:groupSlug'], + path: ['/group/:groupId'], exact: true, }); @@ -88,7 +88,7 @@ const GroupPopover = (props: IGroupPopoverContainer) => { {!shouldHideAction && (
- + diff --git a/src/components/sidebar-menu.tsx b/src/components/sidebar-menu.tsx index f8d091b20..17ca4fc81 100644 --- a/src/components/sidebar-menu.tsx +++ b/src/components/sidebar-menu.tsx @@ -11,7 +11,7 @@ import { useAccount } from 'soapbox/api/hooks'; import Account from 'soapbox/components/account'; import { Stack, Divider, HStack, Icon, IconButton, Text } from 'soapbox/components/ui'; import ProfileStats from 'soapbox/features/ui/components/profile-stats'; -import { useAppDispatch, useAppSelector, useGroupsPath, useFeatures } from 'soapbox/hooks'; +import { useAppDispatch, useAppSelector, useFeatures } from 'soapbox/hooks'; import { makeGetOtherAccounts } from 'soapbox/selectors'; import type { List as ImmutableList } from 'immutable'; @@ -88,7 +88,6 @@ const SidebarMenu: React.FC = (): JSX.Element | null => { const sidebarOpen = useAppSelector((state) => state.sidebar.sidebarOpen); const settings = useAppSelector((state) => getSettings(state)); const followRequestsCount = useAppSelector((state) => state.user_lists.follow_requests.items.count()); - const groupsPath = useGroupsPath(); const closeButtonRef = React.useRef(null); @@ -209,7 +208,7 @@ const SidebarMenu: React.FC = (): JSX.Element | null => { {features.groups && ( { const features = useFeatures(); const { isDeveloper } = useSettings(); const { account } = useOwnAccount(); - const groupsPath = useGroupsPath(); const notificationCount = useAppSelector((state) => state.notifications.unread); const followRequestsCount = useAppSelector((state) => state.user_lists.follow_requests.items.count()); @@ -142,7 +141,7 @@ const SidebarNavigation = () => { {features.groups && ( } diff --git a/src/components/status-action-bar.tsx b/src/components/status-action-bar.tsx index 61242fee0..f4cad0d0b 100644 --- a/src/components/status-action-bar.tsx +++ b/src/components/status-action-bar.tsx @@ -13,7 +13,7 @@ import { initMuteModal } from 'soapbox/actions/mutes'; import { initReport, ReportableEntities } from 'soapbox/actions/reports'; import { deleteStatus, editStatus, toggleMuteStatus } from 'soapbox/actions/statuses'; import { deleteFromTimelines } from 'soapbox/actions/timelines'; -import { useBlockGroupMember, useGroup, useGroupRelationship, useMuteGroup, useUnmuteGroup } from 'soapbox/api/hooks'; +import { useBlockGroupMember, useGroup, useGroupRelationship } from 'soapbox/api/hooks'; import { useDeleteGroupStatus } from 'soapbox/api/hooks/groups/useDeleteGroupStatus'; import DropdownMenu from 'soapbox/components/dropdown-menu'; import StatusActionButton from 'soapbox/components/status-action-button'; @@ -69,12 +69,7 @@ const messages = defineMessages({ mention: { id: 'status.mention', defaultMessage: 'Mention @{name}' }, more: { id: 'status.more', defaultMessage: 'More' }, mute: { id: 'account.mute', defaultMessage: 'Mute @{name}' }, - muteConfirm: { id: 'confirmations.mute_group.confirm', defaultMessage: 'Mute' }, muteConversation: { id: 'status.mute_conversation', defaultMessage: 'Mute Conversation' }, - muteGroup: { id: 'group.mute.long_label', defaultMessage: 'Mute Group' }, - muteHeading: { id: 'confirmations.mute_group.heading', defaultMessage: 'Mute Group' }, - muteMessage: { id: 'confirmations.mute_group.message', defaultMessage: 'You are about to mute the group. Do you want to continue?' }, - muteSuccess: { id: 'group.mute.success', defaultMessage: 'Muted the group' }, open: { id: 'status.open', defaultMessage: 'Show Post Details' }, pin: { id: 'status.pin', defaultMessage: 'Pin on profile' }, pinToGroup: { id: 'status.pin_to_group', defaultMessage: 'Pin to Group' }, @@ -101,8 +96,6 @@ const messages = defineMessages({ share: { id: 'status.share', defaultMessage: 'Share' }, unbookmark: { id: 'status.unbookmark', defaultMessage: 'Remove bookmark' }, unmuteConversation: { id: 'status.unmute_conversation', defaultMessage: 'Unmute Conversation' }, - unmuteGroup: { id: 'group.unmute.long_label', defaultMessage: 'Unmute Group' }, - unmuteSuccess: { id: 'group.unmute.success', defaultMessage: 'Unmuted the group' }, unpin: { id: 'status.unpin', defaultMessage: 'Unpin from profile' }, unpinFromGroup: { id: 'status.unpin_to_group', defaultMessage: 'Unpin from Group' }, zap: { id: 'status.zap', defaultMessage: 'Zap' }, @@ -128,12 +121,9 @@ const StatusActionBar: React.FC = ({ const intl = useIntl(); const history = useHistory(); const dispatch = useAppDispatch(); - const match = useRouteMatch<{ groupSlug: string }>('/group/:groupSlug'); + const match = useRouteMatch<{ groupId: string }>('/group/:groupId'); const { group } = useGroup((status.group as Group)?.id as string); - const muteGroup = useMuteGroup(group as Group); - const unmuteGroup = useUnmuteGroup(group as Group); - const isMutingGroup = !!group?.relationship?.muting; const deleteGroupStatus = useDeleteGroupStatus(group as Group, status.id); const blockGroupMember = useBlockGroupMember(group as Group, status.account); @@ -294,27 +284,6 @@ const StatusActionBar: React.FC = ({ dispatch(initMuteModal(status.account)); }; - const handleMuteGroupClick: React.EventHandler = () => - dispatch(openModal('CONFIRM', { - heading: intl.formatMessage(messages.muteHeading), - message: intl.formatMessage(messages.muteMessage), - confirm: intl.formatMessage(messages.muteConfirm), - confirmationTheme: 'primary', - onConfirm: () => muteGroup.mutate(undefined, { - onSuccess() { - toast.success(intl.formatMessage(messages.muteSuccess)); - }, - }), - })); - - const handleUnmuteGroupClick: React.EventHandler = () => { - unmuteGroup.mutate(undefined, { - onSuccess() { - toast.success(intl.formatMessage(messages.unmuteSuccess)); - }, - }); - }; - const handleBlockClick: React.EventHandler = (e) => { const account = status.account; @@ -542,14 +511,6 @@ const StatusActionBar: React.FC = ({ } menu.push(null); - if (features.groupsMuting && status.group) { - menu.push({ - text: isMutingGroup ? intl.formatMessage(messages.unmuteGroup) : intl.formatMessage(messages.muteGroup), - icon: require('@tabler/icons/outline/volume-3.svg'), - action: isMutingGroup ? handleUnmuteGroupClick : handleMuteGroupClick, - }); - menu.push(null); - } menu.push({ text: intl.formatMessage(messages.mute, { name: username }), diff --git a/src/components/status-media.tsx b/src/components/status-media.tsx index 2c96c0078..77be4b031 100644 --- a/src/components/status-media.tsx +++ b/src/components/status-media.tsx @@ -3,7 +3,6 @@ import React, { Suspense } from 'react'; import { openModal } from 'soapbox/actions/modals'; import AttachmentThumbs from 'soapbox/components/attachment-thumbs'; import PreviewCard from 'soapbox/components/preview-card'; -import { GroupLinkPreview } from 'soapbox/features/groups/components/group-link-preview'; import PlaceholderCard from 'soapbox/features/placeholder/components/placeholder-card'; import { MediaGallery, Video, Audio } from 'soapbox/features/ui/util/async-components'; import { useAppDispatch } from 'soapbox/hooks'; @@ -112,10 +111,6 @@ const StatusMedia: React.FC = ({ ); } - } else if (status.spoiler_text.length === 0 && !status.quote && status.card?.group) { - media = ( - - ); } else if (status.spoiler_text.length === 0 && !status.quote && status.card) { media = ( = (props) => { ), group: ( - + = (props) => { defaultMessage='Posted in {group}' values={{ group: ( - + diff --git a/src/components/thumb-navigation.tsx b/src/components/thumb-navigation.tsx index 1aee06594..275bb5a6a 100644 --- a/src/components/thumb-navigation.tsx +++ b/src/components/thumb-navigation.tsx @@ -3,12 +3,11 @@ import { FormattedMessage } from 'react-intl'; import ThumbNavigationLink from 'soapbox/components/thumb-navigation-link'; import { useStatContext } from 'soapbox/contexts/stat-context'; -import { useAppSelector, useFeatures, useGroupsPath, useOwnAccount } from 'soapbox/hooks'; +import { useAppSelector, useFeatures, useOwnAccount } from 'soapbox/hooks'; const ThumbNavigation: React.FC = (): JSX.Element => { const { account } = useOwnAccount(); const features = useFeatures(); - const groupsPath = useGroupsPath(); const { unreadChatsCount } = useStatContext(); @@ -60,7 +59,7 @@ const ThumbNavigation: React.FC = (): JSX.Element => { src={require('@tabler/icons/outline/circles.svg')} activeSrc={require('@tabler/icons/filled/circles.svg')} text={} - to={groupsPath} + to='/groups' exact /> )} diff --git a/src/contexts/chat-context.tsx b/src/contexts/chat-context.tsx index 14354549e..f1afe4b8b 100644 --- a/src/contexts/chat-context.tsx +++ b/src/contexts/chat-context.tsx @@ -2,12 +2,11 @@ import React, { createContext, useContext, useEffect, useMemo, useState } from ' import { useHistory, useParams } from 'react-router-dom'; import { toggleMainWindow } from 'soapbox/actions/chats'; -import { useAppDispatch, useOwnAccount, useSettings } from 'soapbox/hooks'; +import { useAppDispatch, useSettings } from 'soapbox/hooks'; import { IChat, useChat } from 'soapbox/queries/chats'; const ChatContext = createContext({ isOpen: false, - needsAcceptance: false, }); enum ChatWidgetScreens { @@ -25,7 +24,6 @@ const ChatProvider: React.FC = ({ children }) => { const history = useHistory(); const dispatch = useAppDispatch(); const { chats } = useSettings(); - const { account } = useOwnAccount(); const path = history.location.pathname; const isUsingMainChatPage = Boolean(path.match(/^\/chats/)); @@ -36,7 +34,6 @@ const ChatProvider: React.FC = ({ children }) => { const { data: chat } = useChat(currentChatId as string); - const needsAcceptance = !chat?.accepted && chat?.created_by_account !== account?.id; const isOpen = chats.mainWindow === 'open'; const changeScreen = (screen: ChatWidgetScreens, currentChatId?: string | null) => { @@ -48,14 +45,13 @@ const ChatProvider: React.FC = ({ children }) => { const value = useMemo(() => ({ chat, - needsAcceptance, isOpen, isUsingMainChatPage, toggleChatPane, screen, changeScreen, currentChatId, - }), [chat, currentChatId, needsAcceptance, isUsingMainChatPage, isOpen, screen, changeScreen]); + }), [chat, currentChatId, isUsingMainChatPage, isOpen, screen, changeScreen]); useEffect(() => { if (chatId) { @@ -76,7 +72,6 @@ interface IChatContext { chat: IChat | null; isOpen: boolean; isUsingMainChatPage?: boolean; - needsAcceptance: boolean; toggleChatPane(): void; screen: ChatWidgetScreens; currentChatId: string | null; diff --git a/src/entity-store/entities.ts b/src/entity-store/entities.ts index 13cdf4eaa..2b6f00c8f 100644 --- a/src/entity-store/entities.ts +++ b/src/entity-store/entities.ts @@ -8,7 +8,6 @@ enum Entities { GROUP_MEMBERSHIPS = 'GroupMemberships', GROUP_MUTES = 'GroupMutes', GROUP_RELATIONSHIPS = 'GroupRelationships', - GROUP_TAGS = 'GroupTags', PATRON_USERS = 'PatronUsers', RELATIONSHIPS = 'Relationships', RELAYS = 'Relays', @@ -23,7 +22,6 @@ interface EntityTypes { [Entities.GROUPS]: Schemas.Group; [Entities.GROUP_MEMBERSHIPS]: Schemas.GroupMember; [Entities.GROUP_RELATIONSHIPS]: Schemas.GroupRelationship; - [Entities.GROUP_TAGS]: Schemas.GroupTag; [Entities.PATRON_USERS]: Schemas.PatronUser; [Entities.RELATIONSHIPS]: Schemas.Relationship; [Entities.RELAYS]: Schemas.Relay; diff --git a/src/features/account/components/header.tsx b/src/features/account/components/header.tsx index 392ecd834..4489c133c 100644 --- a/src/features/account/components/header.tsx +++ b/src/features/account/components/header.tsx @@ -24,7 +24,7 @@ import ActionButton from 'soapbox/features/ui/components/action-button'; import SubscriptionButton from 'soapbox/features/ui/components/subscription-button'; import { useAppDispatch, useAppSelector, useFeatures, useOwnAccount } from 'soapbox/hooks'; import { normalizeAttachment } from 'soapbox/normalizers'; -import { ChatKeys, useChats } from 'soapbox/queries/chats'; +import { useChats } from 'soapbox/queries/chats'; import { queryClient } from 'soapbox/queries/client'; import { Account } from 'soapbox/schemas'; import toast from 'soapbox/toast'; @@ -101,7 +101,7 @@ const Header: React.FC = ({ account }) => { onSuccess: (response) => { history.push(`/chats/${response.data.id}`); queryClient.invalidateQueries({ - queryKey: ChatKeys.chatSearch(), + queryKey: ['chats', 'search'], }); }, }); @@ -555,24 +555,7 @@ const Header: React.FC = ({ account }) => { return null; } - if (features.chatsWithFollowers) { // Truth Social - const canChat = account.relationship?.followed_by; - if (!canChat) { - return null; - } - - return ( - createAndNavigateToChat.mutate(account.id)} - title={intl.formatMessage(messages.chat, { name: account.username })} - theme='outlined' - className='px-2' - iconClassName='h-4 w-4' - disabled={createAndNavigateToChat.isPending} - /> - ); - } else if (account.pleroma?.accepts_chat_messages) { + if (account.pleroma?.accepts_chat_messages) { return ( ( - - - Homepage - -); - -describe('', () => { - it('handles successful responses from the API', async() => { - __stub(mock => { - mock.onPost('/api/v1/truth/password_reset/confirm') - .reply(200, {}); - }); - - render( - , - {}, - null, - { initialEntries: ['/edit'] }, - ); - - fireEvent.submit( - screen.getByTestId('form'), { - preventDefault: () => {}, - }, - ); - - await waitFor(() => { - expect(screen.getByTestId('home')).toHaveTextContent('Homepage'); - expect(screen.queryByTestId('form-group-error')).not.toBeInTheDocument(); - }); - }); - - it('handles failed responses from the API', async() => { - __stub(mock => { - mock.onPost('/api/v1/truth/password_reset/confirm') - .reply(403, {}); - }); - - render( - , - {}, - null, - { initialEntries: ['/edit'] }, - ); - - await fireEvent.submit( - screen.getByTestId('form'), { - preventDefault: () => {}, - }, - ); - - await waitFor(() => { - expect(screen.queryByTestId('home')).not.toBeInTheDocument(); - expect(screen.queryByTestId('form-group-error')).toBeInTheDocument(); - }); - }); -}); diff --git a/src/features/auth-login/components/password-reset-confirm.tsx b/src/features/auth-login/components/password-reset-confirm.tsx deleted file mode 100644 index c079b418d..000000000 --- a/src/features/auth-login/components/password-reset-confirm.tsx +++ /dev/null @@ -1,81 +0,0 @@ -import React from 'react'; -import { defineMessages, FormattedMessage, useIntl } from 'react-intl'; -import { Redirect } from 'react-router-dom'; - -import { resetPasswordConfirm } from 'soapbox/actions/security'; -import { BigCard } from 'soapbox/components/big-card'; -import { Button, Form, FormActions, FormGroup, Input } from 'soapbox/components/ui'; -import { useAppDispatch } from 'soapbox/hooks'; - -const token = new URLSearchParams(window.location.search).get('reset_password_token'); - -const messages = defineMessages({ - resetPasswordFail: { id: 'reset_password.fail', defaultMessage: 'Expired token, please try again.' }, - passwordPlaceholder: { id: 'reset_password.password.placeholder', defaultMessage: 'Placeholder' }, -}); - -const Statuses = { - IDLE: 'IDLE', - LOADING: 'LOADING', - SUCCESS: 'SUCCESS', - FAIL: 'FAIL', -}; - -const PasswordResetConfirm = () => { - const intl = useIntl(); - const dispatch = useAppDispatch(); - - const [password, setPassword] = React.useState(''); - const [status, setStatus] = React.useState(Statuses.IDLE); - - const isLoading = status === Statuses.LOADING; - - const handleSubmit: React.FormEventHandler = React.useCallback((event) => { - event.preventDefault(); - - setStatus(Statuses.LOADING); - dispatch(resetPasswordConfirm(password, token as string)) - .then(() => setStatus(Statuses.SUCCESS)) - .catch(() => setStatus(Statuses.FAIL)); - }, [password]); - - const onChange: React.ChangeEventHandler = React.useCallback((event) => { - setPassword(event.target.value); - }, []); - - const renderErrors = () => { - if (status === Statuses.FAIL) { - return [intl.formatMessage(messages.resetPasswordFail)]; - } - - return []; - }; - - if (status === Statuses.SUCCESS) { - return ; - } - - return ( - }> -
- } errors={renderErrors()}> - - - - - - -
-
- ); -}; - -export default PasswordResetConfirm; diff --git a/src/features/chats/components/chat-list-item.test.tsx b/src/features/chats/components/chat-list-item.test.tsx index cd6ad0421..294456dc3 100644 --- a/src/features/chats/components/chat-list-item.test.tsx +++ b/src/features/chats/components/chat-list-item.test.tsx @@ -8,20 +8,17 @@ import ChatListItem from './chat-list-item'; const chat: any = { id: '1', unread: 5, - created_by_account: '2', last_message: { account_id: '2', chat_id: '1', content: 'hello world', created_at: '2022-09-09T16:02:26.186Z', - discarded_at: null, id: '12332423234', unread: true, }, created_at: '2022-09-09T16:02:26.186Z', updated_at: '2022-09-09T16:02:26.186Z', accepted: true, - discarded_at: null, account: { acct: 'username', display_name: 'johnnie', diff --git a/src/features/chats/components/chat-list.tsx b/src/features/chats/components/chat-list.tsx index 0c353e940..f896f027b 100644 --- a/src/features/chats/components/chat-list.tsx +++ b/src/features/chats/components/chat-list.tsx @@ -14,15 +14,14 @@ import ChatListItem from './chat-list-item'; interface IChatList { onClickChat: (chat: any) => void; useWindowScroll?: boolean; - searchValue?: string; } -const ChatList: React.FC = ({ onClickChat, useWindowScroll = false, searchValue }) => { +const ChatList: React.FC = ({ onClickChat, useWindowScroll = false }) => { const dispatch = useAppDispatch(); const chatListRef = useRef(null); - const { chatsQuery: { data: chats, isFetching, hasNextPage, fetchNextPage } } = useChats(searchValue); + const { chatsQuery: { data: chats, isFetching, hasNextPage, fetchNextPage } } = useChats(); const [isNearBottom, setNearBottom] = useState(false); const [isNearTop, setNearTop] = useState(true); diff --git a/src/features/chats/components/chat-message-list-intro.tsx b/src/features/chats/components/chat-message-list-intro.tsx deleted file mode 100644 index d41c55240..000000000 --- a/src/features/chats/components/chat-message-list-intro.tsx +++ /dev/null @@ -1,122 +0,0 @@ -import clsx from 'clsx'; -import React from 'react'; -import { defineMessages, useIntl } from 'react-intl'; -import { useHistory } from 'react-router-dom'; - -import { openModal } from 'soapbox/actions/modals'; -import Link from 'soapbox/components/link'; -import { Avatar, Button, HStack, Icon, Stack, Text } from 'soapbox/components/ui'; -import { useChatContext } from 'soapbox/contexts/chat-context'; -import { useAppDispatch, useFeatures } from 'soapbox/hooks'; -import { useChatActions } from 'soapbox/queries/chats'; -import { secondsToDays } from 'soapbox/utils/numbers'; - -const messages = defineMessages({ - leaveChatHeading: { id: 'chat_message_list_intro.leave_chat.heading', defaultMessage: 'Leave Chat' }, - leaveChatMessage: { id: 'chat_message_list_intro.leave_chat.message', defaultMessage: 'Are you sure you want to leave this chat? Messages will be deleted for you and this chat will be removed from your inbox.' }, - leaveChatConfirm: { id: 'chat_message_list_intro.leave_chat.confirm', defaultMessage: 'Leave Chat' }, - intro: { id: 'chat_message_list_intro.intro', defaultMessage: 'wants to start a chat with you' }, - accept: { id: 'chat_message_list_intro.actions.accept', defaultMessage: 'Accept' }, - leaveChat: { id: 'chat_message_list_intro.actions.leave_chat', defaultMessage: 'Leave chat' }, - report: { id: 'chat_message_list_intro.actions.report', defaultMessage: 'Report' }, - messageLifespan: { id: 'chat_message_list_intro.actions.message_lifespan', defaultMessage: 'Messages older than {day, plural, one {# day} other {# days}} are deleted.' }, -}); - -const ChatMessageListIntro = () => { - const dispatch = useAppDispatch(); - const intl = useIntl(); - const features = useFeatures(); - const history = useHistory(); - - const { chat, isUsingMainChatPage, needsAcceptance } = useChatContext(); - const { acceptChat, deleteChat } = useChatActions(chat?.id as string); - - const handleLeaveChat = () => { - dispatch(openModal('CONFIRM', { - heading: intl.formatMessage(messages.leaveChatHeading), - message: intl.formatMessage(messages.leaveChatMessage), - confirm: intl.formatMessage(messages.leaveChatConfirm), - confirmationTheme: 'primary', - onConfirm: () => { - deleteChat.mutate(undefined, { - onSuccess() { - if (isUsingMainChatPage) { - history.push('/chats'); - } - }, - }); - }, - })); - }; - - if (!chat || !features.chatAcceptance) { - return null; - } - - return ( - - - - - - - - {needsAcceptance ? ( - <> - @{chat.account.acct} - {' '} - {intl.formatMessage(messages.intro)} - - ) : ( - - @{chat.account.acct} - - )} - - - - {needsAcceptance ? ( - - - - - - ) : ( - - - {chat.message_expiration && ( - - {intl.formatMessage(messages.messageLifespan, { day: secondsToDays(chat.message_expiration) })} - - )} - - )} - - ); -}; - -export default ChatMessageListIntro; diff --git a/src/features/chats/components/chat-message-list.test.tsx b/src/features/chats/components/chat-message-list.test.tsx index 23c522e81..c62805799 100644 --- a/src/features/chats/components/chat-message-list.test.tsx +++ b/src/features/chats/components/chat-message-list.test.tsx @@ -13,7 +13,6 @@ import { ChatMessage } from 'soapbox/types/entities'; import ChatMessageList from './chat-message-list'; const chat: IChat = { - accepted: true, account: buildAccount({ username: 'username', verified: true, @@ -23,15 +22,9 @@ const chat: IChat = { avatar_static: 'avatar', display_name: 'my name', }), - chat_type: 'direct', created_at: '2020-06-10T02:05:06.000Z', - created_by_account: '2', - discarded_at: null, id: '14', last_message: null, - latest_read_message_by_account: [], - latest_read_message_created_at: null, - message_expiration: 1209600, unread: 5, }; @@ -41,8 +34,6 @@ const chatMessages: ChatMessage[] = [ chat_id: '14', content: 'this is the first chat', created_at: '2022-09-09T16:02:26.186Z', - emoji_reactions: null, - expiration: 1209600, id: '1', unread: false, pending: false, @@ -52,8 +43,6 @@ const chatMessages: ChatMessage[] = [ chat_id: '14', content: 'this is the second chat', created_at: '2022-09-09T16:04:26.186Z', - emoji_reactions: null, - expiration: 1209600, id: '2', unread: true, pending: false, diff --git a/src/features/chats/components/chat-message-list.tsx b/src/features/chats/components/chat-message-list.tsx index 0d8272cd0..84c9de270 100644 --- a/src/features/chats/components/chat-message-list.tsx +++ b/src/features/chats/components/chat-message-list.tsx @@ -4,11 +4,10 @@ import { Components, Virtuoso, VirtuosoHandle } from 'react-virtuoso'; import { Avatar, Button, Divider, Spinner, Stack, Text } from 'soapbox/components/ui'; import PlaceholderChatMessage from 'soapbox/features/placeholder/components/placeholder-chat-message'; -import { useAppSelector, useOwnAccount } from 'soapbox/hooks'; +import { useAppSelector } from 'soapbox/hooks'; import { IChat, useChatActions, useChatMessages } from 'soapbox/queries/chats'; import ChatMessage from './chat-message'; -import ChatMessageListIntro from './chat-message-list-intro'; import type { ChatMessage as ChatMessageEntity } from 'soapbox/types/entities'; @@ -69,10 +68,6 @@ interface IChatMessageList { /** Scrollable list of chat messages. */ const ChatMessageList: React.FC = ({ chat }) => { const intl = useIntl(); - const { account } = useOwnAccount(); - - const myLastReadMessageDateString = chat.latest_read_message_by_account?.find((latest) => latest.id === account?.id)?.date; - const myLastReadMessageTimestamp = myLastReadMessageDateString ? new Date(myLastReadMessageDateString) : null; const node = useRef(null); const [firstItemIndex, setFirstItemIndex] = useState(START_INDEX - 20); @@ -174,14 +169,13 @@ const ChatMessageList: React.FC = ({ chat }) => { const lastMessageId = lastMessage.id; const isMessagePending = lastMessage.pending; - const isAlreadyRead = myLastReadMessageTimestamp ? myLastReadMessageTimestamp >= new Date(lastMessage.created_at) : false; /** * Only "mark the message as read" if.. * 1) it is not pending and * 2) it has not already been read */ - if (!isMessagePending && !isAlreadyRead) { + if (!isMessagePending) { markChatAsRead(lastMessageId); } }, [formattedChatMessages.length]); @@ -265,10 +259,6 @@ const ChatMessageList: React.FC = ({ chat }) => { return ; } - if (!hasNextPage && !isLoading) { - return ; - } - return null; }, }} diff --git a/src/features/chats/components/chat-message-reaction-wrapper/chat-message-reaction-wrapper.tsx b/src/features/chats/components/chat-message-reaction-wrapper/chat-message-reaction-wrapper.tsx deleted file mode 100644 index 6e604ff09..000000000 --- a/src/features/chats/components/chat-message-reaction-wrapper/chat-message-reaction-wrapper.tsx +++ /dev/null @@ -1,56 +0,0 @@ -import React, { useState, useEffect } from 'react'; - -import { Portal } from 'soapbox/components/ui'; -import EmojiSelector from 'soapbox/components/ui/emoji-selector/emoji-selector'; - -interface IChatMessageReactionWrapper { - onOpen(isOpen: boolean): void; - onSelect(emoji: string): void; - children: JSX.Element; -} - -/** - * Emoji Reaction Selector - */ -function ChatMessageReactionWrapper(props: IChatMessageReactionWrapper) { - const { onOpen, onSelect, children } = props; - - const [isOpen, setIsOpen] = useState(false); - - const [referenceElement, setReferenceElement] = useState(null); - - const handleSelect = (emoji: string) => { - onSelect(emoji); - setIsOpen(false); - }; - - const onToggleVisibility = () => setIsOpen((prevValue) => !prevValue); - - useEffect(() => { - onOpen(isOpen); - }, [isOpen]); - - return ( - - {React.cloneElement(children, { - ref: setReferenceElement, - onClick: onToggleVisibility, - })} - - {isOpen && ( - - setIsOpen(false)} - offsetOptions={{ mainAxis: 12, crossAxis: -10 }} - all={false} - /> - - )} - - ); -} - -export default ChatMessageReactionWrapper; \ No newline at end of file diff --git a/src/features/chats/components/chat-message-reaction.test.tsx b/src/features/chats/components/chat-message-reaction.test.tsx deleted file mode 100644 index 88f102711..000000000 --- a/src/features/chats/components/chat-message-reaction.test.tsx +++ /dev/null @@ -1,77 +0,0 @@ -import userEvent from '@testing-library/user-event'; -import React from 'react'; - -import { render, screen } from 'soapbox/jest/test-helpers'; - -import ChatMessageReaction from './chat-message-reaction'; - -const emojiReaction = ({ - name: '👍', - count: 1, - me: false, -}); - -describe('', () => { - it('renders properly', () => { - render( - , - ); - - expect(screen.getByRole('img').getAttribute('alt')).toEqual(emojiReaction.name); - expect(screen.getByRole('button')).toHaveTextContent(String(emojiReaction.count)); - }); - - it('triggers the "onAddReaction" function', async () => { - const onAddFn = vi.fn(); - const onRemoveFn = vi.fn(); - const user = userEvent.setup(); - - render( - , - ); - - expect(onAddFn).not.toBeCalled(); - expect(onRemoveFn).not.toBeCalled(); - - await user.click(screen.getByRole('button')); - - // add function triggered - expect(onAddFn).toBeCalled(); - expect(onRemoveFn).not.toBeCalled(); - }); - - it('triggers the "onRemoveReaction" function', async () => { - const onAddFn = vi.fn(); - const onRemoveFn = vi.fn(); - const user = userEvent.setup(); - - render( - , - ); - - expect(onAddFn).not.toBeCalled(); - expect(onRemoveFn).not.toBeCalled(); - - await user.click(screen.getByRole('button')); - - // remove function triggered - expect(onAddFn).not.toBeCalled(); - expect(onRemoveFn).toBeCalled(); - }); -}); \ No newline at end of file diff --git a/src/features/chats/components/chat-message-reaction.tsx b/src/features/chats/components/chat-message-reaction.tsx deleted file mode 100644 index 3b054bd50..000000000 --- a/src/features/chats/components/chat-message-reaction.tsx +++ /dev/null @@ -1,45 +0,0 @@ -import clsx from 'clsx'; -import React from 'react'; - -import { Text } from 'soapbox/components/ui'; -import emojify from 'soapbox/features/emoji'; -import { EmojiReaction } from 'soapbox/types/entities'; - -interface IChatMessageReaction { - emojiReaction: EmojiReaction; - onRemoveReaction(emoji: string): void; - onAddReaction(emoji: string): void; -} - -const ChatMessageReaction = (props: IChatMessageReaction) => { - const { emojiReaction, onAddReaction, onRemoveReaction } = props; - - const isAlreadyReacted = emojiReaction.me; - - const handleClick = () => { - if (isAlreadyReacted) { - onRemoveReaction(emojiReaction.name); - } else { - onAddReaction(emojiReaction.name); - } - }; - - return ( - - ); -}; - -export default ChatMessageReaction; diff --git a/src/features/chats/components/chat-message.tsx b/src/features/chats/components/chat-message.tsx index 3498ecddf..5921149ca 100644 --- a/src/features/chats/components/chat-message.tsx +++ b/src/features/chats/components/chat-message.tsx @@ -6,20 +6,16 @@ import React, { useMemo, useState } from 'react'; import { defineMessages, useIntl } from 'react-intl'; import { openModal } from 'soapbox/actions/modals'; -import { initReport, ReportableEntities } from 'soapbox/actions/reports'; import DropdownMenu from 'soapbox/components/dropdown-menu'; import { HStack, Icon, Stack, Text } from 'soapbox/components/ui'; import emojify from 'soapbox/features/emoji'; import { MediaGallery } from 'soapbox/features/ui/util/async-components'; -import { useAppDispatch, useAppSelector, useFeatures } from 'soapbox/hooks'; +import { useAppDispatch, useAppSelector } from 'soapbox/hooks'; import { ChatKeys, IChat, useChatActions } from 'soapbox/queries/chats'; import { queryClient } from 'soapbox/queries/client'; import { stripHTML } from 'soapbox/utils/html'; import { onlyEmoji } from 'soapbox/utils/rich-content'; -import ChatMessageReaction from './chat-message-reaction'; -import ChatMessageReactionWrapper from './chat-message-reaction-wrapper/chat-message-reaction-wrapper'; - import type { Menu as IMenu } from 'soapbox/components/dropdown-menu'; import type { ChatMessage as ChatMessageEntity } from 'soapbox/types/entities'; @@ -59,13 +55,11 @@ const ChatMessage = (props: IChatMessage) => { const { chat, chatMessage } = props; const dispatch = useAppDispatch(); - const features = useFeatures(); const intl = useIntl(); const me = useAppSelector((state) => state.me); - const { createReaction, deleteChatMessage, deleteReaction } = useChatActions(chat.id); + const { deleteChatMessage } = useChatActions(chat.id); - const [isReactionSelectorOpen, setIsReactionSelectorOpen] = useState(false); const [isMenuOpen, setIsMenuOpen] = useState(false); const handleDeleteMessage = useMutation({ @@ -78,32 +72,14 @@ const ChatMessage = (props: IChatMessage) => { }); const content = parseContent(chatMessage); - const lastReadMessageDateString = chat.latest_read_message_by_account?.find((latest) => latest.id === chat.account.id)?.date; - const lastReadMessageTimestamp = lastReadMessageDateString ? new Date(lastReadMessageDateString) : null; const isMyMessage = chatMessage.account_id === me; - // did this occur before this time? - const isRead = isMyMessage - && lastReadMessageTimestamp - && lastReadMessageTimestamp >= new Date(chatMessage.created_at); - const isOnlyEmoji = useMemo(() => { const hiddenEl = document.createElement('div'); hiddenEl.innerHTML = content; return onlyEmoji(hiddenEl, BIG_EMOJI_LIMIT, false); }, []); - const emojiReactionRows = useMemo(() => { - if (!chatMessage.emoji_reactions) { - return []; - } - - return chatMessage.emoji_reactions.reduce((rows: any, key: any, index) => { - return (index % 4 === 0 ? rows.push([key]) - : rows[rows.length - 1].push(key)) && rows; - }, []); - }, [chatMessage.emoji_reactions]); - const onOpenMedia = (media: any, index: number) => { dispatch(openModal('MEDIA', { media, index })); }; @@ -171,13 +147,6 @@ const ChatMessage = (props: IChatMessage) => { destructive: true, }); } else { - if (features.reportChats) { - menu.push({ - text: intl.formatMessage(messages.report), - action: () => dispatch(initReport(ReportableEntities.CHAT_MESSAGE, chat.account, { chatMessage })), - icon: require('@tabler/icons/outline/flag.svg'), - }); - } menu.push({ text: intl.formatMessage(messages.deleteForMe), action: () => handleDeleteMessage.mutate(chatMessage.id), @@ -194,7 +163,7 @@ const ChatMessage = (props: IChatMessage) => { className={ clsx({ 'group relative px-4 py-2 hover:bg-gray-200/40 dark:hover:bg-gray-800/40': true, - 'bg-gray-200/40 dark:bg-gray-800/40': isMenuOpen || isReactionSelectorOpen, + 'bg-gray-200/40 dark:bg-gray-800/40': isMenuOpen, }) } data-testid='chat-message' @@ -205,29 +174,10 @@ const ChatMessage = (props: IChatMessage) => { 'p-1 flex items-center space-x-0.5 z-10 absolute opacity-0 transition-opacity group-hover:opacity-100 focus:opacity-100 rounded-md shadow-lg bg-white dark:bg-gray-900 dark:ring-2 dark:ring-primary-700': true, 'top-2 right-2': !isMyMessage, 'top-2 left-2': isMyMessage, - '!opacity-100': isMenuOpen || isReactionSelectorOpen, + '!opacity-100': isMenuOpen, }) } > - {features.chatEmojiReactions && ( - createReaction.mutate({ emoji, messageId: chatMessage.id, chatMessage })} - > - - - )} {menu.length > 0 && ( { - {(chatMessage.emoji_reactions?.length) ? ( -
- {emojiReactionRows?.map((emojiReactionRow: any, idx: number) => ( - - {emojiReactionRow.map((emojiReaction: any, idx: number) => ( - createReaction.mutate({ emoji, messageId: chatMessage.id, chatMessage })} - onRemoveReaction={(emoji) => deleteReaction.mutate({ emoji, messageId: chatMessage.id })} - /> - ))} - - ))} -
- ) : null} - { {intl.formatTime(chatMessage.created_at)} - - {(isMyMessage && features.chatsReadReceipts) ? ( - <> - {isRead ? ( - - - - ) : ( - - - - )} - - ) : null}
diff --git a/src/features/chats/components/chat-page/chat-page.tsx b/src/features/chats/components/chat-page/chat-page.tsx index 8384c8744..178607898 100644 --- a/src/features/chats/components/chat-page/chat-page.tsx +++ b/src/features/chats/components/chat-page/chat-page.tsx @@ -3,24 +3,19 @@ import React, { useEffect, useLayoutEffect, useRef, useState } from 'react'; import { matchPath, Route, Switch, useHistory } from 'react-router-dom'; import { Stack } from 'soapbox/components/ui'; -import { useOwnAccount } from 'soapbox/hooks'; import ChatPageMain from './components/chat-page-main'; import ChatPageNew from './components/chat-page-new'; import ChatPageSettings from './components/chat-page-settings'; import ChatPageSidebar from './components/chat-page-sidebar'; -import Welcome from './components/welcome'; interface IChatPage { chatId?: string; } const ChatPage: React.FC = ({ chatId }) => { - const { account } = useOwnAccount(); const history = useHistory(); - const isOnboarded = account?.source?.chats_onboarded ?? true; - const path = history.location.pathname; const isSidebarHidden = matchPath(path, { path: ['/chats/settings', '/chats/new', '/chats/:chatId'], @@ -62,40 +57,36 @@ const ChatPage: React.FC = ({ chatId }) => { style={{ height }} className='h-screen overflow-hidden bg-white text-gray-900 shadow-lg black:bg-transparent sm:rounded-t-xl dark:bg-primary-900 dark:text-gray-100 dark:shadow-none' > - {isOnboarded ? ( -
+ - - - + + - - - - - - - - - - - - - -
- ) : ( - - )} + + + + + + + + + + + + + + ); }; diff --git a/src/features/chats/components/chat-page/components/chat-page-main.tsx b/src/features/chats/components/chat-page/components/chat-page-main.tsx index 15178e0c7..df3a0f4f7 100644 --- a/src/features/chats/components/chat-page/components/chat-page-main.tsx +++ b/src/features/chats/components/chat-page/components/chat-page-main.tsx @@ -4,13 +4,11 @@ import { Link, useHistory, useParams } from 'react-router-dom'; import { blockAccount, unblockAccount } from 'soapbox/actions/accounts'; import { openModal } from 'soapbox/actions/modals'; -import List, { ListItem } from 'soapbox/components/list'; -import { Avatar, HStack, Icon, IconButton, Menu, MenuButton, MenuItem, MenuList, Stack, Text, Tooltip } from 'soapbox/components/ui'; +import { Avatar, HStack, Icon, IconButton, Menu, MenuButton, MenuItem, MenuList, Stack, Text } from 'soapbox/components/ui'; import VerificationBadge from 'soapbox/components/verification-badge'; import { useChatContext } from 'soapbox/contexts/chat-context'; import { useAppDispatch, useAppSelector, useFeatures } from 'soapbox/hooks'; -import { MessageExpirationValues, useChat, useChatActions, useChats } from 'soapbox/queries/chats'; -import { secondsToDays } from 'soapbox/utils/numbers'; +import { useChat, useChatActions, useChats } from 'soapbox/queries/chats'; import Chat from '../../chat'; @@ -31,15 +29,6 @@ const messages = defineMessages({ unblockUser: { id: 'chat_settings.options.unblock_user', defaultMessage: 'Unblock @{acct}' }, reportUser: { id: 'chat_settings.options.report_user', defaultMessage: 'Report @{acct}' }, leaveChat: { id: 'chat_settings.options.leave_chat', defaultMessage: 'Leave Chat' }, - autoDeleteLabel: { id: 'chat_settings.auto_delete.label', defaultMessage: 'Auto-delete messages' }, - autoDeleteHint: { id: 'chat_settings.auto_delete.hint', defaultMessage: 'Sent messages will auto-delete after the time period selected' }, - autoDelete2Minutes: { id: 'chat_settings.auto_delete.2minutes', defaultMessage: '2 minutes' }, - autoDelete7Days: { id: 'chat_settings.auto_delete.7days', defaultMessage: '7 days' }, - autoDelete14Days: { id: 'chat_settings.auto_delete.14days', defaultMessage: '14 days' }, - autoDelete30Days: { id: 'chat_settings.auto_delete.30days', defaultMessage: '30 days' }, - autoDelete90Days: { id: 'chat_settings.auto_delete.90days', defaultMessage: '90 days' }, - autoDeleteMessage: { id: 'chat_window.auto_delete_label', defaultMessage: 'Auto-delete after {day, plural, one {# day} other {# days}}' }, - autoDeleteMessageTooltip: { id: 'chat_window.auto_delete_tooltip', defaultMessage: 'Chat messages are set to auto-delete after {day, plural, one {# day} other {# days}} upon sending.' }, }); const ChatPageMain = () => { @@ -56,9 +45,7 @@ const ChatPageMain = () => { const inputRef = useRef(null); - const { deleteChat, updateChat } = useChatActions(chat?.id as string); - - const handleUpdateChat = (value: MessageExpirationValues) => updateChat.mutate({ message_expiration: value }); + const { deleteChat } = useChatActions(chat?.id as string); const isBlocking = useAppSelector((state) => state.getIn(['relationships', chat?.account?.id, 'blocking'])); @@ -139,23 +126,6 @@ const ChatPageMain = () => { {chat.account.verified && } - - {chat.message_expiration && ( - - - {intl.formatMessage(messages.autoDeleteMessage, { day: secondsToDays(chat.message_expiration) })} - - - )} @@ -177,35 +147,6 @@ const ChatPageMain = () => { - {features.chatsExpiration && ( - - - handleUpdateChat(MessageExpirationValues.SEVEN)} - isSelected={chat.message_expiration === MessageExpirationValues.SEVEN} - /> - handleUpdateChat(MessageExpirationValues.FOURTEEN)} - isSelected={chat.message_expiration === MessageExpirationValues.FOURTEEN} - /> - handleUpdateChat(MessageExpirationValues.THIRTY)} - isSelected={chat.message_expiration === MessageExpirationValues.THIRTY} - /> - handleUpdateChat(MessageExpirationValues.NINETY)} - isSelected={chat.message_expiration === MessageExpirationValues.NINETY} - /> - - )} - { const updateCredentials = useUpdateCredentials(); const [data, setData] = useState({ - chats_onboarded: true, accepts_chat_messages: account?.pleroma?.accepts_chat_messages === true, }); diff --git a/src/features/chats/components/chat-page/components/chat-page-sidebar.tsx b/src/features/chats/components/chat-page/components/chat-page-sidebar.tsx index 3f08e1a6b..da1a86905 100644 --- a/src/features/chats/components/chat-page/components/chat-page-sidebar.tsx +++ b/src/features/chats/components/chat-page/components/chat-page-sidebar.tsx @@ -1,13 +1,11 @@ -import React, { useState } from 'react'; +import React from 'react'; import { defineMessages, useIntl } from 'react-intl'; import { useHistory } from 'react-router-dom'; import { CardTitle, HStack, IconButton, Stack } from 'soapbox/components/ui'; -import { useDebounce, useFeatures } from 'soapbox/hooks'; import { IChat } from 'soapbox/queries/chats'; import ChatList from '../../chat-list'; -import ChatSearchInput from '../../chat-search-input'; const messages = defineMessages({ title: { id: 'column.chats', defaultMessage: 'Chats' }, @@ -16,11 +14,6 @@ const messages = defineMessages({ const ChatPageSidebar = () => { const intl = useIntl(); const history = useHistory(); - const features = useFeatures(); - - const [search, setSearch] = useState(''); - - const debouncedSearch = useDebounce(search, 300); const handleClickChat = (chat: IChat) => { history.push(`/chats/${chat.id}`); @@ -54,21 +47,10 @@ const ChatPageSidebar = () => { /> - - {features.chatsSearch && ( - setSearch(e.target.value)} - onClear={() => setSearch('')} - /> - )} - + ); diff --git a/src/features/chats/components/chat-page/components/welcome.tsx b/src/features/chats/components/chat-page/components/welcome.tsx deleted file mode 100644 index f78de0e32..000000000 --- a/src/features/chats/components/chat-page/components/welcome.tsx +++ /dev/null @@ -1,80 +0,0 @@ -import React, { useState } from 'react'; -import { defineMessages, FormattedMessage, useIntl } from 'react-intl'; - -import List, { ListItem } from 'soapbox/components/list'; -import { Button, CardBody, CardTitle, Form, Stack, Text, Toggle } from 'soapbox/components/ui'; -import { useOwnAccount } from 'soapbox/hooks'; -import { useUpdateCredentials } from 'soapbox/queries/accounts'; - -type FormData = { - accepts_chat_messages?: boolean; - chats_onboarded: boolean; -} - -const messages = defineMessages({ - title: { id: 'chat.welcome.title', defaultMessage: 'Welcome to {br} Chats!' }, - subtitle: { id: 'chat.welcome.subtitle', defaultMessage: 'Exchange direct messages with other users.' }, - acceptingMessageLabel: { id: 'chat.welcome.accepting_messages.label', defaultMessage: 'Allow users to start a new chat with you' }, - notice: { id: 'chat.welcome.notice', defaultMessage: 'You can change these settings later.' }, - submit: { id: 'chat.welcome.submit', defaultMessage: 'Save & Continue' }, -}); - -const Welcome = () => { - const { account } = useOwnAccount(); - const intl = useIntl(); - const updateCredentials = useUpdateCredentials(); - - const [data, setData] = useState({ - chats_onboarded: true, - accepts_chat_messages: account?.pleroma?.accepts_chat_messages === true, - }); - - const handleSubmit = (event: React.FormEvent) => { - event.preventDefault(); - - updateCredentials.mutate(data); - }; - - return ( - -
- - {intl.formatMessage(messages.title, { br:
})} -
- - - {intl.formatMessage(messages.subtitle)} - -
- -
- - } /> - - - - - setData((prevData) => ({ ...prevData, accepts_chat_messages: event.target.checked }))} - /> - - - - - - - {intl.formatMessage(messages.notice)} - - - -
-
- ); -}; - -export default Welcome; diff --git a/src/features/chats/components/chat-pane/chat-pane.test.tsx b/src/features/chats/components/chat-pane/chat-pane.test.tsx index 3997c242c..fd0358431 100644 --- a/src/features/chats/components/chat-pane/chat-pane.test.tsx +++ b/src/features/chats/components/chat-pane/chat-pane.test.tsx @@ -45,21 +45,19 @@ describe('', () => { // }); // }); - describe('when the software is not Truth Social', () => { - beforeEach(() => { - __stub((mock) => { - mock.onGet('/api/v1/pleroma/chats').reply(200, chats, { - link: '; rel=\'prev\'', - }); - }); - }); - - it('does not render the search input', async () => { - renderComponentWithChatContext(); - - await waitFor(() => { - expect(screen.queryAllByTestId('chat-search-input')).toHaveLength(0); + beforeEach(() => { + __stub((mock) => { + mock.onGet('/api/v1/pleroma/chats').reply(200, chats, { + link: '; rel=\'prev\'', }); }); }); + + it('does not render the search input', async () => { + renderComponentWithChatContext(); + + await waitFor(() => { + expect(screen.queryAllByTestId('chat-search-input')).toHaveLength(0); + }); + }); }); diff --git a/src/features/chats/components/chat-pane/chat-pane.tsx b/src/features/chats/components/chat-pane/chat-pane.tsx index 1f0dde8f8..03e4b0555 100644 --- a/src/features/chats/components/chat-pane/chat-pane.tsx +++ b/src/features/chats/components/chat-pane/chat-pane.tsx @@ -1,16 +1,14 @@ -import React, { useState } from 'react'; +import React from 'react'; import { FormattedMessage } from 'react-intl'; import { Stack } from 'soapbox/components/ui'; import { ChatWidgetScreens, useChatContext } from 'soapbox/contexts/chat-context'; import { useStatContext } from 'soapbox/contexts/stat-context'; -import { useDebounce, useFeatures } from 'soapbox/hooks'; import { IChat, useChats } from 'soapbox/queries/chats'; import ChatList from '../chat-list'; import ChatSearch from '../chat-search/chat-search'; import EmptyResultsBlankslate from '../chat-search/empty-results-blankslate'; -import ChatSearchInput from '../chat-search-input'; import ChatPaneHeader from '../chat-widget/chat-pane-header'; import ChatWindow from '../chat-widget/chat-window'; import ChatSearchHeader from '../chat-widget/headers/chat-search-header'; @@ -19,46 +17,21 @@ import { Pane } from '../ui'; import Blankslate from './blankslate'; const ChatPane = () => { - const features = useFeatures(); - const debounce = useDebounce; const { unreadChatsCount } = useStatContext(); - const [value, setValue] = useState(); - const debouncedValue = debounce(value as string, 300); - const { screen, changeScreen, isOpen, toggleChatPane } = useChatContext(); - const { chatsQuery: { data: chats, isLoading } } = useChats(debouncedValue); - - const hasSearchValue = Number(debouncedValue?.length) > 0; + const { chatsQuery: { data: chats, isLoading } } = useChats(); const handleClickChat = (nextChat: IChat) => { changeScreen(ChatWidgetScreens.CHAT, nextChat.id); - setValue(undefined); - }; - - const clearValue = () => { - if (hasSearchValue) { - setValue(''); - } }; const renderBody = () => { - if (hasSearchValue || Number(chats?.length) > 0 || isLoading) { + if (Number(chats?.length) > 0 || isLoading) { return ( - {features.chatsSearch && ( -
- setValue(event.target.value)} - onClear={clearValue} - /> -
- )} - {(Number(chats?.length) > 0 || isLoading) ? ( ) : ( @@ -105,7 +78,6 @@ const ChatPane = () => { onToggle={toggleChatPane} secondaryAction={() => { changeScreen(ChatWidgetScreens.SEARCH); - setValue(undefined); if (!isOpen) { toggleChatPane(); diff --git a/src/features/chats/components/chat-search-input.tsx b/src/features/chats/components/chat-search-input.tsx deleted file mode 100644 index 703202ee1..000000000 --- a/src/features/chats/components/chat-search-input.tsx +++ /dev/null @@ -1,47 +0,0 @@ -import React from 'react'; -import { defineMessages, useIntl } from 'react-intl'; - -import { Icon, Input } from 'soapbox/components/ui'; - -const messages = defineMessages({ - searchPlaceholder: { id: 'chats.search_placeholder', defaultMessage: 'Start a chat with…' }, -}); - -interface IChatSearchInput { - /** Search term. */ - value: string; - /** Callback when the search value changes. */ - onChange: React.ChangeEventHandler; - /** Callback when the input is cleared. */ - onClear: React.MouseEventHandler; -} - -/** Search input for filtering chats. */ -const ChatSearchInput: React.FC = ({ value, onChange, onClear }) => { - const intl = useIntl(); - - return ( - -
- {features.chatsExpiration && ( - - - - - - )} - diff --git a/src/features/group/components/group-member-count.tsx b/src/features/group/components/group-member-count.tsx index 797d618b2..8c277aabd 100644 --- a/src/features/group/components/group-member-count.tsx +++ b/src/features/group/components/group-member-count.tsx @@ -12,7 +12,7 @@ interface IGroupMemberCount { const GroupMemberCount = ({ group }: IGroupMemberCount) => { return ( - + {shortNumberFormat(group.members_count)} {' '} diff --git a/src/features/group/components/group-member-list-item.test.tsx b/src/features/group/components/group-member-list-item.test.tsx index e91901013..fa0b9d424 100644 --- a/src/features/group/components/group-member-list-item.test.tsx +++ b/src/features/group/components/group-member-list-item.test.tsx @@ -27,7 +27,7 @@ describe('', () => { relationship: buildGroupRelationship(), }); - render(); + render(); await waitFor(() => { expect(screen.getByTestId('group-member-list-item')).toHaveTextContent(groupMember.account.display_name); @@ -52,7 +52,7 @@ describe('', () => { }); it('should render the correct badge', async () => { - render(); + render(); await waitFor(() => { expect(screen.getByTestId('role-badge')).toHaveTextContent('owner'); @@ -73,7 +73,7 @@ describe('', () => { }); it('should render the correct badge', async () => { - render(); + render(); await waitFor(() => { expect(screen.getByTestId('role-badge')).toHaveTextContent('admin'); @@ -94,7 +94,7 @@ describe('', () => { }); it('should render no correct badge', async () => { - render(); + render(); await waitFor(() => { expect(screen.queryAllByTestId('role-badge')).toHaveLength(0); @@ -125,36 +125,19 @@ describe('', () => { }); }); - describe('when "canPromoteToAdmin is true', () => { - it('should render dropdown with correct Owner actions', async () => { - const user = userEvent.setup(); + it('should render dropdown with correct Owner actions', async () => { + const user = userEvent.setup(); - render(); + 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'); + await waitFor(async() => { + await user.click(screen.getByTestId('icon-button')); }); - }); - 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'); - }); + 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'); }); }); @@ -180,7 +163,7 @@ describe('', () => { it('should render dropdown with correct Owner actions', async () => { const user = userEvent.setup(); - render(); + render(); await waitFor(async() => { await user.click(screen.getByTestId('icon-button')); @@ -219,7 +202,7 @@ describe('', () => { it('should render dropdown with correct Admin actions', async () => { const user = userEvent.setup(); - render(); + render(); await waitFor(async() => { await user.click(screen.getByTestId('icon-button')); @@ -252,7 +235,7 @@ describe('', () => { }); it('should not render the dropdown', async () => { - render(); + render(); await waitFor(async() => { expect(screen.queryAllByTestId('icon-button')).toHaveLength(0); @@ -280,7 +263,7 @@ describe('', () => { }); it('should not render the dropdown', async () => { - render(); + render(); await waitFor(async() => { expect(screen.queryAllByTestId('icon-button')).toHaveLength(0); @@ -310,7 +293,7 @@ describe('', () => { }); it('should not render the dropdown', async () => { - render(); + render(); await waitFor(async() => { expect(screen.queryAllByTestId('icon-button')).toHaveLength(0); diff --git a/src/features/group/components/group-member-list-item.tsx b/src/features/group/components/group-member-list-item.tsx index eea6514f8..9f7689b67 100644 --- a/src/features/group/components/group-member-list-item.tsx +++ b/src/features/group/components/group-member-list-item.tsx @@ -11,12 +11,10 @@ import { HStack } from 'soapbox/components/ui'; 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 { useAppDispatch } from 'soapbox/hooks'; import { GroupRoles } from 'soapbox/schemas/group-member'; import toast from 'soapbox/toast'; -import { MAX_ADMIN_COUNT } from '../group-members'; - import type { Menu as IMenu } from 'soapbox/components/dropdown-menu'; import type { Group, GroupMember } from 'soapbox/types/entities'; @@ -43,14 +41,12 @@ const messages = defineMessages({ interface IGroupMemberListItem { member: GroupMember; group: Group; - canPromoteToAdmin: boolean; } const GroupMemberListItem = (props: IGroupMemberListItem) => { - const { canPromoteToAdmin, member, group } = props; + const { member, group } = props; const dispatch = useAppDispatch(); - const features = useFeatures(); const intl = useIntl(); const blockGroupMember = useBlockGroupMember(group, member.account); @@ -95,13 +91,6 @@ const GroupMemberListItem = (props: IGroupMemberListItem) => { }; const handleAdminAssignment = () => { - if (!canPromoteToAdmin) { - toast.error(intl.formatMessage(messages.adminLimitTitle), { - summary: intl.formatMessage(messages.adminLimitSummary, { count: MAX_ADMIN_COUNT }), - }); - return; - } - dispatch(openModal('CONFIRM', { heading: intl.formatMessage(messages.promoteConfirm), message: intl.formatMessage(messages.promoteConfirmMessage, { name: account?.username }), @@ -156,13 +145,11 @@ const GroupMemberListItem = (props: IGroupMemberListItem) => { (isMemberAdmin || isMemberUser) && member.role !== group.relationship.role ) { - if (features.groupsKick) { - items.push({ - text: intl.formatMessage(messages.groupModKick, { name: account.username }), - icon: require('@tabler/icons/outline/user-minus.svg'), - action: handleKickFromGroup, - }); - } + items.push({ + text: intl.formatMessage(messages.groupModKick, { name: account.username }), + icon: require('@tabler/icons/outline/user-minus.svg'), + action: handleKickFromGroup, + }); items.push({ text: intl.formatMessage(messages.groupModBlock, { name: account.username }), diff --git a/src/features/group/components/group-options-button.tsx b/src/features/group/components/group-options-button.tsx index c52bc1eaa..15ca4b399 100644 --- a/src/features/group/components/group-options-button.tsx +++ b/src/features/group/components/group-options-button.tsx @@ -2,29 +2,21 @@ import React, { useMemo } from 'react'; import { defineMessages, useIntl } from 'react-intl'; import { openModal } from 'soapbox/actions/modals'; -import { initReport, ReportableEntities } from 'soapbox/actions/reports'; -import { useLeaveGroup, useMuteGroup, useUnmuteGroup } from 'soapbox/api/hooks'; +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 { useAppDispatch } from 'soapbox/hooks'; import { GroupRoles } from 'soapbox/schemas/group-member'; import toast from 'soapbox/toast'; -import type { Account, Group } from 'soapbox/types/entities'; +import type { Group } from 'soapbox/types/entities'; const messages = defineMessages({ confirmationConfirm: { id: 'confirmations.leave_group.confirm', defaultMessage: 'Leave' }, confirmationHeading: { id: 'confirmations.leave_group.heading', defaultMessage: 'Leave group' }, confirmationMessage: { id: 'confirmations.leave_group.message', defaultMessage: 'You are about to leave the group. Do you want to continue?' }, - muteConfirm: { id: 'confirmations.mute_group.confirm', defaultMessage: 'Mute' }, - muteHeading: { id: 'confirmations.mute_group.heading', defaultMessage: 'Mute Group' }, - muteMessage: { id: 'confirmations.mute_group.message', defaultMessage: 'You are about to mute the group. Do you want to continue?' }, - muteSuccess: { id: 'group.mute.success', defaultMessage: 'Muted the group' }, - unmuteSuccess: { id: 'group.unmute.success', defaultMessage: 'Unmuted the group' }, leave: { id: 'group.leave.label', defaultMessage: 'Leave' }, leaveSuccess: { id: 'group.leave.success', defaultMessage: 'Left the group' }, - mute: { id: 'group.mute.label', defaultMessage: 'Mute' }, - unmute: { id: 'group.unmute.label', defaultMessage: 'Unmute' }, report: { id: 'group.report.label', defaultMessage: 'Report' }, share: { id: 'group.share.label', defaultMessage: 'Share' }, }); @@ -34,12 +26,9 @@ interface IGroupActionButton { } const GroupOptionsButton = ({ group }: IGroupActionButton) => { - const { account } = useOwnAccount(); const dispatch = useAppDispatch(); const intl = useIntl(); - const muteGroup = useMuteGroup(group); - const unmuteGroup = useUnmuteGroup(group); const leaveGroup = useLeaveGroup(group); const isMember = group.relationship?.role === GroupRoles.USER; @@ -57,27 +46,6 @@ const GroupOptionsButton = ({ group }: IGroupActionButton) => { }); }; - const handleMute = () => - dispatch(openModal('CONFIRM', { - heading: intl.formatMessage(messages.muteHeading), - message: intl.formatMessage(messages.muteMessage), - confirm: intl.formatMessage(messages.muteConfirm), - confirmationTheme: 'primary', - onConfirm: () => muteGroup.mutate(undefined, { - onSuccess() { - toast.success(intl.formatMessage(messages.muteSuccess)); - }, - }), - })); - - const handleUnmute = () => { - unmuteGroup.mutate(undefined, { - onSuccess() { - toast.success(intl.formatMessage(messages.unmuteSuccess)); - }, - }); - }; - const handleLeave = () => dispatch(openModal('CONFIRM', { heading: intl.formatMessage(messages.confirmationHeading), @@ -103,22 +71,6 @@ const GroupOptionsButton = ({ group }: IGroupActionButton) => { }); } - if (isInGroup) { - items.push({ - text: isMuting ? intl.formatMessage(messages.unmute) : intl.formatMessage(messages.mute), - icon: require('@tabler/icons/outline/volume-3.svg'), - action: isMuting ? handleUnmute : handleMute, - }); - } - - if (isMember || isAdmin) { - items.push({ - text: intl.formatMessage(messages.report), - icon: require('@tabler/icons/outline/flag.svg'), - action: () => dispatch(initReport(ReportableEntities.GROUP, account as Account, { group })), - }); - } - if (isAdmin) { items.push(null); items.push({ diff --git a/src/features/group/components/group-tag-list-item.test.tsx b/src/features/group/components/group-tag-list-item.test.tsx deleted file mode 100644 index 57c3cffbe..000000000 --- a/src/features/group/components/group-tag-list-item.test.tsx +++ /dev/null @@ -1,124 +0,0 @@ -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 pinned', () => { - const tag = buildGroupTag({ pinned: true, visible: true }); - - it('does render the pin icon', () => { - render(); - screen.debug(); - expect(screen.queryAllByTestId('pin-icon')).toHaveLength(1); - }); - }); - - describe('when the tag is not pinned', () => { - const tag = buildGroupTag({ pinned: false, visible: true }); - - it('does not render the pin icon', () => { - render(); - expect(screen.queryAllByTestId('pin-icon')).toHaveLength(0); - }); - }); - }); - }); -}); \ No newline at end of file diff --git a/src/features/group/components/group-tag-list-item.tsx b/src/features/group/components/group-tag-list-item.tsx deleted file mode 100644 index 45a5dc82f..000000000 --- a/src/features/group/components/group-tag-list-item.tsx +++ /dev/null @@ -1,196 +0,0 @@ -import React from 'react'; -import { defineMessages, useIntl } from 'react-intl'; -import { Link } from 'react-router-dom'; - -import { useUpdateGroupTag } from 'soapbox/api/hooks'; -import { HStack, Icon, 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 { GroupRoles } from 'soapbox/schemas/group-member'; -import toast from 'soapbox/toast'; -import { shortNumberFormat } from 'soapbox/utils/numbers'; - -import type { Group, GroupTag } from 'soapbox/schemas'; - -const messages = defineMessages({ - hideTag: { id: 'group.tags.hide', defaultMessage: 'Hide topic' }, - showTag: { id: 'group.tags.show', defaultMessage: 'Show topic' }, - total: { id: 'group.tags.total', defaultMessage: 'Total Posts' }, - pinTag: { id: 'group.tags.pin', defaultMessage: 'Pin topic' }, - unpinTag: { id: 'group.tags.unpin', defaultMessage: 'Unpin topic' }, - pinSuccess: { id: 'group.tags.pin.success', defaultMessage: 'Pinned!' }, - unpinSuccess: { id: 'group.tags.unpin.success', defaultMessage: 'Unpinned!' }, - visibleSuccess: { id: 'group.tags.visible.success', defaultMessage: 'Topic marked as visible' }, - hiddenSuccess: { id: 'group.tags.hidden.success', defaultMessage: 'Topic marked as hidden' }, -}); - -interface IGroupMemberListItem { - tag: GroupTag; - group: Group; - isPinnable: boolean; -} - -const GroupTagListItem = (props: IGroupMemberListItem) => { - const { group, tag, isPinnable } = props; - const dispatch = useAppDispatch(); - - const intl = useIntl(); - const { updateGroupTag } = useUpdateGroupTag(group.id, tag.id); - - const isOwner = group.relationship?.role === GroupRoles.OWNER; - - const toggleVisibility = () => { - const isHiding = tag.visible; - - updateGroupTag({ - group_tag_type: isHiding ? 'hidden' : 'normal', - }, { - onSuccess() { - const entity: GroupTag = { - ...tag, - visible: !tag.visible, - pinned: isHiding ? false : tag.pinned, // unpin if we're hiding - }; - dispatch(importEntities([entity], Entities.GROUP_TAGS)); - - toast.success( - entity.visible ? - intl.formatMessage(messages.visibleSuccess) : - intl.formatMessage(messages.hiddenSuccess), - ); - }, - }); - }; - - const togglePin = () => { - updateGroupTag({ - group_tag_type: tag.pinned ? 'normal' : 'pinned', - }, { - onSuccess() { - const entity = { - ...tag, - pinned: !tag.pinned, - }; - dispatch(importEntities([entity], Entities.GROUP_TAGS)); - - toast.success( - entity.pinned ? - intl.formatMessage(messages.pinSuccess) : - intl.formatMessage(messages.unpinSuccess), - ); - }, - }); - }; - - const renderPinIcon = () => { - if (!isOwner && tag.pinned) { - return ( - - ); - } - - if (!isOwner) { - return null; - } - - if (isPinnable) { - return ( - - - - ); - } - - if (!isPinnable && tag.pinned) { - return ( - - - - - ); - } - }; - - return ( - - - - - #{tag.name} - - - {intl.formatMessage(messages.total)}: - {' '} - - {shortNumberFormat(tag.uses)} - - - - - - - {tag.visible ? ( - renderPinIcon() - ) : null} - - {isOwner ? ( - - - - ) : null} - - - ); -}; - -export default GroupTagListItem; \ No newline at end of file diff --git a/src/features/group/components/group-tags-field.tsx b/src/features/group/components/group-tags-field.tsx deleted file mode 100644 index 1a9f3118e..000000000 --- a/src/features/group/components/group-tags-field.tsx +++ /dev/null @@ -1,59 +0,0 @@ -import React, { useMemo } from 'react'; -import { FormattedMessage, defineMessages, useIntl } from 'react-intl'; - -import { Input, Streamfield } from 'soapbox/components/ui'; - -import type { StreamfieldComponent } from 'soapbox/components/ui/streamfield/streamfield'; - -const messages = defineMessages({ - hashtagPlaceholder: { id: 'manage_group.fields.hashtag_placeholder', defaultMessage: 'Add a topic' }, -}); - -interface IGroupTagsField { - tags: string[]; - onChange(tags: string[]): void; - onAddItem(): void; - onRemoveItem(i: number): void; - maxItems?: number; -} - -const GroupTagsField: React.FC = ({ tags, onChange, onAddItem, onRemoveItem, maxItems = 3 }) => { - return ( - } - hint={} - component={HashtagField} - values={tags} - onChange={onChange} - onAddItem={onAddItem} - onRemoveItem={onRemoveItem} - maxItems={maxItems} - minItems={1} - /> - ); -}; - -const HashtagField: StreamfieldComponent = ({ value, onChange, autoFocus = false }) => { - const intl = useIntl(); - - const formattedValue = useMemo(() => { - return `#${value}`; - }, [value]); - - const handleChange: React.ChangeEventHandler = ({ target }) => { - onChange(target.value.replace('#', '')); - }; - - return ( - - ); -}; - -export default GroupTagsField; \ No newline at end of file diff --git a/src/features/group/edit-group.tsx b/src/features/group/edit-group.tsx index aa80d7ce5..13c6279b1 100644 --- a/src/features/group/edit-group.tsx +++ b/src/features/group/edit-group.tsx @@ -1,7 +1,7 @@ -import React, { useEffect, useState } from 'react'; +import React, { useState } from 'react'; import { defineMessages, FormattedMessage, useIntl } from 'react-intl'; -import { useGroup, useGroupTags, useUpdateGroup } from 'soapbox/api/hooks'; +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 { useImageField, useTextField } from 'soapbox/hooks/forms'; @@ -11,8 +11,6 @@ import { isDefaultAvatar, isDefaultHeader } from 'soapbox/utils/accounts'; import AvatarPicker from '../edit-profile/components/avatar-picker'; import HeaderPicker from '../edit-profile/components/header-picker'; -import GroupTagsField from './components/group-tags-field'; - const nonDefaultAvatar = (url: string | undefined) => url && isDefaultAvatar(url) ? undefined : url; const nonDefaultHeader = (url: string | undefined) => url && isDefaultHeader(url) ? undefined : url; @@ -35,10 +33,8 @@ const EditGroup: React.FC = ({ params: { groupId } }) => { const { group, isLoading } = useGroup(groupId); const { updateGroup } = useUpdateGroup(groupId); - const { invalidate } = useGroupTags(groupId); const [isSubmitting, setIsSubmitting] = useState(false); - const [tags, setTags] = useState(['']); const avatar = useImageField({ maxPixels: 400 * 400, preview: nonDefaultAvatar(group?.avatar) }); const header = useImageField({ maxPixels: 1920 * 1080, preview: nonDefaultHeader(group?.header) }); @@ -61,10 +57,8 @@ const EditGroup: React.FC = ({ params: { groupId } }) => { note: note.value, avatar: avatar.file === null ? '' : avatar.file, header: header.file === null ? '' : header.file, - tags, }, { onSuccess() { - invalidate(); toast.success(intl.formatMessage(messages.groupSaved)); }, onError(error) { @@ -79,22 +73,6 @@ const EditGroup: React.FC = ({ params: { groupId } }) => { setIsSubmitting(false); } - const handleAddTag = () => { - setTags([...tags, '']); - }; - - const handleRemoveTag = (i: number) => { - const newTags = [...tags]; - newTags.splice(i, 1); - setTags(newTags); - }; - - useEffect(() => { - if (group) { - setTags(group.tags.map((t) => t.name)); - } - }, [group?.id]); - if (isLoading) { return ; } @@ -130,15 +108,6 @@ const EditGroup: React.FC = ({ params: { groupId } }) => { /> -
- -
- - - - ( -
- -
- )} - /> - - ) : ( - - - - - - - - - - )} -
- ); -}; \ No newline at end of file diff --git a/src/features/groups/components/discover/search/results.test.tsx b/src/features/groups/components/discover/search/results.test.tsx deleted file mode 100644 index 1c3017c9b..000000000 --- a/src/features/groups/components/discover/search/results.test.tsx +++ /dev/null @@ -1,67 +0,0 @@ -import userEvent from '@testing-library/user-event'; -import React from 'react'; -import { VirtuosoGridMockContext, VirtuosoMockContext } from 'react-virtuoso'; - -import { buildAccount, buildGroup } from 'soapbox/jest/factory'; -import { render, screen, waitFor } from 'soapbox/jest/test-helpers'; - -import Results from './results'; - -const userId = '1'; -const store = { - me: userId, - accounts: { - [userId]: buildAccount({ - id: userId, - acct: 'justin-username', - display_name: 'Justin L', - avatar: 'test.jpg', - source: { - chats_onboarded: false, - }, - }), - }, -}; - -const renderApp = (children: React.ReactNode) => ( - render( - - - {children} - - , - undefined, - store, - ) -); - -const groupSearchResult = { - groups: [buildGroup()], - hasNextPage: false, - isFetching: false, - fetchNextPage: vi.fn(), -} as any; - -describe('', () => { - describe('with a list layout', () => { - it('should render the GroupListItem components', async () => { - renderApp(); - await waitFor(() => { - expect(screen.getByTestId('group-list-item')).toBeInTheDocument(); - }); - }); - }); - - describe('with a grid layout', () => { - it('should render the GroupGridItem components', async () => { - const user = userEvent.setup(); - renderApp(); - - await user.click(screen.getByTestId('layout-grid-action')); - - await waitFor(() => { - expect(screen.getByTestId('group-grid-item')).toBeInTheDocument(); - }); - }); - }); -}); \ No newline at end of file diff --git a/src/features/groups/components/discover/search/results.tsx b/src/features/groups/components/discover/search/results.tsx deleted file mode 100644 index 3a01ea16f..000000000 --- a/src/features/groups/components/discover/search/results.tsx +++ /dev/null @@ -1,92 +0,0 @@ -import clsx from 'clsx'; -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 GroupGridItem from '../group-grid-item'; -import GroupListItem from '../group-list-item'; -import LayoutButtons, { GroupLayout } from '../layout-buttons'; - -import type { Group } from 'soapbox/types/entities'; - -interface Props { - groupSearchResult: ReturnType; -} - -const GridList: Components['List'] = React.forwardRef((props, ref) => { - const { context, ...rest } = props; - return
; -}); - -export default (props: Props) => { - const { groupSearchResult } = props; - - const [layout, setLayout] = useState(GroupLayout.LIST); - - const { groups, hasNextPage, isFetching, fetchNextPage } = groupSearchResult; - - const handleLoadMore = () => { - if (hasNextPage && !isFetching) { - fetchNextPage(); - } - }; - - const renderGroupList = useCallback((group: Group, index: number) => ( -
- -
- ), []); - - const renderGroupGrid = useCallback((group: Group) => ( - - ), []); - - return ( - - - - - - - setLayout(selectedLayout)} - /> - - - {layout === GroupLayout.LIST ? ( - renderGroupList(group, index)} - endReached={handleLoadMore} - /> - ) : ( - renderGroupGrid(group)} - components={{ - Item: (props) => ( -
- ), - List: GridList, - }} - endReached={handleLoadMore} - /> - )} - - ); -}; \ No newline at end of file diff --git a/src/features/groups/components/discover/search/search.test.tsx b/src/features/groups/components/discover/search/search.test.tsx deleted file mode 100644 index 5c25cc5a3..000000000 --- a/src/features/groups/components/discover/search/search.test.tsx +++ /dev/null @@ -1,63 +0,0 @@ -import React from 'react'; - -import { __stub } from 'soapbox/api'; -import { buildGroup } from 'soapbox/jest/factory'; -import { render, screen, waitFor } from 'soapbox/jest/test-helpers'; -import { instanceSchema } from 'soapbox/schemas'; - -import Search from './search'; - -const store = { - instance: instanceSchema.parse({ - version: '3.4.1 (compatible; TruthSocial 1.0.0+unreleased)', - }), -}; - -const renderApp = (children: React.ReactElement) => render(children, undefined, store); - -describe('', () => { - describe('with no results', () => { - beforeEach(() => { - __stub((mock) => { - mock.onGet('/api/v1/groups/search').reply(200, []); - }); - }); - - it('should render the blankslate', async () => { - renderApp(); - - await waitFor(() => { - expect(screen.getByTestId('no-results')).toBeInTheDocument(); - }); - }); - }); - - describe('with results', () => { - beforeEach(() => { - __stub((mock) => { - mock.onGet('/api/v1/groups/search').reply(200, [ - buildGroup({ - display_name: 'Group', - id: '1', - }), - ]); - }); - }); - - it('should render the results', async () => { - renderApp(); - - await waitFor(() => { - expect(screen.getByTestId('results')).toBeInTheDocument(); - }); - }); - }); - - describe('before starting a search', () => { - it('should render the RecentSearches component', () => { - renderApp(); - - expect(screen.getByTestId('recent-searches')).toBeInTheDocument(); - }); - }); -}); \ No newline at end of file diff --git a/src/features/groups/components/discover/search/search.tsx b/src/features/groups/components/discover/search/search.tsx deleted file mode 100644 index 2d414d0c5..000000000 --- a/src/features/groups/components/discover/search/search.tsx +++ /dev/null @@ -1,99 +0,0 @@ -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 { saveGroupSearch } from 'soapbox/utils/groups'; - -import Blankslate from './blankslate'; -import RecentSearches from './recent-searches'; -import Results from './results'; - -interface Props { - onSelect(value: string): void; - searchValue: string; -} - -export default (props: Props) => { - const { onSelect, searchValue } = props; - - const { account: me } = useOwnAccount(); - const debounce = useDebounce; - - const debouncedValue = debounce(searchValue as string, 300); - const debouncedValueToSave = debounce(searchValue as string, 1000); - - const groupSearchResult = useGroupSearch(debouncedValue); - const { groups, isLoading, isFetched, isError } = groupSearchResult; - - const hasSearchResults = isFetched && groups.length > 0; - const hasNoSearchResults = isFetched && groups.length === 0; - - useEffect(() => { - if (debouncedValueToSave && debouncedValueToSave.length >= 0) { - saveGroupSearch(me?.id as string, debouncedValueToSave); - } - }, [debouncedValueToSave]); - - if (isLoading) { - return ( - - - - - - ); - } - - if (isError) { - return ( - - } - subtitle={ - - } - /> - ); - } - - if (hasNoSearchResults) { - return ( - - } - subtitle={ - - } - /> - ); - } - - if (hasSearchResults) { - return ( - - ); - } - - return ( - - ); -}; \ No newline at end of file diff --git a/src/features/groups/components/discover/suggested-groups.tsx b/src/features/groups/components/discover/suggested-groups.tsx deleted file mode 100644 index 5d73cc3f0..000000000 --- a/src/features/groups/components/discover/suggested-groups.tsx +++ /dev/null @@ -1,81 +0,0 @@ -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 GroupGridItem from './group-grid-item'; - -const SuggestedGroups = () => { - const { groups, isFetching, isFetched, isError } = useSuggestedGroups(); - const isEmpty = (isFetched && groups.length === 0) || isError; - - const [groupCover, setGroupCover] = useState(null); - - return ( - - - - - - - - - - - - - - {isEmpty ? ( - - - - ) : ( - - {({ width }: { width: number }) => ( - <> - {isFetching ? ( - new Array(20).fill(0).map((_, idx) => ( -
- -
- )) - ) : ( - groups.map((group) => ( - - )) - )} - - )} -
- )} -
- ); -}; - -export default SuggestedGroups; \ No newline at end of file diff --git a/src/features/groups/components/discover/tag-list-item.test.tsx b/src/features/groups/components/discover/tag-list-item.test.tsx deleted file mode 100644 index 6d8485d99..000000000 --- a/src/features/groups/components/discover/tag-list-item.test.tsx +++ /dev/null @@ -1,16 +0,0 @@ -import React from 'react'; - -import { buildGroupTag } from 'soapbox/jest/factory'; -import { render, screen } from 'soapbox/jest/test-helpers'; - -import TagListItem from './tag-list-item'; - -describe(' { - it('should render correctly', () => { - const tag = buildGroupTag({ name: 'tag 1', groups: 5 }); - render(); - - expect(screen.getByTestId('tag-list-item')).toHaveTextContent(tag.name); - expect(screen.getByTestId('tag-list-item')).toHaveTextContent('Number of groups: 5'); - }); -}); \ No newline at end of file diff --git a/src/features/groups/components/discover/tag-list-item.tsx b/src/features/groups/components/discover/tag-list-item.tsx deleted file mode 100644 index 102614105..000000000 --- a/src/features/groups/components/discover/tag-list-item.tsx +++ /dev/null @@ -1,43 +0,0 @@ -import React from 'react'; -import { FormattedMessage } from 'react-intl'; -import { Link } from 'react-router-dom'; - -import { Stack, Text } from 'soapbox/components/ui'; - -import type { GroupTag } from 'soapbox/schemas'; - -interface ITagListItem { - tag: GroupTag; -} - -const TagListItem = (props: ITagListItem) => { - const { tag } = props; - - return ( - - - - #{tag.name} - - - - - :{' '} - {tag.groups} - - - - ); -}; - -export default TagListItem; \ No newline at end of file diff --git a/src/features/groups/components/group-link-preview.tsx b/src/features/groups/components/group-link-preview.tsx deleted file mode 100644 index 6a7645bc9..000000000 --- a/src/features/groups/components/group-link-preview.tsx +++ /dev/null @@ -1,39 +0,0 @@ -import React from 'react'; -import { FormattedMessage } from 'react-intl'; - -import { Avatar, Button, CardTitle, Stack } from 'soapbox/components/ui'; -import { type Card as StatusCard } from 'soapbox/types/entities'; - -interface IGroupLinkPreview { - card: StatusCard; -} - -const GroupLinkPreview: React.FC = ({ card }) => { - const { group } = card; - if (!group) return null; - - return ( - -
- - - - - } /> - - - - - ); -}; - -export { GroupLinkPreview }; diff --git a/src/features/groups/components/pending-groups-row.tsx b/src/features/groups/components/pending-groups-row.tsx deleted file mode 100644 index 101da43bc..000000000 --- a/src/features/groups/components/pending-groups-row.tsx +++ /dev/null @@ -1,28 +0,0 @@ -import React from 'react'; - -import { usePendingGroups } from 'soapbox/api/hooks'; -import { PendingItemsRow } from 'soapbox/components/pending-items-row'; -import { Divider } from 'soapbox/components/ui'; -import { useFeatures } from 'soapbox/hooks'; - -export default () => { - const features = useFeatures(); - - const { groups, isFetching } = usePendingGroups(); - - if (!features.groupsPending || isFetching || groups.length === 0) { - return null; - } - - return ( - <> - - - - - ); -}; \ No newline at end of file diff --git a/src/features/groups/discover.test.tsx b/src/features/groups/discover.test.tsx deleted file mode 100644 index 880b17c1c..000000000 --- a/src/features/groups/discover.test.tsx +++ /dev/null @@ -1,79 +0,0 @@ -import userEvent from '@testing-library/user-event'; -import React from 'react'; - -import { buildAccount } from 'soapbox/jest/factory'; -import { render, screen, waitFor } from 'soapbox/jest/test-helpers'; -import { instanceSchema } from 'soapbox/schemas'; - -import Discover from './discover'; - -vi.mock('../../../hooks/useDimensions', () => ({ - useDimensions: () => [{ scrollWidth: 190 }, null, { width: 300 }], -})); - -(window as any).ResizeObserver = class ResizeObserver { - - observe() { } - disconnect() { } - -}; - -const userId = '1'; -const store: any = { - me: userId, - accounts: { - [userId]: buildAccount({ - id: userId, - acct: 'justin-username', - display_name: 'Justin L', - avatar: 'test.jpg', - source: { - chats_onboarded: false, - }, - }), - }, - instance: instanceSchema.parse({ - version: '3.4.1 (compatible; TruthSocial 1.0.0)', - software: 'TRUTHSOCIAL', - }), -}; - -const renderApp = () => ( - render( - , - undefined, - store, - ) -); - -describe('', () => { - describe('before the user starts searching', () => { - it('it should render popular groups', async () => { - renderApp(); - - await waitFor(() => { - expect(screen.getByTestId('popular-groups')).toBeInTheDocument(); - expect(screen.getByTestId('suggested-groups')).toBeInTheDocument(); - expect(screen.getByTestId('popular-tags')).toBeInTheDocument(); - expect(screen.queryAllByTestId('recent-searches')).toHaveLength(0); - expect(screen.queryAllByTestId('group-search-icon')).toHaveLength(0); - - }); - }); - }); - - describe('when the user focuses on the input', () => { - it('should render the search experience', async () => { - const user = userEvent.setup(); - renderApp(); - - await user.click(screen.getByTestId('search')); - - await waitFor(() => { - expect(screen.getByTestId('group-search-icon')).toBeInTheDocument(); - expect(screen.getByTestId('recent-searches')).toBeInTheDocument(); - expect(screen.queryAllByTestId('popular-groups')).toHaveLength(0); - }); - }); - }); -}); \ No newline at end of file diff --git a/src/features/groups/discover.tsx b/src/features/groups/discover.tsx deleted file mode 100644 index 0bb5af179..000000000 --- a/src/features/groups/discover.tsx +++ /dev/null @@ -1,84 +0,0 @@ -import React, { useState } from 'react'; -import { defineMessages, useIntl } from 'react-intl'; - -import { HStack, Icon, IconButton, Input, Stack } from 'soapbox/components/ui'; - -import PopularGroups from './components/discover/popular-groups'; -import PopularTags from './components/discover/popular-tags'; -import Search from './components/discover/search/search'; -import SuggestedGroups from './components/discover/suggested-groups'; -import TabBar, { TabItems } from './components/tab-bar'; - -const messages = defineMessages({ - placeholder: { id: 'groups.discover.search.placeholder', defaultMessage: 'Search' }, -}); - -const Discover: React.FC = () => { - const intl = useIntl(); - - const [isSearching, setIsSearching] = useState(false); - const [value, setValue] = useState(''); - - const hasSearchValue = value && value.length > 0; - - const cancelSearch = () => { - clearValue(); - setIsSearching(false); - }; - - const clearValue = () => setValue(''); - - return ( - - - - - - {isSearching ? ( - - ) : null} - - setValue(event.target.value)} - onFocus={() => setIsSearching(true)} - outerClassName='mt-0 w-full' - theme='search' - append={ - - } - /> - - - {isSearching ? ( - setValue(newValue)} - /> - ) : ( - <> - - - - - )} - - - ); -}; - -export default Discover; diff --git a/src/features/groups/index.tsx b/src/features/groups/index.tsx index f85b9d474..e09de8985 100644 --- a/src/features/groups/index.tsx +++ b/src/features/groups/index.tsx @@ -1,36 +1,23 @@ -import React, { useState } from 'react'; -import { defineMessages, FormattedMessage, useIntl } from 'react-intl'; +import React from 'react'; +import { FormattedMessage } 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 { Button, Stack, Text } from 'soapbox/components/ui'; +import { useAppDispatch, useAppSelector } from 'soapbox/hooks'; import { PERMISSION_CREATE_GROUPS, hasPermission } from 'soapbox/utils/permissions'; import PlaceholderGroupCard from '../placeholder/components/placeholder-group-card'; -import PendingGroupsRow from './components/pending-groups-row'; -import TabBar, { TabItems } from './components/tab-bar'; - -const messages = defineMessages({ - placeholder: { id: 'groups.search.placeholder', defaultMessage: 'Search My Groups' }, -}); - const Groups: React.FC = () => { - const debounce = useDebounce; const dispatch = useAppDispatch(); - const features = useFeatures(); - const intl = useIntl(); const canCreateGroup = useAppSelector((state) => hasPermission(state, PERMISSION_CREATE_GROUPS)); - const [searchValue, setSearchValue] = useState(''); - const debouncedValue = debounce(searchValue, 300); - - const { groups, isLoading, hasNextPage, fetchNextPage } = useGroups(debouncedValue); + const { groups, isLoading, hasNextPage, fetchNextPage } = useGroups(); const handleLoadMore = () => { if (hasNextPage) { @@ -72,10 +59,6 @@ const Groups: React.FC = () => { return ( - {features.groupsDiscovery && ( - - )} - {canCreateGroup && ( )} - {features.groupsSearch ? ( - setSearchValue(event.target.value)} - placeholder={intl.formatMessage(messages.placeholder)} - theme='search' - value={searchValue} - /> - ) : null} - - - { hasMore={hasNextPage} > {groups.map((group) => ( - + ))} diff --git a/src/features/groups/pending-requests.tsx b/src/features/groups/pending-requests.tsx deleted file mode 100644 index 34a5a56e0..000000000 --- a/src/features/groups/pending-requests.tsx +++ /dev/null @@ -1,67 +0,0 @@ -import React from 'react'; -import { defineMessages, FormattedMessage, useIntl } from 'react-intl'; -import { Link } from 'react-router-dom'; - -import { usePendingGroups } from 'soapbox/api/hooks'; -import GroupCard from 'soapbox/components/group-card'; -import ScrollableList from 'soapbox/components/scrollable-list'; -import { Column, Stack, Text } from 'soapbox/components/ui'; - -import PlaceholderGroupCard from '../placeholder/components/placeholder-group-card'; - -const messages = defineMessages({ - label: { id: 'groups.pending.label', defaultMessage: 'Pending Requests' }, -}); - -export default () => { - const intl = useIntl(); - - const { groups, isLoading } = usePendingGroups(); - - const renderBlankslate = () => ( - - - - - - - - - - - - ); - - return ( - - - {groups.map((group) => ( - - - - ))} - - - ); -}; \ No newline at end of file diff --git a/src/features/groups/popular.tsx b/src/features/groups/popular.tsx deleted file mode 100644 index a2c464d79..000000000 --- a/src/features/groups/popular.tsx +++ /dev/null @@ -1,88 +0,0 @@ -import clsx from 'clsx'; -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 GroupGridItem from './components/discover/group-grid-item'; -import GroupListItem from './components/discover/group-list-item'; -import LayoutButtons, { GroupLayout } from './components/discover/layout-buttons'; - -import type { Group } from 'soapbox/schemas'; - -const messages = defineMessages({ - label: { id: 'groups.popular.label', defaultMessage: 'Suggested Groups' }, -}); - -const GridList: Components['List'] = React.forwardRef((props, ref) => { - const { context, ...rest } = props; - return
; -}); - -const Popular: React.FC = () => { - const intl = useIntl(); - - const [layout, setLayout] = useState(GroupLayout.LIST); - - const { groups, hasNextPage, fetchNextPage } = usePopularGroups(); - - const handleLoadMore = () => { - if (hasNextPage) { - fetchNextPage(); - } - }; - - const renderGroupList = useCallback((group: Group, index: number) => ( -
- -
- ), []); - - const renderGroupGrid = useCallback((group: Group) => ( - - ), []); - - return ( - setLayout(selectedLayout)} - /> - } - > - {layout === GroupLayout.LIST ? ( - renderGroupList(group, index)} - endReached={handleLoadMore} - /> - ) : ( - renderGroupGrid(group)} - components={{ - Item: (props) => ( -
- ), - List: GridList, - }} - endReached={handleLoadMore} - /> - )} - - ); -}; - -export default Popular; diff --git a/src/features/groups/suggested.tsx b/src/features/groups/suggested.tsx deleted file mode 100644 index 8a3e570e0..000000000 --- a/src/features/groups/suggested.tsx +++ /dev/null @@ -1,88 +0,0 @@ -import clsx from 'clsx'; -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 GroupGridItem from './components/discover/group-grid-item'; -import GroupListItem from './components/discover/group-list-item'; -import LayoutButtons, { GroupLayout } from './components/discover/layout-buttons'; - -import type { Group } from 'soapbox/schemas'; - -const messages = defineMessages({ - label: { id: 'groups.suggested.label', defaultMessage: 'Suggested Groups' }, -}); - -const GridList: Components['List'] = React.forwardRef((props, ref) => { - const { context, ...rest } = props; - return
; -}); - -const Suggested: React.FC = () => { - const intl = useIntl(); - - const [layout, setLayout] = useState(GroupLayout.LIST); - - const { groups, hasNextPage, fetchNextPage } = useSuggestedGroups(); - - const handleLoadMore = () => { - if (hasNextPage) { - fetchNextPage(); - } - }; - - const renderGroupList = useCallback((group: Group, index: number) => ( -
- -
- ), []); - - const renderGroupGrid = useCallback((group: Group) => ( - - ), []); - - return ( - setLayout(selectedLayout)} - /> - } - > - {layout === GroupLayout.LIST ? ( - renderGroupList(group, index)} - endReached={handleLoadMore} - /> - ) : ( - renderGroupGrid(group)} - components={{ - Item: (props) => ( -
- ), - List: GridList, - }} - endReached={handleLoadMore} - /> - )} - - ); -}; - -export default Suggested; diff --git a/src/features/groups/tag.tsx b/src/features/groups/tag.tsx deleted file mode 100644 index 44d30be0a..000000000 --- a/src/features/groups/tag.tsx +++ /dev/null @@ -1,115 +0,0 @@ -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 GroupGridItem from './components/discover/group-grid-item'; -import GroupListItem from './components/discover/group-list-item'; - -import type { Group } from 'soapbox/schemas'; - -enum Layout { - LIST = 'LIST', - GRID = 'GRID' -} - -const GridList: Components['List'] = React.forwardRef((props, ref) => { - const { context, ...rest } = props; - return
; -}); - -interface ITag { - params: { id: string }; -} - -const Tag: React.FC = (props) => { - const tagId = props.params.id; - - const [layout, setLayout] = useState(Layout.LIST); - - const { tag, isLoading } = useGroupTag(tagId); - const { groups, hasNextPage, fetchNextPage } = useGroupsFromTag(tagId); - - const handleLoadMore = () => { - if (hasNextPage) { - fetchNextPage(); - } - }; - - const renderGroupList = useCallback((group: Group, index: number) => ( -
- -
- ), []); - - const renderGroupGrid = useCallback((group: Group) => ( - - ), []); - - if (isLoading || !tag) { - return null; - } - - return ( - - - - - - } - > - {layout === Layout.LIST ? ( - renderGroupList(group, index)} - endReached={handleLoadMore} - /> - ) : ( - renderGroupGrid(group)} - components={{ - Item: (props) => ( -
- ), - List: GridList, - }} - endReached={handleLoadMore} - /> - )} - - ); -}; - -export default Tag; diff --git a/src/features/groups/tags.tsx b/src/features/groups/tags.tsx deleted file mode 100644 index aa37a514b..000000000 --- a/src/features/groups/tags.tsx +++ /dev/null @@ -1,62 +0,0 @@ -import clsx from 'clsx'; -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 TagListItem from './components/discover/tag-list-item'; - -import type { GroupTag } from 'soapbox/schemas'; - -const messages = defineMessages({ - title: { id: 'groups.tags.title', defaultMessage: 'Browse Topics' }, -}); - -const Tags: React.FC = () => { - const intl = useIntl(); - - const { tags, isFetched, isError, hasNextPage, fetchNextPage } = usePopularTags(); - const isEmpty = (isFetched && tags.length === 0) || isError; - - const handleLoadMore = () => { - if (hasNextPage) { - fetchNextPage(); - } - }; - - const renderItem = (index: number, tag: GroupTag) => ( -
- -
- ); - - return ( - - {isEmpty ? ( - - - - ) : ( - - )} - - ); -}; - -export default Tags; diff --git a/src/features/mutes/components/group-list-item.tsx b/src/features/mutes/components/group-list-item.tsx index 2e839678c..e71b10f4d 100644 --- a/src/features/mutes/components/group-list-item.tsx +++ b/src/features/mutes/components/group-list-item.tsx @@ -1,56 +1,28 @@ import React from 'react'; -import { FormattedMessage, defineMessages, useIntl } from 'react-intl'; -import { useUnmuteGroup } from 'soapbox/api/hooks'; import GroupAvatar from 'soapbox/components/groups/group-avatar'; -import { Button, HStack, Text } from 'soapbox/components/ui'; +import { HStack, Text } from 'soapbox/components/ui'; import { type Group } from 'soapbox/schemas'; -import toast from 'soapbox/toast'; interface IGroupListItem { group: Group; onUnmute(): void; } -const messages = defineMessages({ - unmuteSuccess: { id: 'group.unmute.success', defaultMessage: 'Unmuted the group' }, -}); +const GroupListItem = ({ group, onUnmute }: IGroupListItem) => ( + + -const GroupListItem = ({ group, onUnmute }: IGroupListItem) => { - const intl = useIntl(); - - const unmuteGroup = useUnmuteGroup(group); - - const handleUnmute = () => { - unmuteGroup.mutate(undefined, { - onSuccess() { - onUnmute(); - toast.success(intl.formatMessage(messages.unmuteSuccess)); - }, - }); - }; - - return ( - - - - - - - - - - ); -}; + + +); export default GroupListItem; \ No newline at end of file diff --git a/src/features/mutes/index.tsx b/src/features/mutes/index.tsx index c7c25bf35..38782db56 100644 --- a/src/features/mutes/index.tsx +++ b/src/features/mutes/index.tsx @@ -1,26 +1,18 @@ -import React, { useState } from 'react'; +import React from 'react'; import { defineMessages, useIntl, FormattedMessage } from 'react-intl'; -import { useMutes, useGroupMutes } from 'soapbox/api/hooks'; +import { useMutes } from 'soapbox/api/hooks'; import ScrollableList from 'soapbox/components/scrollable-list'; -import { Column, Stack, Tabs } from 'soapbox/components/ui'; +import { Column, Stack } from 'soapbox/components/ui'; import AccountContainer from 'soapbox/containers/account-container'; -import { useFeatures } from 'soapbox/hooks'; - -import GroupListItem from './components/group-list-item'; const messages = defineMessages({ heading: { id: 'column.mutes', defaultMessage: 'Mutes' }, }); -enum TabItems { - ACCOUNTS = 'ACCOUNTS', - GROUPS = 'GROUPS' -} const Mutes: React.FC = () => { const intl = useIntl(); - const features = useFeatures(); const { accounts, @@ -29,17 +21,6 @@ const Mutes: React.FC = () => { isLoading: isLoadingAccounts, } = useMutes(); - const { - mutes: groupMutes, - isLoading: isLoadingGroups, - hasNextPage: hasNextGroupsPage, - fetchNextPage: fetchNextGroups, - fetchEntities: fetchMutedGroups, - } = useGroupMutes(); - - const [activeItem, setActiveItem] = useState(TabItems.ACCOUNTS); - const isAccountsTabSelected = activeItem === TabItems.ACCOUNTS; - const scrollableListProps = { itemClassName: 'pb-4 last:pb-0', scrollKey: 'mutes', @@ -49,56 +30,19 @@ const Mutes: React.FC = () => { return ( - {features.groupsMuting && ( - setActiveItem(TabItems.ACCOUNTS), - name: TabItems.ACCOUNTS, - }, - { - text: 'Groups', - action: () => setActiveItem(TabItems.GROUPS), - name: TabItems.GROUPS, - }, - ]} - activeItem={activeItem} - /> - )} - - {isAccountsTabSelected ? ( - - } - > - {accounts.map((accounts) => - , - )} - - ) : ( - - } - > - {groupMutes.map((group) =>( - - ))} - - )} + + } + > + {accounts.map((accounts) => + , + )} + ); diff --git a/src/features/notifications/components/notification.tsx b/src/features/notifications/components/notification.tsx index 7a05a7d23..c1a44133e 100644 --- a/src/features/notifications/components/notification.tsx +++ b/src/features/notifications/components/notification.tsx @@ -1,5 +1,5 @@ import React, { useCallback } from 'react'; -import { defineMessages, useIntl, FormattedMessage, IntlShape, MessageDescriptor, defineMessage } from 'react-intl'; +import { defineMessages, useIntl, IntlShape, MessageDescriptor } from 'react-intl'; import { Link, useHistory } from 'react-router-dom'; import { mentionCompose } from 'soapbox/actions/compose'; @@ -58,11 +58,6 @@ const icons: Record = { 'pleroma:participation_accepted': require('@tabler/icons/outline/calendar-event.svg'), }; -const nameMessage = defineMessage({ - id: 'notification.name', - defaultMessage: '{link}{others}', -}); - const messages: Record = defineMessages({ follow: { id: 'notification.follow', @@ -138,21 +133,10 @@ const buildMessage = ( intl: IntlShape, type: NotificationType, account: AccountEntity, - totalCount: number | null, targetName: string, instanceTitle: string, ): React.ReactNode => { - const link = buildLink(account); - const name = intl.formatMessage(nameMessage, { - link, - others: totalCount && totalCount > 0 ? ( - - ) : '', - }); + const name = buildLink(account); return intl.formatMessage(messages[type], { name, @@ -356,7 +340,7 @@ const Notification: React.FC = (props) => { const targetName = notification.target && typeof notification.target === 'object' ? notification.target.acct : ''; - const message: React.ReactNode = validType(type) && account && typeof account === 'object' ? buildMessage(intl, type, account, notification.total_count, targetName, instance.title) : null; + const message: React.ReactNode = validType(type) && account && typeof account === 'object' ? buildMessage(intl, type, account, targetName, instance.title) : null; const ariaLabel = validType(type) ? ( notificationForScreenReader( diff --git a/src/features/status/components/detailed-status.tsx b/src/features/status/components/detailed-status.tsx index e421ee35b..2c1bbee9a 100644 --- a/src/features/status/components/detailed-status.tsx +++ b/src/features/status/components/detailed-status.tsx @@ -67,7 +67,7 @@ const DetailedStatus: React.FC = ({ defaultMessage='Posted in {group}' values={{ group: ( - + diff --git a/src/features/status/components/thread.tsx b/src/features/status/components/thread.tsx index b6a7c4514..b2b2393fc 100644 --- a/src/features/status/components/thread.tsx +++ b/src/features/status/components/thread.tsx @@ -77,15 +77,11 @@ interface IThread { withMedia?: boolean; useWindowScroll?: boolean; itemClassName?: string; - next: string | undefined; - handleLoadMore: () => void; } const Thread = (props: IThread) => { const { - handleLoadMore, itemClassName, - next, status, useWindowScroll = true, withMedia = true, @@ -439,8 +435,6 @@ const Thread = (props: IThread) => { } initialTopMostItemIndex={initialTopMostItemIndex} useWindowScroll={useWindowScroll} diff --git a/src/features/status/index.tsx b/src/features/status/index.tsx index 58640403c..19be5f2e3 100644 --- a/src/features/status/index.tsx +++ b/src/features/status/index.tsx @@ -1,12 +1,8 @@ -import debounce from 'lodash/debounce'; import React, { useCallback, useEffect, useState } from 'react'; import { defineMessages, useIntl } from 'react-intl'; import { Redirect } from 'react-router-dom'; -import { - fetchStatusWithContext, - fetchNext, -} from 'soapbox/actions/statuses'; +import { fetchStatusWithContext } from 'soapbox/actions/statuses'; import MissingIndicator from 'soapbox/components/missing-indicator'; import PullToRefresh from 'soapbox/components/pull-to-refresh'; import { Column, Stack } from 'soapbox/components/ui'; @@ -38,7 +34,6 @@ const messages = defineMessages({ type RouteParams = { statusId: string; groupId?: string; - groupSlug?: string; }; interface IStatusDetails { @@ -54,14 +49,12 @@ const StatusDetails: React.FC = (props) => { const status = useAppSelector((state) => getStatus(state, { id: props.params.statusId })); const [isLoaded, setIsLoaded] = useState(!!status); - const [next, setNext] = useState(); /** Fetch the status (and context) from the API. */ - const fetchData = async () => { + const fetchData = () => { const { params } = props; const { statusId } = params; - const { next } = await dispatch(fetchStatusWithContext(statusId)); - setNext(next); + return dispatch(fetchStatusWithContext(statusId)); }; // Load data. @@ -73,14 +66,6 @@ const StatusDetails: React.FC = (props) => { }); }, [props.params.statusId]); - const handleLoadMore = useCallback(debounce(() => { - if (next && status) { - dispatch(fetchNext(status.id, next)).then(({ next }) => { - setNext(next); - }).catch(() => { }); - } - }, 300, { leading: true }), [next, status]); - const handleRefresh = () => { return fetchData(); }; @@ -104,8 +89,8 @@ const StatusDetails: React.FC = (props) => { } if (status.group && typeof status.group === 'object') { - if (status.group.slug && !props.params.groupSlug) { - return ; + if (status.group.id && !props.params.groupId) { + return ; } } @@ -118,11 +103,7 @@ const StatusDetails: React.FC = (props) => { - + diff --git a/src/features/test-timeline/index.tsx b/src/features/test-timeline/index.tsx index 623ef9d33..21a455019 100644 --- a/src/features/test-timeline/index.tsx +++ b/src/features/test-timeline/index.tsx @@ -22,7 +22,6 @@ const MOCK_STATUSES: any[] = [ require('soapbox/__fixtures__/pleroma-status-vertical-video-without-metadata.json'), require('soapbox/__fixtures__/pleroma-status-with-poll-with-emojis.json'), require('soapbox/__fixtures__/pleroma-quote-of-quote-post.json'), - require('soapbox/__fixtures__/truthsocial-status-in-moderation.json'), ]; const timelineId = 'test'; diff --git a/src/features/ui/components/compose-button.tsx b/src/features/ui/components/compose-button.tsx index 7686f9c55..e2e03ab12 100644 --- a/src/features/ui/components/compose-button.tsx +++ b/src/features/ui/components/compose-button.tsx @@ -4,15 +4,15 @@ import { useLocation, useRouteMatch } from 'react-router-dom'; import { groupComposeModal } from 'soapbox/actions/compose'; import { openModal } from 'soapbox/actions/modals'; -import { useGroupLookup } from 'soapbox/api/hooks'; +import { useGroup } from 'soapbox/api/hooks'; import { Avatar, Button, HStack } from 'soapbox/components/ui'; import { useAppDispatch } from 'soapbox/hooks'; const ComposeButton = () => { const location = useLocation(); const isOnGroupPage = location.pathname.startsWith('/group/'); - const match = useRouteMatch<{ groupSlug: string }>('/group/:groupSlug'); - const { entity: group } = useGroupLookup(match?.params.groupSlug || ''); + const match = useRouteMatch<{ groupId: string }>('/group/:groupId'); + const { group } = useGroup(match?.params.groupId || ''); const isGroupMember = !!group?.relationship?.member; if (isOnGroupPage && isGroupMember) { @@ -40,8 +40,8 @@ const HomeComposeButton = () => { const GroupComposeButton = () => { const dispatch = useAppDispatch(); - const match = useRouteMatch<{ groupSlug: string }>('/group/:groupSlug'); - const { entity: group } = useGroupLookup(match?.params.groupSlug || ''); + const match = useRouteMatch<{ groupId: string }>('/group/:groupId'); + const { group } = useGroup(match?.params.groupId || ''); if (!group) return null; diff --git a/src/features/ui/components/floating-action-button.tsx b/src/features/ui/components/floating-action-button.tsx index 73c43e7c1..db52a990e 100644 --- a/src/features/ui/components/floating-action-button.tsx +++ b/src/features/ui/components/floating-action-button.tsx @@ -5,7 +5,7 @@ import { useLocation, useRouteMatch } from 'react-router-dom'; import { groupComposeModal } from 'soapbox/actions/compose'; import { openModal } from 'soapbox/actions/modals'; -import { useGroupLookup } from 'soapbox/api/hooks'; +import { useGroup } from 'soapbox/api/hooks'; import { Avatar, HStack, Icon } from 'soapbox/components/ui'; import { useAppDispatch } from 'soapbox/hooks'; @@ -53,8 +53,8 @@ const GroupFAB: React.FC = () => { const intl = useIntl(); const dispatch = useAppDispatch(); - const match = useRouteMatch<{ groupSlug: string }>('/group/:groupSlug'); - const { entity: group } = useGroupLookup(match?.params.groupSlug || ''); + const match = useRouteMatch<{ groupId: string }>('/group/:groupId'); + const { group } = useGroup(match?.params.groupId || ''); if (!group) return null; diff --git a/src/features/ui/components/modals/compose-modal.tsx b/src/features/ui/components/modals/compose-modal.tsx index b5fc73c33..e9fe0e7b4 100644 --- a/src/features/ui/components/modals/compose-modal.tsx +++ b/src/features/ui/components/modals/compose-modal.tsx @@ -2,12 +2,11 @@ import clsx from 'clsx'; import React, { useRef } from 'react'; import { defineMessages, FormattedMessage, useIntl } from 'react-intl'; -import { cancelReplyCompose, setGroupTimelineVisible, uploadCompose } from 'soapbox/actions/compose'; +import { cancelReplyCompose, uploadCompose } from 'soapbox/actions/compose'; import { openModal, closeModal } from 'soapbox/actions/modals'; -import { useGroup } from 'soapbox/api/hooks'; import { checkComposeContent } from 'soapbox/components/modal-root'; -import { HStack, Modal, Text, Toggle } from 'soapbox/components/ui'; -import { useAppDispatch, useAppSelector, useCompose, useDraggedFiles } from 'soapbox/hooks'; +import { Modal } from 'soapbox/components/ui'; +import { useAppDispatch, useCompose, useDraggedFiles } from 'soapbox/hooks'; import ComposeForm from '../../../compose/components/compose-form'; @@ -82,50 +81,9 @@ const ComposeModal: React.FC = ({ onClose, composeId = 'compose-m 'ring-2 ring-offset-2 ring-primary-600': isDraggedOver, })} > - } - autoFocus - /> + ); }; -interface IComposeFormGroupToggle { - composeId: string; - groupId: string | null; -} - -const ComposeFormGroupToggle: React.FC = ({ composeId, groupId }) => { - const dispatch = useAppDispatch(); - const { group } = useGroup(groupId || '', false); - - const groupTimelineVisible = useAppSelector((state) => !!state.compose.get(composeId)?.group_timeline_visible); - - const handleToggleChange = () => { - dispatch(setGroupTimelineVisible(composeId, !groupTimelineVisible)); - }; - - const labelId = `group-timeline-visible+${composeId}`; - - if (!group) return null; - if (group.locked) return null; - - return ( - - - - - ); -}; - export default ComposeModal; diff --git a/src/features/ui/components/modals/manage-group-modal/create-group-modal.tsx b/src/features/ui/components/modals/manage-group-modal/create-group-modal.tsx index 11782047e..e608134b9 100644 --- a/src/features/ui/components/modals/manage-group-modal/create-group-modal.tsx +++ b/src/features/ui/components/modals/manage-group-modal/create-group-modal.tsx @@ -3,9 +3,8 @@ import React, { useMemo, useState } from 'react'; import { defineMessages, FormattedMessage, useIntl } from 'react-intl'; import { z } from 'zod'; -import { useCreateGroup, useGroupValidation, type CreateGroupParams } from 'soapbox/api/hooks'; +import { useCreateGroup, type CreateGroupParams } from 'soapbox/api/hooks'; import { Modal, Stack } from 'soapbox/components/ui'; -import { useDebounce } from 'soapbox/hooks'; import { type Group } from 'soapbox/schemas'; import toast from 'soapbox/toast'; @@ -31,7 +30,6 @@ interface ICreateGroupModal { const CreateGroupModal: React.FC = ({ onClose }) => { const intl = useIntl(); - const debounce = useDebounce; const [group, setGroup] = useState(null); const [params, setParams] = useState({ @@ -41,9 +39,6 @@ const CreateGroupModal: React.FC = ({ onClose }) => { const { createGroup, isSubmitting } = useCreateGroup(); - const debouncedName = debounce(params.display_name || '', 300); - const { data: { isValid } } = useGroupValidation(debouncedName); - const handleClose = () => { onClose('MANAGE_GROUP'); }; @@ -117,7 +112,7 @@ const CreateGroupModal: React.FC = ({ onClose }) => { title={renderModalTitle()} confirmationAction={handleNextStep} confirmationText={confirmationText} - confirmationDisabled={isSubmitting || (currentStep === Steps.TWO && !isValid)} + confirmationDisabled={isSubmitting} confirmationFullWidth onClose={handleClose} > diff --git a/src/features/ui/components/modals/manage-group-modal/steps/details-step.tsx b/src/features/ui/components/modals/manage-group-modal/steps/details-step.tsx index 2e4dec53a..a1c47ca65 100644 --- a/src/features/ui/components/modals/manage-group-modal/steps/details-step.tsx +++ b/src/features/ui/components/modals/manage-group-modal/steps/details-step.tsx @@ -1,12 +1,11 @@ import React from 'react'; import { defineMessages, FormattedMessage, useIntl } from 'react-intl'; -import { CreateGroupParams, useGroupValidation } from 'soapbox/api/hooks'; +import { CreateGroupParams } from 'soapbox/api/hooks'; import { Form, FormGroup, Input, Textarea } from 'soapbox/components/ui'; import AvatarPicker from 'soapbox/features/edit-profile/components/avatar-picker'; import HeaderPicker from 'soapbox/features/edit-profile/components/header-picker'; -import GroupTagsField from 'soapbox/features/group/components/group-tags-field'; -import { useAppSelector, useDebounce, useInstance } from 'soapbox/hooks'; +import { useAppSelector, useInstance } from 'soapbox/hooks'; import { usePreview } from 'soapbox/hooks/forms'; import resizeImage from 'soapbox/utils/resize-image'; @@ -23,18 +22,13 @@ interface IDetailsStep { const DetailsStep: React.FC = ({ params, onChange }) => { const intl = useIntl(); - const debounce = useDebounce; const instance = useInstance(); const { display_name: displayName = '', note = '', - tags = [''], } = params; - const debouncedName = debounce(displayName, 300); - const { data: { isValid, message: errorMessage } } = useGroupValidation(debouncedName); - const avatarSrc = usePreview(params.avatar); const headerSrc = usePreview(params.header); @@ -65,28 +59,6 @@ const DetailsStep: React.FC = ({ params, onChange }) => { const handleImageClear = (property: keyof CreateGroupParams) => () => onChange({ [property]: undefined }); - const handleTagsChange = (tags: string[]) => { - onChange({ - ...params, - tags, - }); - }; - - const handleAddTag = () => { - onChange({ - ...params, - tags: [...tags, ''], - }); - }; - - const handleRemoveTag = (i: number) => { - const newTags = [...tags]; - newTags.splice(i, 1); - onChange({ - ...params, - tags: newTags, - }); - }; return (
@@ -98,7 +70,6 @@ const DetailsStep: React.FC = ({ params, onChange }) => { } hintText={} - errors={isValid ? [] : [errorMessage as string]} > = ({ params, onChange }) => { maxLength={Number(instance.configuration.groups.max_characters_description)} /> - -
- -
); }; diff --git a/src/features/ui/components/modals/media-modal.tsx b/src/features/ui/components/modals/media-modal.tsx index fe30f7247..a04eb7ac4 100644 --- a/src/features/ui/components/modals/media-modal.tsx +++ b/src/features/ui/components/modals/media-modal.tsx @@ -1,11 +1,10 @@ import clsx from 'clsx'; -import debounce from 'lodash/debounce'; import React, { useCallback, useEffect, useState } from 'react'; import { defineMessages, useIntl, FormattedMessage } from 'react-intl'; import { useHistory } from 'react-router-dom'; import ReactSwipeableViews from 'react-swipeable-views'; -import { fetchNext, fetchStatusWithContext } from 'soapbox/actions/statuses'; +import { fetchStatusWithContext } from 'soapbox/actions/statuses'; import ExtendedVideoPlayer from 'soapbox/components/extended-video-player'; import MissingIndicator from 'soapbox/components/missing-indicator'; import StatusActionBar from 'soapbox/components/status-action-bar'; @@ -68,7 +67,6 @@ const MediaModal: React.FC = (props) => { const actualStatus = useAppSelector((state) => getStatus(state, { id: status?.id as string })); const [isLoaded, setIsLoaded] = useState(!!status); - const [next, setNext] = useState(); const [index, setIndex] = useState(null); const [navigationHidden, setNavigationHidden] = useState(false); const [isFullScreen, setIsFullScreen] = useState(!status); @@ -185,19 +183,8 @@ const MediaModal: React.FC = (props) => { return null; }).toArray(); - const handleLoadMore = useCallback(debounce(() => { - if (next && status) { - dispatch(fetchNext(status?.id, next)).then(({ next }) => { - setNext(next); - }).catch(() => { }); - } - }, 300, { leading: true }), [next, status]); - /** Fetch the status (and context) from the API. */ - const fetchData = async () => { - const { next } = await dispatch(fetchStatusWithContext(status?.id as string)); - setNext(next); - }; + const fetchData = () => dispatch(fetchStatusWithContext(status?.id as string)); // Load data. useEffect(() => { @@ -351,8 +338,6 @@ const MediaModal: React.FC = (props) => { withMedia={false} useWindowScroll={false} itemClassName='px-4' - next={next} - handleLoadMore={handleLoadMore} />
)} diff --git a/src/features/ui/components/modals/report-modal/report-modal.tsx b/src/features/ui/components/modals/report-modal/report-modal.tsx index 77fbb1946..c38a5952d 100644 --- a/src/features/ui/components/modals/report-modal/report-modal.tsx +++ b/src/features/ui/components/modals/report-modal/report-modal.tsx @@ -6,10 +6,8 @@ import { submitReport, submitReportSuccess, submitReportFail, ReportableEntities import { expandAccountTimeline } from 'soapbox/actions/timelines'; import { useAccount } from 'soapbox/api/hooks'; import AttachmentThumbs from 'soapbox/components/attachment-thumbs'; -import GroupCard from 'soapbox/components/group-card'; -import List, { ListItem } from 'soapbox/components/list'; import StatusContent from 'soapbox/components/status-content'; -import { Avatar, HStack, Icon, Modal, ProgressBar, Stack, Text } from 'soapbox/components/ui'; +import { Modal, ProgressBar, Stack, Text } from 'soapbox/components/ui'; import AccountContainer from 'soapbox/containers/account-container'; import { useAppDispatch, useAppSelector, useInstance } from 'soapbox/hooks'; @@ -22,9 +20,6 @@ const messages = defineMessages({ done: { id: 'report.done', defaultMessage: 'Done' }, next: { id: 'report.next', defaultMessage: 'Next' }, submit: { id: 'report.submit', defaultMessage: 'Submit' }, - reportContext: { id: 'report.chat_message.context', defaultMessage: 'When reporting a user’s message, the five messages before and five messages after the one selected will be passed along to our moderation team for context.' }, - reportMessage: { id: 'report.chat_message.title', defaultMessage: 'Report message' }, - reportGroup: { id: 'report.group.title', defaultMessage: 'Report Group' }, cancel: { id: 'common.cancel', defaultMessage: 'Cancel' }, previous: { id: 'report.previous', defaultMessage: 'Previous' }, }); @@ -36,26 +31,9 @@ enum Steps { } const reportSteps = { - [ReportableEntities.ACCOUNT]: { - ONE: ReasonStep, - TWO: OtherActionsStep, - THREE: ConfirmationStep, - }, - [ReportableEntities.CHAT_MESSAGE]: { - ONE: ReasonStep, - TWO: OtherActionsStep, - THREE: ConfirmationStep, - }, - [ReportableEntities.STATUS]: { - ONE: ReasonStep, - TWO: OtherActionsStep, - THREE: ConfirmationStep, - }, - [ReportableEntities.GROUP]: { - ONE: ReasonStep, - TWO: null, - THREE: ConfirmationStep, - }, + ONE: ReasonStep, + TWO: OtherActionsStep, + THREE: ConfirmationStep, }; const SelectedStatus = ({ statusId }: { statusId: string }) => { @@ -107,14 +85,11 @@ const ReportModal = ({ onClose }: IReportModal) => { const { rules } = useInstance(); const ruleIds = useAppSelector((state) => state.reports.new.rule_ids); const selectedStatusIds = useAppSelector((state) => state.reports.new.status_ids); - const selectedChatMessage = useAppSelector((state) => state.reports.new.chat_message); - const selectedGroup = useAppSelector((state) => state.reports.new.group); const shouldRequireRule = rules.length > 0; const isReportingAccount = entityType === ReportableEntities.ACCOUNT; const isReportingStatus = entityType === ReportableEntities.STATUS; - const isReportingGroup = entityType === ReportableEntities.GROUP; const [currentStep, setCurrentStep] = useState(Steps.ONE); @@ -166,40 +141,23 @@ const ReportModal = ({ onClose }: IReportModal) => { const confirmationText = useMemo(() => { switch (currentStep) { case Steps.ONE: - if (isReportingGroup) { - return intl.formatMessage(messages.submit); - } else { - return intl.formatMessage(messages.next); - } + return intl.formatMessage(messages.next); case Steps.TWO: - if (isReportingGroup) { - return intl.formatMessage(messages.done); - } else { - return intl.formatMessage(messages.submit); - } + return intl.formatMessage(messages.submit); case Steps.THREE: return intl.formatMessage(messages.done); default: return intl.formatMessage(messages.next); } - }, [currentStep, isReportingGroup]); + }, [currentStep]); const handleNextStep = () => { switch (currentStep) { case Steps.ONE: - if (isReportingGroup) { - handleSubmit(); - } else { - setCurrentStep(Steps.TWO); - } + setCurrentStep(Steps.TWO); break; case Steps.TWO: - if (isReportingGroup) { - dispatch(submitReportSuccess()); - onClose(); - } else { - handleSubmit(); - } + handleSubmit(); break; case Steps.THREE: dispatch(submitReportSuccess()); @@ -210,64 +168,13 @@ const ReportModal = ({ onClose }: IReportModal) => { } }; - const renderSelectedChatMessage = () => { - if (account) { - return ( - - -
- -
- -
- -
-
- - - } - > - {intl.formatMessage(messages.reportContext)} - - -
- ); - } - }; - - const renderSelectedGroup = () => { - if (selectedGroup) { - return ; - } - }; - const renderSelectedEntity = () => { - switch (entityType) { - case ReportableEntities.STATUS: - return renderSelectedStatuses(); - case ReportableEntities.CHAT_MESSAGE: - return renderSelectedChatMessage(); - case ReportableEntities.GROUP: - if (currentStep === Steps.TWO) { - return null; - } - - return renderSelectedGroup(); - default: - return null; - } + if (entityType === ReportableEntities.STATUS) return renderSelectedStatuses(); + return null; }; const renderTitle = () => { - switch (entityType) { - case ReportableEntities.CHAT_MESSAGE: - return intl.formatMessage(messages.reportMessage); - case ReportableEntities.GROUP: - return intl.formatMessage(messages.reportGroup); - default: - return @{account?.acct}
}} />; - } + return @{account?.acct} }} />; }; const isConfirmationButtonDisabled = useMemo(() => { @@ -301,7 +208,7 @@ const ReportModal = ({ onClose }: IReportModal) => { return null; } - const StepToRender = reportSteps[entityType][currentStep]; + const StepToRender = reportSteps[currentStep]; return ( ( const ConfirmationStep: React.FC = () => { const intl = useIntl(); const links = useAppSelector((state) => getSoapboxConfig(state).get('links') as any); - const entityType = useAppSelector((state) => state.reports.new.entityType); - const entity = entityType === ReportableEntities.GROUP - ? intl.formatMessage(messages.groupEntity) - : intl.formatMessage(messages.accountEntity); + const entity = intl.formatMessage(messages.accountEntity); return ( diff --git a/src/features/ui/components/modals/report-modal/steps/other-actions-step.tsx b/src/features/ui/components/modals/report-modal/steps/other-actions-step.tsx index 67a56d66b..d4250b551 100644 --- a/src/features/ui/components/modals/report-modal/steps/other-actions-step.tsx +++ b/src/features/ui/components/modals/report-modal/steps/other-actions-step.tsx @@ -45,43 +45,41 @@ const OtherActionsStep = ({ account }: IOtherActionsStep) => { return ( - {features.reportMultipleStatuses && ( - - - {intl.formatMessage(messages.otherStatuses)} - + + + {intl.formatMessage(messages.otherStatuses)} + - - {showAdditionalStatuses ? ( - -
- {statusIds.map((statusId) => )} -
+ + {showAdditionalStatuses ? ( + +
+ {statusIds.map((statusId) => )} +
-
- -
-
- ) : ( - - )} -
-
- )} +
+ +
+
+ ) : ( + + )} + +
diff --git a/src/features/ui/components/modals/report-modal/steps/reason-step.tsx b/src/features/ui/components/modals/report-modal/steps/reason-step.tsx index 7703ab995..c72c3ab9c 100644 --- a/src/features/ui/components/modals/report-modal/steps/reason-step.tsx +++ b/src/features/ui/components/modals/report-modal/steps/reason-step.tsx @@ -2,12 +2,11 @@ import clsx from 'clsx'; import React, { useEffect, useRef, useState } from 'react'; import { defineMessages, useIntl } from 'react-intl'; -import { changeReportComment, changeReportRule, ReportableEntities } from 'soapbox/actions/reports'; +import { changeReportComment, changeReportRule } from 'soapbox/actions/reports'; import { FormGroup, Stack, Text, Textarea } from 'soapbox/components/ui'; import { useAppDispatch, useAppSelector, useInstance } from 'soapbox/hooks'; import type { Account } from 'soapbox/schemas'; -import type { Rule } from 'soapbox/schemas/rule'; const messages = defineMessages({ placeholder: { id: 'report.placeholder', defaultMessage: 'Additional comments' }, @@ -29,7 +28,6 @@ const ReasonStep: React.FC = () => { const [isNearBottom, setNearBottom] = useState(false); const [isNearTop, setNearTop] = useState(true); - const entityType = useAppSelector((state) => state.reports.new.entityType); const comment = useAppSelector((state) => state.reports.new.comment); const { rules } = useInstance(); const ruleIds = useAppSelector((state) => state.reports.new.rule_ids); @@ -57,32 +55,6 @@ const ReasonStep: React.FC = () => { } }; - const filterRuleType = (rule: Rule) => { - let ruleTypeToFilter = 'content'; - - switch (entityType) { - case ReportableEntities.ACCOUNT: - ruleTypeToFilter = 'account'; - break; - case ReportableEntities.STATUS: - case ReportableEntities.CHAT_MESSAGE: - ruleTypeToFilter = 'content'; - break; - case ReportableEntities.GROUP: - ruleTypeToFilter = 'group'; - break; - default: - ruleTypeToFilter = 'content'; - break; - } - - if (rule.rule_type) { - return rule.rule_type === ruleTypeToFilter; - } - - return true; - }; - useEffect(() => { if (rules.length > 0 && rulesListRef.current) { const { clientHeight } = rulesListRef.current; @@ -108,7 +80,7 @@ const ReasonStep: React.FC = () => { onScroll={handleRulesScrolling} ref={rulesListRef} > - {rules.filter(filterRuleType).map((rule, idx) => { + {rules.map((rule, idx) => { const isSelected = ruleIds.includes(String(rule.id)); return ( diff --git a/src/features/ui/components/panels/suggested-groups-panel.tsx b/src/features/ui/components/panels/suggested-groups-panel.tsx deleted file mode 100644 index d2671bc14..000000000 --- a/src/features/ui/components/panels/suggested-groups-panel.tsx +++ /dev/null @@ -1,33 +0,0 @@ -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'; - -const SuggestedGroupsPanel = () => { - const { groups, isFetching, isFetched, isError } = useSuggestedGroups(); - const isEmpty = (isFetched && groups.length === 0) || isError; - - if (isEmpty) { - return null; - } - - return ( - - {isFetching ? ( - new Array(3).fill(0).map((_, idx) => ( - - )) - ) : ( - groups.slice(0, 3).map((group) => ( - - )) - )} - - ); -}; - -export default SuggestedGroupsPanel; diff --git a/src/features/ui/components/profile-info-panel.tsx b/src/features/ui/components/profile-info-panel.tsx index 9f89bfeb8..0aa56c765 100644 --- a/src/features/ui/components/profile-info-panel.tsx +++ b/src/features/ui/components/profile-info-panel.tsx @@ -15,16 +15,6 @@ import ProfileStats from './profile-stats'; import type { Account } from 'soapbox/schemas'; -/** Basically ensure the URL isn't `javascript:alert('hi')` or something like that */ -const isSafeUrl = (text: string): boolean => { - try { - const url = new URL(text); - return ['http:', 'https:'].includes(url.protocol); - } catch (e) { - return false; - } -}; - const messages = defineMessages({ linkVerifiedOn: { id: 'account.link_verified_on', defaultMessage: 'Ownership of this link was checked on {date}' }, account_locked: { id: 'account.locked_info', defaultMessage: 'This account privacy status is set to locked. The owner manually reviews who can follow them.' }, @@ -205,25 +195,6 @@ const ProfileInfoPanel: React.FC = ({ account, username }) => ) : null} - {account.website ? ( - - - -
- - {isSafeUrl(account.website) ? ( - {account.website} - ) : ( - account.website - )} - -
-
- ) : null} - {renderBirthday()}
diff --git a/src/features/ui/index.tsx b/src/features/ui/index.tsx index f31315c02..b409d7cfa 100644 --- a/src/features/ui/index.tsx +++ b/src/features/ui/index.tsx @@ -26,7 +26,6 @@ import EventPage from 'soapbox/pages/event-page'; import EventsPage from 'soapbox/pages/events-page'; import GroupPage from 'soapbox/pages/group-page'; import GroupsPage from 'soapbox/pages/groups-page'; -import GroupsPendingPage from 'soapbox/pages/groups-pending-page'; import HomePage from 'soapbox/pages/home-page'; import LandingPage from 'soapbox/pages/landing-page'; import ManageGroupsPage from 'soapbox/pages/manage-groups-page'; @@ -71,7 +70,6 @@ import { EditProfile, EditEmail, EditPassword, - EmailConfirmation, DeleteAccount, SoapboxConfig, ExportData, @@ -111,15 +109,7 @@ import { Events, GroupGallery, Groups, - GroupsDiscover, - GroupsPopular, - GroupsSuggested, - GroupsTag, - GroupsTags, - PendingGroupRequests, GroupMembers, - GroupTags, - GroupTagTimeline, GroupTimeline, ManageGroup, GroupBlockedMembers, @@ -131,7 +121,6 @@ import { RegistrationPage, LoginPage, PasswordReset, - PasswordResetConfirm, RegisterInvite, ExternalLogin, LandingTimeline, @@ -173,7 +162,6 @@ const SwitchingColumnsArea: React.FC = ({ children }) => {standalone && } - {isLoggedIn ? ( @@ -289,14 +277,6 @@ const SwitchingColumnsArea: React.FC = ({ children }) => {features.groups && } - {features.groupsDiscovery && } - {features.groupsDiscovery && } - {features.groupsDiscovery && } - {features.groupsDiscovery && } - {features.groupsDiscovery && } - {features.groupsPending && } - {features.groupsTags && } - {features.groupsTags && } {features.groups && } {features.groups && } {features.groups && } @@ -361,7 +341,6 @@ const SwitchingColumnsArea: React.FC = ({ children }) => - diff --git a/src/features/ui/util/async-components.ts b/src/features/ui/util/async-components.ts index 738cfc847..1b955faca 100644 --- a/src/features/ui/util/async-components.ts +++ b/src/features/ui/util/async-components.ts @@ -65,7 +65,6 @@ export const RegistrationPage = lazy(() => import('soapbox/features/auth-login/c export const Settings = lazy(() => import('soapbox/features/settings')); export const EditProfile = lazy(() => import('soapbox/features/edit-profile')); export const EditEmail = lazy(() => import('soapbox/features/edit-email')); -export const EmailConfirmation = lazy(() => import('soapbox/features/email-confirmation')); export const EditPassword = lazy(() => import('soapbox/features/edit-password')); export const DeleteAccount = lazy(() => import('soapbox/features/delete-account')); export const SoapboxConfig = lazy(() => import('soapbox/features/soapbox-config')); @@ -73,7 +72,6 @@ export const ExportData = lazy(() => import('soapbox/features/export-data')); export const ImportData = lazy(() => import('soapbox/features/import-data')); export const Backups = lazy(() => import('soapbox/features/backups')); export const PasswordReset = lazy(() => import('soapbox/features/auth-login/components/password-reset')); -export const PasswordResetConfirm = lazy(() => import('soapbox/features/auth-login/components/password-reset-confirm')); export const MfaForm = lazy(() => import('soapbox/features/security/mfa-form')); export const ChatIndex = lazy(() => import('soapbox/features/chats')); export const ChatWidget = lazy(() => import('soapbox/features/chats/components/chat-widget/chat-widget')); @@ -136,15 +134,7 @@ export const EventMapModal = lazy(() => import('soapbox/features/ui/components/m export const EventParticipantsModal = lazy(() => import('soapbox/features/ui/components/modals/event-participants-modal')); export const Events = lazy(() => import('soapbox/features/events')); export const Groups = lazy(() => import('soapbox/features/groups')); -export const GroupsDiscover = lazy(() => import('soapbox/features/groups/discover')); -export const GroupsPopular = lazy(() => import('soapbox/features/groups/popular')); -export const GroupsSuggested = lazy(() => import('soapbox/features/groups/suggested')); -export const GroupsTag = lazy(() => import('soapbox/features/groups/tag')); -export const GroupsTags = lazy(() => import('soapbox/features/groups/tags')); -export const PendingGroupRequests = lazy(() => import('soapbox/features/groups/pending-requests')); export const GroupMembers = lazy(() => import('soapbox/features/group/group-members')); -export const GroupTags = lazy(() => import('soapbox/features/group/group-tags')); -export const GroupTagTimeline = lazy(() => import('soapbox/features/group/group-tag-timeline')); export const GroupTimeline = lazy(() => import('soapbox/features/group/group-timeline')); export const ManageGroup = lazy(() => import('soapbox/features/group/manage-group')); export const EditGroup = lazy(() => import('soapbox/features/group/edit-group')); @@ -154,7 +144,6 @@ export const GroupGallery = lazy(() => import('soapbox/features/group/group-gall export const CreateGroupModal = lazy(() => import('soapbox/features/ui/components/modals/manage-group-modal/create-group-modal')); export const NewGroupPanel = lazy(() => import('soapbox/features/ui/components/panels/new-group-panel')); export const MyGroupsPanel = lazy(() => import('soapbox/features/ui/components/panels/my-groups-panel')); -export const SuggestedGroupsPanel = lazy(() => import('soapbox/features/ui/components/panels/suggested-groups-panel')); export const GroupMediaPanel = lazy(() => import('soapbox/features/ui/components/group-media-panel')); export const NewEventPanel = lazy(() => import('soapbox/features/ui/components/panels/new-event-panel')); export const Announcements = lazy(() => import('soapbox/features/admin/announcements')); diff --git a/src/hooks/index.ts b/src/hooks/index.ts index 1da5fb9e4..8d3b7a510 100644 --- a/src/hooks/index.ts +++ b/src/hooks/index.ts @@ -7,7 +7,6 @@ export { useCompose } from './useCompose'; export { useDebounce } from './useDebounce'; export { useDraggedFiles } from './useDraggedFiles'; export { useGetState } from './useGetState'; -export { useGroupsPath } from './useGroupsPath'; export { useDimensions } from './useDimensions'; export { useFeatures } from './useFeatures'; export { useInstance } from './useInstance'; diff --git a/src/hooks/useGroupsPath.test.ts b/src/hooks/useGroupsPath.test.ts deleted file mode 100644 index 72af53731..000000000 --- a/src/hooks/useGroupsPath.test.ts +++ /dev/null @@ -1,80 +0,0 @@ -import { __stub } from 'soapbox/api'; -import { buildAccount, buildGroup, buildGroupRelationship } from 'soapbox/jest/factory'; -import { renderHook, waitFor } from 'soapbox/jest/test-helpers'; -import { instanceSchema } from 'soapbox/schemas'; - -import { useGroupsPath } from './useGroupsPath'; - -describe('useGroupsPath()', () => { - test('without the groupsDiscovery feature', () => { - const store = { - instance: instanceSchema.parse({ - version: '2.7.2 (compatible; Pleroma 2.3.0)', - }), - }; - - const { result } = renderHook(useGroupsPath, undefined, store); - - expect(result.current).toEqual('/groups'); - }); - - describe('with the "groupsDiscovery" feature', () => { - let store: any; - - beforeEach(() => { - const userId = '1'; - store = { - instance: instanceSchema.parse({ - version: '3.4.1 (compatible; TruthSocial 1.0.0+unreleased)', - }), - me: userId, - accounts: { - [userId]: buildAccount({ - id: userId, - acct: 'justin-username', - display_name: 'Justin L', - avatar: 'test.jpg', - source: { - chats_onboarded: false, - }, - }), - }, - }; - }); - - describe('when the user has no groups', () => { - test('should default to the discovery page', () => { - const { result } = renderHook(useGroupsPath, undefined, store); - - expect(result.current).toEqual('/groups/discover'); - }); - }); - - describe('when the user has groups', () => { - beforeEach(() => { - __stub((mock) => { - mock.onGet('/api/v1/groups').reply(200, [ - buildGroup({ - display_name: 'Group', - id: '1', - }), - ]); - - mock.onGet('/api/v1/groups/relationships?id[]=1').reply(200, [ - buildGroupRelationship({ - id: '1', - }), - ]); - }); - }); - - test('should default to the "My Groups" page', async () => { - const { result } = renderHook(useGroupsPath, undefined, store); - - await waitFor(() => { - expect(result.current).toEqual('/groups'); - }); - }); - }); - }); -}); diff --git a/src/hooks/useGroupsPath.ts b/src/hooks/useGroupsPath.ts deleted file mode 100644 index 71aa3c8da..000000000 --- a/src/hooks/useGroupsPath.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { useGroups } from 'soapbox/api/hooks/groups/useGroups'; - -import { useFeatures } from './useFeatures'; - -/** - * Determine the correct URL to use for /groups. - * If the user does not have any Groups, let's default to the discovery tab. - * Otherwise, let's default to My Groups. - * - * @returns String (as link) - */ -const useGroupsPath = () => { - const features = useFeatures(); - const { groups } = useGroups(); - - if (!features.groupsDiscovery) { - return '/groups'; - } - - return groups.length > 0 ? '/groups' : '/groups/discover'; -}; - -export { useGroupsPath }; \ No newline at end of file diff --git a/src/jest/factory.ts b/src/jest/factory.ts index 3bb61f77c..62817d4aa 100644 --- a/src/jest/factory.ts +++ b/src/jest/factory.ts @@ -6,7 +6,6 @@ import { groupMemberSchema, groupRelationshipSchema, groupSchema, - groupTagSchema, relationshipSchema, statusSchema, type Account, @@ -14,7 +13,6 @@ import { type Group, type GroupMember, type GroupRelationship, - type GroupTag, type Relationship, type Status, Instance, @@ -55,13 +53,6 @@ function buildGroupRelationship(props: PartialDeep = {}): Gro }, props)); } -function buildGroupTag(props: PartialDeep = {}): GroupTag { - return groupTagSchema.parse(Object.assign({ - id: uuidv4(), - name: uuidv4(), - }, props)); -} - function buildGroupMember( props: PartialDeep = {}, accountProps: PartialDeep = {}, @@ -96,7 +87,6 @@ export { buildGroup, buildGroupMember, buildGroupRelationship, - buildGroupTag, buildInstance, buildRelationship, buildStatus, diff --git a/src/jest/fixtures/chats.json b/src/jest/fixtures/chats.json index e1d60383d..101851fc7 100644 --- a/src/jest/fixtures/chats.json +++ b/src/jest/fixtures/chats.json @@ -2,20 +2,17 @@ { "id": "1", "unread": 0, - "created_by_account": "2", "last_message": { "account_id": "2", "chat_id": "85", "content": "last message content", "created_at": "2022-09-28T17:43:01.432Z", "id": "1166", - "unread": false, - "discarded_at": "2022-09-29T19:09:30.253Z" + "unread": false }, "created_at": "2022-08-26T14:49:16.360Z", "updated_at": "2022-09-29T19:09:30.257Z", "accepted": true, - "discarded_at": null, "account": { "id": "2", "username": "leonard", @@ -25,27 +22,23 @@ "avatar": "original.jpg", "avatar_static": "original.jpg", "verified": false, - "accepting_messages": true, - "chats_onboarded": true + "accepting_messages": true } }, { "id": "2", "unread": 0, - "created_by_account": "3", "last_message": { "account_id": "3", "chat_id": "125", "content": "\u003cp\u003eInventore enim numquam nihil facilis nostrum eum natus provident quis veritatis esse dolorem praesentium rem cumque.\u003c/p\u003e", "created_at": "2022-09-23T14:09:29.625Z", "id": "1033", - "unread": false, - "discarded_at": null + "unread": false }, "created_at": "2022-09-22T15:06:49.675Z", "updated_at": "2022-09-23T14:09:29.628Z", "accepted": true, - "discarded_at": null, "account": { "id": "3", "username": "sheldon", @@ -55,8 +48,7 @@ "avatar": "original.jpg", "avatar_static": "original.jpg", "verified": false, - "accepting_messages": true, - "chats_onboarded": true + "accepting_messages": true } } ] \ No newline at end of file diff --git a/src/locales/en.json b/src/locales/en.json index b3aa5d72d..b719ef896 100644 --- a/src/locales/en.json +++ b/src/locales/en.json @@ -261,11 +261,6 @@ "chat.page_settings.submit": "Save", "chat.page_settings.title": "Message Settings", "chat.retry": "Retry?", - "chat.welcome.accepting_messages.label": "Allow users to start a new chat with you", - "chat.welcome.notice": "You can change these settings later.", - "chat.welcome.submit": "Save & Continue", - "chat.welcome.subtitle": "Exchange direct messages with other users.", - "chat.welcome.title": "Welcome to {br} Chats!", "chat_composer.unblock": "Unblock", "chat_list_item.blocked_you": "This user has blocked you", "chat_list_item.blocking": "You have blocked this user", @@ -274,14 +269,6 @@ "chat_message_list.network_failure.action": "Try again", "chat_message_list.network_failure.subtitle": "We encountered a network failure.", "chat_message_list.network_failure.title": "Whoops!", - "chat_message_list_intro.actions.accept": "Accept", - "chat_message_list_intro.actions.leave_chat": "Leave chat", - "chat_message_list_intro.actions.message_lifespan": "Messages older than {day, plural, one {# day} other {# days}} are deleted.", - "chat_message_list_intro.actions.report": "Report", - "chat_message_list_intro.intro": "wants to start a chat with you", - "chat_message_list_intro.leave_chat.confirm": "Leave Chat", - "chat_message_list_intro.leave_chat.heading": "Leave Chat", - "chat_message_list_intro.leave_chat.message": "Are you sure you want to leave this chat? Messages will be deleted for you and this chat will be removed from your inbox.", "chat_pane.blankslate.action": "Message someone", "chat_pane.blankslate.body": "Search for someone to chat with.", "chat_pane.blankslate.title": "No messages yet", @@ -291,14 +278,6 @@ "chat_search.empty_results_blankslate.title": "No matches found", "chat_search.placeholder": "Type a name", "chat_search.title": "Messages", - "chat_settings.auto_delete.14days": "14 days", - "chat_settings.auto_delete.2minutes": "2 minutes", - "chat_settings.auto_delete.30days": "30 days", - "chat_settings.auto_delete.7days": "7 days", - "chat_settings.auto_delete.90days": "90 days", - "chat_settings.auto_delete.days": "{day, plural, one {# day} other {# days}}", - "chat_settings.auto_delete.hint": "Sent messages will auto-delete after the time period selected", - "chat_settings.auto_delete.label": "Auto-delete messages", "chat_settings.block.confirm": "Block", "chat_settings.block.heading": "Block @{acct}", "chat_settings.block.message": "Blocking will prevent this profile from direct messaging you and viewing your content. You can unblock later.", @@ -313,8 +292,6 @@ "chat_settings.unblock.confirm": "Unblock", "chat_settings.unblock.heading": "Unblock @{acct}", "chat_settings.unblock.message": "Unblocking will allow this profile to direct message you and view your content.", - "chat_window.auto_delete_label": "Auto-delete after {day, plural, one {# day} other {# days}}", - "chat_window.auto_delete_tooltip": "Chat messages are set to auto-delete after {day, plural, one {# day} other {# days}} upon sending.", "chats.actions.copy": "Copy", "chats.actions.delete": "Delete for both", "chats.actions.delete_for_me": "Delete for me", @@ -326,7 +303,6 @@ "chats.main.blankslate.title": "No messages yet", "chats.main.blankslate_with_chats.subtitle": "Select from one of your open chats or create a new message.", "chats.main.blankslate_with_chats.title": "Select a chat", - "chats.search_placeholder": "Start a chat with…", "column.admin.announcements": "Announcements", "column.admin.awaiting_approval": "Awaiting Approval", "column.admin.create_announcement": "Create announcement", @@ -570,9 +546,6 @@ "confirmations.mute.confirm": "Mute", "confirmations.mute.heading": "Mute @{name}", "confirmations.mute.message": "Are you sure you want to mute {name}?", - "confirmations.mute_group.confirm": "Mute", - "confirmations.mute_group.heading": "Mute Group", - "confirmations.mute_group.message": "You are about to mute the group. Do you want to continue?", "confirmations.redraft.confirm": "Delete & redraft", "confirmations.redraft.heading": "Delete & redraft", "confirmations.redraft.message": "Are you sure you want to delete this post and re-draft it? Favorites and reposts will be lost, and replies to the original post will be orphaned.", @@ -663,8 +636,6 @@ "edit_profile.fields.nip05_placeholder": "user@{domain}", "edit_profile.fields.nip05_unverified": "Name could not be verified and won't be used.", "edit_profile.fields.stranger_notifications_label": "Block notifications from strangers", - "edit_profile.fields.website_label": "Website", - "edit_profile.fields.website_placeholder": "Display a Link", "edit_profile.header": "Edit Profile", "edit_profile.hints.accepts_email_list": "Opt-in to news and marketing updates.", "edit_profile.hints.bot": "This account mainly performs automated actions and might not be monitored", @@ -675,7 +646,6 @@ "edit_profile.hints.stranger_notifications": "Only show notifications from people you follow", "edit_profile.save": "Save", "edit_profile.success": "Your profile has been successfully saved!", - "email_confirmation.success": "Your email has been confirmed!", "embed.instructions": "Embed this post on your website by copying the code below.", "emoji_button.activity": "Activity", "emoji_button.add_custom": "Add custom emoji", @@ -849,9 +819,6 @@ "group.manage": "Manage Group", "group.member.admin.limit.summary": "You can assign up to {count, plural, one {admin} other {admins}} for the group at this time.", "group.member.admin.limit.title": "Admin limit reached", - "group.mute.label": "Mute", - "group.mute.long_label": "Mute Group", - "group.mute.success": "Muted the group", "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", @@ -873,56 +840,14 @@ "group.tabs.media": "Media", "group.tabs.members": "Members", "group.tabs.tags": "Topics", - "group.tags.empty": "There are no topics in this group yet.", - "group.tags.hidden.success": "Topic marked as hidden", - "group.tags.hide": "Hide topic", - "group.tags.hint": "Add up to 3 keywords that will serve as core topics of discussion in the group.", - "group.tags.label": "Tags", - "group.tags.pin": "Pin topic", - "group.tags.pin.success": "Pinned!", - "group.tags.show": "Show topic", - "group.tags.total": "Total Posts", - "group.tags.unpin": "Unpin topic", - "group.tags.unpin.success": "Unpinned!", - "group.tags.visible.success": "Topic marked as visible", - "group.unmute.label": "Unmute", - "group.unmute.long_label": "Unmute Group", - "group.unmute.success": "Unmuted the group", "group.update.success": "Group successfully saved", "group.upload_avatar": "Upload avatar", "group.upload_banner": "Upload photo", "group.upload_banner.title": "Upload background picture", - "groups.discover.popular.empty": "Unable to fetch popular groups at this time. Please check back later.", - "groups.discover.popular.show_more": "Show More", - "groups.discover.popular.title": "Popular Groups", - "groups.discover.search.error.subtitle": "Please try again later.", - "groups.discover.search.error.title": "An error occurred", - "groups.discover.search.no_results.subtitle": "Try searching for another group.", - "groups.discover.search.no_results.title": "No matches found", - "groups.discover.search.placeholder": "Search", - "groups.discover.search.recent_searches.blankslate.subtitle": "Search group names, topics or keywords", - "groups.discover.search.recent_searches.blankslate.title": "No recent searches", - "groups.discover.search.recent_searches.clear_all": "Clear all", - "groups.discover.search.recent_searches.title": "Recent searches", - "groups.discover.search.results.groups": "Groups", "groups.discover.search.results.member_count": "{members, plural, one {member} other {members}}", - "groups.discover.suggested.empty": "Unable to fetch suggested groups at this time. Please check back later.", - "groups.discover.suggested.show_more": "Show More", - "groups.discover.suggested.title": "Suggested For You", - "groups.discover.tags.empty": "Unable to fetch popular topics at this time. Please check back later.", - "groups.discover.tags.show_more": "Show More", - "groups.discover.tags.title": "Browse Topics", - "groups.discovery.tags.no_of_groups": "Number of groups", "groups.empty.subtitle": "Start discovering groups to join or create your own.", "groups.empty.title": "No Groups yet", "groups.pending.count": "{number, plural, one {# pending request} other {# pending requests}}", - "groups.pending.empty.subtitle": "You have no pending requests at this time.", - "groups.pending.empty.title": "No pending requests", - "groups.pending.label": "Pending Requests", - "groups.popular.label": "Suggested Groups", - "groups.search.placeholder": "Search My Groups", - "groups.suggested.label": "Suggested Groups", - "groups.tags.title": "Browse Topics", "hashtag.follow": "Follow hashtag", "header.login.label": "Log in", "header.register.label": "Register", @@ -1092,7 +1017,6 @@ "mute_modal.auto_expire": "Automatically expire mute?", "mute_modal.duration": "Duration", "mute_modal.hide_notifications": "Hide notifications from this user?", - "mutes.empty.groups": "You haven't muted any groups yet.", "navbar.login.action": "Log in", "navbar.login.email.placeholder": "E-mail address", "navbar.login.forgot_password": "Forgot password?", @@ -1162,8 +1086,6 @@ "notification.mention": "{name} mentioned you", "notification.mentioned": "{name} mentioned you", "notification.move": "{name} moved to {targetName}", - "notification.name": "{link}{others}", - "notification.others": "+ {count, plural, one {# other} other {# others}}", "notification.pleroma:chat_mention": "{name} sent you a message", "notification.pleroma:emoji_reaction": "{name} reacted to your post", "notification.pleroma:event_reminder": "An event you are participating in starts soon", @@ -1343,16 +1265,12 @@ "reply_mentions.reply_empty": "Replying to post", "report.block": "Block {target}", "report.block_hint": "Do you also want to block this account?", - "report.chat_message.context": "When reporting a user’s message, the five messages before and five messages after the one selected will be passed along to our moderation team for context.", - "report.chat_message.title": "Report message", "report.confirmation.content": "If we find that this {entity} is violating the {link} we will take further action on the matter.", "report.confirmation.entity.account": "account", - "report.confirmation.entity.group": "group", "report.confirmation.title": "Thanks for submitting your report.", "report.done": "Done", "report.forward": "Forward to {target}", "report.forward_hint": "The account is from another server. Send a copy of the report there as well?", - "report.group.title": "Report Group", "report.next": "Next", "report.other_actions.add_additional": "Would you like to add additional statuses to this report?", "report.other_actions.add_more": "Add more", @@ -1365,10 +1283,6 @@ "report.reason.title": "Reason for reporting", "report.submit": "Submit", "report.target": "Reporting {target}", - "reset_password.fail": "Expired token, please try again.", - "reset_password.header": "Set New Password", - "reset_password.password.label": "Password", - "reset_password.password.placeholder": "Placeholder", "save": "Save", "schedule.post_time": "Post Date/Time", "schedule.remove": "Remove schedule", diff --git a/src/normalizers/account.test.ts b/src/normalizers/account.test.ts index 15a26ecdc..fa5335c97 100644 --- a/src/normalizers/account.test.ts +++ b/src/normalizers/account.test.ts @@ -86,30 +86,12 @@ describe('normalizeAccount()', () => { expect(result.verified).toBe(false); }); - it('normalizes a verified Truth Social user', async () => { - const account = await import('soapbox/__fixtures__/realDonaldTrump.json'); - const result = normalizeAccount(account); - expect(result.verified).toBe(true); - }); - it('normalizes Fedibird location', async () => { const account = await import('soapbox/__fixtures__/fedibird-account.json'); const result = normalizeAccount(account); expect(result.location).toBe('Texas, USA'); }); - it('normalizes Truth Social location', async () => { - const account = await import('soapbox/__fixtures__/truthsocial-account.json'); - const result = normalizeAccount(account); - expect(result.location).toBe('Texas'); - }); - - it('normalizes Truth Social website', async () => { - const account = await import('soapbox/__fixtures__/truthsocial-account.json'); - const result = normalizeAccount(account); - expect(result.website).toBe('https://soapbox.pub'); - }); - it('sets display_name from username', () => { const account = { username: 'alex' }; const result = normalizeAccount(account); diff --git a/src/normalizers/account.ts b/src/normalizers/account.ts index 74a84e46b..4ffbe007d 100644 --- a/src/normalizers/account.ts +++ b/src/normalizers/account.ts @@ -27,7 +27,6 @@ export const AccountRecord = ImmutableRecord({ avatar_static: '', birthday: '', bot: false, - chats_onboarded: true, created_at: '', discoverable: false, display_name: '', @@ -52,7 +51,6 @@ export const AccountRecord = ImmutableRecord({ uri: '', url: '', username: '', - website: '', verified: false, // Internal fields @@ -147,7 +145,7 @@ const getTags = (account: ImmutableMap): ImmutableList => { return ImmutableList(ImmutableList.isList(tags) ? tags : []); }; -/** Normalize Truth Social/Pleroma verified */ +/** Normalize Pleroma verified */ const normalizeVerified = (account: ImmutableMap) => { return account.update('verified', verified => { return [ @@ -164,7 +162,7 @@ const normalizeDonor = (account: ImmutableMap) => { return account.setIn(['pleroma', 'tags'], updated); }; -/** Normalize Fedibird/Truth Social/Pleroma location */ +/** Normalize Fedibird/Pleroma location */ const normalizeLocation = (account: ImmutableMap) => { return account.update('location', location => { return [ @@ -265,10 +263,9 @@ const normalizeDiscoverable = (account: ImmutableMap) => { return account.set('discoverable', discoverable); }; -/** Normalize message acceptance between Pleroma and Truth Social. */ +/** Normalize message acceptance. */ const normalizeMessageAcceptance = (account: ImmutableMap) => { - const acceptance = Boolean(account.getIn(['pleroma', 'accepts_chat_messages']) || account.get('accepting_messages')); - return account.set('accepts_chat_messages', acceptance); + return account.set('accepts_chat_messages', account.getIn(['pleroma', 'accepts_chat_messages'])); }; /** Normalize undefined/null birthday to empty string. */ diff --git a/src/normalizers/chat-message.ts b/src/normalizers/chat-message.ts index 5ec03bb59..eb0ec7120 100644 --- a/src/normalizers/chat-message.ts +++ b/src/normalizers/chat-message.ts @@ -6,10 +6,8 @@ import { } from 'immutable'; import { normalizeAttachment } from 'soapbox/normalizers/attachment'; -import { emojiReactionSchema } from 'soapbox/schemas'; -import { filteredArray } from 'soapbox/schemas/utils'; -import type { Attachment, Card, Emoji, EmojiReaction } from 'soapbox/types/entities'; +import type { Attachment, Card, Emoji } from 'soapbox/types/entities'; export const ChatMessageRecord = ImmutableRecord({ account_id: '', @@ -20,7 +18,6 @@ export const ChatMessageRecord = ImmutableRecord({ created_at: '', emojis: ImmutableList(), expiration: null as number | null, - emoji_reactions: null as readonly EmojiReaction[] | null, id: '', unread: false, deleting: false, @@ -40,11 +37,6 @@ const normalizeMedia = (status: ImmutableMap) => { } }; -const normalizeChatMessageEmojiReaction = (chatMessage: ImmutableMap) => { - const emojiReactions = ImmutableList(chatMessage.get('emoji_reactions') || []); - return chatMessage.set('emoji_reactions', filteredArray(emojiReactionSchema).parse(emojiReactions.toJS())); -}; - /** Rewrite `

` to empty string. */ const fixContent = (chatMessage: ImmutableMap) => { if (chatMessage.get('content') === '

') { @@ -58,7 +50,6 @@ export const normalizeChatMessage = (chatMessage: Record) => { return ChatMessageRecord( ImmutableMap(fromJS(chatMessage)).withMutations(chatMessage => { normalizeMedia(chatMessage); - normalizeChatMessageEmojiReaction(chatMessage); fixContent(chatMessage); }), ); diff --git a/src/normalizers/notification.ts b/src/normalizers/notification.ts index 45eb93fb3..4c12b017f 100644 --- a/src/normalizers/notification.ts +++ b/src/normalizers/notification.ts @@ -22,7 +22,6 @@ export const NotificationRecord = ImmutableRecord({ status: null as EmbeddedEntity, target: null as EmbeddedEntity, // move type: '', - total_count: null as number | null, // grouped notifications }); const normalizeType = (notification: ImmutableMap) => { diff --git a/src/pages/group-page.tsx b/src/pages/group-page.tsx index e2fbfccdd..a1e2bf545 100644 --- a/src/pages/group-page.tsx +++ b/src/pages/group-page.tsx @@ -10,9 +10,8 @@ import { CtaBanner, GroupMediaPanel, SignUpPanel, - SuggestedGroupsPanel, } from 'soapbox/features/ui/util/async-components'; -import { useFeatures, useOwnAccount } from 'soapbox/hooks'; +import { useOwnAccount } from 'soapbox/hooks'; import type { Group } from 'soapbox/schemas'; @@ -90,7 +89,6 @@ const BlockedBlankslate = ({ group }: { group: Group }) => ( /** Page to display a group. */ const GroupPage: React.FC = ({ params, children }) => { const intl = useIntl(); - const features = useFeatures(); const match = useRouteMatch(); const { account: me } = useOwnAccount(); @@ -108,34 +106,26 @@ const GroupPage: React.FC = ({ params, children }) => { const items = []; items.push({ text: intl.formatMessage(messages.all), - to: `/group/${group?.slug}`, - name: '/group/:groupSlug', + to: `/group/${id}`, + name: '/group/:groupId', }); - if (features.groupsTags) { - items.push({ - text: intl.formatMessage(messages.tags), - to: `/group/${group?.slug}/tags`, - name: '/group/:groupSlug/tags', - }); - } - items.push( { text: intl.formatMessage(messages.media), - to: `/group/${group?.slug}/media`, - name: '/group/:groupSlug/media', + to: `/group/${id}/media`, + name: '/group/:groupId/media', }, { text: intl.formatMessage(messages.members), - to: `/group/${group?.slug}/members`, - name: '/group/:groupSlug/members', + to: `/group/${id}/members`, + name: '/group/:groupId/members', count: pending.length, }, ); return items; - }, [features.groupsTags, pending.length, group?.slug]); + }, [pending.length, id]); const renderChildren = () => { if (isDeleted) { @@ -174,7 +164,6 @@ const GroupPage: React.FC = ({ params, children }) => { )} - diff --git a/src/pages/groups-page.tsx b/src/pages/groups-page.tsx index ed3f62501..26db8c562 100644 --- a/src/pages/groups-page.tsx +++ b/src/pages/groups-page.tsx @@ -1,9 +1,8 @@ import React from 'react'; -import { Route, Routes } from 'react-router-dom-v5-compat'; import { Column, Layout } from 'soapbox/components/ui'; import LinkFooter from 'soapbox/features/ui/components/link-footer'; -import { MyGroupsPanel, NewGroupPanel, SuggestedGroupsPanel } from 'soapbox/features/ui/util/async-components'; +import { MyGroupsPanel, NewGroupPanel } from 'soapbox/features/ui/util/async-components'; interface IGroupsPage { children: React.ReactNode; @@ -22,10 +21,7 @@ const GroupsPage: React.FC = ({ children }) => ( - - } /> - } /> - + diff --git a/src/pages/groups-pending-page.tsx b/src/pages/groups-pending-page.tsx deleted file mode 100644 index f692cf7bf..000000000 --- a/src/pages/groups-pending-page.tsx +++ /dev/null @@ -1,26 +0,0 @@ -import React from 'react'; - -import { Layout } from 'soapbox/components/ui'; -import LinkFooter from 'soapbox/features/ui/components/link-footer'; -import { NewGroupPanel, SuggestedGroupsPanel } from 'soapbox/features/ui/util/async-components'; - -interface IGroupsPage { - children: React.ReactNode; -} - -/** Page to display groups. */ -const GroupsPendingPage: React.FC = ({ children }) => ( - <> - - {children} - - - - - - - - -); - -export default GroupsPendingPage; diff --git a/src/pages/search-page.tsx b/src/pages/search-page.tsx index 338076d42..041560a0f 100644 --- a/src/pages/search-page.tsx +++ b/src/pages/search-page.tsx @@ -6,7 +6,6 @@ import { TrendsPanel, SignUpPanel, CtaBanner, - SuggestedGroupsPanel, } from 'soapbox/features/ui/util/async-components'; import { useAppSelector, useFeatures } from 'soapbox/hooks'; @@ -43,9 +42,6 @@ const SearchPage: React.FC = ({ children }) => { )} - {features.groups && ( - - )} diff --git a/src/queries/accounts.ts b/src/queries/accounts.ts index 06b7cb514..8b6fbc419 100644 --- a/src/queries/accounts.ts +++ b/src/queries/accounts.ts @@ -26,12 +26,10 @@ export type IAccount = { url: string; username: string; verified: boolean; - website: string; } type UpdateCredentialsData = { accepts_chat_messages?: boolean; - chats_onboarded?: boolean; } const useUpdateCredentials = () => { diff --git a/src/queries/chats.test.ts b/src/queries/chats.test.ts index 21a7ebd0d..66aafc3c9 100644 --- a/src/queries/chats.test.ts +++ b/src/queries/chats.test.ts @@ -7,13 +7,11 @@ import { buildAccount, buildRelationship } from 'soapbox/jest/factory'; import { createTestStore, mockStore, queryClient, renderHook, rootState, waitFor } from 'soapbox/jest/test-helpers'; import { normalizeChatMessage } from 'soapbox/normalizers'; import { Store } from 'soapbox/store'; -import { ChatMessage } from 'soapbox/types/entities'; import { flattenPages } from 'soapbox/utils/queries'; -import { ChatKeys, IChat, isLastMessage, useChat, useChatActions, useChatMessages, useChats } from './chats'; +import { ChatKeys, IChat, useChat, useChatActions, useChatMessages, useChats } from './chats'; const chat: IChat = { - accepted: true, account: buildAccount({ username: 'username', verified: true, @@ -23,15 +21,9 @@ const chat: IChat = { avatar_static: 'avatar', display_name: 'my name', }), - chat_type: 'direct', created_at: '2020-06-10T02:05:06.000Z', - created_by_account: '1', - discarded_at: null, id: '1', last_message: null, - latest_read_message_by_account: [], - latest_read_message_created_at: null, - message_expiration: 1209600, unread: 0, }; @@ -41,7 +33,6 @@ const buildChatMessage = (id: string) => normalizeChatMessage({ account_id: '1', content: `chat message #${id}`, created_at: '2020-06-10T02:05:06.000Z', - emoji_reactions: null, expiration: 1209600, unread: true, }); @@ -58,53 +49,6 @@ describe('ChatKeys', () => { expect(ChatKeys.chatMessages(id)).toEqual(['chats', 'messages', id]); }); - - it('has a "chatSearch" key', () => { - const searchQuery = 'che'; - - expect(ChatKeys.chatSearch()).toEqual(['chats', 'search']); - expect(ChatKeys.chatSearch(searchQuery)).toEqual(['chats', 'search', searchQuery]); - }); -}); - -describe('isLastMessage', () => { - describe('when its the last message', () => { - it('is truthy', () => { - const id = '5'; - const newChat = { ...chat, last_message: { id } } as any; - const initialQueryData = { - pages: [ - { result: [newChat], hasMore: false, link: undefined }, - ], - pageParams: [undefined], - }; - const initialFlattenedData = flattenPages(initialQueryData); - expect(sumBy(initialFlattenedData, (chat: IChat) => chat.unread)).toBe(0); - - queryClient.setQueryData(ChatKeys.chatSearch(), initialQueryData); - - expect(isLastMessage(id)).toBeTruthy(); - }); - }); - - describe('when its not the last message', () => { - it('is not truthy', () => { - const id = '5'; - const newChat = { ...chat, last_message: { id } } as any; - const initialQueryData = { - pages: [ - { result: [newChat], hasMore: false, link: undefined }, - ], - pageParams: [undefined], - }; - const initialFlattenedData = flattenPages(initialQueryData); - expect(sumBy(initialFlattenedData, (chat: IChat) => chat.unread)).toBe(0); - - queryClient.setQueryData(ChatKeys.chatSearch(), initialQueryData); - - expect(isLastMessage('10')).not.toBeTruthy(); - }); - }); }); describe('useChatMessages', () => { @@ -298,7 +242,7 @@ describe('useChatActions', () => { const initialFlattenedData = flattenPages(initialQueryData); expect(sumBy(initialFlattenedData, (chat: IChat) => chat.unread)).toBe(0); - queryClient.setQueryData(ChatKeys.chatSearch(), initialQueryData); + queryClient.setQueryData(['chats', 'search'], initialQueryData); const { result } = renderHook(() => useChatActions(chat.id).markChatAsRead('2')); @@ -306,7 +250,7 @@ describe('useChatActions', () => { expect(result.current).resolves.toBeDefined(); }); - const nextQueryData = queryClient.getQueryData(ChatKeys.chatSearch()); + const nextQueryData = queryClient.getQueryData(['chats', 'search']); const nextFlattenedData = flattenPages(nextQueryData as any); expect(sumBy(nextFlattenedData as any, (chat: IChat) => chat.unread)).toBe(nextUnreadCount); }); @@ -348,87 +292,4 @@ describe('useChatActions', () => { expect(result.current.data.data).toEqual({ hello: 'world' }); }); }); - - describe('updateChat()', () => { - const nextUnreadCount = 5; - - beforeEach(() => { - __stub((mock) => { - mock - .onPatch(`/api/v1/pleroma/chats/${chat.id}`) - .reply(200, { ...chat, unread: nextUnreadCount }); - }); - }); - - it('updates the queryCache for the chat', async() => { - const initialQueryData = { ...chat }; - expect(initialQueryData.message_expiration).toBe(1209600); - queryClient.setQueryData(ChatKeys.chat(chat.id), initialQueryData); - - const { result } = renderHook(() => { - const { updateChat } = useChatActions(chat.id); - - useEffect(() => { - updateChat.mutate({ message_expiration: 1200 }); - }, []); - - return updateChat; - }); - - await waitFor(() => { - expect(result.current.isLoading).toBe(false); - }); - - const nextQueryData = queryClient.getQueryData(ChatKeys.chat(chat.id)); - expect((nextQueryData as any).message_expiration).toBe(1200); - }); - }); - - describe('createReaction()', () => { - const chatMessage = buildChatMessage('1'); - - beforeEach(() => { - __stub((mock) => { - mock - .onPost(`/api/v1/pleroma/chats/${chat.id}/messages/${chatMessage.id}/reactions`) - .reply(200, { ...chatMessage.toJS(), emoji_reactions: [{ name: '👍', count: 1, me: true }] }); - }); - }); - - it('successfully updates the Chat Message record', async () => { - const initialQueryData = { - pages: [ - { result: [chatMessage], hasMore: false, link: undefined }, - ], - pageParams: [undefined], - }; - - queryClient.setQueryData(ChatKeys.chatMessages(chat.id), initialQueryData); - - const { result } = renderHook(() => { - const { createReaction } = useChatActions(chat.id); - - useEffect(() => { - createReaction.mutate({ - messageId: chatMessage.id, - emoji: '👍', - chatMessage, - }); - }, []); - - return createReaction; - }); - - await waitFor(() => { - expect(result.current.isLoading).toBe(false); - }); - - const updatedChatMessage = (queryClient.getQueryData(ChatKeys.chatMessages(chat.id)) as any).pages[0].result[0] as ChatMessage; - expect(updatedChatMessage.emoji_reactions).toEqual([{ - name: '👍', - count: 1, - me: true, - }]); - }); - }); }); diff --git a/src/queries/chats.ts b/src/queries/chats.ts index 8bf382900..ae14d0e4b 100644 --- a/src/queries/chats.ts +++ b/src/queries/chats.ts @@ -7,9 +7,8 @@ import { ChatWidgetScreens, useChatContext } from 'soapbox/contexts/chat-context import { useStatContext } from 'soapbox/contexts/stat-context'; import { useApi, useAppDispatch, useAppSelector, useFeatures, useOwnAccount } from 'soapbox/hooks'; import { normalizeChatMessage } from 'soapbox/normalizers'; -import toast from 'soapbox/toast'; import { ChatMessage } from 'soapbox/types/entities'; -import { reOrderChatListItems, updateChatMessage } from 'soapbox/utils/chats'; +import { reOrderChatListItems } from 'soapbox/utils/chats'; import { flattenPages, PaginatedResult, updatePageItem } from 'soapbox/utils/queries'; import { queryClient } from './client'; @@ -17,64 +16,24 @@ import { useFetchRelationships } from './relationships'; import type { Account } from 'soapbox/schemas'; -export const messageExpirationOptions = [604800, 1209600, 2592000, 7776000]; - -export enum MessageExpirationValues { - 'SEVEN' = messageExpirationOptions[0], - 'FOURTEEN' = messageExpirationOptions[1], - 'THIRTY' = messageExpirationOptions[2], - 'NINETY' = messageExpirationOptions[3] -} - export interface IChat { - accepted: boolean; account: Account; - chat_type: 'channel' | 'direct'; created_at: string; - created_by_account: string; - discarded_at: null | string; id: string; last_message: null | { account_id: string; chat_id: string; content: string; created_at: string; - discarded_at: string | null; id: string; unread: boolean; }; - latest_read_message_by_account?: { - id: string; - date: string; - }[]; - latest_read_message_created_at: null | string; - message_expiration?: MessageExpirationValues; unread: number; } -type UpdateChatVariables = { - message_expiration: MessageExpirationValues; -} - -type CreateReactionVariables = { - messageId: string; - emoji: string; - chatMessage?: ChatMessage; -} - const ChatKeys = { chat: (chatId?: string) => ['chats', 'chat', chatId] as const, chatMessages: (chatId: string) => ['chats', 'messages', chatId] as const, - chatSearch: (searchQuery?: string) => searchQuery ? ['chats', 'search', searchQuery] : ['chats', 'search'] as const, -}; - -/** Check if item is most recent */ -const isLastMessage = (chatMessageId: string): boolean => { - const queryData = queryClient.getQueryData>>(ChatKeys.chatSearch()); - const items = flattenPages(queryData); - const chat = items?.find((item) => item.last_message?.id === chatMessageId); - - return !!chat; }; const useChatMessages = (chat: IChat) => { @@ -122,7 +81,7 @@ const useChatMessages = (chat: IChat) => { }; }; -const useChats = (search?: string) => { +const useChats = () => { const api = useApi(); const dispatch = useAppDispatch(); const features = useFeatures(); @@ -133,11 +92,7 @@ const useChats = (search?: string) => { const endpoint = features.chatsV2 ? '/api/v2/pleroma/chats' : '/api/v1/pleroma/chats'; const nextPageLink = pageParam?.link; const uri = nextPageLink || endpoint; - const response = await api.get(uri, { - params: search ? { - search, - } : undefined, - }); + const response = await api.get(uri); const { data } = response; const link = getNextLink(response); @@ -157,7 +112,7 @@ const useChats = (search?: string) => { }; const queryInfo = useInfiniteQuery({ - queryKey: ChatKeys.chatSearch(search), + queryKey: ['chats', 'search'], queryFn: ({ pageParam }) => getChats(pageParam), placeholderData: keepPreviousData, enabled: features.chats, @@ -219,8 +174,8 @@ const useChatActions = (chatId: string) => { const markChatAsRead = async (lastReadId: string) => { return api.post(`/api/v1/pleroma/chats/${chatId}/read`, { last_read_id: lastReadId }) .then(({ data }) => { - updatePageItem(ChatKeys.chatSearch(), data, (o, n) => o.id === n.id); - const queryData = queryClient.getQueryData>>(ChatKeys.chatSearch()); + updatePageItem(['chats', 'search'], data, (o, n) => o.id === n.id); + const queryData = queryClient.getQueryData>>(['chats', 'search']); if (queryData) { const flattenedQueryData: any = flattenPages(queryData)?.map((chat: any) => { @@ -293,7 +248,7 @@ const useChatActions = (chatId: string) => { }, onSuccess: (response: any, variables, context) => { const nextChat = { ...chat, last_message: response.data }; - updatePageItem(ChatKeys.chatSearch(), nextChat, (o, n) => o.id === n.id); + updatePageItem(['chats', 'search'], nextChat, (o, n) => o.id === n.id); updatePageItem( ChatKeys.chatMessages(variables.chatId), normalizeChatMessage(response.data), @@ -302,86 +257,23 @@ const useChatActions = (chatId: string) => { reOrderChatListItems(); }, }); - - const updateChat = useMutation({ - mutationFn: (data: UpdateChatVariables) => api.patch(`/api/v1/pleroma/chats/${chatId}`, data), - onMutate: async (data) => { - // Cancel any outgoing refetches (so they don't overwrite our optimistic update) - await queryClient.cancelQueries({ - queryKey: ChatKeys.chat(chatId), - }); - - // Snapshot the previous value - const prevChat = { ...chat }; - const nextChat = { ...chat, ...data }; - - // Optimistically update to the new value - queryClient.setQueryData(ChatKeys.chat(chatId), nextChat); - - // Return a context object with the snapshotted value - return { prevChat }; - }, - // If the mutation fails, use the context returned from onMutate to roll back - onError: (_error: any, _newData: any, context: any) => { - changeScreen(ChatWidgetScreens.CHAT, context.prevChat.id); - queryClient.setQueryData(ChatKeys.chat(chatId), context.prevChat); - toast.error('Chat Settings failed to update.'); - }, - onSuccess() { - queryClient.invalidateQueries({ queryKey: ChatKeys.chat(chatId) }); - queryClient.invalidateQueries({ queryKey: ChatKeys.chatSearch() }); - toast.success('Chat Settings updated successfully'); - }, - }); - const deleteChatMessage = (chatMessageId: string) => api.delete(`/api/v1/pleroma/chats/${chatId}/messages/${chatMessageId}`); - const acceptChat = useMutation({ - mutationFn: () => api.post(`/api/v1/pleroma/chats/${chatId}/accept`), - onSuccess(response) { - changeScreen(ChatWidgetScreens.CHAT, response.data.id); - queryClient.invalidateQueries({ queryKey: ChatKeys.chat(chatId) }); - queryClient.invalidateQueries({ queryKey: ChatKeys.chatMessages(chatId) }); - queryClient.invalidateQueries({ queryKey: ChatKeys.chatSearch() }); - }, - }); - const deleteChat = useMutation({ mutationFn: () => api.delete(`/api/v1/pleroma/chats/${chatId}`), onSuccess() { changeScreen(ChatWidgetScreens.INBOX); queryClient.invalidateQueries({ queryKey: ChatKeys.chatMessages(chatId) }); - queryClient.invalidateQueries({ queryKey: ChatKeys.chatSearch() }); - }, - }); - - const createReaction = useMutation({ - mutationFn: (data: CreateReactionVariables) => api.post(`/api/v1/pleroma/chats/${chatId}/messages/${data.messageId}/reactions`, { - emoji: data.emoji, - }), - // TODO: add optimistic updates - onSuccess(response) { - updateChatMessage(response.data); - }, - }); - - const deleteReaction = useMutation({ - mutationFn: (data: CreateReactionVariables) => api.delete(`/api/v1/pleroma/chats/${chatId}/messages/${data.messageId}/reactions/${data.emoji}`), - onSuccess() { - queryClient.invalidateQueries({ queryKey: ChatKeys.chatMessages(chatId) }); + queryClient.invalidateQueries({ queryKey: ['chats', 'search'] }); }, }); return { - acceptChat, createChatMessage, - createReaction, deleteChat, deleteChatMessage, - deleteReaction, markChatAsRead, - updateChat, }; }; -export { ChatKeys, useChat, useChatActions, useChats, useChatMessages, isLastMessage }; +export { ChatKeys, useChat, useChatActions, useChats, useChatMessages }; diff --git a/src/reducers/compose.ts b/src/reducers/compose.ts index 624ea0b3a..9c0f60a2a 100644 --- a/src/reducers/compose.ts +++ b/src/reducers/compose.ts @@ -52,7 +52,6 @@ import { COMPOSE_SET_STATUS, COMPOSE_EVENT_REPLY, COMPOSE_EDITOR_STATE_SET, - COMPOSE_SET_GROUP_TIMELINE_VISIBLE, ComposeAction, COMPOSE_CHANGE_MEDIA_ORDER, } from '../actions/compose'; @@ -108,7 +107,6 @@ export const ReducerCompose = ImmutableRecord({ tagHistory: ImmutableList(), text: '', to: ImmutableOrderedSet(), - group_timeline_visible: false, // TruthSocial }); type State = ImmutableMap; @@ -345,12 +343,8 @@ export default function compose(state = initialState, action: ComposeAction | Ev map.set('spoiler_text', ''); if (action.status.visibility === 'group') { - if (action.status.group?.group_visibility === 'everyone') { - map.set('privacy', privacyPreference('public', defaultCompose.privacy)); - } else if (action.status.group?.group_visibility === 'members_only') { - map.set('group_id', action.status.getIn(['group', 'id']) as string); - map.set('privacy', 'group'); - } + map.set('group_id', action.status.getIn(['group', 'id']) as string); + map.set('privacy', 'group'); } })); case COMPOSE_SUBMIT_REQUEST: @@ -508,8 +502,6 @@ export default function compose(state = initialState, action: ComposeAction | Ev return updateCompose(state, action.id, compose => compose.update('to', mentions => mentions!.add(action.account))); case COMPOSE_REMOVE_FROM_MENTIONS: return updateCompose(state, action.id, compose => compose.update('to', mentions => mentions!.delete(action.account))); - case COMPOSE_SET_GROUP_TIMELINE_VISIBLE: - return updateCompose(state, action.id, compose => compose.set('group_timeline_visible', action.groupTimelineVisible)); case ME_FETCH_SUCCESS: case ME_PATCH_SUCCESS: return updateCompose(state, 'default', compose => importAccount(compose, action.me)); diff --git a/src/schemas/account.ts b/src/schemas/account.ts index b085120b7..e9a8fa08a 100644 --- a/src/schemas/account.ts +++ b/src/schemas/account.ts @@ -97,7 +97,6 @@ const baseAccountSchema = z.object({ roles: filteredArray(roleSchema), source: z.object({ approved: z.boolean().catch(true), - chats_onboarded: z.boolean().catch(true), fields: filteredArray(fieldSchema), note: z.string().catch(''), pleroma: z.object({ @@ -114,7 +113,6 @@ const baseAccountSchema = z.object({ url: z.string().url(), username: z.string().catch(''), verified: z.boolean().catch(false), - website: z.string().catch(''), }); type BaseAccount = z.infer; diff --git a/src/schemas/card.ts b/src/schemas/card.ts index dc4ba2e6b..a1a2b43e8 100644 --- a/src/schemas/card.ts +++ b/src/schemas/card.ts @@ -3,8 +3,6 @@ import punycode from 'punycode'; import DOMPurify from 'isomorphic-dompurify'; import { z } from 'zod'; -import { groupSchema } from './group'; - const IDNA_PREFIX = 'xn--'; /** @@ -17,7 +15,6 @@ const cardSchema = z.object({ blurhash: z.string().nullable().catch(null), description: z.string().catch(''), embed_url: z.string().url().catch(''), - group: groupSchema.nullable().catch(null), // TruthSocial height: z.number().catch(0), html: z.string().catch(''), image: z.string().nullable().catch(null), diff --git a/src/schemas/chat-message.ts b/src/schemas/chat-message.ts index fe0ff0f6b..cac922347 100644 --- a/src/schemas/chat-message.ts +++ b/src/schemas/chat-message.ts @@ -3,7 +3,7 @@ import { z } from 'zod'; import { attachmentSchema } from './attachment'; import { cardSchema } from './card'; import { customEmojiSchema } from './custom-emoji'; -import { contentSchema, emojiSchema, filteredArray } from './utils'; +import { contentSchema, filteredArray } from './utils'; const chatMessageSchema = z.object({ account_id: z.string(), @@ -13,8 +13,6 @@ const chatMessageSchema = z.object({ content: contentSchema, created_at: z.string().datetime().catch(new Date().toUTCString()), emojis: filteredArray(customEmojiSchema), - expiration: z.number().optional().catch(undefined), - emoji_reactions: z.array(emojiSchema).min(1).nullable().catch(null), id: z.string(), unread: z.coerce.boolean(), deleting: z.coerce.boolean(), diff --git a/src/schemas/group-tag.ts b/src/schemas/group-tag.ts deleted file mode 100644 index 0d07003ff..000000000 --- a/src/schemas/group-tag.ts +++ /dev/null @@ -1,15 +0,0 @@ -import z from 'zod'; - -const groupTagSchema = z.object({ - id: z.string(), - 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), -}); - -type GroupTag = z.infer; - -export { groupTagSchema, type GroupTag }; diff --git a/src/schemas/group.test.ts b/src/schemas/group.test.ts deleted file mode 100644 index b227ad856..000000000 --- a/src/schemas/group.test.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { groupSchema } from './group'; - -test('groupSchema with a TruthSocial group', async () => { - const data = await import('soapbox/__fixtures__/group-truthsocial.json'); - const group = groupSchema.parse(data); - expect(group.display_name_html).toEqual('PATRIOT PATRIOTS'); -}); \ No newline at end of file diff --git a/src/schemas/group.ts b/src/schemas/group.ts index 9d97a4047..239d8eb35 100644 --- a/src/schemas/group.ts +++ b/src/schemas/group.ts @@ -6,7 +6,6 @@ import { unescapeHTML } from 'soapbox/utils/html'; import { customEmojiSchema } from './custom-emoji'; import { groupRelationshipSchema } from './group-relationship'; -import { groupTagSchema } from './group-tag'; import { filteredArray, makeCustomEmojiMap } from './utils'; const avatarMissing = require('soapbox/assets/images/avatar-missing.png'); @@ -20,7 +19,6 @@ const groupSchema = z.object({ display_name: z.string().catch(''), domain: z.string().catch(''), emojis: filteredArray(customEmojiSchema), - group_visibility: z.string().catch(''), // TruthSocial header: z.string().catch(headerMissing), header_static: z.string().catch(''), id: z.coerce.string(), @@ -30,18 +28,15 @@ const groupSchema = z.object({ owner: z.object({ id: z.string() }), note: z.string().transform(note => note === '

' ? '' : note).catch(''), relationship: groupRelationshipSchema.nullable().catch(null), // Dummy field to be overwritten later - slug: z.string().catch(''), // TruthSocial source: z.object({ note: z.string(), }).optional(), // TruthSocial statuses_visibility: z.string().catch('public'), - tags: z.array(groupTagSchema).catch([]), uri: z.string().catch(''), url: z.string().catch(''), }).transform(group => { group.avatar_static = group.avatar_static || group.avatar; group.header_static = group.header_static || group.header; - group.locked = group.locked || group.group_visibility === 'members_only'; // TruthSocial const customEmojiMap = makeCustomEmojiMap(group.emojis); return { diff --git a/src/schemas/index.ts b/src/schemas/index.ts index 3e8d00f7f..de9031130 100644 --- a/src/schemas/index.ts +++ b/src/schemas/index.ts @@ -11,7 +11,6 @@ export { emojiReactionSchema, type EmojiReaction } from './emoji-reaction'; 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 { instanceSchema, type Instance } from './instance'; export { mentionSchema, type Mention } from './mention'; export { moderationLogEntrySchema, type ModerationLogEntry } from './moderation-log-entry'; diff --git a/src/schemas/notification.ts b/src/schemas/notification.ts index 3c77de6bf..535bce068 100644 --- a/src/schemas/notification.ts +++ b/src/schemas/notification.ts @@ -10,7 +10,6 @@ const baseNotificationSchema = z.object({ created_at: z.string().datetime().catch(new Date().toUTCString()), id: z.string(), type: z.string(), - total_count: z.number().optional().catch(undefined), // TruthSocial }); const mentionNotificationSchema = baseNotificationSchema.extend({ diff --git a/src/schemas/rule.ts b/src/schemas/rule.ts index 888ee3d50..bc32491ff 100644 --- a/src/schemas/rule.ts +++ b/src/schemas/rule.ts @@ -4,7 +4,6 @@ const baseRuleSchema = z.object({ id: z.string(), text: z.string().catch(''), hint: z.string().catch(''), - rule_type: z.enum(['account', 'content', 'group']).nullable().catch(null), }); const ruleSchema = z.preprocess((data: any) => { diff --git a/src/settings.ts b/src/settings.ts index ce713f17e..27b473e44 100644 --- a/src/settings.ts +++ b/src/settings.ts @@ -51,6 +51,3 @@ export const pushNotificationsSetting = new Settings('soapbox_push_notification_ /** Remember hashtag usage. */ export const tagHistory = new Settings('soapbox_tag_history'); - -/** Remember group usage. */ -export const groupSearchHistory = new Settings('soapbox_group_search_history'); diff --git a/src/utils/chats.test.ts b/src/utils/chats.test.ts index c7eb306c3..79f24725a 100644 --- a/src/utils/chats.test.ts +++ b/src/utils/chats.test.ts @@ -6,7 +6,6 @@ import { queryClient } from 'soapbox/queries/client'; import { updateChatMessage } from './chats'; const chat: IChat = { - accepted: true, account: buildAccount({ username: 'username', verified: true, @@ -16,15 +15,9 @@ const chat: IChat = { avatar_static: 'avatar', display_name: 'my name', }), - chat_type: 'direct', created_at: '2020-06-10T02:05:06.000Z', - created_by_account: '1', - discarded_at: null, id: '1', last_message: null, - latest_read_message_by_account: [], - latest_read_message_created_at: null, - message_expiration: 1209600, unread: 0, }; @@ -34,8 +27,6 @@ const buildChatMessage = (id: string) => normalizeChatMessage({ account_id: '1', content: `chat message #${id}`, created_at: '2020-06-10T02:05:06.000Z', - emoji_reactions: null, - expiration: 1209600, unread: true, }); diff --git a/src/utils/chats.ts b/src/utils/chats.ts index 423565e49..e8083b464 100644 --- a/src/utils/chats.ts +++ b/src/utils/chats.ts @@ -18,14 +18,14 @@ interface ChatPayload extends Omit { * @param newChat - Chat entity. */ const updateChatInChatSearchQuery = (newChat: ChatPayload) => { - updatePageItem(ChatKeys.chatSearch(), newChat as any, (o, n) => o.id === n.id); + updatePageItem(['chats', 'search'], newChat as any, (o, n) => o.id === n.id); }; /** * Re-order the ChatSearch query by the last message timestamp. */ const reOrderChatListItems = () => { - sortQueryData(ChatKeys.chatSearch(), (chatA, chatB) => { + sortQueryData(['chats', 'search'], (chatA, chatB) => { return compareDate( chatA.last_message?.created_at as string, chatB.last_message?.created_at as string, @@ -40,7 +40,7 @@ const reOrderChatListItems = () => { */ const checkIfChatExists = (chatId: string) => { const currentChats = flattenPages( - queryClient.getQueryData>>(ChatKeys.chatSearch()), + queryClient.getQueryData>>(['chats', 'search']), ); return currentChats?.find((chat: Chat) => chat.id === chatId); @@ -51,7 +51,7 @@ const checkIfChatExists = (chatId: string) => { */ const invalidateChatSearchQuery = () => { queryClient.invalidateQueries({ - queryKey: ChatKeys.chatSearch(), + queryKey: ['chats', 'search'], }); }; @@ -80,7 +80,7 @@ const updateChatListItem = (newChat: ChatPayload) => { /** Get unread chats count. */ const getUnreadChatsCount = (): number => { const chats = flattenPages( - queryClient.getQueryData>>(ChatKeys.chatSearch()), + queryClient.getQueryData>>(['chats', 'search']), ); return sumBy(chats, chat => chat.unread); diff --git a/src/utils/features.test.ts b/src/utils/features.test.ts index 248085876..d3a995ab1 100644 --- a/src/utils/features.test.ts +++ b/src/utils/features.test.ts @@ -34,15 +34,6 @@ describe('parseVersion', () => { }); }); - it('with a Truth Social version string', () => { - const version = '3.4.1 (compatible; TruthSocial 1.0.0)'; - expect(parseVersion(version)).toEqual({ - software: 'TruthSocial', - version: '1.0.0', - compatVersion: '3.4.1', - }); - }); - it('with a Mastodon fork', () => { const version = '3.5.1+glitch'; expect(parseVersion(version)).toEqual({ diff --git a/src/utils/features.ts b/src/utils/features.ts index 9e04d0821..f492e6ce5 100644 --- a/src/utils/features.ts +++ b/src/utils/features.ts @@ -68,12 +68,6 @@ export const PLEROMA = 'Pleroma'; */ export const TAKAHE = 'Takahe'; -/** - * Truth Social, the Mastodon fork powering truthsocial.com - * @see {@link https://help.truthsocial.com/open-source} - */ -export const TRUTHSOCIAL = 'TruthSocial'; - /** * Wildebeest, backend running on top of Cloudflare Pages. */ @@ -124,7 +118,7 @@ const getInstanceFeatures = (instance: Instance) => { * Ability to create accounts. * @see POST /api/v1/accounts */ - accountCreation: v.software !== TRUTHSOCIAL, + accountCreation: true, /** * Ability to pin other accounts on one's profile. @@ -138,10 +132,7 @@ const getInstanceFeatures = (instance: Instance) => { * Ability to set one's location on their profile. * @see PATCH /api/v1/accounts/update_credentials */ - accountLocation: any([ - v.software === PLEROMA && v.build === REBASED && gte(v.version, '2.4.50'), - v.software === TRUTHSOCIAL, - ]), + accountLocation: v.software === PLEROMA && v.build === REBASED && gte(v.version, '2.4.50'), /** * Look up an account by the acct. @@ -153,7 +144,6 @@ const getInstanceFeatures = (instance: Instance) => { v.software === MASTODON && gte(v.compatVersion, '3.4.0'), v.software === PLEROMA && gte(v.version, '2.4.50'), v.software === TAKAHE && gte(v.version, '0.6.1'), - v.software === TRUTHSOCIAL, v.software === DITTO, ]), @@ -170,7 +160,6 @@ const getInstanceFeatures = (instance: Instance) => { accountNotifies: any([ v.software === MASTODON && gte(v.compatVersion, '3.3.0'), v.software === PLEROMA && gte(v.version, '2.4.50'), - v.software === TRUTHSOCIAL, ]), /** @@ -180,12 +169,6 @@ const getInstanceFeatures = (instance: Instance) => { */ accountSubscriptions: v.software === PLEROMA && gte(v.version, '1.0.0'), - /** - * Ability to set one's website on their profile. - * @see PATCH /api/v1/accounts/update_credentials - */ - accountWebsite: v.software === TRUTHSOCIAL, - /** * Ability to manage announcements by admins. * @see GET /api/v1/pleroma/admin/announcements @@ -285,72 +268,29 @@ const getInstanceFeatures = (instance: Instance) => { v.software === PLEROMA, ]), - /** - * Ability to accept a chat. - * POST /api/v1/pleroma/chats/:id/accept - */ - chatAcceptance: v.software === TRUTHSOCIAL, - - /** - * Ability to add reactions to chat messages. - */ - chatEmojiReactions: v.software === TRUTHSOCIAL, - /** * Pleroma chats API. * @see {@link https://docs.pleroma.social/backend/development/API/chats/} */ - chats: any([ - v.software === TRUTHSOCIAL, - features.includes('pleroma_chat_messages'), - ]), + chats: features.includes('pleroma_chat_messages'), /** * Ability to delete a chat. * @see DELETE /api/v1/pleroma/chats/:id */ - chatsDelete: any([ - v.software === TRUTHSOCIAL, - v.build === REBASED, - ]), - - /** - * Ability to set disappearing messages on chats. - * @see PATCH /api/v1/pleroma/chats/:id - */ - chatsExpiration: v.software === TRUTHSOCIAL, + chatsDelete: v.build === REBASED, /** * Whether chat messages can accept a `media_id` attachment. * @see POST /api/v1/pleroma/chats/:id/messages */ - chatsMedia: v.software !== TRUTHSOCIAL || v.build === UNRELEASED, - - /** - * Whether chat messages have read receipts. - * @see GET /api/v1/pleroma/chats/:id/messages - */ - chatsReadReceipts: v.software === TRUTHSOCIAL, - - /** - * Ability to search among chats. - * @see GET /api/v1/pleroma/chats - */ - chatsSearch: v.software === TRUTHSOCIAL, + chatsMedia: v.build === UNRELEASED, /** * Paginated chats API. * @see GET /api/v2/pleroma/chats */ - chatsV2: any([ - v.software === TRUTHSOCIAL, - v.software === PLEROMA && gte(v.version, '2.3.0'), - ]), - - /** - * Ability to only chat with people that follow you. - */ - chatsWithFollowers: v.software === TRUTHSOCIAL, + chatsV2: v.software === PLEROMA && gte(v.version, '2.3.0'), /** * Mastodon's newer solution for direct messaging. @@ -414,7 +354,6 @@ const getInstanceFeatures = (instance: Instance) => { v.software === PIXELFED, v.software === PLEROMA, v.software === TAKAHE && gte(v.version, '0.7.0'), - v.software === TRUTHSOCIAL, v.software === WILDEBEEST, ]), @@ -444,10 +383,7 @@ const getInstanceFeatures = (instance: Instance) => { * Ability to embed posts on external sites. * @see GET /api/oembed */ - embeds: any([ - v.software === MASTODON, - v.software === TRUTHSOCIAL, - ]), + embeds: v.software === MASTODON, /** * Ability to add emoji reactions to a status. @@ -490,10 +426,7 @@ const getInstanceFeatures = (instance: Instance) => { * Ability to address recipients of a status explicitly (with `to`). * @see POST /api/v1/statuses */ - explicitAddressing: any([ - v.software === PLEROMA && gte(v.version, '1.0.0'), - v.software === TRUTHSOCIAL, - ]), + explicitAddressing: v.software === PLEROMA && gte(v.version, '1.0.0'), /** Whether to allow exporting follows/blocks/mutes to CSV by paginating the API. */ exportData: true, @@ -505,7 +438,6 @@ const getInstanceFeatures = (instance: Instance) => { v.software === ICESHRIMP, v.software === MASTODON, v.software === TAKAHE && gte(v.version, '0.6.1'), - v.software === TRUTHSOCIAL, features.includes('exposable_reactions'), ]), @@ -610,52 +542,7 @@ const getInstanceFeatures = (instance: Instance) => { * @see POST /api/v1/admin/groups/:group_id/unsuspend * @see DELETE /api/v1/admin/groups/:group_id */ - groups: v.software === TRUTHSOCIAL, - - /** - * Cap # of Group Admins to 5 - */ - groupsAdminMax: v.software === TRUTHSOCIAL, - - /** - * Can see trending/suggested Groups. - */ - groupsDiscovery: v.software === TRUTHSOCIAL, - - /** - * Can kick user from Group. - */ - groupsKick: v.software !== TRUTHSOCIAL, - - /** - * Can mute a Group. - */ - groupsMuting: v.software === TRUTHSOCIAL, - - /** - * Can query pending Group requests. - */ - groupsPending: v.software === TRUTHSOCIAL, - - /** - * Can promote members to Admins. - */ - groupsPromoteToAdmin: v.software !== TRUTHSOCIAL, - - /** - * Can search my own groups. - */ - groupsSearch: v.software === TRUTHSOCIAL, - - /** - * Can see topics for Groups. - */ - groupsTags: v.software === TRUTHSOCIAL, - - /** - * Can validate group names. - */ - groupsValidation: v.software === TRUTHSOCIAL, + groups: false, /** * Can hide follows/followers lists and counts. @@ -705,10 +592,7 @@ const getInstanceFeatures = (instance: Instance) => { /** * Can sign in using username instead of e-mail address. */ - logInWithUsername: any([ - v.software === PLEROMA, - v.software === TRUTHSOCIAL, - ]), + logInWithUsername: v.software === PLEROMA, /** * Can perform moderation actions with account and reports. @@ -802,13 +686,6 @@ const getInstanceFeatures = (instance: Instance) => { v.software === TAKAHE && gte(v.version, '0.6.2'), ]), - /** - * Supports pagination in threads. - * @see GET /api/v1/statuses/:id/context/ancestors - * @see GET /api/v1/statuses/:id/context/descendants - */ - paginatedContext: v.software === TRUTHSOCIAL, - /** * Displays a form to follow a user when logged out. * @see POST /main/ostatus @@ -825,14 +702,13 @@ const getInstanceFeatures = (instance: Instance) => { v.software === MASTODON && gte(v.version, '2.8.0'), v.software === PLEROMA, v.software === TAKAHE && gte(v.version, '0.8.0'), - v.software === TRUTHSOCIAL, ]), /** * Can set privacy scopes on statuses. * @see POST /api/v1/statuses */ - privacyScopes: ![TRUTHSOCIAL, DITTO].includes(v.software!), + privacyScopes: v.software !== DITTO, /** * A directory of discoverable profiles from the instance. @@ -896,18 +772,6 @@ const getInstanceFeatures = (instance: Instance) => { v.software === PLEROMA && v.build === REBASED && gte(v.version, '2.4.50'), ]), - /** - * Ability to report chat messages. - * @see POST /api/v1/reports - */ - reportChats: v.software === TRUTHSOCIAL, - - /** - * Ability to select more than one status when reporting. - * @see POST /api/v1/reports - */ - reportMultipleStatuses: v.software !== TRUTHSOCIAL, - /** * Can request a password reset email through the API. * @see POST /auth/password @@ -959,10 +823,7 @@ const getInstanceFeatures = (instance: Instance) => { * @see POST /api/pleroma/change_email * @see POST /api/pleroma/delete_account */ - security: any([ - v.software === PLEROMA, - v.software === TRUTHSOCIAL, - ]), + security: v.software === PLEROMA, /** * Ability to manage account sessions. @@ -975,16 +836,13 @@ const getInstanceFeatures = (instance: Instance) => { * Can store client settings in the database. * @see PATCH /api/v1/accounts/update_credentials */ - settingsStore: any([ - v.software === PLEROMA, - v.software === TRUTHSOCIAL, - ]), + settingsStore: v.software === PLEROMA, /** * Can set content warnings on statuses. * @see POST /api/v1/statuses */ - spoilers: v.software !== TRUTHSOCIAL, + spoilers: true, /** * Can display suggested accounts. @@ -992,7 +850,6 @@ const getInstanceFeatures = (instance: Instance) => { */ suggestions: any([ v.software === MASTODON && gte(v.compatVersion, '2.4.3'), - v.software === TRUTHSOCIAL, features.includes('v2_suggestions'), ]), @@ -1004,7 +861,6 @@ const getInstanceFeatures = (instance: Instance) => { v.software === FRIENDICA, v.software === ICESHRIMP, v.software === MASTODON && gte(v.compatVersion, '3.4.0'), - v.software === TRUTHSOCIAL, features.includes('v2_suggestions'), ]), @@ -1032,7 +888,6 @@ const getInstanceFeatures = (instance: Instance) => { v.software === FRIENDICA && gte(v.version, '2022.12.0'), v.software === ICESHRIMP, v.software === MASTODON && gte(v.compatVersion, '3.0.0'), - v.software === TRUTHSOCIAL, v.software === DITTO, ]), diff --git a/src/utils/groups.ts b/src/utils/groups.ts deleted file mode 100644 index 28264e090..000000000 --- a/src/utils/groups.ts +++ /dev/null @@ -1,35 +0,0 @@ -import { groupSearchHistory } from 'soapbox/settings'; - -const RECENT_SEARCHES_KEY = 'soapbox:recent-group-searches'; - -const clearRecentGroupSearches = (currentUserId: string) => groupSearchHistory.remove(currentUserId); - -const saveGroupSearch = (currentUserId: string, search: string) => { - let currentSearches: string[] = []; - - if (groupSearchHistory.get(currentUserId)) { - currentSearches = groupSearchHistory.get(currentUserId); - } - - if (currentSearches.indexOf(search) === -1) { - currentSearches.unshift(search); - if (currentSearches.length > 10) { - currentSearches.pop(); - } - - groupSearchHistory.set(currentUserId, currentSearches); - - return currentSearches; - } else { - // The search term has already been searched. Move it to the beginning - // of the cached list. - const indexOfSearch = currentSearches.indexOf(search); - const nextCurrentSearches = [...currentSearches]; - nextCurrentSearches.splice(0, 0, ...nextCurrentSearches.splice(indexOfSearch, 1)); - localStorage.setItem(RECENT_SEARCHES_KEY, JSON.stringify(nextCurrentSearches)); - - return nextCurrentSearches; - } -}; - -export { clearRecentGroupSearches, saveGroupSearch }; diff --git a/src/utils/notification.ts b/src/utils/notification.ts index fdd8e25fe..43e73be11 100644 --- a/src/utils/notification.ts +++ b/src/utils/notification.ts @@ -22,7 +22,6 @@ const NOTIFICATION_TYPES = [ /** Notification types to exclude from the "All" filter by default. */ const EXCLUDE_TYPES = [ 'pleroma:chat_mention', - 'chat', // TruthSocial ] as const; type NotificationType = typeof NOTIFICATION_TYPES[number];