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

Wyświetl plik

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

Wyświetl plik

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

Wyświetl plik

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

Wyświetl plik

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

Wyświetl plik

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