Groups: Snackbar messages, timelines handling

Signed-off-by: marcin mikołajczak <git@mkljczk.pl>
environments/review-mastodon-g-0qbqe2/deployments/1773
marcin mikołajczak 2022-12-17 13:35:52 +01:00
rodzic d299512694
commit bed8619cf0
6 zmienionych plików z 61 dodań i 40 usunięć

Wyświetl plik

@ -1,18 +1,18 @@
import { GroupRole } from 'soapbox/reducers/group-memberships';
import { defineMessages } from 'react-intl';
import api, { getLinks } from '../api';
import { fetchRelationships } from './accounts';
import { importFetchedGroups, importFetchedAccounts } from './importer';
import { closeModal, openModal } from './modals';
import snackbar from './snackbar';
import { deleteFromTimelines } from './timelines';
import type { AxiosError } from 'axios';
import type { GroupRole } from 'soapbox/reducers/group-memberships';
import type { AppDispatch, RootState } from 'soapbox/store';
import type { APIEntity, Group } from 'soapbox/types/entities';
type GroupMedia = 'header' | 'avatar';
const GROUP_EDITOR_SET = 'GROUP_EDITOR_SET';
const GROUP_CREATE_REQUEST = 'GROUP_CREATE_REQUEST';
@ -110,6 +110,15 @@ const GROUP_EDITOR_MEDIA_CHANGE = 'GROUP_EDITOR_MEDIA_CHANGE';
const GROUP_EDITOR_RESET = 'GROUP_EDITOR_RESET';
const messages = defineMessages({
success: { id: 'manage_group.submit_success', defaultMessage: 'The group was created' },
editSuccess: { id: 'manage_group.edit_success', defaultMessage: 'The group was edited' },
joinSuccess: { id: 'group.join.success', defaultMessage: 'Joined the group' },
joinRequestSuccess: { id: 'group.join.request_success', defaultMessage: 'Requested to join the group' },
leaveSuccess: { id: 'group.leave.success', defaultMessage: 'Left the group' },
view: { id: 'snackbar.view', defaultMessage: 'View' },
});
const editGroup = (group: Group) => (dispatch: AppDispatch) => {
dispatch({
type: GROUP_EDITOR_SET,
@ -130,6 +139,7 @@ const createGroup = (params: Record<string, any>, shouldReset?: boolean) =>
.then(({ data }) => {
dispatch(importFetchedGroups([data]));
dispatch(createGroupSuccess(data));
dispatch(snackbar.success(messages.success, messages.view, `/groups/${data.id}`));
if (shouldReset) {
dispatch(resetGroupEditor());
@ -160,6 +170,7 @@ const updateGroup = (id: string, params: Record<string, any>, shouldReset?: bool
.then(({ data }) => {
dispatch(importFetchedGroups([data]));
dispatch(updateGroupSuccess(data));
dispatch(snackbar.success(messages.editSuccess));
if (shouldReset) {
dispatch(resetGroupEditor());
@ -305,6 +316,7 @@ const joinGroup = (id: string) =>
api(getState).post(`/api/v1/groups/${id}/join`).then(response => {
dispatch(joinGroupSuccess(response.data));
dispatch(snackbar.success(locked ? messages.joinRequestSuccess : messages.joinSuccess));
}).catch(error => {
dispatch(joinGroupFail(error, locked));
});
@ -316,6 +328,7 @@ const leaveGroup = (id: string) =>
api(getState).post(`/api/v1/groups/${id}/leave`).then(response => {
dispatch(leaveGroupSuccess(response.data));
dispatch(snackbar.success(messages.leaveSuccess));
}).catch(error => {
dispatch(leaveGroupFail(error));
});
@ -826,7 +839,7 @@ const changeGroupEditorPrivacy = (value: boolean) => ({
value,
});
const changeGroupEditorMedia = (mediaType: GroupMedia, file: File) => ({
const changeGroupEditorMedia = (mediaType: 'header' | 'avatar', file: File) => ({
type: GROUP_EDITOR_MEDIA_CHANGE,
mediaType,
value: file,

Wyświetl plik

@ -80,11 +80,12 @@ const StatusActionButton = React.forwardRef<HTMLButtonElement, IStatusActionButt
type='button'
className={classNames(
'flex items-center p-1 rounded-full',
'text-gray-600 hover:text-gray-600 dark:hover:text-white',
'text-gray-600',
'bg-white dark:bg-transparent',
'focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-primary-500 dark:ring-offset-0',
{
'text-black dark:text-white': active && emoji,
'hover:text-gray-600 dark:hover:text-white': !filteredProps.disabled,
'text-accent-300 hover:text-accent-300 dark:hover:text-accent-300': active && !emoji && color === COLORS.accent,
'text-success-600 hover:text-success-600 dark:hover:text-success-600': active && !emoji && color === COLORS.success,
'space-x-1': !text,

Wyświetl plik

@ -46,6 +46,8 @@ interface IStatusList extends Omit<IScrollableList, 'onLoadMore' | 'children'> {
divideType?: 'space' | 'border',
/** Whether to display ads. */
showAds?: boolean,
/** Whether to show group information. */
showGroup?: boolean,
}
/** Feed of statuses, built atop ScrollableList. */
@ -59,6 +61,7 @@ const StatusList: React.FC<IStatusList> = ({
isLoading,
isPartial,
showAds = false,
showGroup = true,
...other
}) => {
const { data: ads } = useAds();
@ -135,6 +138,7 @@ const StatusList: React.FC<IStatusList> = ({
onMoveUp={handleMoveUp}
onMoveDown={handleMoveDown}
contextType={timelineId}
showGroup={showGroup}
/>
);
};
@ -167,6 +171,7 @@ const StatusList: React.FC<IStatusList> = ({
onMoveUp={handleMoveUp}
onMoveDown={handleMoveDown}
contextType={timelineId}
showGroup={showGroup}
/>
));
};

Wyświetl plik

@ -2,7 +2,7 @@ import classNames from 'clsx';
import React, { useEffect, useRef, useState } from 'react';
import { HotKeys } from 'react-hotkeys';
import { useIntl, FormattedMessage, defineMessages } from 'react-intl';
import { NavLink, useHistory } from 'react-router-dom';
import { Link, useHistory } from 'react-router-dom';
import { mentionCompose, replyCompose } from 'soapbox/actions/compose';
import { toggleFavourite, toggleReblog } from 'soapbox/actions/interactions';
@ -23,10 +23,9 @@ import StatusReplyMentions from './status-reply-mentions';
import SensitiveContentOverlay from './statuses/sensitive-content-overlay';
import { Card, HStack, Stack, Text } from './ui';
import type { Map as ImmutableMap } from 'immutable';
import type {
Account as AccountEntity,
// Group as GroupEntity,
Group as GroupEntity,
Status as StatusEntity,
} from 'soapbox/types/entities';
@ -46,12 +45,12 @@ export interface IStatus {
unread?: boolean,
onMoveUp?: (statusId: string, featured?: boolean) => void,
onMoveDown?: (statusId: string, featured?: boolean) => void,
group?: ImmutableMap<string, any>,
focusable?: boolean,
featured?: boolean,
hideActionBar?: boolean,
hoverable?: boolean,
variant?: 'default' | 'rounded',
showGroup?: boolean,
withDismiss?: boolean,
accountAction?: React.ReactElement,
}
@ -70,6 +69,7 @@ const Status: React.FC<IStatus> = (props) => {
unread,
hideActionBar,
variant = 'rounded',
showGroup = true,
withDismiss,
} = props;
@ -235,7 +235,7 @@ const Status: React.FC<IStatus> = (props) => {
const displayNameHtml = { __html: String(status.getIn(['account', 'display_name_html'])) };
reblogElement = (
<NavLink
<Link
to={`/@${status.getIn(['account', 'acct'])}`}
onClick={(event) => event.stopPropagation()}
className='hidden sm:flex items-center text-gray-700 dark:text-gray-600 text-xs font-medium space-x-1 hover:underline'
@ -253,12 +253,12 @@ const Status: React.FC<IStatus> = (props) => {
}}
/>
</HStack>
</NavLink>
</Link>
);
reblogElementMobile = (
<div className='pb-5 -mt-2 sm:hidden truncate'>
<NavLink
<Link
to={`/@${status.getIn(['account', 'acct'])}`}
onClick={(event) => event.stopPropagation()}
className='flex items-center text-gray-700 dark:text-gray-600 text-xs font-medium space-x-1 hover:underline'
@ -276,7 +276,7 @@ const Status: React.FC<IStatus> = (props) => {
}}
/>
</span>
</NavLink>
</Link>
</div>
);
@ -300,7 +300,7 @@ const Status: React.FC<IStatus> = (props) => {
}
}
// const group = actualStatus.group as GroupEntity | null;
const group = actualStatus.group as GroupEntity | null;
const handlers = muted ? undefined : {
reply: handleHotkeyReply,
@ -345,26 +345,6 @@ const Status: React.FC<IStatus> = (props) => {
</div>
)}
{/* {group && (
<div className='pt-4 px-4'>
<HStack alignItems='center' space={1}>
<Icon src={require('@tabler/icons/circles.svg')} className='text-gray-600 dark:text-gray-400' />
<Text size='sm' theme='muted' weight='medium'>
<FormattedMessage
id='status.group'
defaultMessage='Posted in {group}'
values={{ group: (
<Link className='hover:underline' to={`/groups/${group.id}`} onClick={(e) => e.stopPropagation()}>
<span dangerouslySetInnerHTML={{ __html: group.display_name_html }} />
</Link>
) }}
/>
</Text>
</HStack>
</div>
)} */}
<Card
variant={variant}
className={classNames('status__wrapper', `status-${actualStatus.visibility}`, {
@ -375,6 +355,26 @@ const Status: React.FC<IStatus> = (props) => {
})}
data-id={status.id}
>
{showGroup && group && (
<div className='mb-4'>
<HStack alignItems='center' space={1}>
<Icon src={require('@tabler/icons/circles.svg')} className='text-gray-600 dark:text-gray-400' />
<Text size='sm' theme='muted' weight='medium'>
<FormattedMessage
id='status.group'
defaultMessage='Posted in {group}'
values={{ group: (
<Link className='hover:underline' to={`/groups/${group.id}`} onClick={(e) => e.stopPropagation()}>
<span dangerouslySetInnerHTML={{ __html: group.display_name_html }} />
</Link>
) }}
/>
</Text>
</HStack>
</div>
)}
{reblogElementMobile}
<div className='mb-4'>

Wyświetl plik

@ -64,7 +64,8 @@ const GroupTimeline: React.FC<IGroupTimeline> = (props) => {
timelineId={`group:${groupId}`}
onLoadMore={handleLoadMore}
emptyMessage={<FormattedMessage id='empty_column.group' defaultMessage='There are no posts in this group yet.' />}
divideType='space'
divideType='border'
showGroup={false}
/>
</Stack>
);

Wyświetl plik

@ -35,7 +35,6 @@ import {
} from '../actions/timelines';
import type { AnyAction } from 'redux';
import type { StatusVisibility } from 'soapbox/normalizers/status';
import type { APIEntity, Status } from 'soapbox/types/entities';
const TRUNCATE_LIMIT = 40;
@ -242,8 +241,10 @@ const timelineDisconnect = (state: State, timelineId: string) => {
}));
};
const getTimelinesByVisibility = (visibility: StatusVisibility) => {
switch (visibility) {
const getTimelinesForStatus = (status: APIEntity) => {
switch (status.visibility) {
case 'group':
return [`group:${status.group?.id || status.group_id}`];
case 'direct':
return ['direct'];
case 'public':
@ -269,7 +270,7 @@ const importPendingStatus = (state: State, params: APIEntity, idempotencyKey: st
const statusId = `末pending-${idempotencyKey}`;
return state.withMutations(state => {
const timelineIds = getTimelinesByVisibility(params.visibility);
const timelineIds = getTimelinesForStatus(params);
timelineIds.forEach(timelineId => {
updateTimelineQueue(state, timelineId, statusId);
@ -293,7 +294,7 @@ const importStatus = (state: State, status: APIEntity, idempotencyKey: string) =
return state.withMutations(state => {
replacePendingStatus(state, idempotencyKey, status.id);
const timelineIds = getTimelinesByVisibility(status.visibility);
const timelineIds = getTimelinesForStatus(status);
timelineIds.forEach(timelineId => {
updateTimeline(state, timelineId, status.id);