Merge remote-tracking branch 'origin/develop' into groups-zod
This commit is contained in:
commit
5278c8eb0f
21 changed files with 367 additions and 343 deletions
|
@ -40,14 +40,6 @@ const GROUP_RELATIONSHIPS_FETCH_REQUEST = 'GROUP_RELATIONSHIPS_FETCH_REQUEST';
|
|||
const GROUP_RELATIONSHIPS_FETCH_SUCCESS = 'GROUP_RELATIONSHIPS_FETCH_SUCCESS';
|
||||
const GROUP_RELATIONSHIPS_FETCH_FAIL = 'GROUP_RELATIONSHIPS_FETCH_FAIL';
|
||||
|
||||
const GROUP_JOIN_REQUEST = 'GROUP_JOIN_REQUEST';
|
||||
const GROUP_JOIN_SUCCESS = 'GROUP_JOIN_SUCCESS';
|
||||
const GROUP_JOIN_FAIL = 'GROUP_JOIN_FAIL';
|
||||
|
||||
const GROUP_LEAVE_REQUEST = 'GROUP_LEAVE_REQUEST';
|
||||
const GROUP_LEAVE_SUCCESS = 'GROUP_LEAVE_SUCCESS';
|
||||
const GROUP_LEAVE_FAIL = 'GROUP_LEAVE_FAIL';
|
||||
|
||||
const GROUP_DELETE_STATUS_REQUEST = 'GROUP_DELETE_STATUS_REQUEST';
|
||||
const GROUP_DELETE_STATUS_SUCCESS = 'GROUP_DELETE_STATUS_SUCCESS';
|
||||
const GROUP_DELETE_STATUS_FAIL = 'GROUP_DELETE_STATUS_FAIL';
|
||||
|
@ -312,70 +304,6 @@ const fetchGroupRelationshipsFail = (error: AxiosError) => ({
|
|||
skipNotFound: true,
|
||||
});
|
||||
|
||||
const joinGroup = (id: string) =>
|
||||
(dispatch: AppDispatch, getState: () => RootState) => {
|
||||
const locked = (getState().groups.items.get(id) as any).locked || false;
|
||||
|
||||
dispatch(joinGroupRequest(id, locked));
|
||||
|
||||
return api(getState).post(`/api/v1/groups/${id}/join`).then(response => {
|
||||
dispatch(joinGroupSuccess(response.data));
|
||||
toast.success(locked ? messages.joinRequestSuccess : messages.joinSuccess);
|
||||
}).catch(error => {
|
||||
dispatch(joinGroupFail(error, locked));
|
||||
});
|
||||
};
|
||||
|
||||
const leaveGroup = (id: string) =>
|
||||
(dispatch: AppDispatch, getState: () => RootState) => {
|
||||
dispatch(leaveGroupRequest(id));
|
||||
|
||||
return api(getState).post(`/api/v1/groups/${id}/leave`).then(response => {
|
||||
dispatch(leaveGroupSuccess(response.data));
|
||||
toast.success(messages.leaveSuccess);
|
||||
}).catch(error => {
|
||||
dispatch(leaveGroupFail(error));
|
||||
});
|
||||
};
|
||||
|
||||
const joinGroupRequest = (id: string, locked: boolean) => ({
|
||||
type: GROUP_JOIN_REQUEST,
|
||||
id,
|
||||
locked,
|
||||
skipLoading: true,
|
||||
});
|
||||
|
||||
const joinGroupSuccess = (relationship: APIEntity) => ({
|
||||
type: GROUP_JOIN_SUCCESS,
|
||||
relationship,
|
||||
skipLoading: true,
|
||||
});
|
||||
|
||||
const joinGroupFail = (error: AxiosError, locked: boolean) => ({
|
||||
type: GROUP_JOIN_FAIL,
|
||||
error,
|
||||
locked,
|
||||
skipLoading: true,
|
||||
});
|
||||
|
||||
const leaveGroupRequest = (id: string) => ({
|
||||
type: GROUP_LEAVE_REQUEST,
|
||||
id,
|
||||
skipLoading: true,
|
||||
});
|
||||
|
||||
const leaveGroupSuccess = (relationship: APIEntity) => ({
|
||||
type: GROUP_LEAVE_SUCCESS,
|
||||
relationship,
|
||||
skipLoading: true,
|
||||
});
|
||||
|
||||
const leaveGroupFail = (error: AxiosError) => ({
|
||||
type: GROUP_LEAVE_FAIL,
|
||||
error,
|
||||
skipLoading: true,
|
||||
});
|
||||
|
||||
const groupDeleteStatus = (groupId: string, statusId: string) =>
|
||||
(dispatch: AppDispatch, getState: () => RootState) => {
|
||||
dispatch(groupDeleteStatusRequest(groupId, statusId));
|
||||
|
@ -895,12 +823,6 @@ export {
|
|||
GROUP_RELATIONSHIPS_FETCH_REQUEST,
|
||||
GROUP_RELATIONSHIPS_FETCH_SUCCESS,
|
||||
GROUP_RELATIONSHIPS_FETCH_FAIL,
|
||||
GROUP_JOIN_REQUEST,
|
||||
GROUP_JOIN_SUCCESS,
|
||||
GROUP_JOIN_FAIL,
|
||||
GROUP_LEAVE_REQUEST,
|
||||
GROUP_LEAVE_SUCCESS,
|
||||
GROUP_LEAVE_FAIL,
|
||||
GROUP_DELETE_STATUS_REQUEST,
|
||||
GROUP_DELETE_STATUS_SUCCESS,
|
||||
GROUP_DELETE_STATUS_FAIL,
|
||||
|
@ -973,14 +895,6 @@ export {
|
|||
fetchGroupRelationshipsRequest,
|
||||
fetchGroupRelationshipsSuccess,
|
||||
fetchGroupRelationshipsFail,
|
||||
joinGroup,
|
||||
leaveGroup,
|
||||
joinGroupRequest,
|
||||
joinGroupSuccess,
|
||||
joinGroupFail,
|
||||
leaveGroupRequest,
|
||||
leaveGroupSuccess,
|
||||
leaveGroupFail,
|
||||
groupDeleteStatus,
|
||||
groupDeleteStatusRequest,
|
||||
groupDeleteStatusSuccess,
|
||||
|
|
|
@ -23,7 +23,12 @@ export const getLinks = (response: AxiosResponse): LinkHeader => {
|
|||
|
||||
export const getNextLink = (response: AxiosResponse) => {
|
||||
const nextLink = new LinkHeader(response.headers?.link);
|
||||
return nextLink.refs.find((ref) => ref.uri)?.uri;
|
||||
return nextLink.refs.find(link => link.rel === 'next')?.uri;
|
||||
};
|
||||
|
||||
export const getPrevLink = (response: AxiosResponse) => {
|
||||
const prevLink = new LinkHeader(response.headers?.link);
|
||||
return prevLink.refs.find(link => link.rel === 'prev')?.uri;
|
||||
};
|
||||
|
||||
export const baseClient = (...params: any[]) => {
|
||||
|
|
|
@ -5,7 +5,7 @@ import { __stub } from 'soapbox/api';
|
|||
import { ChatContext } from 'soapbox/contexts/chat-context';
|
||||
import { StatProvider } from 'soapbox/contexts/stat-context';
|
||||
import chats from 'soapbox/jest/fixtures/chats.json';
|
||||
import { mockStore, render, rootState, screen, waitFor } from 'soapbox/jest/test-helpers';
|
||||
import { render, screen, waitFor } from 'soapbox/jest/test-helpers';
|
||||
|
||||
import ChatPane from '../chat-pane';
|
||||
|
||||
|
@ -22,28 +22,28 @@ const renderComponentWithChatContext = (store = {}) => render(
|
|||
);
|
||||
|
||||
describe('<ChatPane />', () => {
|
||||
describe('when there are no chats', () => {
|
||||
let store: ReturnType<typeof mockStore>;
|
||||
// describe('when there are no chats', () => {
|
||||
// let store: ReturnType<typeof mockStore>;
|
||||
|
||||
beforeEach(() => {
|
||||
const state = rootState.setIn(['instance', 'version'], '2.7.2 (compatible; Pleroma 2.2.0)');
|
||||
store = mockStore(state);
|
||||
// beforeEach(() => {
|
||||
// const state = rootState.setIn(['instance', 'version'], '2.7.2 (compatible; Pleroma 2.2.0)');
|
||||
// store = mockStore(state);
|
||||
|
||||
__stub((mock) => {
|
||||
mock.onGet('/api/v1/pleroma/chats').reply(200, [], {
|
||||
link: null,
|
||||
});
|
||||
});
|
||||
});
|
||||
// __stub((mock) => {
|
||||
// mock.onGet('/api/v1/pleroma/chats').reply(200, [], {
|
||||
// link: null,
|
||||
// });
|
||||
// });
|
||||
// });
|
||||
|
||||
it('renders the blankslate', async () => {
|
||||
renderComponentWithChatContext(store);
|
||||
// it('renders the blankslate', async () => {
|
||||
// renderComponentWithChatContext(store);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByTestId('chat-pane-blankslate')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
});
|
||||
// await waitFor(() => {
|
||||
// expect(screen.getByTestId('chat-pane-blankslate')).toBeInTheDocument();
|
||||
// });
|
||||
// });
|
||||
// });
|
||||
|
||||
describe('when the software is not Truth Social', () => {
|
||||
beforeEach(() => {
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import React, { useEffect, useMemo, useState } from 'react';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { defineMessages, useIntl } from 'react-intl';
|
||||
|
||||
import { Select } from 'soapbox/components/ui';
|
||||
|
@ -20,15 +20,7 @@ const DurationSelector = ({ onDurationChange }: IDurationSelector) => {
|
|||
const [hours, setHours] = useState<number>(0);
|
||||
const [minutes, setMinutes] = useState<number>(0);
|
||||
|
||||
const value = useMemo(() => {
|
||||
const now: any = new Date();
|
||||
const future: any = new Date();
|
||||
now.setDate(now.getDate() + days);
|
||||
now.setMinutes(now.getMinutes() + minutes);
|
||||
now.setHours(now.getHours() + hours);
|
||||
|
||||
return Math.round((now - future) / 1000);
|
||||
}, [days, hours, minutes]);
|
||||
const value = (days * 24 * 60 * 60) + (hours * 60 * 60) + (minutes * 60);
|
||||
|
||||
useEffect(() => {
|
||||
if (days === 7) {
|
||||
|
|
|
@ -35,27 +35,29 @@ const AccountAuthorize: React.FC<IAccountAuthorize> = ({ id }) => {
|
|||
if (!account) return null;
|
||||
|
||||
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)}
|
||||
icon={require('@tabler/icons/check.svg')}
|
||||
onClick={onAuthorize}
|
||||
/>
|
||||
<Button
|
||||
theme='danger'
|
||||
size='sm'
|
||||
text={intl.formatMessage(messages.reject)}
|
||||
icon={require('@tabler/icons/x.svg')}
|
||||
onClick={onReject}
|
||||
/>
|
||||
</HStack>
|
||||
</HStack>
|
||||
<div className='p-2.5'>
|
||||
<Account
|
||||
account={account}
|
||||
action={
|
||||
<HStack className='ml-1' space={2}>
|
||||
<Button
|
||||
theme='secondary'
|
||||
size='sm'
|
||||
text={intl.formatMessage(messages.authorize)}
|
||||
icon={require('@tabler/icons/check.svg')}
|
||||
onClick={onAuthorize}
|
||||
/>
|
||||
<Button
|
||||
theme='danger'
|
||||
size='sm'
|
||||
text={intl.formatMessage(messages.reject)}
|
||||
icon={require('@tabler/icons/x.svg')}
|
||||
onClick={onReject}
|
||||
/>
|
||||
</HStack>
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
import React from 'react';
|
||||
import { defineMessages, FormattedMessage, useIntl } from 'react-intl';
|
||||
|
||||
import { joinGroup, leaveGroup } from 'soapbox/actions/groups';
|
||||
import { openModal } from 'soapbox/actions/modals';
|
||||
import { Button } from 'soapbox/components/ui';
|
||||
import { useAppDispatch } from 'soapbox/hooks';
|
||||
import { useCancelMembershipRequest, useJoinGroup, useLeaveGroup } from 'soapbox/queries/groups';
|
||||
import { Group } from 'soapbox/types/entities';
|
||||
|
||||
interface IGroupActionButton {
|
||||
|
@ -21,25 +21,32 @@ const GroupActionButton = ({ group }: IGroupActionButton) => {
|
|||
const dispatch = useAppDispatch();
|
||||
const intl = useIntl();
|
||||
|
||||
const isNonMember = !group.relationship || !group.relationship.member;
|
||||
const joinGroup = useJoinGroup();
|
||||
const leaveGroup = useLeaveGroup();
|
||||
const cancelRequest = useCancelMembershipRequest();
|
||||
|
||||
const isRequested = group.relationship?.requested;
|
||||
const isNonMember = !group.relationship?.member && !isRequested;
|
||||
const isAdmin = group.relationship?.role === 'admin';
|
||||
|
||||
const onJoinGroup = () => dispatch(joinGroup(group.id));
|
||||
const onJoinGroup = () => joinGroup.mutate(group);
|
||||
|
||||
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: () => leaveGroup.mutate(group),
|
||||
}));
|
||||
|
||||
const onCancelRequest = () => cancelRequest.mutate(group);
|
||||
|
||||
if (isNonMember) {
|
||||
return (
|
||||
<Button
|
||||
theme='primary'
|
||||
onClick={onJoinGroup}
|
||||
disabled={joinGroup.isLoading}
|
||||
>
|
||||
{group.locked
|
||||
? <FormattedMessage id='group.join.private' defaultMessage='Request Access' />
|
||||
|
@ -52,7 +59,8 @@ const GroupActionButton = ({ group }: IGroupActionButton) => {
|
|||
return (
|
||||
<Button
|
||||
theme='secondary'
|
||||
onClick={onLeaveGroup}
|
||||
onClick={onCancelRequest}
|
||||
disabled={cancelRequest.isLoading}
|
||||
>
|
||||
<FormattedMessage id='group.cancel_request' defaultMessage='Cancel Request' />
|
||||
</Button>
|
||||
|
@ -74,6 +82,7 @@ const GroupActionButton = ({ group }: IGroupActionButton) => {
|
|||
<Button
|
||||
theme='secondary'
|
||||
onClick={onLeaveGroup}
|
||||
disabled={leaveGroup.isLoading}
|
||||
>
|
||||
<FormattedMessage id='group.leave' defaultMessage='Leave Group' />
|
||||
</Button>
|
||||
|
|
|
@ -125,7 +125,7 @@ const GroupHeader: React.FC<IGroupHeader> = ({ group }) => {
|
|||
dangerouslySetInnerHTML={{ __html: group.display_name_html }}
|
||||
/>
|
||||
|
||||
<Stack space={1}>
|
||||
<Stack space={1} alignItems='center'>
|
||||
<HStack className='text-gray-700 dark:text-gray-600' space={2} wrap>
|
||||
<GroupRelationship group={group} />
|
||||
<GroupPrivacy group={group} />
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import React, { useState } from 'react';
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
|
||||
import { Carousel, Stack, Text } from 'soapbox/components/ui';
|
||||
import PlaceholderGroupDiscover from 'soapbox/features/placeholder/components/placeholder-group-discover';
|
||||
|
@ -7,46 +8,59 @@ import { usePopularGroups } from 'soapbox/queries/groups';
|
|||
import Group from './group';
|
||||
|
||||
const PopularGroups = () => {
|
||||
const { groups, isFetching } = usePopularGroups();
|
||||
const { groups, isFetching, isFetched, isError } = usePopularGroups();
|
||||
const isEmpty = (isFetched && groups.length === 0) || isError;
|
||||
|
||||
const [groupCover, setGroupCover] = useState<HTMLDivElement | null>(null);
|
||||
|
||||
return (
|
||||
<Stack space={4}>
|
||||
<Text size='xl' weight='bold'>
|
||||
Popular Groups
|
||||
<FormattedMessage
|
||||
id='groups.discover.popular.title'
|
||||
defaultMessage='Popular Groups'
|
||||
/>
|
||||
</Text>
|
||||
|
||||
<Carousel
|
||||
itemWidth={250}
|
||||
itemCount={groups.length}
|
||||
controlsHeight={groupCover?.clientHeight}
|
||||
>
|
||||
{({ width }: { width: number }) => (
|
||||
<>
|
||||
{isFetching ? (
|
||||
new Array(20).fill(0).map((_, idx) => (
|
||||
<div
|
||||
className='relative flex shrink-0 flex-col space-y-2 px-0.5'
|
||||
style={{ width: width || 'auto' }}
|
||||
key={idx}
|
||||
>
|
||||
<PlaceholderGroupDiscover />
|
||||
</div>
|
||||
))
|
||||
) : (
|
||||
groups.map((group) => (
|
||||
<Group
|
||||
key={group.id}
|
||||
group={group}
|
||||
width={width}
|
||||
ref={setGroupCover}
|
||||
/>
|
||||
))
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</Carousel>
|
||||
{isEmpty ? (
|
||||
<Text theme='muted'>
|
||||
<FormattedMessage
|
||||
id='groups.discover.popular.empty'
|
||||
defaultMessage='Unable to fetch popular groups at this time. Please check back later.'
|
||||
/>
|
||||
</Text>
|
||||
) : (
|
||||
<Carousel
|
||||
itemWidth={250}
|
||||
itemCount={groups.length}
|
||||
controlsHeight={groupCover?.clientHeight}
|
||||
>
|
||||
{({ width }: { width: number }) => (
|
||||
<>
|
||||
{isFetching ? (
|
||||
new Array(20).fill(0).map((_, idx) => (
|
||||
<div
|
||||
className='relative flex shrink-0 flex-col space-y-2 px-0.5'
|
||||
style={{ width: width || 'auto' }}
|
||||
key={idx}
|
||||
>
|
||||
<PlaceholderGroupDiscover />
|
||||
</div>
|
||||
))
|
||||
) : (
|
||||
groups.map((group) => (
|
||||
<Group
|
||||
key={group.id}
|
||||
group={group}
|
||||
width={width}
|
||||
ref={setGroupCover}
|
||||
/>
|
||||
))
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</Carousel>
|
||||
)}
|
||||
</Stack>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -0,0 +1,20 @@
|
|||
import React from 'react';
|
||||
|
||||
import { Stack, Text } from 'soapbox/components/ui';
|
||||
|
||||
interface Props {
|
||||
title: React.ReactNode | string
|
||||
subtitle: React.ReactNode | string
|
||||
}
|
||||
|
||||
export default ({ title, subtitle }: Props) => (
|
||||
<Stack space={2} className='px-4 py-2' data-testid='no-results'>
|
||||
<Text weight='bold' size='lg'>
|
||||
{title}
|
||||
</Text>
|
||||
|
||||
<Text theme='muted'>
|
||||
{subtitle}
|
||||
</Text>
|
||||
</Stack>
|
||||
);
|
|
@ -1,22 +0,0 @@
|
|||
import React from 'react';
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
|
||||
import { Stack, Text } from 'soapbox/components/ui';
|
||||
|
||||
export default () => (
|
||||
<Stack space={2} className='px-4 py-2' data-testid='no-results'>
|
||||
<Text weight='bold' size='lg'>
|
||||
<FormattedMessage
|
||||
id='groups.discover.search.no_results.title'
|
||||
defaultMessage='No matches found'
|
||||
/>
|
||||
</Text>
|
||||
|
||||
<Text theme='muted'>
|
||||
<FormattedMessage
|
||||
id='groups.discover.search.no_results.subtitle'
|
||||
defaultMessage='Try searching for another group.'
|
||||
/>
|
||||
</Text>
|
||||
</Stack>
|
||||
);
|
|
@ -1,4 +1,5 @@
|
|||
import React, { useEffect } from 'react';
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
|
||||
import { Stack } from 'soapbox/components/ui';
|
||||
import PlaceholderGroupSearch from 'soapbox/features/placeholder/components/placeholder-group-search';
|
||||
|
@ -6,7 +7,7 @@ import { useDebounce, useOwnAccount } from 'soapbox/hooks';
|
|||
import { useGroupSearch } from 'soapbox/queries/groups/search';
|
||||
import { saveGroupSearch } from 'soapbox/utils/groups';
|
||||
|
||||
import NoResultsBlankslate from './no-results-blankslate';
|
||||
import Blankslate from './blankslate';
|
||||
import RecentSearches from './recent-searches';
|
||||
import Results from './results';
|
||||
|
||||
|
@ -25,7 +26,7 @@ export default (props: Props) => {
|
|||
const debouncedValueToSave = debounce(searchValue as string, 1000);
|
||||
|
||||
const groupSearchResult = useGroupSearch(debouncedValue);
|
||||
const { groups, isFetching, isFetched } = groupSearchResult;
|
||||
const { groups, isFetching, isFetched, isError } = groupSearchResult;
|
||||
|
||||
const hasSearchResults = isFetched && groups.length > 0;
|
||||
const hasNoSearchResults = isFetched && groups.length === 0;
|
||||
|
@ -46,8 +47,42 @@ export default (props: Props) => {
|
|||
);
|
||||
}
|
||||
|
||||
if (isError) {
|
||||
return (
|
||||
<Blankslate
|
||||
title={
|
||||
<FormattedMessage
|
||||
id='groups.discover.search.error.title'
|
||||
defaultMessage='An error occurred'
|
||||
/>
|
||||
}
|
||||
subtitle={
|
||||
<FormattedMessage
|
||||
id='groups.discover.search.error.subtitle'
|
||||
defaultMessage='Please try again later.'
|
||||
/>
|
||||
}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
if (hasNoSearchResults) {
|
||||
return <NoResultsBlankslate />;
|
||||
return (
|
||||
<Blankslate
|
||||
title={
|
||||
<FormattedMessage
|
||||
id='groups.discover.search.no_results.title'
|
||||
defaultMessage='No matches found'
|
||||
/>
|
||||
}
|
||||
subtitle={
|
||||
<FormattedMessage
|
||||
id='groups.discover.search.no_results.subtitle'
|
||||
defaultMessage='Try searching for another group.'
|
||||
/>
|
||||
}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
if (hasSearchResults) {
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import React, { useState } from 'react';
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
|
||||
import { Carousel, Stack, Text } from 'soapbox/components/ui';
|
||||
import PlaceholderGroupDiscover from 'soapbox/features/placeholder/components/placeholder-group-discover';
|
||||
|
@ -7,46 +8,59 @@ import { useSuggestedGroups } from 'soapbox/queries/groups';
|
|||
import Group from './group';
|
||||
|
||||
const SuggestedGroups = () => {
|
||||
const { groups, isFetching } = useSuggestedGroups();
|
||||
const { groups, isFetching, isFetched, isError } = useSuggestedGroups();
|
||||
const isEmpty = (isFetched && groups.length === 0) || isError;
|
||||
|
||||
const [groupCover, setGroupCover] = useState<HTMLDivElement | null>(null);
|
||||
|
||||
return (
|
||||
<Stack space={4}>
|
||||
<Text size='xl' weight='bold'>
|
||||
Suggested For You
|
||||
<FormattedMessage
|
||||
id='groups.discover.suggested.title'
|
||||
defaultMessage='Suggested For You'
|
||||
/>
|
||||
</Text>
|
||||
|
||||
<Carousel
|
||||
itemWidth={250}
|
||||
itemCount={groups.length}
|
||||
controlsHeight={groupCover?.clientHeight}
|
||||
>
|
||||
{({ width }: { width: number }) => (
|
||||
<>
|
||||
{isFetching ? (
|
||||
new Array(20).fill(0).map((_, idx) => (
|
||||
<div
|
||||
className='relative flex shrink-0 flex-col space-y-2 px-0.5'
|
||||
style={{ width: width || 'auto' }}
|
||||
key={idx}
|
||||
>
|
||||
<PlaceholderGroupDiscover />
|
||||
</div>
|
||||
))
|
||||
) : (
|
||||
groups.map((group) => (
|
||||
<Group
|
||||
key={group.id}
|
||||
group={group}
|
||||
width={width}
|
||||
ref={setGroupCover}
|
||||
/>
|
||||
))
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</Carousel>
|
||||
{isEmpty ? (
|
||||
<Text theme='muted'>
|
||||
<FormattedMessage
|
||||
id='groups.discover.suggested.empty'
|
||||
defaultMessage='Unable to fetch suggested groups at this time. Please check back later.'
|
||||
/>
|
||||
</Text>
|
||||
) : (
|
||||
<Carousel
|
||||
itemWidth={250}
|
||||
itemCount={groups.length}
|
||||
controlsHeight={groupCover?.clientHeight}
|
||||
>
|
||||
{({ width }: { width: number }) => (
|
||||
<>
|
||||
{isFetching ? (
|
||||
new Array(20).fill(0).map((_, idx) => (
|
||||
<div
|
||||
className='relative flex shrink-0 flex-col space-y-2 px-0.5'
|
||||
style={{ width: width || 'auto' }}
|
||||
key={idx}
|
||||
>
|
||||
<PlaceholderGroupDiscover />
|
||||
</div>
|
||||
))
|
||||
) : (
|
||||
groups.map((group) => (
|
||||
<Group
|
||||
key={group.id}
|
||||
group={group}
|
||||
width={width}
|
||||
ref={setGroupCover}
|
||||
/>
|
||||
))
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</Carousel>
|
||||
)}
|
||||
</Stack>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -2,7 +2,7 @@ import { Map as ImmutableMap } from 'immutable';
|
|||
|
||||
import { __stub } from 'soapbox/api';
|
||||
import { renderHook, waitFor } from 'soapbox/jest/test-helpers';
|
||||
import { normalizeAccount, normalizeGroup, normalizeInstance } from 'soapbox/normalizers';
|
||||
import { normalizeAccount, normalizeGroup, normalizeGroupRelationship, normalizeInstance } from 'soapbox/normalizers';
|
||||
|
||||
import { useGroupsPath } from '../useGroupsPath';
|
||||
|
||||
|
@ -58,23 +58,16 @@ describe('useGroupsPath()', () => {
|
|||
id: '1',
|
||||
}),
|
||||
]);
|
||||
|
||||
mock.onGet('/api/v1/groups/relationships?id[]=1').reply(200, [
|
||||
normalizeGroupRelationship({
|
||||
id: '1',
|
||||
}),
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
test('should default to the discovery page', async () => {
|
||||
const store = {
|
||||
entities: {
|
||||
Groups: {
|
||||
store: {
|
||||
'1': normalizeGroup({}),
|
||||
},
|
||||
lists: {
|
||||
'': new Set(['1']),
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
test('should default to the "My Groups" page', async () => {
|
||||
const { result } = renderHook(useGroupsPath, undefined, store);
|
||||
|
||||
await waitFor(() => {
|
||||
|
|
|
@ -789,7 +789,7 @@
|
|||
"group.join.private": "Request Access",
|
||||
"group.join.public": "Join Group",
|
||||
"group.join.request_success": "Requested to join the group",
|
||||
"group.join.success": "Joined the group",
|
||||
"group.join.success": "Group joined successfully!",
|
||||
"group.leave": "Leave Group",
|
||||
"group.leave.success": "Left the group",
|
||||
"group.manage": "Manage Group",
|
||||
|
@ -801,6 +801,10 @@
|
|||
"group.tabs.all": "All",
|
||||
"group.tabs.members": "Members",
|
||||
"group.user_subheading": "Users",
|
||||
"groups.discover.popular.empty": "Unable to fetch popular groups at this time. Please check back later.",
|
||||
"groups.discover.popular.title": "Popular Groups",
|
||||
"groups.discover.search.error.subtitle": "Please try again later.",
|
||||
"groups.discover.search.error.title": "An error occurred",
|
||||
"groups.discover.search.no_results.subtitle": "Try searching for another group.",
|
||||
"groups.discover.search.no_results.title": "No matches found",
|
||||
"groups.discover.search.placeholder": "Search",
|
||||
|
@ -810,6 +814,8 @@
|
|||
"groups.discover.search.recent_searches.title": "Recent searches",
|
||||
"groups.discover.search.results.groups": "Groups",
|
||||
"groups.discover.search.results.member_count": "{members, plural, one {member} other {members}}",
|
||||
"groups.discover.suggested.empty": "Unable to fetch suggested groups at this time. Please check back later.",
|
||||
"groups.discover.suggested.title": "Suggested For You",
|
||||
"groups.empty.subtitle": "Start discovering groups to join or create your own.",
|
||||
"groups.empty.title": "No Groups yet",
|
||||
"hashtag.column_header.tag_mode.all": "and {additional}",
|
||||
|
|
|
@ -10,7 +10,9 @@ import {
|
|||
|
||||
export const GroupRelationshipRecord = ImmutableRecord({
|
||||
id: '',
|
||||
blocked_by: false,
|
||||
member: false,
|
||||
notifying: null,
|
||||
requested: false,
|
||||
role: null as 'admin' | 'moderator' | 'user' | null,
|
||||
});
|
||||
|
|
|
@ -37,7 +37,7 @@ const GroupPage: React.FC<IGroupPage> = ({ params, children }) => {
|
|||
|
||||
const { group } = useGroup(id);
|
||||
|
||||
const isNonMember = !group?.relationship || !group.relationship.member;
|
||||
const isNonMember = !group?.relationship?.member;
|
||||
const isPrivate = group?.locked;
|
||||
|
||||
// if ((group as any) === false) {
|
||||
|
@ -80,7 +80,6 @@ const GroupPage: React.FC<IGroupPage> = ({ params, children }) => {
|
|||
Content is only visible to group members
|
||||
</Text>
|
||||
</Stack>
|
||||
|
||||
) : children}
|
||||
</Column>
|
||||
|
||||
|
|
|
@ -175,35 +175,35 @@ describe('useChatMessages', () => {
|
|||
});
|
||||
|
||||
describe('useChats', () => {
|
||||
let store: ReturnType<typeof mockStore>;
|
||||
// let store: ReturnType<typeof mockStore>;
|
||||
|
||||
beforeEach(() => {
|
||||
queryClient.clear();
|
||||
});
|
||||
|
||||
describe('with a successful request', () => {
|
||||
beforeEach(() => {
|
||||
const state = rootState.setIn(['instance', 'version'], '2.7.2 (compatible; Pleroma 2.2.0)');
|
||||
store = mockStore(state);
|
||||
// describe('with a successful request', () => {
|
||||
// beforeEach(() => {
|
||||
// const state = rootState.setIn(['instance', 'version'], '2.7.2 (compatible; Pleroma 2.2.0)');
|
||||
// store = mockStore(state);
|
||||
|
||||
__stub((mock) => {
|
||||
mock.onGet('/api/v1/pleroma/chats')
|
||||
.reply(200, [
|
||||
chat,
|
||||
], {
|
||||
link: '<https://example.com/api/v1/pleroma/chats?since_id=2>; rel="prev"',
|
||||
});
|
||||
});
|
||||
});
|
||||
// __stub((mock) => {
|
||||
// mock.onGet('/api/v1/pleroma/chats')
|
||||
// .reply(200, [
|
||||
// chat,
|
||||
// ], {
|
||||
// link: '<https://example.com/api/v1/pleroma/chats?since_id=2>; rel="prev"',
|
||||
// });
|
||||
// });
|
||||
// });
|
||||
|
||||
it('is successful', async () => {
|
||||
const { result } = renderHook(() => useChats().chatsQuery, undefined, store);
|
||||
// it('is successful', async () => {
|
||||
// const { result } = renderHook(() => useChats().chatsQuery, undefined, store);
|
||||
|
||||
await waitFor(() => expect(result.current.isFetching).toBe(false));
|
||||
// await waitFor(() => expect(result.current.isFetching).toBe(false));
|
||||
|
||||
expect(result.current.data?.length).toBe(1);
|
||||
});
|
||||
});
|
||||
// expect(result.current.data?.length).toBe(1);
|
||||
// });
|
||||
// });
|
||||
|
||||
describe('with an unsuccessful query', () => {
|
||||
beforeEach(() => {
|
||||
|
|
|
@ -1,13 +1,21 @@
|
|||
import { useInfiniteQuery, useQuery } from '@tanstack/react-query';
|
||||
import { useInfiniteQuery, useMutation, useQuery } from '@tanstack/react-query';
|
||||
import { defineMessages, useIntl } from 'react-intl';
|
||||
|
||||
import { fetchGroupRelationships } from 'soapbox/actions/groups';
|
||||
import { importFetchedGroups } from 'soapbox/actions/importer';
|
||||
import { getNextLink } from 'soapbox/api';
|
||||
import { useApi, useAppDispatch, useFeatures, useOwnAccount } from 'soapbox/hooks';
|
||||
import { normalizeGroup } from 'soapbox/normalizers';
|
||||
import { Group } from 'soapbox/types/entities';
|
||||
import { useApi, useFeatures, useOwnAccount } from 'soapbox/hooks';
|
||||
import { normalizeGroup, normalizeGroupRelationship } from 'soapbox/normalizers';
|
||||
import toast from 'soapbox/toast';
|
||||
import { Group, GroupRelationship } from 'soapbox/types/entities';
|
||||
import { flattenPages, PaginatedResult } from 'soapbox/utils/queries';
|
||||
|
||||
import { queryClient } from './client';
|
||||
|
||||
const messages = defineMessages({
|
||||
joinSuccess: { id: 'group.join.success', defaultMessage: 'Group joined successfully!' },
|
||||
joinRequestSuccess: { id: 'group.join.request_success', defaultMessage: 'Requested to join the group' },
|
||||
leaveSuccess: { id: 'group.leave.success', defaultMessage: 'Left the group' },
|
||||
});
|
||||
|
||||
const GroupKeys = {
|
||||
group: (id: string) => ['groups', 'group', id] as const,
|
||||
myGroups: (userId: string) => ['groups', userId] as const,
|
||||
|
@ -15,29 +23,54 @@ const GroupKeys = {
|
|||
suggestedGroups: ['groups', 'suggested'] as const,
|
||||
};
|
||||
|
||||
const useGroups = () => {
|
||||
const useGroupsApi = () => {
|
||||
const api = useApi();
|
||||
|
||||
const getGroupRelationships = async (ids: string[]) => {
|
||||
const queryString = ids.map((id) => `id[]=${id}`).join('&');
|
||||
const { data } = await api.get<GroupRelationship[]>(`/api/v1/groups/relationships?${queryString}`);
|
||||
|
||||
return data;
|
||||
};
|
||||
|
||||
const fetchGroups = async (endpoint: string) => {
|
||||
const response = await api.get<Group[]>(endpoint);
|
||||
const groups = [response.data].flat();
|
||||
const relationships = await getGroupRelationships(groups.map((group) => group.id));
|
||||
const result = groups.map((group) => {
|
||||
const relationship = relationships.find((relationship) => relationship.id === group.id);
|
||||
|
||||
return normalizeGroup({
|
||||
...group,
|
||||
relationship: relationship ? normalizeGroupRelationship(relationship) : null,
|
||||
});
|
||||
});
|
||||
|
||||
return {
|
||||
response,
|
||||
groups: result,
|
||||
};
|
||||
};
|
||||
|
||||
return { fetchGroups };
|
||||
};
|
||||
|
||||
const useGroups = () => {
|
||||
const account = useOwnAccount();
|
||||
const dispatch = useAppDispatch();
|
||||
const features = useFeatures();
|
||||
const { fetchGroups } = useGroupsApi();
|
||||
|
||||
const getGroups = async (pageParam?: any): Promise<PaginatedResult<Group>> => {
|
||||
const endpoint = '/api/v1/groups';
|
||||
const nextPageLink = pageParam?.link;
|
||||
const uri = nextPageLink || endpoint;
|
||||
const response = await api.get<Group[]>(uri);
|
||||
const { data } = response;
|
||||
const { response, groups } = await fetchGroups(uri);
|
||||
|
||||
const link = getNextLink(response);
|
||||
const hasMore = !!link;
|
||||
const result = data.map(normalizeGroup);
|
||||
|
||||
// Note: Temporary while part of Groups is using Redux
|
||||
dispatch(importFetchedGroups(result));
|
||||
dispatch(fetchGroupRelationships(result.map((item) => item.id)));
|
||||
|
||||
return {
|
||||
result,
|
||||
result: groups,
|
||||
hasMore,
|
||||
link,
|
||||
};
|
||||
|
@ -67,14 +100,13 @@ const useGroups = () => {
|
|||
};
|
||||
|
||||
const usePopularGroups = () => {
|
||||
const api = useApi();
|
||||
const features = useFeatures();
|
||||
const { fetchGroups } = useGroupsApi();
|
||||
|
||||
const getQuery = async () => {
|
||||
const { data } = await api.get<Group[]>('/api/v1/groups/search?q=group'); // '/api/v1/truth/trends/groups'
|
||||
const result = data.map(normalizeGroup);
|
||||
const { groups } = await fetchGroups('/api/v1/truth/trends/groups');
|
||||
|
||||
return result;
|
||||
return groups;
|
||||
};
|
||||
|
||||
const queryInfo = useQuery<Group[]>(GroupKeys.popularGroups, getQuery, {
|
||||
|
@ -89,14 +121,13 @@ const usePopularGroups = () => {
|
|||
};
|
||||
|
||||
const useSuggestedGroups = () => {
|
||||
const api = useApi();
|
||||
const features = useFeatures();
|
||||
const { fetchGroups } = useGroupsApi();
|
||||
|
||||
const getQuery = async () => {
|
||||
const { data } = await api.get<Group[]>('/api/mock/groups'); // /api/v1/truth/suggestions/groups
|
||||
const result = data.map(normalizeGroup);
|
||||
const { groups } = await fetchGroups('/api/v1/truth/suggestions/groups');
|
||||
|
||||
return result;
|
||||
return groups;
|
||||
};
|
||||
|
||||
const queryInfo = useQuery<Group[]>(GroupKeys.suggestedGroups, getQuery, {
|
||||
|
@ -111,12 +142,12 @@ const useSuggestedGroups = () => {
|
|||
};
|
||||
|
||||
const useGroup = (id: string) => {
|
||||
const api = useApi();
|
||||
const features = useFeatures();
|
||||
const { fetchGroups } = useGroupsApi();
|
||||
|
||||
const getGroup = async () => {
|
||||
const { data } = await api.get(`/api/v1/groups/${id}`);
|
||||
return normalizeGroup(data);
|
||||
const { groups } = await fetchGroups(`/api/v1/groups/${id}`);
|
||||
return groups[0];
|
||||
};
|
||||
|
||||
const queryInfo = useQuery(GroupKeys.group(id), getGroup, {
|
||||
|
@ -129,4 +160,43 @@ const useGroup = (id: string) => {
|
|||
};
|
||||
};
|
||||
|
||||
export { useGroups, useGroup, usePopularGroups, useSuggestedGroups };
|
||||
const useJoinGroup = () => {
|
||||
const api = useApi();
|
||||
const intl = useIntl();
|
||||
|
||||
return useMutation((group: Group) => api.post<GroupRelationship>(`/api/v1/groups/${group.id}/join`), {
|
||||
onSuccess(_response, group) {
|
||||
queryClient.invalidateQueries(['groups']);
|
||||
toast.success(
|
||||
group.locked
|
||||
? intl.formatMessage(messages.joinRequestSuccess)
|
||||
: intl.formatMessage(messages.joinSuccess),
|
||||
);
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
const useLeaveGroup = () => {
|
||||
const api = useApi();
|
||||
const intl = useIntl();
|
||||
|
||||
return useMutation((group: Group) => api.post<GroupRelationship>(`/api/v1/groups/${group.id}/leave`), {
|
||||
onSuccess() {
|
||||
queryClient.invalidateQueries({ queryKey: ['groups'] });
|
||||
toast.success(intl.formatMessage(messages.leaveSuccess));
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
const useCancelMembershipRequest = () => {
|
||||
const api = useApi();
|
||||
const me = useOwnAccount();
|
||||
|
||||
return useMutation((group: Group) => api.post(`/api/v1/groups/${group.id}/membership_requests/${me?.id}/reject`), {
|
||||
onSuccess() {
|
||||
queryClient.invalidateQueries({ queryKey: ['groups'] });
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
export { useGroups, useGroup, usePopularGroups, useSuggestedGroups, useJoinGroup, useLeaveGroup, useCancelMembershipRequest };
|
||||
|
|
|
@ -5,12 +5,6 @@ import {
|
|||
GROUP_UPDATE_SUCCESS,
|
||||
GROUP_DELETE_SUCCESS,
|
||||
GROUP_RELATIONSHIPS_FETCH_SUCCESS,
|
||||
GROUP_JOIN_REQUEST,
|
||||
GROUP_JOIN_SUCCESS,
|
||||
GROUP_JOIN_FAIL,
|
||||
GROUP_LEAVE_REQUEST,
|
||||
GROUP_LEAVE_SUCCESS,
|
||||
GROUP_LEAVE_FAIL,
|
||||
} from 'soapbox/actions/groups';
|
||||
import { normalizeGroupRelationship } from 'soapbox/normalizers';
|
||||
|
||||
|
@ -37,17 +31,6 @@ export default function groupRelationships(state: State = ImmutableMap(), action
|
|||
return state.set(action.group.id, normalizeGroupRelationship({ id: action.group.id, member: true, requested: false, role: 'admin' }));
|
||||
case GROUP_DELETE_SUCCESS:
|
||||
return state.delete(action.id);
|
||||
case GROUP_JOIN_REQUEST:
|
||||
return state.getIn([action.id, 'member']) ? state : state.setIn([action.id, action.locked ? 'requested' : 'member'], true);
|
||||
case GROUP_JOIN_FAIL:
|
||||
return state.setIn([action.id, action.locked ? 'requested' : 'member'], false);
|
||||
case GROUP_LEAVE_REQUEST:
|
||||
return state.setIn([action.id, 'member'], false);
|
||||
case GROUP_LEAVE_FAIL:
|
||||
return state.setIn([action.id, 'member'], true);
|
||||
case GROUP_JOIN_SUCCESS:
|
||||
case GROUP_LEAVE_SUCCESS:
|
||||
return normalizeRelationships(state, [action.relationship]);
|
||||
case GROUP_RELATIONSHIPS_FETCH_SUCCESS:
|
||||
return normalizeRelationships(state, action.relationships);
|
||||
default:
|
||||
|
|
|
@ -212,9 +212,9 @@
|
|||
"@storybook/react": "^6.5.16",
|
||||
"@storybook/testing-library": "^0.0.13",
|
||||
"@tailwindcss/aspect-ratio": "^0.4.2",
|
||||
"@testing-library/jest-dom": "^5.16.4",
|
||||
"@testing-library/jest-dom": "^5.16.5",
|
||||
"@testing-library/react-hooks": "^8.0.1",
|
||||
"@testing-library/user-event": "^14.0.3",
|
||||
"@testing-library/user-event": "^14.4.3",
|
||||
"@typescript-eslint/eslint-plugin": "^5.15.0",
|
||||
"@typescript-eslint/parser": "^5.15.0",
|
||||
"babel-jest": "^29.4.1",
|
||||
|
|
40
yarn.lock
40
yarn.lock
|
@ -2,6 +2,11 @@
|
|||
# yarn lockfile v1
|
||||
|
||||
|
||||
"@adobe/css-tools@^4.0.1":
|
||||
version "4.2.0"
|
||||
resolved "https://registry.yarnpkg.com/@adobe/css-tools/-/css-tools-4.2.0.tgz#e1a84fca468f4b337816fcb7f0964beb620ba855"
|
||||
integrity sha512-E09FiIft46CmH5Qnjb0wsW54/YQd69LsxeKUOWawmws1XWvyFGURnAChH0mlr7YPFR1ofwvUQfcL0J3lMxXqPA==
|
||||
|
||||
"@ampproject/remapping@^2.1.0":
|
||||
version "2.1.2"
|
||||
resolved "https://registry.yarnpkg.com/@ampproject/remapping/-/remapping-2.1.2.tgz#4edca94973ded9630d20101cd8559cedb8d8bd34"
|
||||
|
@ -3905,16 +3910,16 @@
|
|||
lz-string "^1.5.0"
|
||||
pretty-format "^27.0.2"
|
||||
|
||||
"@testing-library/jest-dom@^5.16.4":
|
||||
version "5.16.4"
|
||||
resolved "https://registry.yarnpkg.com/@testing-library/jest-dom/-/jest-dom-5.16.4.tgz#938302d7b8b483963a3ae821f1c0808f872245cd"
|
||||
integrity sha512-Gy+IoFutbMQcky0k+bqqumXZ1cTGswLsFqmNLzNdSKkU9KGV2u9oXhukCbbJ9/LRPKiqwxEE8VpV/+YZlfkPUA==
|
||||
"@testing-library/jest-dom@^5.16.5":
|
||||
version "5.16.5"
|
||||
resolved "https://registry.yarnpkg.com/@testing-library/jest-dom/-/jest-dom-5.16.5.tgz#3912846af19a29b2dbf32a6ae9c31ef52580074e"
|
||||
integrity sha512-N5ixQ2qKpi5OLYfwQmUb/5mSV9LneAcaUfp32pn4yCnpb8r/Yz0pXFPck21dIicKmi+ta5WRAknkZCfA8refMA==
|
||||
dependencies:
|
||||
"@adobe/css-tools" "^4.0.1"
|
||||
"@babel/runtime" "^7.9.2"
|
||||
"@types/testing-library__jest-dom" "^5.9.1"
|
||||
aria-query "^5.0.0"
|
||||
chalk "^3.0.0"
|
||||
css "^3.0.0"
|
||||
css.escape "^1.5.1"
|
||||
dom-accessibility-api "^0.5.6"
|
||||
lodash "^4.17.15"
|
||||
|
@ -3944,10 +3949,10 @@
|
|||
dependencies:
|
||||
"@babel/runtime" "^7.12.5"
|
||||
|
||||
"@testing-library/user-event@^14.0.3":
|
||||
version "14.0.3"
|
||||
resolved "https://registry.yarnpkg.com/@testing-library/user-event/-/user-event-14.0.3.tgz#463667596122c13d997f70b73426947ab71de962"
|
||||
integrity sha512-zIgBG5CxfXbMsm4wBS6iQC3TBNMZk16O25i4shS9MM+eSG7PZHrsBF6LFIesUkepkZ3QKKgstB2/Nola6nvy4A==
|
||||
"@testing-library/user-event@^14.4.3":
|
||||
version "14.4.3"
|
||||
resolved "https://registry.yarnpkg.com/@testing-library/user-event/-/user-event-14.4.3.tgz#af975e367743fa91989cd666666aec31a8f50591"
|
||||
integrity sha512-kCUc5MEwaEMakkO5x7aoD+DLi02ehmEM2QCGWvNqAS1dV/fAvORWEjnjsEIvml59M7Y5kCkWN6fCCyPOe8OL6Q==
|
||||
|
||||
"@tootallnate/once@2":
|
||||
version "2.0.0"
|
||||
|
@ -7316,15 +7321,6 @@ css.escape@^1.5.1:
|
|||
resolved "https://registry.yarnpkg.com/css.escape/-/css.escape-1.5.1.tgz#42e27d4fa04ae32f931a4b4d4191fa9cddee97cb"
|
||||
integrity sha1-QuJ9T6BK4y+TGktNQZH6nN3ul8s=
|
||||
|
||||
css@^3.0.0:
|
||||
version "3.0.0"
|
||||
resolved "https://registry.yarnpkg.com/css/-/css-3.0.0.tgz#4447a4d58fdd03367c516ca9f64ae365cee4aa5d"
|
||||
integrity sha512-DG9pFfwOrzc+hawpmqX/dHYHJG+Bsdb0klhyi1sDneOgGOXy9wQIC8hzyVp1e4NRYDBdxcylvywPkkXCHAzTyQ==
|
||||
dependencies:
|
||||
inherits "^2.0.4"
|
||||
source-map "^0.6.1"
|
||||
source-map-resolve "^0.6.0"
|
||||
|
||||
cssesc@^3.0.0:
|
||||
version "3.0.0"
|
||||
resolved "https://registry.yarnpkg.com/cssesc/-/cssesc-3.0.0.tgz#37741919903b868565e1c09ea747445cd18983ee"
|
||||
|
@ -15898,14 +15894,6 @@ source-map-resolve@^0.5.0:
|
|||
source-map-url "^0.4.0"
|
||||
urix "^0.1.0"
|
||||
|
||||
source-map-resolve@^0.6.0:
|
||||
version "0.6.0"
|
||||
resolved "https://registry.yarnpkg.com/source-map-resolve/-/source-map-resolve-0.6.0.tgz#3d9df87e236b53f16d01e58150fc7711138e5ed2"
|
||||
integrity sha512-KXBr9d/fO/bWo97NXsPIAW1bFSBOuCnjbNTBMO7N59hsv5i9yzRDfcYwwt0l04+VqnKC+EwzvJZIP/qkuMgR/w==
|
||||
dependencies:
|
||||
atob "^2.1.2"
|
||||
decode-uri-component "^0.2.0"
|
||||
|
||||
source-map-support@0.5.13:
|
||||
version "0.5.13"
|
||||
resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.13.tgz#31b24a9c2e73c2de85066c0feb7d44767ed52932"
|
||||
|
|
Loading…
Reference in a new issue