kopia lustrzana https://gitlab.com/soapbox-pub/soapbox
Manage group pages
Signed-off-by: marcin mikołajczak <git@mkljczk.pl>environments/review-mastodon-g-0qbqe2/deployments/1797
rodzic
18b297ad63
commit
6b92d5f3a5
|
@ -131,7 +131,7 @@ const createGroup = (params: Record<string, any>, shouldReset?: boolean) =>
|
|||
(dispatch: AppDispatch, getState: () => RootState) => {
|
||||
dispatch(createGroupRequest());
|
||||
|
||||
api(getState).post('/api/v1/groups', params, {
|
||||
return api(getState).post('/api/v1/groups', params, {
|
||||
headers: {
|
||||
'Content-Type': 'multipart/form-data',
|
||||
},
|
||||
|
@ -166,7 +166,7 @@ const updateGroup = (id: string, params: Record<string, any>, shouldReset?: bool
|
|||
(dispatch: AppDispatch, getState: () => RootState) => {
|
||||
dispatch(updateGroupRequest());
|
||||
|
||||
api(getState).put(`/api/v1/groups/${id}`, params)
|
||||
return api(getState).put(`/api/v1/groups/${id}`, params)
|
||||
.then(({ data }) => {
|
||||
dispatch(importFetchedGroups([data]));
|
||||
dispatch(updateGroupSuccess(data));
|
||||
|
@ -196,7 +196,7 @@ const updateGroupFail = (error: AxiosError) => ({
|
|||
const deleteGroup = (id: string) => (dispatch: AppDispatch, getState: () => RootState) => {
|
||||
dispatch(deleteGroupRequest(id));
|
||||
|
||||
api(getState).delete(`/api/v1/groups/${id}`)
|
||||
return api(getState).delete(`/api/v1/groups/${id}`)
|
||||
.then(() => dispatch(deleteGroupSuccess(id)))
|
||||
.catch(err => dispatch(deleteGroupFail(id, err)));
|
||||
};
|
||||
|
@ -221,7 +221,7 @@ const fetchGroup = (id: string) => (dispatch: AppDispatch, getState: () => RootS
|
|||
dispatch(fetchGroupRelationships([id]));
|
||||
dispatch(fetchGroupRequest(id));
|
||||
|
||||
api(getState).get(`/api/v1/groups/${id}`)
|
||||
return api(getState).get(`/api/v1/groups/${id}`)
|
||||
.then(({ data }) => {
|
||||
dispatch(importFetchedGroups([data]));
|
||||
dispatch(fetchGroupSuccess(data));
|
||||
|
@ -248,7 +248,7 @@ const fetchGroupFail = (id: string, error: AxiosError) => ({
|
|||
const fetchGroups = () => (dispatch: AppDispatch, getState: () => RootState) => {
|
||||
dispatch(fetchGroupsRequest());
|
||||
|
||||
api(getState).get('/api/v1/groups')
|
||||
return api(getState).get('/api/v1/groups')
|
||||
.then(({ data }) => {
|
||||
dispatch(importFetchedGroups(data));
|
||||
dispatch(fetchGroupsSuccess(data));
|
||||
|
@ -282,7 +282,7 @@ const fetchGroupRelationships = (groupIds: string[]) =>
|
|||
|
||||
dispatch(fetchGroupRelationshipsRequest(newGroupIds));
|
||||
|
||||
api(getState).get(`/api/v1/groups/relationships?${newGroupIds.map(id => `id[]=${id}`).join('&')}`).then(response => {
|
||||
return api(getState).get(`/api/v1/groups/relationships?${newGroupIds.map(id => `id[]=${id}`).join('&')}`).then(response => {
|
||||
dispatch(fetchGroupRelationshipsSuccess(response.data));
|
||||
}).catch(error => {
|
||||
dispatch(fetchGroupRelationshipsFail(error));
|
||||
|
@ -314,7 +314,7 @@ const joinGroup = (id: string) =>
|
|||
|
||||
dispatch(joinGroupRequest(id, locked));
|
||||
|
||||
api(getState).post(`/api/v1/groups/${id}/join`).then(response => {
|
||||
return api(getState).post(`/api/v1/groups/${id}/join`).then(response => {
|
||||
dispatch(joinGroupSuccess(response.data));
|
||||
dispatch(snackbar.success(locked ? messages.joinRequestSuccess : messages.joinSuccess));
|
||||
}).catch(error => {
|
||||
|
@ -326,7 +326,7 @@ const leaveGroup = (id: string) =>
|
|||
(dispatch: AppDispatch, getState: () => RootState) => {
|
||||
dispatch(leaveGroupRequest(id));
|
||||
|
||||
api(getState).post(`/api/v1/groups/${id}/leave`).then(response => {
|
||||
return api(getState).post(`/api/v1/groups/${id}/leave`).then(response => {
|
||||
dispatch(leaveGroupSuccess(response.data));
|
||||
dispatch(snackbar.success(messages.leaveSuccess));
|
||||
}).catch(error => {
|
||||
|
@ -376,7 +376,7 @@ const groupDeleteStatus = (groupId: string, statusId: string) =>
|
|||
(dispatch: AppDispatch, getState: () => RootState) => {
|
||||
dispatch(groupDeleteStatusRequest(groupId, statusId));
|
||||
|
||||
api(getState).delete(`/api/v1/groups/${groupId}/statuses/${statusId}`)
|
||||
return api(getState).delete(`/api/v1/groups/${groupId}/statuses/${statusId}`)
|
||||
.then(() => {
|
||||
dispatch(deleteFromTimelines(statusId));
|
||||
dispatch(groupDeleteStatusSuccess(groupId, statusId));
|
||||
|
@ -434,7 +434,7 @@ const fetchGroupBlocks = (id: string) =>
|
|||
(dispatch: AppDispatch, getState: () => RootState) => {
|
||||
dispatch(fetchGroupBlocksRequest(id));
|
||||
|
||||
api(getState).get(`/api/v1/groups/${id}/blocks`).then(response => {
|
||||
return api(getState).get(`/api/v1/groups/${id}/blocks`).then(response => {
|
||||
const next = getLinks(response).refs.find(link => link.rel === 'next');
|
||||
|
||||
dispatch(importFetchedAccounts(response.data));
|
||||
|
@ -473,7 +473,7 @@ const expandGroupBlocks = (id: string) =>
|
|||
|
||||
dispatch(expandGroupBlocksRequest(id));
|
||||
|
||||
api(getState).get(url).then(response => {
|
||||
return api(getState).get(url).then(response => {
|
||||
const next = getLinks(response).refs.find(link => link.rel === 'next');
|
||||
|
||||
dispatch(importFetchedAccounts(response.data));
|
||||
|
@ -620,7 +620,7 @@ const fetchGroupMemberships = (id: string, role: GroupRole) =>
|
|||
(dispatch: AppDispatch, getState: () => RootState) => {
|
||||
dispatch(fetchGroupMembershipsRequest(id, role));
|
||||
|
||||
api(getState).get(`/api/v1/groups/${id}/memberships`, { params: { role } }).then(response => {
|
||||
return api(getState).get(`/api/v1/groups/${id}/memberships`, { params: { role } }).then(response => {
|
||||
const next = getLinks(response).refs.find(link => link.rel === 'next');
|
||||
|
||||
dispatch(importFetchedAccounts(response.data.map((membership: APIEntity) => membership.account)));
|
||||
|
@ -662,7 +662,7 @@ const expandGroupMemberships = (id: string, role: GroupRole) =>
|
|||
|
||||
dispatch(expandGroupMembershipsRequest(id, role));
|
||||
|
||||
api(getState).get(url).then(response => {
|
||||
return api(getState).get(url).then(response => {
|
||||
const next = getLinks(response).refs.find(link => link.rel === 'next');
|
||||
|
||||
dispatch(importFetchedAccounts(response.data.map((membership: APIEntity) => membership.account)));
|
||||
|
@ -698,7 +698,7 @@ const fetchGroupMembershipRequests = (id: string) =>
|
|||
(dispatch: AppDispatch, getState: () => RootState) => {
|
||||
dispatch(fetchGroupMembershipRequestsRequest(id));
|
||||
|
||||
api(getState).get(`/api/v1/groups/${id}/membership_requests`).then(response => {
|
||||
return api(getState).get(`/api/v1/groups/${id}/membership_requests`).then(response => {
|
||||
const next = getLinks(response).refs.find(link => link.rel === 'next');
|
||||
|
||||
dispatch(importFetchedAccounts(response.data));
|
||||
|
@ -737,7 +737,7 @@ const expandGroupMembershipRequests = (id: string) =>
|
|||
|
||||
dispatch(expandGroupMembershipRequestsRequest(id));
|
||||
|
||||
api(getState).get(url).then(response => {
|
||||
return api(getState).get(url).then(response => {
|
||||
const next = getLinks(response).refs.find(link => link.rel === 'next');
|
||||
|
||||
dispatch(importFetchedAccounts(response.data));
|
||||
|
@ -770,7 +770,7 @@ const authorizeGroupMembershipRequest = (groupId: string, accountId: string) =>
|
|||
(dispatch: AppDispatch, getState: () => RootState) => {
|
||||
dispatch(authorizeGroupMembershipRequestRequest(groupId, accountId));
|
||||
|
||||
api(getState)
|
||||
return api(getState)
|
||||
.post(`/api/v1/groups/${groupId}/membership_requests/${accountId}/authorize`)
|
||||
.then(() => dispatch(authorizeGroupMembershipRequestSuccess(groupId, accountId)))
|
||||
.catch(error => dispatch(authorizeGroupMembershipRequestFail(groupId, accountId, error)));
|
||||
|
@ -799,7 +799,7 @@ const rejectGroupMembershipRequest = (groupId: string, accountId: string) =>
|
|||
(dispatch: AppDispatch, getState: () => RootState) => {
|
||||
dispatch(rejectGroupMembershipRequestRequest(groupId, accountId));
|
||||
|
||||
api(getState)
|
||||
return api(getState)
|
||||
.post(`/api/v1/groups/${groupId}/membership_requests/${accountId}/reject`)
|
||||
.then(() => dispatch(rejectGroupMembershipRequestSuccess(groupId, accountId)))
|
||||
.catch(error => dispatch(rejectGroupMembershipRequestFail(groupId, accountId, error)));
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import React from 'react';
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
import { Link, useHistory } from 'react-router-dom';
|
||||
|
||||
import HoverRefWrapper from 'soapbox/components/hover-ref-wrapper';
|
||||
|
@ -11,6 +12,7 @@ import { displayFqn } from 'soapbox/utils/state';
|
|||
import RelativeTimestamp from './relative-timestamp';
|
||||
import { Avatar, Emoji, HStack, Icon, IconButton, Stack, Text } from './ui';
|
||||
|
||||
import type { StatusApprovalStatus } from 'soapbox/normalizers/status';
|
||||
import type { Account as AccountEntity } from 'soapbox/types/entities';
|
||||
|
||||
interface IInstanceFavicon {
|
||||
|
@ -68,6 +70,7 @@ interface IAccount {
|
|||
withLinkToProfile?: boolean,
|
||||
withRelationship?: boolean,
|
||||
showEdit?: boolean,
|
||||
approvalStatus?: StatusApprovalStatus,
|
||||
emoji?: string,
|
||||
note?: string,
|
||||
}
|
||||
|
@ -92,6 +95,7 @@ const Account = ({
|
|||
withLinkToProfile = true,
|
||||
withRelationship = true,
|
||||
showEdit = false,
|
||||
approvalStatus,
|
||||
emoji,
|
||||
note,
|
||||
}: IAccount) => {
|
||||
|
@ -236,6 +240,18 @@ const Account = ({
|
|||
</>
|
||||
) : null}
|
||||
|
||||
{approvalStatus && ['pending', 'rejected'].includes(approvalStatus) && (
|
||||
<>
|
||||
<Text tag='span' theme='muted' size='sm'>·</Text>
|
||||
|
||||
<Text tag='span' theme='muted' size='sm'>
|
||||
{approvalStatus === 'pending'
|
||||
? <FormattedMessage id='status.pending' defaultMessage='Pending approval' />
|
||||
: <FormattedMessage id='status.rejected' defaultMessage='Rejected' />}
|
||||
</Text>
|
||||
</>
|
||||
)}
|
||||
|
||||
{showEdit ? (
|
||||
<>
|
||||
<Text tag='span' theme='muted' size='sm'>·</Text>
|
||||
|
|
|
@ -388,6 +388,7 @@ const Status: React.FC<IStatus> = (props) => {
|
|||
showEdit={!!actualStatus.edited_at}
|
||||
showProfileHoverCard={hoverable}
|
||||
withLinkToProfile={hoverable}
|
||||
approvalStatus={actualStatus.approval_status}
|
||||
/>
|
||||
</div>
|
||||
|
||||
|
|
|
@ -0,0 +1,24 @@
|
|||
import React, { useCallback } from 'react';
|
||||
|
||||
import GroupCard from 'soapbox/components/group-card';
|
||||
import { useAppSelector } from 'soapbox/hooks';
|
||||
import { makeGetGroup } from 'soapbox/selectors';
|
||||
|
||||
interface IGroupContainer {
|
||||
id: string
|
||||
}
|
||||
|
||||
const GroupContainer: React.FC<IGroupContainer> = (props) => {
|
||||
const { id, ...rest } = props;
|
||||
|
||||
const getGroup = useCallback(makeGetGroup(), []);
|
||||
const group = useAppSelector(state => getGroup(state, id));
|
||||
|
||||
if (group) {
|
||||
return <GroupCard group={group} {...rest} />;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
export default GroupContainer;
|
|
@ -9,8 +9,10 @@ import IconButton from 'soapbox/components/icon-button';
|
|||
import ScrollableList from 'soapbox/components/scrollable-list';
|
||||
import { HStack, Tabs, Text } from 'soapbox/components/ui';
|
||||
import AccountContainer from 'soapbox/containers/account-container';
|
||||
import GroupContainer from 'soapbox/containers/group-container';
|
||||
import StatusContainer from 'soapbox/containers/status-container';
|
||||
import PlaceholderAccount from 'soapbox/features/placeholder/components/placeholder-account';
|
||||
import PlaceholderGroupCard from 'soapbox/features/placeholder/components/placeholder-group-card';
|
||||
import PlaceholderHashtag from 'soapbox/features/placeholder/components/placeholder-hashtag';
|
||||
import PlaceholderStatus from 'soapbox/features/placeholder/components/placeholder-status';
|
||||
import { useAppDispatch, useAppSelector } from 'soapbox/hooks';
|
||||
|
@ -179,23 +181,15 @@ const SearchResults = () => {
|
|||
if (selectedFilter === 'groups') {
|
||||
hasMore = results.groupsHasMore;
|
||||
loaded = results.groupsLoaded;
|
||||
placeholderComponent = PlaceholderGroupCard;
|
||||
|
||||
if (results.groups && results.groups.size > 0) {
|
||||
searchResults = results.groups.map((groupId: string) => (
|
||||
<></>
|
||||
<GroupContainer id={groupId} />
|
||||
));
|
||||
resultsIds = results.groups;
|
||||
} else if (!submitted && trendingStatuses && !trendingStatuses.isEmpty()) {
|
||||
// searchResults = trendingStatuses.map((statusId: string) => (
|
||||
// // @ts-ignore
|
||||
// <StatusContainer
|
||||
// key={statusId}
|
||||
// id={statusId}
|
||||
// onMoveUp={handleMoveUp}
|
||||
// onMoveDown={handleMoveDown}
|
||||
// />
|
||||
// ));
|
||||
// resultsIds = trendingStatuses;
|
||||
searchResults = null;
|
||||
} else if (loaded) {
|
||||
noResultsMessage = (
|
||||
<div className='empty-column-indicator'>
|
||||
|
|
|
@ -3,10 +3,10 @@ import React from 'react';
|
|||
import { FormattedMessage } from 'react-intl';
|
||||
|
||||
import { getSettings } from 'soapbox/actions/settings';
|
||||
import Account from 'soapbox/components/account';
|
||||
import Badge from 'soapbox/components/badge';
|
||||
import RelativeTimestamp from 'soapbox/components/relative-timestamp';
|
||||
import { Stack, Text } from 'soapbox/components/ui';
|
||||
import AccountContainer from 'soapbox/containers/account-container';
|
||||
import ActionButton from 'soapbox/features/ui/components/action-button';
|
||||
import { useAppSelector } from 'soapbox/hooks';
|
||||
import { makeGetAccount } from 'soapbox/selectors';
|
||||
|
@ -51,8 +51,8 @@ const AccountCard: React.FC<IAccountCard> = ({ id }) => {
|
|||
</div>
|
||||
|
||||
<Stack space={4} className='p-3'>
|
||||
<AccountContainer
|
||||
id={account.id}
|
||||
<Account
|
||||
account={account}
|
||||
withRelationship={false}
|
||||
/>
|
||||
|
||||
|
|
|
@ -2,7 +2,7 @@ import { List as ImmutableList } from 'immutable';
|
|||
import React from 'react';
|
||||
import { defineMessages, FormattedMessage, useIntl } from 'react-intl';
|
||||
|
||||
import { editGroup, joinGroup, leaveGroup } from 'soapbox/actions/groups';
|
||||
import { joinGroup, leaveGroup } from 'soapbox/actions/groups';
|
||||
import { openModal } from 'soapbox/actions/modals';
|
||||
import StillImage from 'soapbox/components/still-image';
|
||||
import { Avatar, Button, HStack, Icon, Stack, Text } from 'soapbox/components/ui';
|
||||
|
@ -47,24 +47,15 @@ const GroupHeader: React.FC<IGroupHeader> = ({ group }) => {
|
|||
);
|
||||
}
|
||||
|
||||
const onJoinGroup = () => {
|
||||
dispatch(joinGroup(group.id));
|
||||
};
|
||||
const onJoinGroup = () => dispatch(joinGroup(group.id));
|
||||
|
||||
const onLeaveGroup = () => {
|
||||
const onLeaveGroup = () =>
|
||||
dispatch(openModal('CONFIRM', {
|
||||
heading: intl.formatMessage(messages.confirmationHeading),
|
||||
message: intl.formatMessage(messages.confirmationMessage),
|
||||
confirm: intl.formatMessage(messages.confirmationConfirm),
|
||||
onConfirm: () => {
|
||||
dispatch(leaveGroup(group.id));
|
||||
},
|
||||
onConfirm: () => dispatch(leaveGroup(group.id)),
|
||||
}));
|
||||
};
|
||||
|
||||
const onEditGroup = () => {
|
||||
dispatch(editGroup(group));
|
||||
};
|
||||
|
||||
const onAvatarClick = () => {
|
||||
const avatar = normalizeAttachment({
|
||||
|
@ -131,13 +122,24 @@ const GroupHeader: React.FC<IGroupHeader> = ({ group }) => {
|
|||
);
|
||||
}
|
||||
|
||||
if (group.relationship.requested) {
|
||||
return (
|
||||
<Button
|
||||
theme='secondary'
|
||||
onClick={onLeaveGroup}
|
||||
>
|
||||
<FormattedMessage id='group.cancel_request' defaultMessage='Cancel request' />
|
||||
</Button>
|
||||
);
|
||||
}
|
||||
|
||||
if (group.relationship?.role === 'admin') {
|
||||
return (
|
||||
<Button
|
||||
theme='secondary'
|
||||
onClick={onEditGroup}
|
||||
to={`/groups/${group.id}/manage`}
|
||||
>
|
||||
<FormattedMessage id='group.manage' defaultMessage='Edit group' />
|
||||
<FormattedMessage id='group.manage' defaultMessage='Manage group' />
|
||||
</Button>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -0,0 +1,104 @@
|
|||
import React, { useCallback, useEffect } from 'react';
|
||||
import { FormattedMessage, defineMessages, useIntl } from 'react-intl';
|
||||
|
||||
import { fetchGroup, fetchGroupBlocks, groupUnblock } from 'soapbox/actions/groups';
|
||||
import snackbar from 'soapbox/actions/snackbar';
|
||||
import Account from 'soapbox/components/account';
|
||||
import ScrollableList from 'soapbox/components/scrollable-list';
|
||||
import { Button, Column, HStack, Spinner } from 'soapbox/components/ui';
|
||||
import { useAppDispatch, useAppSelector } from 'soapbox/hooks';
|
||||
import { makeGetAccount, makeGetGroup } from 'soapbox/selectors';
|
||||
|
||||
import ColumnForbidden from '../ui/components/column-forbidden';
|
||||
|
||||
type RouteParams = { id: string };
|
||||
|
||||
const messages = defineMessages({
|
||||
heading: { id: 'column.group_blocked_members', defaultMessage: 'Blocked members' },
|
||||
unblock: { id: 'group.group_mod_unblock', defaultMessage: 'Unblock' },
|
||||
unblocked: { id: 'group.group_mod_unblock.success', defaultMessage: 'Unblocked @{name} from group' },
|
||||
});
|
||||
|
||||
interface IBlockedMember {
|
||||
accountId: string
|
||||
groupId: string
|
||||
}
|
||||
|
||||
const BlockedMember: React.FC<IBlockedMember> = ({ accountId, groupId }) => {
|
||||
const intl = useIntl();
|
||||
const dispatch = useAppDispatch();
|
||||
|
||||
const getAccount = useCallback(makeGetAccount(), []);
|
||||
|
||||
const account = useAppSelector((state) => getAccount(state, accountId));
|
||||
|
||||
if (!account) return null;
|
||||
|
||||
const handleUnblock = () =>
|
||||
dispatch(groupUnblock(groupId, accountId)).then(() => {
|
||||
dispatch(snackbar.success(intl.formatMessage(messages.unblocked, { name: account.acct })));
|
||||
});
|
||||
|
||||
return (
|
||||
<HStack space={1} alignItems='center' justifyContent='between' className='p-2.5'>
|
||||
<div className='w-full'>
|
||||
<Account account={account} withRelationship={false} />
|
||||
</div>
|
||||
<Button
|
||||
theme='danger'
|
||||
size='sm'
|
||||
text={intl.formatMessage(messages.unblock)}
|
||||
onClick={handleUnblock}
|
||||
/>
|
||||
</HStack>
|
||||
);
|
||||
};
|
||||
|
||||
interface IGroupBlockedMembers {
|
||||
params: RouteParams
|
||||
}
|
||||
|
||||
const GroupBlockedMembers: React.FC<IGroupBlockedMembers> = ({ params }) => {
|
||||
const intl = useIntl();
|
||||
const dispatch = useAppDispatch();
|
||||
|
||||
const id = params?.id || '';
|
||||
|
||||
const getGroup = useCallback(makeGetGroup(), []);
|
||||
const group = useAppSelector(state => getGroup(state, id));
|
||||
const accountIds = useAppSelector((state) => state.user_lists.group_blocks.get(id)?.items);
|
||||
|
||||
useEffect(() => {
|
||||
if (!group) dispatch(fetchGroup(id));
|
||||
dispatch(fetchGroupBlocks(id));
|
||||
}, [id]);
|
||||
|
||||
if (!group || !group.relationship || !accountIds) {
|
||||
return (
|
||||
<Column label={intl.formatMessage(messages.heading)}>
|
||||
<Spinner />
|
||||
</Column>
|
||||
);
|
||||
}
|
||||
|
||||
if (!group.relationship.role || !['admin', 'moderator'].includes(group.relationship.role)) {
|
||||
return (<ColumnForbidden />);
|
||||
}
|
||||
|
||||
const emptyMessage = <FormattedMessage id='empty_column.group_blocks' defaultMessage="The group hasn't blocked any users yet." />;
|
||||
|
||||
return (
|
||||
<Column label={intl.formatMessage(messages.heading)} backHref={`/groups/${id}/manage`}>
|
||||
<ScrollableList
|
||||
scrollKey='group_blocks'
|
||||
emptyMessage={emptyMessage}
|
||||
>
|
||||
{accountIds.map((accountId) =>
|
||||
<BlockedMember key={accountId} accountId={accountId} groupId={id} />,
|
||||
)}
|
||||
</ScrollableList>
|
||||
</Column>
|
||||
);
|
||||
};
|
||||
|
||||
export default GroupBlockedMembers;
|
|
@ -6,10 +6,10 @@ import { Link } from 'react-router-dom';
|
|||
import { expandGroupMemberships, fetchGroup, fetchGroupMemberships, groupBlock, groupDemoteAccount, groupKick, groupPromoteAccount } from 'soapbox/actions/groups';
|
||||
import { openModal } from 'soapbox/actions/modals';
|
||||
import snackbar from 'soapbox/actions/snackbar';
|
||||
import Account from 'soapbox/components/account';
|
||||
import ScrollableList from 'soapbox/components/scrollable-list';
|
||||
import { CardHeader, CardTitle, HStack, IconButton, Menu, MenuButton, MenuDivider, MenuItem, MenuLink, MenuList } from 'soapbox/components/ui';
|
||||
import SvgIcon from 'soapbox/components/ui/icon/svg-icon';
|
||||
import AccountContainer from 'soapbox/containers/account-container';
|
||||
import { useAppDispatch, useAppSelector } from 'soapbox/hooks';
|
||||
import { makeGetAccount } from 'soapbox/selectors';
|
||||
|
||||
|
@ -161,7 +161,7 @@ const GroupMember: React.FC<IGroupMember> = ({ accountId, accountRole, groupId,
|
|||
return (
|
||||
<HStack space={1} alignItems='center' justifyContent='between' className='p-2.5'>
|
||||
<div className='w-full'>
|
||||
<AccountContainer id={accountId} withRelationship={false} />
|
||||
<Account account={account} withRelationship={false} />
|
||||
</div>
|
||||
{menu.length > 0 && (
|
||||
<Menu>
|
||||
|
|
|
@ -0,0 +1,119 @@
|
|||
import React, { useCallback, useEffect } from 'react';
|
||||
import { FormattedMessage, defineMessages, useIntl } from 'react-intl';
|
||||
|
||||
import { authorizeGroupMembershipRequest, fetchGroup, fetchGroupMembershipRequests, rejectGroupMembershipRequest } from 'soapbox/actions/groups';
|
||||
import snackbar from 'soapbox/actions/snackbar';
|
||||
import Account from 'soapbox/components/account';
|
||||
import ScrollableList from 'soapbox/components/scrollable-list';
|
||||
import { Button, Column, HStack, Spinner } from 'soapbox/components/ui';
|
||||
import { useAppDispatch, useAppSelector } from 'soapbox/hooks';
|
||||
import { makeGetAccount, makeGetGroup } from 'soapbox/selectors';
|
||||
|
||||
import ColumnForbidden from '../ui/components/column-forbidden';
|
||||
|
||||
type RouteParams = { id: string };
|
||||
|
||||
const messages = defineMessages({
|
||||
heading: { id: 'column.group_pending_requests', defaultMessage: 'Pending requests' },
|
||||
authorize: { id: 'group.group_mod_authorize', defaultMessage: 'Accept' },
|
||||
authorized: { id: 'group.group_mod_authorize.success', defaultMessage: 'Accepted @{name} to group' },
|
||||
reject: { id: 'group.group_mod_reject', defaultMessage: 'Reject' },
|
||||
rejected: { id: 'group.group_mod_reject.success', defaultMessage: 'Rejected @{name} from group' },
|
||||
});
|
||||
|
||||
interface IMembershipRequest {
|
||||
accountId: string
|
||||
groupId: string
|
||||
}
|
||||
|
||||
const MembershipRequest: React.FC<IMembershipRequest> = ({ accountId, groupId }) => {
|
||||
const intl = useIntl();
|
||||
const dispatch = useAppDispatch();
|
||||
|
||||
const getAccount = useCallback(makeGetAccount(), []);
|
||||
|
||||
const account = useAppSelector((state) => getAccount(state, accountId));
|
||||
|
||||
if (!account) return null;
|
||||
|
||||
const handleAuthorize = () =>
|
||||
dispatch(authorizeGroupMembershipRequest(groupId, accountId)).then(() => {
|
||||
dispatch(snackbar.success(intl.formatMessage(messages.authorized, { name: account.acct })));
|
||||
});
|
||||
|
||||
const handleReject = () =>
|
||||
dispatch(rejectGroupMembershipRequest(groupId, accountId)).then(() => {
|
||||
dispatch(snackbar.success(intl.formatMessage(messages.rejected, { name: account.acct })));
|
||||
});
|
||||
|
||||
return (
|
||||
<HStack space={1} alignItems='center' justifyContent='between' className='p-2.5'>
|
||||
<div className='w-full'>
|
||||
<Account account={account} withRelationship={false} />
|
||||
</div>
|
||||
<HStack space={2}>
|
||||
<Button
|
||||
theme='secondary'
|
||||
size='sm'
|
||||
text={intl.formatMessage(messages.authorize)}
|
||||
onClick={handleAuthorize}
|
||||
/>
|
||||
<Button
|
||||
theme='danger'
|
||||
size='sm'
|
||||
text={intl.formatMessage(messages.reject)}
|
||||
onClick={handleReject}
|
||||
/>
|
||||
</HStack>
|
||||
</HStack>
|
||||
);
|
||||
};
|
||||
|
||||
interface IGroupMembershipRequests {
|
||||
params: RouteParams
|
||||
}
|
||||
|
||||
const GroupMembershipRequests: React.FC<IGroupMembershipRequests> = ({ params }) => {
|
||||
const intl = useIntl();
|
||||
const dispatch = useAppDispatch();
|
||||
|
||||
const id = params?.id || '';
|
||||
|
||||
const getGroup = useCallback(makeGetGroup(), []);
|
||||
const group = useAppSelector(state => getGroup(state, id));
|
||||
const accountIds = useAppSelector((state) => state.user_lists.membership_requests.get(id)?.items);
|
||||
|
||||
useEffect(() => {
|
||||
if (!group) dispatch(fetchGroup(id));
|
||||
dispatch(fetchGroupMembershipRequests(id));
|
||||
}, [id]);
|
||||
|
||||
if (!group || !group.relationship || !accountIds) {
|
||||
return (
|
||||
<Column label={intl.formatMessage(messages.heading)}>
|
||||
<Spinner />
|
||||
</Column>
|
||||
);
|
||||
}
|
||||
|
||||
if (!group.relationship.role || !['admin', 'moderator'].includes(group.relationship.role)) {
|
||||
return (<ColumnForbidden />);
|
||||
}
|
||||
|
||||
const emptyMessage = <FormattedMessage id='empty_column.group_membership_requests' defaultMessage='There are no pending membership requests for this group.' />;
|
||||
|
||||
return (
|
||||
<Column label={intl.formatMessage(messages.heading)} backHref={`/groups/${id}/manage`}>
|
||||
<ScrollableList
|
||||
scrollKey='group_membership_requests'
|
||||
emptyMessage={emptyMessage}
|
||||
>
|
||||
{accountIds.map((accountId) =>
|
||||
<MembershipRequest key={accountId} accountId={accountId} groupId={id} />,
|
||||
)}
|
||||
</ScrollableList>
|
||||
</Column>
|
||||
);
|
||||
};
|
||||
|
||||
export default GroupMembershipRequests;
|
|
@ -0,0 +1,96 @@
|
|||
import React, { useCallback, useEffect } from 'react';
|
||||
import { defineMessages, useIntl } from 'react-intl';
|
||||
import { useHistory } from 'react-router-dom';
|
||||
|
||||
import { deleteGroup, editGroup, fetchGroup } from 'soapbox/actions/groups';
|
||||
import { openModal } from 'soapbox/actions/modals';
|
||||
import List, { ListItem } from 'soapbox/components/list';
|
||||
import { CardBody, Column, Spinner } from 'soapbox/components/ui';
|
||||
import { useAppDispatch, useAppSelector } from 'soapbox/hooks';
|
||||
import { makeGetGroup } from 'soapbox/selectors';
|
||||
|
||||
import ColumnForbidden from '../ui/components/column-forbidden';
|
||||
|
||||
type RouteParams = { id: string };
|
||||
|
||||
const messages = defineMessages({
|
||||
heading: { id: 'column.manage_group', defaultMessage: 'Manage group' },
|
||||
editGroup: { id: 'manage_group.edit_group', defaultMessage: 'Edit group' },
|
||||
pendingRequests: { id: 'manage_group.pending_requests', defaultMessage: 'Pending requests' },
|
||||
blockedMembers: { id: 'manage_group.blocked_members', defaultMessage: 'Blocked members' },
|
||||
deleteGroup: { id: 'manage_group.delete_group', defaultMessage: 'Delete group' },
|
||||
deleteConfirm: { id: 'confirmations.delete_group.confirm', defaultMessage: 'Delete' },
|
||||
deleteHeading: { id: 'confirmations.delete_group.heading', defaultMessage: 'Delete group' },
|
||||
deleteMessage: { id: 'confirmations.delete_group.message', defaultMessage: 'Are you sure you want to delete this group? This is a permanent action that cannot be undone.' },
|
||||
});
|
||||
|
||||
interface IManageGroup {
|
||||
params: RouteParams
|
||||
}
|
||||
|
||||
const ManageGroup: React.FC<IManageGroup> = ({ params }) => {
|
||||
const history = useHistory();
|
||||
const intl = useIntl();
|
||||
const dispatch = useAppDispatch();
|
||||
|
||||
const id = params?.id || '';
|
||||
|
||||
const getGroup = useCallback(makeGetGroup(), []);
|
||||
const group = useAppSelector(state => getGroup(state, id));
|
||||
|
||||
useEffect(() => {
|
||||
if (!group) dispatch(fetchGroup(id));
|
||||
}, [id]);
|
||||
|
||||
if (!group || !group.relationship) {
|
||||
return (
|
||||
<Column label={intl.formatMessage(messages.heading)}>
|
||||
<Spinner />
|
||||
</Column>
|
||||
);
|
||||
}
|
||||
|
||||
if (!group.relationship.role || !['admin', 'moderator'].includes(group.relationship.role)) {
|
||||
return (<ColumnForbidden />);
|
||||
}
|
||||
|
||||
const onEditGroup = () =>
|
||||
dispatch(editGroup(group));
|
||||
|
||||
const onDeleteGroup = () =>
|
||||
dispatch(openModal('CONFIRM', {
|
||||
icon: require('@tabler/icons/trash.svg'),
|
||||
heading: intl.formatMessage(messages.deleteHeading),
|
||||
message: intl.formatMessage(messages.deleteMessage),
|
||||
confirm: intl.formatMessage(messages.deleteConfirm),
|
||||
onConfirm: () => dispatch(deleteGroup(id)),
|
||||
}));
|
||||
|
||||
const navigateToPending = () => history.push(`/groups/${id}/manage/requests`);
|
||||
const navigateToBlocks = () => history.push(`/groups/${id}/manage/blocks`);
|
||||
|
||||
return (
|
||||
<Column label={intl.formatMessage(messages.heading)} backHref={`/groups/${id}`}>
|
||||
<CardBody className='space-y-4'>
|
||||
{group.relationship.role === 'admin' && (
|
||||
<List>
|
||||
<ListItem label={intl.formatMessage(messages.editGroup)} onClick={onEditGroup}>
|
||||
<span dangerouslySetInnerHTML={{ __html: group.display_name_html }} />
|
||||
</ListItem>
|
||||
</List>
|
||||
)}
|
||||
<List>
|
||||
<ListItem label={intl.formatMessage(messages.pendingRequests)} onClick={navigateToPending} />
|
||||
<ListItem label={intl.formatMessage(messages.blockedMembers)} onClick={navigateToBlocks} />
|
||||
</List>
|
||||
{group.relationship.role === 'admin' && (
|
||||
<List>
|
||||
<ListItem label={intl.formatMessage(messages.deleteGroup)} onClick={onDeleteGroup} />
|
||||
</List>
|
||||
)}
|
||||
</CardBody>
|
||||
</Column>
|
||||
);
|
||||
};
|
||||
|
||||
export default ManageGroup;
|
|
@ -5,9 +5,10 @@ import { Link } from 'react-router-dom';
|
|||
import { createSelector } from 'reselect';
|
||||
|
||||
import { fetchGroups } from 'soapbox/actions/groups';
|
||||
import { openModal } from 'soapbox/actions/modals';
|
||||
import GroupCard from 'soapbox/components/group-card';
|
||||
import ScrollableList from 'soapbox/components/scrollable-list';
|
||||
import { Column, Spinner, Stack, Text } from 'soapbox/components/ui';
|
||||
import { Button, Column, Spinner, Stack, Text } from 'soapbox/components/ui';
|
||||
import { useAppSelector } from 'soapbox/hooks';
|
||||
|
||||
import PlaceholderGroupCard from '../placeholder/components/placeholder-group-card';
|
||||
|
@ -37,6 +38,10 @@ const Groups: React.FC = () => {
|
|||
dispatch(fetchGroups());
|
||||
}, []);
|
||||
|
||||
const createGroup = () => {
|
||||
dispatch(openModal('MANAGE_GROUP'));
|
||||
};
|
||||
|
||||
if (!groups) {
|
||||
return (
|
||||
<Column>
|
||||
|
@ -66,21 +71,32 @@ const Groups: React.FC = () => {
|
|||
);
|
||||
|
||||
return (
|
||||
<ScrollableList
|
||||
scrollKey='groups'
|
||||
emptyMessage={emptyMessage}
|
||||
itemClassName='py-3 last:pb-0'
|
||||
isLoading={isLoading}
|
||||
showLoading={isLoading && !groups.count()}
|
||||
placeholderComponent={PlaceholderGroupCard}
|
||||
placeholderCount={3}
|
||||
>
|
||||
{groups.map((group) => (
|
||||
<Link key={group.id} to={`/groups/${group.id}`}>
|
||||
<GroupCard group={group as GroupEntity} />
|
||||
</Link>
|
||||
))}
|
||||
</ScrollableList>
|
||||
<Stack className='gap-4'>
|
||||
<Button
|
||||
className='sm:w-fit sm:self-end xl:hidden'
|
||||
icon={require('@tabler/icons/circles.svg')}
|
||||
onClick={createGroup}
|
||||
theme='secondary'
|
||||
block
|
||||
>
|
||||
<FormattedMessage id='new_group_panel.action' defaultMessage='Create group' />
|
||||
</Button>
|
||||
<ScrollableList
|
||||
scrollKey='groups'
|
||||
emptyMessage={emptyMessage}
|
||||
itemClassName='py-3 first:pt-0 last:pb-0'
|
||||
isLoading={isLoading}
|
||||
showLoading={isLoading && !groups.count()}
|
||||
placeholderComponent={PlaceholderGroupCard}
|
||||
placeholderCount={3}
|
||||
>
|
||||
{groups.map((group) => (
|
||||
<Link key={group.id} to={`/groups/${group.id}`}>
|
||||
<GroupCard group={group as GroupEntity} />
|
||||
</Link>
|
||||
))}
|
||||
</ScrollableList>
|
||||
</Stack>
|
||||
);
|
||||
};
|
||||
|
||||
|
|
|
@ -8,16 +8,16 @@ import { reblog, favourite, unreblog, unfavourite } from 'soapbox/actions/intera
|
|||
import { openModal } from 'soapbox/actions/modals';
|
||||
import { getSettings } from 'soapbox/actions/settings';
|
||||
import { hideStatus, revealStatus } from 'soapbox/actions/statuses';
|
||||
import Account from 'soapbox/components/account';
|
||||
import Icon from 'soapbox/components/icon';
|
||||
import { HStack, Text, Emoji } from 'soapbox/components/ui';
|
||||
import AccountContainer from 'soapbox/containers/account-container';
|
||||
import StatusContainer from 'soapbox/containers/status-container';
|
||||
import { useAppDispatch, useAppSelector, useInstance } from 'soapbox/hooks';
|
||||
import { makeGetNotification } from 'soapbox/selectors';
|
||||
import { NotificationType, validType } from 'soapbox/utils/notification';
|
||||
|
||||
import type { ScrollPosition } from 'soapbox/components/status';
|
||||
import type { Account, Status as StatusEntity, Notification as NotificationEntity } from 'soapbox/types/entities';
|
||||
import type { Account as AccountEntity, Status as StatusEntity, Notification as NotificationEntity } from 'soapbox/types/entities';
|
||||
|
||||
const notificationForScreenReader = (intl: IntlShape, message: string, timestamp: Date) => {
|
||||
const output = [message];
|
||||
|
@ -27,7 +27,7 @@ const notificationForScreenReader = (intl: IntlShape, message: string, timestamp
|
|||
return output.join(', ');
|
||||
};
|
||||
|
||||
const buildLink = (account: Account): JSX.Element => (
|
||||
const buildLink = (account: AccountEntity): JSX.Element => (
|
||||
<bdi>
|
||||
<Link
|
||||
className='text-gray-800 dark:text-gray-200 font-bold hover:underline'
|
||||
|
@ -127,7 +127,7 @@ const messages: Record<NotificationType, MessageDescriptor> = defineMessages({
|
|||
const buildMessage = (
|
||||
intl: IntlShape,
|
||||
type: NotificationType,
|
||||
account: Account,
|
||||
account: AccountEntity,
|
||||
totalCount: number | null,
|
||||
targetName: string,
|
||||
instanceTitle: string,
|
||||
|
@ -287,16 +287,16 @@ const Notification: React.FC<INotificaton> = (props) => {
|
|||
case 'follow':
|
||||
case 'user_approved':
|
||||
return account && typeof account === 'object' ? (
|
||||
<AccountContainer
|
||||
id={account.id}
|
||||
<Account
|
||||
account={account}
|
||||
hidden={hidden}
|
||||
avatarSize={48}
|
||||
/>
|
||||
) : null;
|
||||
case 'follow_request':
|
||||
return account && typeof account === 'object' ? (
|
||||
<AccountContainer
|
||||
id={account.id}
|
||||
<Account
|
||||
account={account}
|
||||
hidden={hidden}
|
||||
avatarSize={48}
|
||||
actionType='follow_request'
|
||||
|
@ -304,8 +304,8 @@ const Notification: React.FC<INotificaton> = (props) => {
|
|||
) : null;
|
||||
case 'move':
|
||||
return account && typeof account === 'object' && notification.target && typeof notification.target === 'object' ? (
|
||||
<AccountContainer
|
||||
id={notification.target.id}
|
||||
<Account
|
||||
account={notification.target}
|
||||
hidden={hidden}
|
||||
avatarSize={48}
|
||||
/>
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
import classNames from 'clsx';
|
||||
import React from 'react';
|
||||
|
||||
import Account from 'soapbox/components/account';
|
||||
import AttachmentThumbs from 'soapbox/components/attachment-thumbs';
|
||||
import StatusContent from 'soapbox/components/status-content';
|
||||
import StatusReplyMentions from 'soapbox/components/status-reply-mentions';
|
||||
import { HStack } from 'soapbox/components/ui';
|
||||
import AccountContainer from 'soapbox/containers/account-container';
|
||||
import PollPreview from 'soapbox/features/ui/components/poll-preview';
|
||||
import { useAppSelector } from 'soapbox/hooks';
|
||||
|
||||
|
@ -31,9 +31,9 @@ const ScheduledStatus: React.FC<IScheduledStatus> = ({ statusId, ...other }) =>
|
|||
<div className={classNames('status', `status-${status.visibility}`, { 'status-reply': !!status.in_reply_to_id })} data-id={status.id}>
|
||||
<div className='mb-4'>
|
||||
<HStack justifyContent='between' alignItems='start'>
|
||||
<AccountContainer
|
||||
<Account
|
||||
key={account.id}
|
||||
id={account.id}
|
||||
account={account}
|
||||
timestamp={status.created_at}
|
||||
futureTimestamp
|
||||
/>
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import React, { useEffect, useRef, useState } from 'react';
|
||||
import { FormattedDate, FormattedMessage, useIntl } from 'react-intl';
|
||||
|
||||
import Account from 'soapbox/components/account';
|
||||
import Icon from 'soapbox/components/icon';
|
||||
import StatusContent from 'soapbox/components/status-content';
|
||||
import StatusMedia from 'soapbox/components/status-media';
|
||||
|
@ -8,7 +9,6 @@ import StatusReplyMentions from 'soapbox/components/status-reply-mentions';
|
|||
import SensitiveContentOverlay from 'soapbox/components/statuses/sensitive-content-overlay';
|
||||
import TranslateButton from 'soapbox/components/translate-button';
|
||||
import { HStack, Stack, Text } from 'soapbox/components/ui';
|
||||
import AccountContainer from 'soapbox/containers/account-container';
|
||||
import QuotedStatus from 'soapbox/features/status/containers/quoted-status-container';
|
||||
import { getActualStatus } from 'soapbox/utils/status';
|
||||
|
||||
|
@ -84,12 +84,13 @@ const DetailedStatus: React.FC<IDetailedStatus> = ({
|
|||
<div className='border-box'>
|
||||
<div ref={node} className='detailed-actualStatus' tabIndex={-1}>
|
||||
<div className='mb-4'>
|
||||
<AccountContainer
|
||||
<Account
|
||||
key={account.id}
|
||||
id={account.id}
|
||||
account={account}
|
||||
timestamp={actualStatus.created_at}
|
||||
avatarSize={42}
|
||||
hideActions
|
||||
approvalStatus={actualStatus.approval_status}
|
||||
/>
|
||||
</div>
|
||||
|
||||
|
|
|
@ -74,7 +74,7 @@ const ManageGroupModal: React.FC<IManageGroupModal> = ({ onClose }) => {
|
|||
return (
|
||||
<Modal
|
||||
title={id
|
||||
? <FormattedMessage id='navigation_bar.manage_group' defaultMessage='Manage Group' />
|
||||
? <FormattedMessage id='navigation_bar.edit_group' defaultMessage='Edit Group' />
|
||||
: <FormattedMessage id='navigation_bar.create_group' defaultMessage='Create Group' />}
|
||||
confirmationAction={handleNextStep}
|
||||
confirmationText={confirmationText}
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
import classNames from 'clsx';
|
||||
import React from 'react';
|
||||
|
||||
import Account from 'soapbox/components/account';
|
||||
import StatusContent from 'soapbox/components/status-content';
|
||||
import StatusReplyMentions from 'soapbox/components/status-reply-mentions';
|
||||
import { Card, HStack } from 'soapbox/components/ui';
|
||||
import AccountContainer from 'soapbox/containers/account-container';
|
||||
import PlaceholderCard from 'soapbox/features/placeholder/components/placeholder-card';
|
||||
import PlaceholderMediaGallery from 'soapbox/features/placeholder/components/placeholder-media-gallery';
|
||||
import QuotedStatus from 'soapbox/features/status/containers/quoted-status-container';
|
||||
|
@ -65,9 +65,9 @@ const PendingStatus: React.FC<IPendingStatus> = ({ idempotencyKey, className, mu
|
|||
>
|
||||
<div className='mb-4'>
|
||||
<HStack justifyContent='between' alignItems='start'>
|
||||
<AccountContainer
|
||||
<Account
|
||||
key={account.id}
|
||||
id={account.id}
|
||||
account={account}
|
||||
timestamp={status.created_at}
|
||||
hideActions
|
||||
/>
|
||||
|
|
|
@ -116,6 +116,9 @@ import {
|
|||
Groups,
|
||||
GroupMembers,
|
||||
GroupTimeline,
|
||||
ManageGroup,
|
||||
GroupBlockedMembers,
|
||||
GroupMembershipRequests,
|
||||
} from './util/async-components';
|
||||
import { WrappedRoute } from './util/react-router-helpers';
|
||||
|
||||
|
@ -280,6 +283,9 @@ const SwitchingColumnsArea: React.FC = ({ children }) => {
|
|||
{features.groups && <WrappedRoute path='/groups' exact page={GroupsPage} component={Groups} content={children} />}
|
||||
{features.groups && <WrappedRoute path='/groups/:id' exact page={GroupPage} component={GroupTimeline} content={children} />}
|
||||
{features.groups && <WrappedRoute path='/groups/:id/members' exact page={GroupPage} component={GroupMembers} content={children} />}
|
||||
{features.groups && <WrappedRoute path='/groups/:id/manage' exact page={DefaultPage} component={ManageGroup} content={children} />}
|
||||
{features.groups && <WrappedRoute path='/groups/:id/manage/blocks' exact page={DefaultPage} component={GroupBlockedMembers} content={children} />}
|
||||
{features.groups && <WrappedRoute path='/groups/:id/manage/requests' exact page={DefaultPage} component={GroupMembershipRequests} content={children} />}
|
||||
|
||||
<WrappedRoute path='/statuses/new' page={DefaultPage} component={NewStatus} content={children} exact />
|
||||
<WrappedRoute path='/statuses/:statusId' exact page={StatusPage} component={Status} content={children} />
|
||||
|
|
|
@ -554,6 +554,18 @@ export function GroupTimeline() {
|
|||
return import(/* webpackChunkName: "features/groups" */'../../group/group-timeline');
|
||||
}
|
||||
|
||||
export function ManageGroup() {
|
||||
return import(/* webpackChunkName: "features/groups" */'../../group/manage-group');
|
||||
}
|
||||
|
||||
export function GroupBlockedMembers() {
|
||||
return import(/* webpackChunkName: "features/groups" */'../../group/group-blocked-members');
|
||||
}
|
||||
|
||||
export function GroupMembershipRequests() {
|
||||
return import(/* webpackChunkName: "features/groups" */'../../group/group-membership-requests');
|
||||
}
|
||||
|
||||
export function ManageGroupModal() {
|
||||
return import(/* webpackChunkName: "features/manage_group_modal" */'../components/modals/manage-group-modal/manage-group-modal');
|
||||
}
|
||||
|
|
|
@ -910,7 +910,7 @@
|
|||
"navigation_bar.in_reply_to": "W odpowiedzi do",
|
||||
"navigation_bar.invites": "Zaproszenia",
|
||||
"navigation_bar.logout": "Wyloguj",
|
||||
"navigation_bar.manage_group": "Zarządzaj grupą",
|
||||
"navigation_bar.edit_group": "Edytuj grupę",
|
||||
"navigation_bar.mutes": "Wyciszeni użytkownicy",
|
||||
"navigation_bar.preferences": "Preferencje",
|
||||
"navigation_bar.profile_directory": "Katalog profilów",
|
||||
|
|
|
@ -19,6 +19,7 @@ import { normalizePoll } from 'soapbox/normalizers/poll';
|
|||
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 EventJoinMode = 'free' | 'restricted' | 'invite';
|
||||
|
@ -40,6 +41,7 @@ export const EventRecord = ImmutableRecord({
|
|||
export const StatusRecord = ImmutableRecord({
|
||||
account: null as EmbeddedEntity<Account | ReducerAccount>,
|
||||
application: null as ImmutableMap<string, any> | null,
|
||||
approval_status: 'approved' as StatusApprovalStatus,
|
||||
bookmarked: false,
|
||||
card: null as Card | null,
|
||||
content: '',
|
||||
|
|
Ładowanie…
Reference in New Issue