From 2bc6ff3fa3d70f3778970412513185fb6dfe3aab Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Sun, 2 Apr 2023 18:32:50 -0500 Subject: [PATCH 01/16] Use Link header for home timeline pagination --- app/soapbox/actions/timelines.ts | 56 ++++++++++++++++--- .../features/feed-filtering/feed-carousel.tsx | 2 +- app/soapbox/features/home-timeline/index.tsx | 5 +- app/soapbox/features/test-timeline/index.tsx | 2 +- app/soapbox/reducers/timelines.ts | 24 +++++++- 5 files changed, 74 insertions(+), 15 deletions(-) diff --git a/app/soapbox/actions/timelines.ts b/app/soapbox/actions/timelines.ts index 7ae023338..cafaa6aa5 100644 --- a/app/soapbox/actions/timelines.ts +++ b/app/soapbox/actions/timelines.ts @@ -4,7 +4,7 @@ import { getSettings } from 'soapbox/actions/settings'; import { normalizeStatus } from 'soapbox/normalizers'; import { shouldFilter } from 'soapbox/utils/timelines'; -import api, { getLinks } from '../api'; +import api, { getNextLink, getPrevLink } from '../api'; import { importFetchedStatus, importFetchedStatuses } from './importer'; @@ -139,7 +139,7 @@ const parseTags = (tags: Record = {}, mode: 'any' | 'all' | 'none }; const replaceHomeTimeline = ( - accountId: string | null, + accountId: string | undefined, { maxId }: Record = {}, done?: () => void, ) => (dispatch: AppDispatch, _getState: () => RootState) => { @@ -162,7 +162,12 @@ const expandTimeline = (timelineId: string, path: string, params: Record 0) { + if ( + !params.max_id && + !params.pinned && + (timeline.items || ImmutableOrderedSet()).size > 0 && + !path.includes('max_id=') + ) { params.since_id = timeline.getIn(['items', 0]); } @@ -171,9 +176,16 @@ const expandTimeline = (timelineId: string, path: string, params: Record { - const next = getLinks(response).refs.find(link => link.rel === 'next'); dispatch(importFetchedStatuses(response.data)); - dispatch(expandTimelineSuccess(timelineId, response.data, next ? next.uri : null, response.status === 206, isLoadingRecent, isLoadingMore)); + dispatch(expandTimelineSuccess( + timelineId, + response.data, + getNextLink(response), + getPrevLink(response), + response.status === 206, + isLoadingRecent, + isLoadingMore, + )); done(); }).catch(error => { dispatch(expandTimelineFail(timelineId, error, isLoadingMore)); @@ -181,9 +193,26 @@ const expandTimeline = (timelineId: string, path: string, params: Record = {}, done = noOp) => { - const endpoint = accountId ? `/api/v1/accounts/${accountId}/statuses` : '/api/v1/timelines/home'; - const params: any = { max_id: maxId }; +interface ExpandHomeTimelineOpts { + accountId?: string + maxId?: string + url?: string +} + +interface HomeTimelineParams { + max_id?: string + exclude_replies?: boolean + with_muted?: boolean +} + +const expandHomeTimeline = ({ url, accountId, maxId }: ExpandHomeTimelineOpts = {}, done = noOp) => { + const endpoint = url || (accountId ? `/api/v1/accounts/${accountId}/statuses` : '/api/v1/timelines/home'); + const params: HomeTimelineParams = {}; + + if (!url && maxId) { + params.max_id = maxId; + } + if (accountId) { params.exclude_replies = true; params.with_muted = true; @@ -237,11 +266,20 @@ const expandTimelineRequest = (timeline: string, isLoadingMore: boolean) => ({ skipLoading: !isLoadingMore, }); -const expandTimelineSuccess = (timeline: string, statuses: APIEntity[], next: string | null, partial: boolean, isLoadingRecent: boolean, isLoadingMore: boolean) => ({ +const expandTimelineSuccess = ( + timeline: string, + statuses: APIEntity[], + next: string | undefined, + prev: string | undefined, + partial: boolean, + isLoadingRecent: boolean, + isLoadingMore: boolean, +) => ({ type: TIMELINE_EXPAND_SUCCESS, timeline, statuses, next, + prev, partial, isLoadingRecent, skipLoading: !isLoadingMore, diff --git a/app/soapbox/features/feed-filtering/feed-carousel.tsx b/app/soapbox/features/feed-filtering/feed-carousel.tsx index af6cb47bb..9d62538ce 100644 --- a/app/soapbox/features/feed-filtering/feed-carousel.tsx +++ b/app/soapbox/features/feed-filtering/feed-carousel.tsx @@ -30,7 +30,7 @@ const CarouselItem = React.forwardRef(( setLoading(true); if (isSelected) { - dispatch(replaceHomeTimeline(null, { maxId: null }, () => setLoading(false))); + dispatch(replaceHomeTimeline(undefined, { maxId: null }, () => setLoading(false))); if (onPinned) { onPinned(null); diff --git a/app/soapbox/features/home-timeline/index.tsx b/app/soapbox/features/home-timeline/index.tsx index aaaec2bb3..611fcf7ce 100644 --- a/app/soapbox/features/home-timeline/index.tsx +++ b/app/soapbox/features/home-timeline/index.tsx @@ -27,9 +27,10 @@ const HomeTimeline: React.FC = () => { const isPartial = useAppSelector(state => state.timelines.get('home')?.isPartial === true); const currentAccountId = useAppSelector(state => state.timelines.get('home')?.feedAccountId as string | undefined); const currentAccountRelationship = useAppSelector(state => currentAccountId ? state.relationships.get(currentAccountId) : null); + const next = useAppSelector(state => state.timelines.get('home')?.next); const handleLoadMore = (maxId: string) => { - dispatch(expandHomeTimeline({ maxId, accountId: currentAccountId })); + dispatch(expandHomeTimeline({ url: next, maxId, accountId: currentAccountId })); }; // Mastodon generates the feed in Redis, and can return a partial timeline @@ -52,7 +53,7 @@ const HomeTimeline: React.FC = () => { }; const handleRefresh = () => { - return dispatch(expandHomeTimeline({ maxId: null, accountId: currentAccountId })); + return dispatch(expandHomeTimeline({ accountId: currentAccountId })); }; useEffect(() => { diff --git a/app/soapbox/features/test-timeline/index.tsx b/app/soapbox/features/test-timeline/index.tsx index 51ab1491e..136d0d324 100644 --- a/app/soapbox/features/test-timeline/index.tsx +++ b/app/soapbox/features/test-timeline/index.tsx @@ -35,7 +35,7 @@ const TestTimeline: React.FC = () => { React.useEffect(() => { dispatch(importFetchedStatuses(MOCK_STATUSES)); - dispatch(expandTimelineSuccess(timelineId, MOCK_STATUSES, null, false, false, false)); + dispatch(expandTimelineSuccess(timelineId, MOCK_STATUSES, undefined, undefined, false, false, false)); }, []); return ( diff --git a/app/soapbox/reducers/timelines.ts b/app/soapbox/reducers/timelines.ts index 1713ee964..b5fe0e049 100644 --- a/app/soapbox/reducers/timelines.ts +++ b/app/soapbox/reducers/timelines.ts @@ -46,6 +46,8 @@ const TimelineRecord = ImmutableRecord({ top: true, isLoading: false, hasMore: true, + next: undefined as string | undefined, + prev: undefined as string | undefined, items: ImmutableOrderedSet(), queuedItems: ImmutableOrderedSet(), //max= MAX_QUEUED_ITEMS feedAccountId: null, @@ -87,13 +89,23 @@ const setFailed = (state: State, timelineId: string, failed: boolean) => { return state.update(timelineId, TimelineRecord(), timeline => timeline.set('loadingFailed', failed)); }; -const expandNormalizedTimeline = (state: State, timelineId: string, statuses: ImmutableList>, next: string | null, isPartial: boolean, isLoadingRecent: boolean) => { +const expandNormalizedTimeline = ( + state: State, + timelineId: string, + statuses: ImmutableList>, + next: string | undefined, + prev: string | undefined, + isPartial: boolean, + isLoadingRecent: boolean, +) => { const newIds = getStatusIds(statuses); return state.update(timelineId, TimelineRecord(), timeline => timeline.withMutations(timeline => { timeline.set('isLoading', false); timeline.set('loadingFailed', false); timeline.set('isPartial', isPartial); + timeline.set('next', next); + timeline.set('prev', prev); if (!next && !isLoadingRecent) timeline.set('hasMore', false); @@ -322,7 +334,15 @@ export default function timelines(state: State = initialState, action: AnyAction case TIMELINE_EXPAND_FAIL: return handleExpandFail(state, action.timeline); case TIMELINE_EXPAND_SUCCESS: - return expandNormalizedTimeline(state, action.timeline, fromJS(action.statuses) as ImmutableList>, action.next, action.partial, action.isLoadingRecent); + return expandNormalizedTimeline( + state, + action.timeline, + fromJS(action.statuses) as ImmutableList>, + action.next, + action.prev, + action.partial, + action.isLoadingRecent, + ); case TIMELINE_UPDATE: return updateTimeline(state, action.timeline, action.statusId); case TIMELINE_UPDATE_QUEUE: From 39f2734bd478eacdf7ff60088bb21468fe45f2cc Mon Sep 17 00:00:00 2001 From: Chewbacca Date: Mon, 10 Apr 2023 13:06:06 -0400 Subject: [PATCH 02/16] Fix replying to status from Group --- app/soapbox/actions/statuses.ts | 4 ++++ app/soapbox/hooks/api/groups/useGroups.ts | 18 ++++++++++++++++-- 2 files changed, 20 insertions(+), 2 deletions(-) diff --git a/app/soapbox/actions/statuses.ts b/app/soapbox/actions/statuses.ts index b14108de2..ab6a855ee 100644 --- a/app/soapbox/actions/statuses.ts +++ b/app/soapbox/actions/statuses.ts @@ -5,6 +5,7 @@ import { shouldHaveCard } from 'soapbox/utils/status'; import api, { getNextLink } from '../api'; import { setComposeToStatus } from './compose'; +import { fetchGroupRelationships } from './groups'; import { importFetchedStatus, importFetchedStatuses } from './importer'; import { openModal } from './modals'; import { deleteFromTimelines } from './timelines'; @@ -124,6 +125,9 @@ const fetchStatus = (id: string) => { return api(getState).get(`/api/v1/statuses/${id}`).then(({ data: status }) => { dispatch(importFetchedStatus(status)); + if (status.group) { + dispatch(fetchGroupRelationships([status.group.id])); + } dispatch({ type: STATUS_FETCH_SUCCESS, status, skipLoading }); return status; }).catch(error => { diff --git a/app/soapbox/hooks/api/groups/useGroups.ts b/app/soapbox/hooks/api/groups/useGroups.ts index f77f66cdc..9db3001ce 100644 --- a/app/soapbox/hooks/api/groups/useGroups.ts +++ b/app/soapbox/hooks/api/groups/useGroups.ts @@ -1,8 +1,10 @@ +import { useEffect } from 'react'; import { z } from 'zod'; +import { fetchGroupRelationshipsSuccess } from 'soapbox/actions/groups'; import { Entities } from 'soapbox/entity-store/entities'; import { useEntities, useEntity } from 'soapbox/entity-store/hooks'; -import { useApi } from 'soapbox/hooks'; +import { useApi, useAppDispatch } from 'soapbox/hooks'; import { groupSchema, Group } from 'soapbox/schemas/group'; import { groupRelationshipSchema, GroupRelationship } from 'soapbox/schemas/group-relationship'; @@ -48,12 +50,24 @@ function useGroup(groupId: string, refetch = true) { function useGroupRelationship(groupId: string) { const api = useApi(); + const dispatch = useAppDispatch(); - return useEntity( + const { entity: groupRelationship, ...result } = useEntity( [Entities.GROUP_RELATIONSHIPS, groupId], () => api.get(`/api/v1/groups/relationships?id[]=${groupId}`), { schema: z.array(groupRelationshipSchema).transform(arr => arr[0]) }, ); + + useEffect(() => { + if (groupRelationship?.id) { + dispatch(fetchGroupRelationshipsSuccess([groupRelationship])); + } + }, [groupRelationship?.id]); + + return { + entity: groupRelationship, + ...result, + }; } function useGroupRelationships(groupIds: string[]) { From c8d9197065648afdcae9727afcdf79c5ee19c47f Mon Sep 17 00:00:00 2001 From: Chewbacca Date: Wed, 12 Apr 2023 09:17:26 -0400 Subject: [PATCH 03/16] Support autoFocus in Streamfield --- app/soapbox/components/ui/streamfield/streamfield.tsx | 8 +++++++- .../features/group/components/group-tags-field.tsx | 10 ++++------ 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/app/soapbox/components/ui/streamfield/streamfield.tsx b/app/soapbox/components/ui/streamfield/streamfield.tsx index bb2ca0ba5..9ca795ada 100644 --- a/app/soapbox/components/ui/streamfield/streamfield.tsx +++ b/app/soapbox/components/ui/streamfield/streamfield.tsx @@ -16,6 +16,7 @@ const messages = defineMessages({ export type StreamfieldComponent = React.ComponentType<{ value: T onChange: (value: T) => void + autoFocus: boolean }>; interface IStreamfield { @@ -72,7 +73,12 @@ const Streamfield: React.FC = ({ {values.map((value, i) => value?._destroy ? null : ( - + 0} + /> {values.length > minItems && onRemoveItem && ( = ({ tags, onChange, onAddItem, ); }; -interface IHashtagField { - value: string - onChange: (value: string) => void -} - -const HashtagField: React.FC = ({ value, onChange }) => { +const HashtagField: StreamfieldComponent = ({ value, onChange, autoFocus = false }) => { const intl = useIntl(); const handleChange: React.ChangeEventHandler = ({ target }) => { @@ -49,6 +46,7 @@ const HashtagField: React.FC = ({ value, onChange }) => { value={value} onChange={handleChange} placeholder={intl.formatMessage(messages.hashtagPlaceholder)} + autoFocus={autoFocus} /> ); }; From 3a9e4de5cc92f9eb7409e63d7c6c0535b4b56177 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?marcin=20miko=C5=82ajczak?= Date: Wed, 12 Apr 2023 19:45:48 +0200 Subject: [PATCH 04/16] Fix: scheduled poast box datepicker cut off again MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: marcin mikołajczak --- app/soapbox/pages/home-page.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/soapbox/pages/home-page.tsx b/app/soapbox/pages/home-page.tsx index 1a91a0320..2ba7cac0a 100644 --- a/app/soapbox/pages/home-page.tsx +++ b/app/soapbox/pages/home-page.tsx @@ -43,7 +43,7 @@ const HomePage: React.FC = ({ children }) => { <> {me && ( - + From d4c9d086d53bc14363e672a69cc5ee84d41fbb01 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?marcin=20miko=C5=82ajczak?= Date: Wed, 12 Apr 2023 19:53:24 +0200 Subject: [PATCH 05/16] Fix renderLocation in ComposeEventModal MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: marcin mikołajczak --- .../modals/compose-event-modal/compose-event-modal.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/soapbox/features/ui/components/modals/compose-event-modal/compose-event-modal.tsx b/app/soapbox/features/ui/components/modals/compose-event-modal/compose-event-modal.tsx index 2ecbbda4f..6a23268d5 100644 --- a/app/soapbox/features/ui/components/modals/compose-event-modal/compose-event-modal.tsx +++ b/app/soapbox/features/ui/components/modals/compose-event-modal/compose-event-modal.tsx @@ -184,7 +184,7 @@ const ComposeEventModal: React.FC = ({ onClose }) => { {location.description} - {[location.street, location.locality, location.country].filter(val => val.trim()).join(' · ')} + {[location.street, location.locality, location.country].filter(val => val?.trim()).join(' · ')} onChangeLocation(null)} /> From 5f3250b1757db2c1495b298fd7512e1716f67da6 Mon Sep 17 00:00:00 2001 From: Chewbacca Date: Wed, 12 Apr 2023 15:04:42 -0400 Subject: [PATCH 06/16] Resolve bug with toast during modal --- app/soapbox/components/modal-root.tsx | 4 +++- app/soapbox/containers/soapbox.tsx | 9 ++++++++- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/app/soapbox/components/modal-root.tsx b/app/soapbox/components/modal-root.tsx index 84c1252c9..2358a951f 100644 --- a/app/soapbox/components/modal-root.tsx +++ b/app/soapbox/components/modal-root.tsx @@ -181,7 +181,9 @@ const ModalRoot: React.FC = ({ children, onCancel, onClose, type }) }; const getSiblings = () => { - return Array(...(ref.current!.parentElement!.childNodes as any as ChildNode[])).filter(node => node !== ref.current); + return Array(...(ref.current!.parentElement!.childNodes as any as ChildNode[])) + .filter(node => (node as HTMLDivElement).id !== 'toaster') + .filter(node => node !== ref.current); }; useEffect(() => { diff --git a/app/soapbox/containers/soapbox.tsx b/app/soapbox/containers/soapbox.tsx index fb6ce9481..75134b00d 100644 --- a/app/soapbox/containers/soapbox.tsx +++ b/app/soapbox/containers/soapbox.tsx @@ -191,7 +191,14 @@ const SoapboxMount = () => { - + +
+ +
From 575382b22600f16058609bc082f652ac9507eda0 Mon Sep 17 00:00:00 2001 From: Chewbacca Date: Mon, 3 Apr 2023 15:46:58 -0400 Subject: [PATCH 07/16] Add ability to share Group statuses --- app/soapbox/components/status-action-bar.tsx | 2 +- app/soapbox/normalizers/status.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/soapbox/components/status-action-bar.tsx b/app/soapbox/components/status-action-bar.tsx index 2938e91e7..5fa3e6c3b 100644 --- a/app/soapbox/components/status-action-bar.tsx +++ b/app/soapbox/components/status-action-bar.tsx @@ -609,7 +609,7 @@ const StatusActionBar: React.FC = ({ replyTitle = intl.formatMessage(messages.replyAll); } - const canShare = ('share' in navigator) && status.visibility === 'public'; + const canShare = ('share' in navigator) && (status.visibility === 'public' || status.visibility === 'group'); return ( diff --git a/app/soapbox/normalizers/status.ts b/app/soapbox/normalizers/status.ts index 031c99c29..7a71f24a8 100644 --- a/app/soapbox/normalizers/status.ts +++ b/app/soapbox/normalizers/status.ts @@ -20,7 +20,7 @@ import type { ReducerAccount } from 'soapbox/reducers/accounts'; import type { Account, Attachment, Card, Emoji, Group, Mention, Poll, EmbeddedEntity } from 'soapbox/types/entities'; export type StatusApprovalStatus = 'pending' | 'approval' | 'rejected'; -export type StatusVisibility = 'public' | 'unlisted' | 'private' | 'direct' | 'self'; +export type StatusVisibility = 'public' | 'unlisted' | 'private' | 'direct' | 'self' | 'group'; export type EventJoinMode = 'free' | 'restricted' | 'invite'; export type EventJoinState = 'pending' | 'reject' | 'accept'; From dd94dbad8e88cd48442cee8ba78392ee45b08fbe Mon Sep 17 00:00:00 2001 From: Chewbacca Date: Wed, 5 Apr 2023 10:11:11 -0400 Subject: [PATCH 08/16] Update toast after saving Groups --- app/soapbox/features/group/edit-group.tsx | 4 +++- app/soapbox/locales/en.json | 1 + app/soapbox/normalizers/__tests__/instance.test.ts | 2 +- app/soapbox/normalizers/instance.ts | 2 +- 4 files changed, 6 insertions(+), 3 deletions(-) diff --git a/app/soapbox/features/group/edit-group.tsx b/app/soapbox/features/group/edit-group.tsx index 1e8024f6f..57f0adfef 100644 --- a/app/soapbox/features/group/edit-group.tsx +++ b/app/soapbox/features/group/edit-group.tsx @@ -5,6 +5,7 @@ import { Button, Column, Form, FormActions, FormGroup, Icon, Input, Spinner, Tex import { useAppSelector, useInstance } from 'soapbox/hooks'; import { useGroup, useUpdateGroup } from 'soapbox/hooks/api'; import { useImageField, useTextField } from 'soapbox/hooks/forms'; +import toast from 'soapbox/toast'; import { isDefaultAvatar, isDefaultHeader } from 'soapbox/utils/accounts'; import AvatarPicker from './components/group-avatar-picker'; @@ -20,7 +21,7 @@ const messages = defineMessages({ heading: { id: 'navigation_bar.edit_group', defaultMessage: 'Edit Group' }, groupNamePlaceholder: { id: 'manage_group.fields.name_placeholder', defaultMessage: 'Group Name' }, groupDescriptionPlaceholder: { id: 'manage_group.fields.description_placeholder', defaultMessage: 'Description' }, - success: { id: 'manage_group.success', defaultMessage: 'Group saved!' }, + groupSaved: { id: 'group.update.success', defaultMessage: 'Group successfully saved' }, }); interface IEditGroup { @@ -64,6 +65,7 @@ const EditGroup: React.FC = ({ params: { id: groupId } }) => { }); setIsSubmitting(false); + toast.success(intl.formatMessage(messages.groupSaved)); } const handleAddTag = () => { diff --git a/app/soapbox/locales/en.json b/app/soapbox/locales/en.json index 265cb1bcc..b7a7a5f6d 100644 --- a/app/soapbox/locales/en.json +++ b/app/soapbox/locales/en.json @@ -811,6 +811,7 @@ "group.tabs.members": "Members", "group.tags.hint": "Add up to 3 keywords that will serve as core topics of discussion in the group.", "group.tags.label": "Tags", + "group.update.success": "Group successfully saved", "group.upload_banner": "Upload photo", "groups.discover.popular.empty": "Unable to fetch popular groups at this time. Please check back later.", "groups.discover.popular.show_more": "Show More", diff --git a/app/soapbox/normalizers/__tests__/instance.test.ts b/app/soapbox/normalizers/__tests__/instance.test.ts index 90472e7f8..f2bac4867 100644 --- a/app/soapbox/normalizers/__tests__/instance.test.ts +++ b/app/soapbox/normalizers/__tests__/instance.test.ts @@ -25,7 +25,7 @@ describe('normalizeInstance()', () => { }, groups: { max_characters_name: 50, - max_characters_description: 100, + max_characters_description: 160, }, }, description: '', diff --git a/app/soapbox/normalizers/instance.ts b/app/soapbox/normalizers/instance.ts index 3632b9058..77233c143 100644 --- a/app/soapbox/normalizers/instance.ts +++ b/app/soapbox/normalizers/instance.ts @@ -37,7 +37,7 @@ export const InstanceRecord = ImmutableRecord({ }), groups: ImmutableMap({ max_characters_name: 50, - max_characters_description: 100, + max_characters_description: 160, }), }), description: '', From dc761f94167f0dbe9b601494072c422f0bed8354 Mon Sep 17 00:00:00 2001 From: Chewbacca Date: Wed, 12 Apr 2023 15:37:20 -0400 Subject: [PATCH 09/16] Update i18n --- app/soapbox/locales/en.json | 1 - 1 file changed, 1 deletion(-) diff --git a/app/soapbox/locales/en.json b/app/soapbox/locales/en.json index b7a7a5f6d..89608c4d5 100644 --- a/app/soapbox/locales/en.json +++ b/app/soapbox/locales/en.json @@ -968,7 +968,6 @@ "manage_group.privacy.private.label": "Private (Owner approval required)", "manage_group.privacy.public.hint": "Discoverable. Anyone can join.", "manage_group.privacy.public.label": "Public", - "manage_group.success": "Group saved!", "manage_group.tagline": "Groups connect you with others based on shared interests.", "media_panel.empty_message": "No media found.", "media_panel.title": "Media", From f55943a79b3666b9f00becc603b9514feacf9238 Mon Sep 17 00:00:00 2001 From: Chewbacca Date: Wed, 12 Apr 2023 16:09:18 -0400 Subject: [PATCH 10/16] Fix outline of StatusActionButton --- app/soapbox/components/status-action-button.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/soapbox/components/status-action-button.tsx b/app/soapbox/components/status-action-button.tsx index 39795fc7e..47b3c11b8 100644 --- a/app/soapbox/components/status-action-button.tsx +++ b/app/soapbox/components/status-action-button.tsx @@ -53,7 +53,7 @@ const StatusActionButton = React.forwardRef Date: Wed, 12 Apr 2023 16:56:56 -0400 Subject: [PATCH 11/16] Handle error from API on Group Save --- app/soapbox/entity-store/hooks/useCreateEntity.ts | 11 ++++++++--- app/soapbox/features/group/edit-group.tsx | 12 +++++++++++- 2 files changed, 19 insertions(+), 4 deletions(-) diff --git a/app/soapbox/entity-store/hooks/useCreateEntity.ts b/app/soapbox/entity-store/hooks/useCreateEntity.ts index 6b4aaff73..24ce3af7d 100644 --- a/app/soapbox/entity-store/hooks/useCreateEntity.ts +++ b/app/soapbox/entity-store/hooks/useCreateEntity.ts @@ -1,3 +1,4 @@ +import { AxiosError } from 'axios'; import { z } from 'zod'; import { useAppDispatch, useLoading } from 'soapbox/hooks'; @@ -23,7 +24,7 @@ function useCreateEntity( const [isSubmitting, setPromise] = useLoading(); const { entityType, listKey } = parseEntitiesPath(expandedPath); - async function createEntity(data: Data, callbacks: EntityCallbacks = {}): Promise { + async function createEntity(data: Data, callbacks: EntityCallbacks = {}): Promise { try { const result = await setPromise(entityFn(data)); const schema = opts.schema || z.custom(); @@ -36,8 +37,12 @@ function useCreateEntity( callbacks.onSuccess(entity); } } catch (error) { - if (callbacks.onError) { - callbacks.onError(error); + if (error instanceof AxiosError) { + if (callbacks.onError) { + callbacks.onError(error); + } + } else { + throw error; } } } diff --git a/app/soapbox/features/group/edit-group.tsx b/app/soapbox/features/group/edit-group.tsx index 57f0adfef..7527a1802 100644 --- a/app/soapbox/features/group/edit-group.tsx +++ b/app/soapbox/features/group/edit-group.tsx @@ -62,10 +62,20 @@ const EditGroup: React.FC = ({ params: { id: groupId } }) => { avatar: avatar.file, header: header.file, tags, + }, { + onSuccess() { + toast.success(intl.formatMessage(messages.groupSaved)); + }, + onError(error) { + const message = (error.response?.data as any)?.error; + + if (error.response?.status === 422 && typeof message !== 'undefined') { + toast.error(message); + } + }, }); setIsSubmitting(false); - toast.success(intl.formatMessage(messages.groupSaved)); } const handleAddTag = () => { From 4cb628d5a491d113afa44d7c1fc948fe9a9db480 Mon Sep 17 00:00:00 2001 From: Chewbacca Date: Fri, 14 Apr 2023 08:06:53 -0400 Subject: [PATCH 12/16] Allow reposting Group posts --- app/soapbox/components/status-action-bar.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/soapbox/components/status-action-bar.tsx b/app/soapbox/components/status-action-bar.tsx index 5fa3e6c3b..585cc32d9 100644 --- a/app/soapbox/components/status-action-bar.tsx +++ b/app/soapbox/components/status-action-bar.tsx @@ -536,7 +536,7 @@ const StatusActionBar: React.FC = ({ return menu; }; - const publicStatus = ['public', 'unlisted'].includes(status.visibility); + const publicStatus = ['public', 'unlisted', 'group'].includes(status.visibility); const replyCount = status.replies_count; const reblogCount = status.reblogs_count; From 8d832856b159ea0b2d31198069080b98110fb33b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?marcin=20miko=C5=82ajczak?= Date: Sun, 16 Apr 2023 00:03:06 +0200 Subject: [PATCH 13/16] Only use 'username or e-mail' if can log in with username MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: marcin mikołajczak --- .../auth-login/components/login-form.tsx | 23 +++++++++++++------ .../auth-login/components/password-reset.tsx | 8 ++++--- .../public-layout/components/header.tsx | 8 ++++--- app/soapbox/features/ui/components/navbar.tsx | 8 ++++--- app/soapbox/utils/features.ts | 5 ++++ 5 files changed, 36 insertions(+), 16 deletions(-) diff --git a/app/soapbox/features/auth-login/components/login-form.tsx b/app/soapbox/features/auth-login/components/login-form.tsx index a91998671..f526548d8 100644 --- a/app/soapbox/features/auth-login/components/login-form.tsx +++ b/app/soapbox/features/auth-login/components/login-form.tsx @@ -5,11 +5,16 @@ import { Link } from 'react-router-dom'; import { Button, Form, FormActions, FormGroup, Input, Stack } from 'soapbox/components/ui'; import ConsumersList from './consumers-list'; +import { useFeatures } from 'soapbox/hooks'; const messages = defineMessages({ username: { id: 'login.fields.username_label', - defaultMessage: 'Email or username', + defaultMessage: 'E-mail or username', + }, + email: { + id: 'login.fields.email_label', + defaultMessage: 'E-mail address', }, password: { id: 'login.fields.password_placeholder', @@ -24,6 +29,10 @@ interface ILoginForm { const LoginForm: React.FC = ({ isLoading, handleSubmit }) => { const intl = useIntl(); + const features = useFeatures(); + + const usernameLabel = intl.formatMessage(features.logInWithUsername ? messages.username : messages.email); + const passwordLabel = intl.formatMessage(messages.password); return (
@@ -33,10 +42,10 @@ const LoginForm: React.FC = ({ isLoading, handleSubmit }) => {
- + = ({ isLoading, handleSubmit }) => { = ({ isLoading, handleSubmit }) => { } > { const dispatch = useAppDispatch(); const intl = useIntl(); + const features = useFeatures(); const [isLoading, setIsLoading] = useState(false); const [success, setSuccess] = useState(false); @@ -43,7 +45,7 @@ const PasswordReset = () => {
- + { const dispatch = useAppDispatch(); const intl = useIntl(); + const features = useFeatures(); const account = useOwnAccount(); const soapboxConfig = useSoapboxConfig(); @@ -123,7 +125,7 @@ const Header = () => { value={username} onChange={(event) => setUsername(event.target.value.trim())} type='text' - placeholder={intl.formatMessage(messages.username)} + placeholder={intl.formatMessage(features.logInWithUsername ? messages.username : messages.email)} className='max-w-[200px]' autoCorrect='off' autoCapitalize='off' diff --git a/app/soapbox/features/ui/components/navbar.tsx b/app/soapbox/features/ui/components/navbar.tsx index 6cfb28728..422d22561 100644 --- a/app/soapbox/features/ui/components/navbar.tsx +++ b/app/soapbox/features/ui/components/navbar.tsx @@ -9,7 +9,7 @@ import { openSidebar } from 'soapbox/actions/sidebar'; import SiteLogo from 'soapbox/components/site-logo'; import { Avatar, Button, Form, HStack, IconButton, Input, Tooltip } from 'soapbox/components/ui'; import Search from 'soapbox/features/compose/components/search'; -import { useAppDispatch, useOwnAccount, useRegistrationStatus } from 'soapbox/hooks'; +import { useAppDispatch, useFeatures, useOwnAccount, useRegistrationStatus } from 'soapbox/hooks'; import ProfileDropdown from './profile-dropdown'; @@ -17,7 +17,8 @@ import type { AxiosError } from 'axios'; const messages = defineMessages({ login: { id: 'navbar.login.action', defaultMessage: 'Log in' }, - username: { id: 'navbar.login.username.placeholder', defaultMessage: 'Email or username' }, + username: { id: 'navbar.login.username.placeholder', defaultMessage: 'E-mail or username' }, + email: { id: 'navbar.login.email.placeholder', defaultMessage: 'E-mail address' }, password: { id: 'navbar.login.password.label', defaultMessage: 'Password' }, forgotPassword: { id: 'navbar.login.forgot_password', defaultMessage: 'Forgot password?' }, }); @@ -25,6 +26,7 @@ const messages = defineMessages({ const Navbar = () => { const dispatch = useAppDispatch(); const intl = useIntl(); + const features = useFeatures(); const { isOpen } = useRegistrationStatus(); const account = useOwnAccount(); const node = useRef(null); @@ -111,7 +113,7 @@ const Navbar = () => { value={username} onChange={(event) => setUsername(event.target.value)} type='text' - placeholder={intl.formatMessage(messages.username)} + placeholder={intl.formatMessage(features.logInWithUsername ? messages.username : messages.email)} className='max-w-[200px]' /> diff --git a/app/soapbox/utils/features.ts b/app/soapbox/utils/features.ts index afbece3b4..9aff34a90 100644 --- a/app/soapbox/utils/features.ts +++ b/app/soapbox/utils/features.ts @@ -597,6 +597,11 @@ const getInstanceFeatures = (instance: Instance) => { v.software === PLEROMA && gte(v.version, '0.9.9'), ]), + /** + * Can sign in using username instead of e-mail address. + */ + logInWithUsername: v.software === PLEROMA, + /** * Can perform moderation actions with account and reports. * @see {@link https://docs.joinmastodon.org/methods/admin/} From ba69f5ba624873c85ba2e63de211626885b5134e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?marcin=20miko=C5=82ajczak?= Date: Sun, 16 Apr 2023 00:06:47 +0200 Subject: [PATCH 14/16] update en.json MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: marcin mikołajczak --- app/soapbox/locales/en.json | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/app/soapbox/locales/en.json b/app/soapbox/locales/en.json index 89608c4d5..a4d547d8e 100644 --- a/app/soapbox/locales/en.json +++ b/app/soapbox/locales/en.json @@ -842,6 +842,7 @@ "hashtag.column_header.tag_mode.any": "or {additional}", "hashtag.column_header.tag_mode.none": "without {additional}", "header.home.label": "Home", + "header.login.email.placeholder": "E-mail address", "header.login.forgot_password": "Forgot password?", "header.login.label": "Log in", "header.login.password.label": "Password", @@ -926,6 +927,7 @@ "lists.subheading": "Your lists", "loading_indicator.label": "Loading…", "location_search.placeholder": "Find an address", + "login.fields.email_label": "E-mail address", "login.fields.instance_label": "Instance", "login.fields.instance_placeholder": "example.com", "login.fields.otp_code_hint": "Enter the two-factor code generated by your phone app or use one of your recovery codes", @@ -1014,6 +1016,7 @@ "mute_modal.duration": "Duration", "mute_modal.hide_notifications": "Hide notifications from this user?", "navbar.login.action": "Log in", + "navbar.login.email.placeholder": "E-mail address", "navbar.login.forgot_password": "Forgot password?", "navbar.login.password.label": "Password", "navbar.login.username.placeholder": "Email or username", @@ -1116,6 +1119,7 @@ "onboarding.suggestions.title": "Suggested accounts", "onboarding.view_feed": "View Feed", "password_reset.confirmation": "Check your email for confirmation.", + "password_reset.fields.email_placeholder": "E-mail address", "password_reset.fields.username_placeholder": "Email or username", "password_reset.header": "Reset Password", "password_reset.reset": "Reset password", From 324c427a725c504ab7120784985c49b5f691b9cb Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Sun, 16 Apr 2023 06:12:01 +0000 Subject: [PATCH 15/16] Add truthsocial to loginWithUsername feature --- app/soapbox/utils/features.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/soapbox/utils/features.ts b/app/soapbox/utils/features.ts index 9aff34a90..36e2811ab 100644 --- a/app/soapbox/utils/features.ts +++ b/app/soapbox/utils/features.ts @@ -600,7 +600,7 @@ const getInstanceFeatures = (instance: Instance) => { /** * Can sign in using username instead of e-mail address. */ - logInWithUsername: v.software === PLEROMA, + logInWithUsername: v.software === PLEROMA || v.software === TRUTHSOCIAL, /** * Can perform moderation actions with account and reports. From 6b4076ce10786b7c2650f7cc3cf2a971a37d4f4d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?marcin=20miko=C5=82ajczak?= Date: Sun, 16 Apr 2023 15:10:41 +0200 Subject: [PATCH 16/16] lint MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: marcin mikołajczak --- app/soapbox/features/auth-login/components/login-form.tsx | 2 +- app/soapbox/utils/features.ts | 5 ++++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/app/soapbox/features/auth-login/components/login-form.tsx b/app/soapbox/features/auth-login/components/login-form.tsx index f526548d8..9a4768903 100644 --- a/app/soapbox/features/auth-login/components/login-form.tsx +++ b/app/soapbox/features/auth-login/components/login-form.tsx @@ -3,9 +3,9 @@ import { FormattedMessage, defineMessages, useIntl } from 'react-intl'; import { Link } from 'react-router-dom'; import { Button, Form, FormActions, FormGroup, Input, Stack } from 'soapbox/components/ui'; +import { useFeatures } from 'soapbox/hooks'; import ConsumersList from './consumers-list'; -import { useFeatures } from 'soapbox/hooks'; const messages = defineMessages({ username: { diff --git a/app/soapbox/utils/features.ts b/app/soapbox/utils/features.ts index 36e2811ab..b24e75525 100644 --- a/app/soapbox/utils/features.ts +++ b/app/soapbox/utils/features.ts @@ -600,7 +600,10 @@ const getInstanceFeatures = (instance: Instance) => { /** * Can sign in using username instead of e-mail address. */ - logInWithUsername: v.software === PLEROMA || v.software === TRUTHSOCIAL, + logInWithUsername: any([ + v.software === PLEROMA, + v.software === TRUTHSOCIAL, + ]), /** * Can perform moderation actions with account and reports.