mirror of
https://github.com/strapi/strapi.git
synced 2025-11-16 10:07:55 +00:00
feat: introduce useClipboard hook to remove dependency (#16751)
This commit is contained in:
parent
750b6c8e8f
commit
e233d8afdc
@ -17,7 +17,7 @@ Borrowed from [`@radix-ui/react-use-callback-ref`](https://www.npmjs.com/package
|
|||||||
## Usage
|
## Usage
|
||||||
|
|
||||||
```jsx
|
```jsx
|
||||||
import { useCallbackRef } from 'path/to/hooks';
|
import { useCallbackRef } from '@strapi/helper-plugin';
|
||||||
|
|
||||||
const MyComponent = ({ callbackFromSomewhere }) => {
|
const MyComponent = ({ callbackFromSomewhere }) => {
|
||||||
const mySafeCallback = useCallbackRef(callbackFromSomewhere);
|
const mySafeCallback = useCallbackRef(callbackFromSomewhere);
|
||||||
|
|||||||
37
docs/docs/docs/01-core/helper-plugin/hooks/use-clipboard.mdx
Normal file
37
docs/docs/docs/01-core/helper-plugin/hooks/use-clipboard.mdx
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
---
|
||||||
|
title: useClipboard
|
||||||
|
description: API reference for the useClipboard hook in Strapi
|
||||||
|
tags:
|
||||||
|
- hooks
|
||||||
|
- helper-plugin
|
||||||
|
---
|
||||||
|
|
||||||
|
A small abstraction around the [`navigation.clipboard`](https://developer.mozilla.org/en-US/docs/Web/API/Clipboard) API.
|
||||||
|
Currently we only expose a `copy` method which abstracts the `writeText` method of the clipboard API.
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
```jsx
|
||||||
|
import { useClipboard } from '@strapi/helper-plugin';
|
||||||
|
|
||||||
|
const MyComponent = () => {
|
||||||
|
const { copy } = useClipboard();
|
||||||
|
const handleClick = async () => {
|
||||||
|
const didCopy = await copy('hello world');
|
||||||
|
|
||||||
|
if (didCopy) {
|
||||||
|
alert('copied!');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return <button onClick={handleClick}>Copy text</button>;
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
## Typescript
|
||||||
|
|
||||||
|
```ts
|
||||||
|
function useClipboard(): {
|
||||||
|
copy: (text: string) => Promise<boolean>;
|
||||||
|
};
|
||||||
|
```
|
||||||
@ -201,3 +201,16 @@ Object.defineProperty(window, 'PointerEvent', {
|
|||||||
window.HTMLElement.prototype.scrollIntoView = jest.fn();
|
window.HTMLElement.prototype.scrollIntoView = jest.fn();
|
||||||
window.HTMLElement.prototype.releasePointerCapture = jest.fn();
|
window.HTMLElement.prototype.releasePointerCapture = jest.fn();
|
||||||
window.HTMLElement.prototype.hasPointerCapture = jest.fn();
|
window.HTMLElement.prototype.hasPointerCapture = jest.fn();
|
||||||
|
|
||||||
|
/* -------------------------------------------------------------------------------------------------
|
||||||
|
* Navigator
|
||||||
|
* -----------------------------------------------------------------------------------------------*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Navigator is a large object so we only mock the properties we need.
|
||||||
|
*/
|
||||||
|
Object.assign(navigator, {
|
||||||
|
clipboard: {
|
||||||
|
writeText: jest.fn(),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { useIntl } from 'react-intl';
|
import { useIntl } from 'react-intl';
|
||||||
import { useNotification, useTracking } from '@strapi/helper-plugin';
|
import { useNotification, useTracking, useClipboard } from '@strapi/helper-plugin';
|
||||||
import { Box, Icon, Typography } from '@strapi/design-system';
|
import { Box, Icon, Typography } from '@strapi/design-system';
|
||||||
import { Check } from '@strapi/icons';
|
import { Check } from '@strapi/icons';
|
||||||
import CardButton from './CardButton';
|
import CardButton from './CardButton';
|
||||||
@ -17,14 +17,18 @@ const InstallPluginButton = ({
|
|||||||
const toggleNotification = useNotification();
|
const toggleNotification = useNotification();
|
||||||
const { formatMessage } = useIntl();
|
const { formatMessage } = useIntl();
|
||||||
const { trackUsage } = useTracking();
|
const { trackUsage } = useTracking();
|
||||||
|
const { copy } = useClipboard();
|
||||||
|
|
||||||
const handleCopy = () => {
|
const handleCopy = async () => {
|
||||||
navigator.clipboard.writeText(commandToCopy);
|
const didCopy = await copy(commandToCopy);
|
||||||
|
|
||||||
|
if (didCopy) {
|
||||||
trackUsage('willInstallPlugin');
|
trackUsage('willInstallPlugin');
|
||||||
toggleNotification({
|
toggleNotification({
|
||||||
type: 'success',
|
type: 'success',
|
||||||
message: { id: 'admin.pages.MarketPlacePage.plugin.copy.success' },
|
message: { id: 'admin.pages.MarketPlacePage.plugin.copy.success' },
|
||||||
});
|
});
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Already installed
|
// Already installed
|
||||||
|
|||||||
@ -1,44 +1,46 @@
|
|||||||
import React, { useRef } from 'react';
|
import React from 'react';
|
||||||
import { useIntl } from 'react-intl';
|
import { useIntl } from 'react-intl';
|
||||||
import { ContentBox, useNotification, useTracking } from '@strapi/helper-plugin';
|
import { ContentBox, useNotification, useTracking, useClipboard } from '@strapi/helper-plugin';
|
||||||
import { IconButton } from '@strapi/design-system';
|
import { IconButton } from '@strapi/design-system';
|
||||||
import { Duplicate, Key } from '@strapi/icons';
|
import { Duplicate, Key } from '@strapi/icons';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { CopyToClipboard } from 'react-copy-to-clipboard';
|
|
||||||
|
|
||||||
const TokenBox = ({ token, tokenType }) => {
|
const TokenBox = ({ token, tokenType }) => {
|
||||||
const { formatMessage } = useIntl();
|
const { formatMessage } = useIntl();
|
||||||
const toggleNotification = useNotification();
|
const toggleNotification = useNotification();
|
||||||
const { trackUsage } = useTracking();
|
const { trackUsage } = useTracking();
|
||||||
const trackUsageRef = useRef(trackUsage);
|
|
||||||
|
|
||||||
return (
|
const { copy } = useClipboard();
|
||||||
<ContentBox
|
|
||||||
endAction={
|
const handleClick = (token) => async () => {
|
||||||
token && (
|
const didCopy = await copy(token);
|
||||||
<span style={{ alignSelf: 'start' }}>
|
|
||||||
<CopyToClipboard
|
if (didCopy) {
|
||||||
onCopy={() => {
|
trackUsage.current('didCopyTokenKey', {
|
||||||
trackUsageRef.current('didCopyTokenKey', {
|
|
||||||
tokenType,
|
tokenType,
|
||||||
});
|
});
|
||||||
toggleNotification({
|
toggleNotification({
|
||||||
type: 'success',
|
type: 'success',
|
||||||
message: { id: 'Settings.tokens.notification.copied' },
|
message: { id: 'Settings.tokens.notification.copied' },
|
||||||
});
|
});
|
||||||
}}
|
}
|
||||||
text={token}
|
};
|
||||||
>
|
|
||||||
|
return (
|
||||||
|
<ContentBox
|
||||||
|
endAction={
|
||||||
|
token && (
|
||||||
|
<span style={{ alignSelf: 'start' }}>
|
||||||
<IconButton
|
<IconButton
|
||||||
label={formatMessage({
|
label={formatMessage({
|
||||||
id: 'app.component.CopyToClipboard.label',
|
id: 'app.component.CopyToClipboard.label',
|
||||||
defaultMessage: 'Copy to clipboard',
|
defaultMessage: 'Copy to clipboard',
|
||||||
})}
|
})}
|
||||||
|
onClick={handleClick(token)}
|
||||||
noBorder
|
noBorder
|
||||||
icon={<Duplicate />}
|
icon={<Duplicate />}
|
||||||
style={{ padding: 0, height: '1rem' }}
|
style={{ padding: 0, height: '1rem' }}
|
||||||
/>
|
/>
|
||||||
</CopyToClipboard>
|
|
||||||
</span>
|
</span>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,30 +1,32 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { IconButton } from '@strapi/design-system';
|
import { IconButton } from '@strapi/design-system';
|
||||||
import { useNotification, ContentBox } from '@strapi/helper-plugin';
|
import { useNotification, ContentBox, useClipboard } from '@strapi/helper-plugin';
|
||||||
import { Duplicate } from '@strapi/icons';
|
import { Duplicate } from '@strapi/icons';
|
||||||
import { CopyToClipboard } from 'react-copy-to-clipboard';
|
|
||||||
import { useIntl } from 'react-intl';
|
import { useIntl } from 'react-intl';
|
||||||
|
|
||||||
const MagicLinkWrapper = ({ children, target }) => {
|
const MagicLinkWrapper = ({ children, target }) => {
|
||||||
const toggleNotification = useNotification();
|
const toggleNotification = useNotification();
|
||||||
const { formatMessage } = useIntl();
|
const { formatMessage } = useIntl();
|
||||||
|
const { copy } = useClipboard();
|
||||||
const handleCopy = () => {
|
|
||||||
toggleNotification({ type: 'info', message: { id: 'notification.link-copied' } });
|
|
||||||
};
|
|
||||||
|
|
||||||
const copyLabel = formatMessage({
|
const copyLabel = formatMessage({
|
||||||
id: 'app.component.CopyToClipboard.label',
|
id: 'app.component.CopyToClipboard.label',
|
||||||
defaultMessage: 'Copy to clipboard',
|
defaultMessage: 'Copy to clipboard',
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const handleClick = async () => {
|
||||||
|
const didCopy = await copy(target);
|
||||||
|
|
||||||
|
if (didCopy) {
|
||||||
|
toggleNotification({ type: 'info', message: { id: 'notification.link-copied' } });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ContentBox
|
<ContentBox
|
||||||
endAction={
|
endAction={
|
||||||
<CopyToClipboard onCopy={handleCopy} text={target}>
|
<IconButton label={copyLabel} noBorder icon={<Duplicate />} onClick={handleClick} />
|
||||||
<IconButton label={copyLabel} noBorder icon={<Duplicate />} />
|
|
||||||
</CopyToClipboard>
|
|
||||||
}
|
}
|
||||||
title={target}
|
title={target}
|
||||||
titleEllipsis
|
titleEllipsis
|
||||||
|
|||||||
@ -110,7 +110,6 @@
|
|||||||
"prop-types": "^15.7.2",
|
"prop-types": "^15.7.2",
|
||||||
"qs": "6.11.1",
|
"qs": "6.11.1",
|
||||||
"react": "^17.0.2",
|
"react": "^17.0.2",
|
||||||
"react-copy-to-clipboard": "^5.1.0",
|
|
||||||
"react-dnd": "15.1.2",
|
"react-dnd": "15.1.2",
|
||||||
"react-dnd-html5-backend": "15.1.3",
|
"react-dnd-html5-backend": "15.1.3",
|
||||||
"react-dom": "^17.0.2",
|
"react-dom": "^17.0.2",
|
||||||
|
|||||||
@ -14,7 +14,6 @@ const aliasExactMatch = [
|
|||||||
'qs',
|
'qs',
|
||||||
'lodash',
|
'lodash',
|
||||||
'react',
|
'react',
|
||||||
'react-copy-to-clipboard',
|
|
||||||
'react-dnd',
|
'react-dnd',
|
||||||
'react-dnd-html5-backend',
|
'react-dnd-html5-backend',
|
||||||
'react-dom',
|
'react-dom',
|
||||||
|
|||||||
@ -0,0 +1,23 @@
|
|||||||
|
import { renderHook } from '@testing-library/react-hooks';
|
||||||
|
|
||||||
|
import { useClipboard } from '../useClipboard';
|
||||||
|
|
||||||
|
describe('useClipboard', () => {
|
||||||
|
it('should return false if the value passed to the function is not a string or number', async () => {
|
||||||
|
const { result } = renderHook(() => useClipboard());
|
||||||
|
|
||||||
|
expect(await result.current.copy({})).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return false if the value passed to copy is an empty string', async () => {
|
||||||
|
const { result } = renderHook(() => useClipboard());
|
||||||
|
|
||||||
|
expect(await result.current.copy('')).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return true if the copy was successful', async () => {
|
||||||
|
const { result } = renderHook(() => useClipboard());
|
||||||
|
|
||||||
|
expect(await result.current.copy('test')).toBe(true);
|
||||||
|
});
|
||||||
|
});
|
||||||
33
packages/core/helper-plugin/src/hooks/useClipboard.js
Normal file
33
packages/core/helper-plugin/src/hooks/useClipboard.js
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
import { useCallback } from 'react';
|
||||||
|
|
||||||
|
export const useClipboard = () => {
|
||||||
|
const copy = useCallback(async (value) => {
|
||||||
|
try {
|
||||||
|
// only strings and numbers casted to strings can be copied to clipboard
|
||||||
|
if (typeof value !== 'string' && typeof value !== 'number') {
|
||||||
|
throw new Error(`Cannot copy typeof ${typeof value} to clipboard, must be a string`);
|
||||||
|
}
|
||||||
|
// empty strings are also considered invalid
|
||||||
|
else if (value === '') {
|
||||||
|
throw new Error(`Cannot copy empty string to clipboard.`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const stringifiedValue = value.toString();
|
||||||
|
|
||||||
|
await navigator.clipboard.writeText(stringifiedValue);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
} catch (error) {
|
||||||
|
/**
|
||||||
|
* Realistically this isn't useful in production as there's nothing the user can do.
|
||||||
|
*/
|
||||||
|
if (process.env.NODE_ENV === 'development') {
|
||||||
|
console.warn('Copy failed', error);
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return { copy };
|
||||||
|
};
|
||||||
@ -67,6 +67,7 @@ export * from './hooks/useAPIErrorHandler';
|
|||||||
export { useFilter } from './hooks/useFilter';
|
export { useFilter } from './hooks/useFilter';
|
||||||
export { useCollator } from './hooks/useCollator';
|
export { useCollator } from './hooks/useCollator';
|
||||||
export { useCallbackRef } from './hooks/useCallbackRef';
|
export { useCallbackRef } from './hooks/useCallbackRef';
|
||||||
|
export { useClipboard } from './hooks/useClipboard';
|
||||||
|
|
||||||
export { default as useQueryParams } from './hooks/useQueryParams';
|
export { default as useQueryParams } from './hooks/useQueryParams';
|
||||||
export { default as useRBAC } from './hooks/useRBAC';
|
export { default as useRBAC } from './hooks/useRBAC';
|
||||||
|
|||||||
@ -2,19 +2,19 @@ import React from 'react';
|
|||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { useIntl } from 'react-intl';
|
import { useIntl } from 'react-intl';
|
||||||
import { IconButton } from '@strapi/design-system';
|
import { IconButton } from '@strapi/design-system';
|
||||||
import { CopyToClipboard } from 'react-copy-to-clipboard';
|
import { useNotification, useClipboard } from '@strapi/helper-plugin';
|
||||||
import { useNotification } from '@strapi/helper-plugin';
|
|
||||||
import { Link as LinkIcon } from '@strapi/icons';
|
import { Link as LinkIcon } from '@strapi/icons';
|
||||||
import getTrad from '../../utils/getTrad';
|
import getTrad from '../../utils/getTrad';
|
||||||
|
|
||||||
export const CopyLinkButton = ({ url }) => {
|
export const CopyLinkButton = ({ url }) => {
|
||||||
const toggleNotification = useNotification();
|
const toggleNotification = useNotification();
|
||||||
const { formatMessage } = useIntl();
|
const { formatMessage } = useIntl();
|
||||||
|
const { copy } = useClipboard();
|
||||||
|
|
||||||
return (
|
const handleClick = async () => {
|
||||||
<CopyToClipboard
|
const didCopy = await copy(url);
|
||||||
text={url}
|
|
||||||
onCopy={() => {
|
if (didCopy) {
|
||||||
toggleNotification({
|
toggleNotification({
|
||||||
type: 'success',
|
type: 'success',
|
||||||
message: {
|
message: {
|
||||||
@ -22,17 +22,19 @@ export const CopyLinkButton = ({ url }) => {
|
|||||||
defaultMessage: 'Link copied into the clipboard',
|
defaultMessage: 'Link copied into the clipboard',
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}}
|
}
|
||||||
>
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
<IconButton
|
<IconButton
|
||||||
label={formatMessage({
|
label={formatMessage({
|
||||||
id: getTrad('control-card.copy-link'),
|
id: getTrad('control-card.copy-link'),
|
||||||
defaultMessage: 'Copy link',
|
defaultMessage: 'Copy link',
|
||||||
})}
|
})}
|
||||||
|
onClick={handleClick}
|
||||||
>
|
>
|
||||||
<LinkIcon />
|
<LinkIcon />
|
||||||
</IconButton>
|
</IconButton>
|
||||||
</CopyToClipboard>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -3,7 +3,7 @@ import { render, screen, fireEvent, waitFor } from '@testing-library/react';
|
|||||||
import { ThemeProvider, lightTheme } from '@strapi/design-system';
|
import { ThemeProvider, lightTheme } from '@strapi/design-system';
|
||||||
import { IntlProvider } from 'react-intl';
|
import { IntlProvider } from 'react-intl';
|
||||||
import { QueryClient, QueryClientProvider } from 'react-query';
|
import { QueryClient, QueryClientProvider } from 'react-query';
|
||||||
import { NotificationsProvider, TrackingProvider } from '@strapi/helper-plugin';
|
import { NotificationsProvider } from '@strapi/helper-plugin';
|
||||||
import { EditAssetDialog } from '../index';
|
import { EditAssetDialog } from '../index';
|
||||||
import en from '../../../translations/en.json';
|
import en from '../../../translations/en.json';
|
||||||
import { downloadFile } from '../../../utils/downloadFile';
|
import { downloadFile } from '../../../utils/downloadFile';
|
||||||
@ -93,7 +93,6 @@ const queryClient = new QueryClient({
|
|||||||
const renderCompo = (props = { canUpdate: true, canCopyLink: true, canDownload: true }) =>
|
const renderCompo = (props = { canUpdate: true, canCopyLink: true, canDownload: true }) =>
|
||||||
render(
|
render(
|
||||||
<QueryClientProvider client={queryClient}>
|
<QueryClientProvider client={queryClient}>
|
||||||
<TrackingProvider>
|
|
||||||
<ThemeProvider theme={lightTheme}>
|
<ThemeProvider theme={lightTheme}>
|
||||||
<IntlProvider locale="en" messages={messageForPlugin} defaultLocale="en">
|
<IntlProvider locale="en" messages={messageForPlugin} defaultLocale="en">
|
||||||
<NotificationsProvider>
|
<NotificationsProvider>
|
||||||
@ -101,7 +100,6 @@ const renderCompo = (props = { canUpdate: true, canCopyLink: true, canDownload:
|
|||||||
</NotificationsProvider>
|
</NotificationsProvider>
|
||||||
</IntlProvider>
|
</IntlProvider>
|
||||||
</ThemeProvider>
|
</ThemeProvider>
|
||||||
</TrackingProvider>
|
|
||||||
</QueryClientProvider>,
|
</QueryClientProvider>,
|
||||||
{ container: document.getElementById('app') }
|
{ container: document.getElementById('app') }
|
||||||
);
|
);
|
||||||
@ -183,12 +181,14 @@ describe('<EditAssetDialog />', () => {
|
|||||||
expect(screen.queryByLabelText('Delete')).not.toBeInTheDocument();
|
expect(screen.queryByLabelText('Delete')).not.toBeInTheDocument();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('copies the link and shows a notification when pressing "Copy link" and the user has permission to copy', () => {
|
it('copies the link and shows a notification when pressing "Copy link" and the user has permission to copy', async () => {
|
||||||
renderCompo({ canUpdate: false, canCopyLink: true, canDownload: false });
|
renderCompo({ canUpdate: false, canCopyLink: true, canDownload: false });
|
||||||
|
|
||||||
fireEvent.click(screen.getByLabelText('Copy link'));
|
fireEvent.click(screen.getByLabelText('Copy link'));
|
||||||
|
|
||||||
expect(screen.getByText('Link copied into the clipboard')).toBeInTheDocument();
|
await waitFor(() =>
|
||||||
|
expect(screen.getByText('Link copied into the clipboard')).toBeInTheDocument()
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('hides the copy link button when the user is not allowed to see it', () => {
|
it('hides the copy link button when the user is not allowed to see it', () => {
|
||||||
|
|||||||
@ -5,7 +5,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { render, screen, fireEvent } from '@testing-library/react';
|
import { render, screen, fireEvent, waitFor } from '@testing-library/react';
|
||||||
import { ThemeProvider, lightTheme } from '@strapi/design-system';
|
import { ThemeProvider, lightTheme } from '@strapi/design-system';
|
||||||
import { IntlProvider } from 'react-intl';
|
import { IntlProvider } from 'react-intl';
|
||||||
import { QueryClient, QueryClientProvider } from 'react-query';
|
import { QueryClient, QueryClientProvider } from 'react-query';
|
||||||
@ -145,12 +145,14 @@ describe('<EditAssetDialog />', () => {
|
|||||||
expect(screen.getByText('Are you sure you want to delete this?')).toBeVisible();
|
expect(screen.getByText('Are you sure you want to delete this?')).toBeVisible();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('copies the link and shows a notification when pressing "Copy link"', () => {
|
it('copies the link and shows a notification when pressing "Copy link"', async () => {
|
||||||
renderCompo();
|
renderCompo();
|
||||||
|
|
||||||
fireEvent.click(screen.getByLabelText('Copy link'));
|
fireEvent.click(screen.getByLabelText('Copy link'));
|
||||||
|
|
||||||
expect(screen.getByText('Link copied into the clipboard')).toBeInTheDocument();
|
await waitFor(() =>
|
||||||
|
expect(screen.getByText('Link copied into the clipboard')).toBeInTheDocument()
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('downloads the file when pressing "Download"', () => {
|
it('downloads the file when pressing "Download"', () => {
|
||||||
|
|||||||
@ -43,7 +43,6 @@
|
|||||||
"mime-types": "2.1.35",
|
"mime-types": "2.1.35",
|
||||||
"prop-types": "^15.7.2",
|
"prop-types": "^15.7.2",
|
||||||
"qs": "6.11.1",
|
"qs": "6.11.1",
|
||||||
"react-copy-to-clipboard": "^5.1.0",
|
|
||||||
"react-dnd": "15.1.2",
|
"react-dnd": "15.1.2",
|
||||||
"react-helmet": "^6.1.0",
|
"react-helmet": "^6.1.0",
|
||||||
"react-intl": "6.4.1",
|
"react-intl": "6.4.1",
|
||||||
|
|||||||
@ -43,7 +43,6 @@
|
|||||||
"lodash": "4.17.21",
|
"lodash": "4.17.21",
|
||||||
"path-to-regexp": "6.2.1",
|
"path-to-regexp": "6.2.1",
|
||||||
"pluralize": "8.0.0",
|
"pluralize": "8.0.0",
|
||||||
"react-copy-to-clipboard": "^5.1.0",
|
|
||||||
"react-helmet": "^6.1.0",
|
"react-helmet": "^6.1.0",
|
||||||
"react-intl": "6.4.1",
|
"react-intl": "6.4.1",
|
||||||
"react-query": "3.24.3",
|
"react-query": "3.24.3",
|
||||||
|
|||||||
31
yarn.lock
31
yarn.lock
@ -7825,7 +7825,6 @@ __metadata:
|
|||||||
prop-types: ^15.7.2
|
prop-types: ^15.7.2
|
||||||
qs: 6.11.1
|
qs: 6.11.1
|
||||||
react: ^17.0.2
|
react: ^17.0.2
|
||||||
react-copy-to-clipboard: ^5.1.0
|
|
||||||
react-dnd: 15.1.2
|
react-dnd: 15.1.2
|
||||||
react-dnd-html5-backend: 15.1.3
|
react-dnd-html5-backend: 15.1.3
|
||||||
react-dom: ^17.0.2
|
react-dom: ^17.0.2
|
||||||
@ -8187,7 +8186,6 @@ __metadata:
|
|||||||
path-to-regexp: 6.2.1
|
path-to-regexp: 6.2.1
|
||||||
pluralize: 8.0.0
|
pluralize: 8.0.0
|
||||||
react: ^17.0.2
|
react: ^17.0.2
|
||||||
react-copy-to-clipboard: ^5.1.0
|
|
||||||
react-dom: ^17.0.2
|
react-dom: ^17.0.2
|
||||||
react-helmet: ^6.1.0
|
react-helmet: ^6.1.0
|
||||||
react-intl: 6.4.1
|
react-intl: 6.4.1
|
||||||
@ -8354,7 +8352,6 @@ __metadata:
|
|||||||
prop-types: ^15.7.2
|
prop-types: ^15.7.2
|
||||||
qs: 6.11.1
|
qs: 6.11.1
|
||||||
react: ^17.0.2
|
react: ^17.0.2
|
||||||
react-copy-to-clipboard: ^5.1.0
|
|
||||||
react-dnd: 15.1.2
|
react-dnd: 15.1.2
|
||||||
react-dom: ^17.0.2
|
react-dom: ^17.0.2
|
||||||
react-helmet: ^6.1.0
|
react-helmet: ^6.1.0
|
||||||
@ -14412,15 +14409,6 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"copy-to-clipboard@npm:^3.3.1":
|
|
||||||
version: 3.3.1
|
|
||||||
resolution: "copy-to-clipboard@npm:3.3.1"
|
|
||||||
dependencies:
|
|
||||||
toggle-selection: ^1.0.6
|
|
||||||
checksum: 3c7b1c333dc6a4b2e9905f52e4df6bbd34ff9f9c97ecd3ca55378a6bc1c191bb12a3252e6289c7b436e9188cff0360d393c0161626851d2301607860bbbdcfd5
|
|
||||||
languageName: node
|
|
||||||
linkType: hard
|
|
||||||
|
|
||||||
"copy-to@npm:^2.0.1":
|
"copy-to@npm:^2.0.1":
|
||||||
version: 2.0.1
|
version: 2.0.1
|
||||||
resolution: "copy-to@npm:2.0.1"
|
resolution: "copy-to@npm:2.0.1"
|
||||||
@ -27844,18 +27832,6 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"react-copy-to-clipboard@npm:^5.1.0":
|
|
||||||
version: 5.1.0
|
|
||||||
resolution: "react-copy-to-clipboard@npm:5.1.0"
|
|
||||||
dependencies:
|
|
||||||
copy-to-clipboard: ^3.3.1
|
|
||||||
prop-types: ^15.8.1
|
|
||||||
peerDependencies:
|
|
||||||
react: ^15.3.0 || 16 || 17 || 18
|
|
||||||
checksum: f00a4551b9b63c944a041a6ab46af5ef20ba1106b3bc25173e7ef9bffbfba17a613368682ab8820cfe8d4b3acc5335cd9ce20229145bcc1e6aa8d1db04c512e5
|
|
||||||
languageName: node
|
|
||||||
linkType: hard
|
|
||||||
|
|
||||||
"react-dnd-html5-backend@npm:15.1.3":
|
"react-dnd-html5-backend@npm:15.1.3":
|
||||||
version: 15.1.3
|
version: 15.1.3
|
||||||
resolution: "react-dnd-html5-backend@npm:15.1.3"
|
resolution: "react-dnd-html5-backend@npm:15.1.3"
|
||||||
@ -31702,13 +31678,6 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"toggle-selection@npm:^1.0.6":
|
|
||||||
version: 1.0.6
|
|
||||||
resolution: "toggle-selection@npm:1.0.6"
|
|
||||||
checksum: a90dc80ed1e7b18db8f4e16e86a5574f87632dc729cfc07d9ea3ced50021ad42bb4e08f22c0913e0b98e3837b0b717e0a51613c65f30418e21eb99da6556a74c
|
|
||||||
languageName: node
|
|
||||||
linkType: hard
|
|
||||||
|
|
||||||
"toidentifier@npm:1.0.1":
|
"toidentifier@npm:1.0.1":
|
||||||
version: 1.0.1
|
version: 1.0.1
|
||||||
resolution: "toidentifier@npm:1.0.1"
|
resolution: "toidentifier@npm:1.0.1"
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user