mirror of
https://github.com/strapi/strapi.git
synced 2026-01-07 20:58:16 +00:00
Merge branch 'master' into fix/graphql-enum-not-generated
This commit is contained in:
commit
dd0c256cef
@ -1,8 +1,12 @@
|
||||
<p align="center">
|
||||
<a href="https://strapi.io">
|
||||
<a href="https://strapi.io/#gh-light-mode-only">
|
||||
<img src="https://strapi.io/assets/strapi-logo-dark.svg" width="318px" alt="Strapi logo" />
|
||||
</a>
|
||||
<a href="https://strapi.io/#gh-dark-mode-only">
|
||||
<img src="https://strapi.io/assets/strapi-logo-light.svg" width="318px" alt="Strapi logo" />
|
||||
</a>
|
||||
</p>
|
||||
|
||||
<h3 align="center">API creation made simple, secure and fast.</h3>
|
||||
<p align="center">The most advanced open-source headless CMS to build powerful APIs with no effort.</p>
|
||||
<p align="center"><a href="https://strapi.io/demo">Try live demo</a></p>
|
||||
|
||||
@ -33,7 +33,7 @@
|
||||
"uuid": "getstarted"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12.x.x <=16.x.x",
|
||||
"node": ">=12.22.0 <=16.x.x",
|
||||
"npm": ">=6.0.0"
|
||||
},
|
||||
"license": "SEE LICENSE IN LICENSE"
|
||||
|
||||
6
examples/kitchensink/config/admin.js
Normal file
6
examples/kitchensink/config/admin.js
Normal file
@ -0,0 +1,6 @@
|
||||
module.exports = ({ env }) => ({
|
||||
// autoOpen: false,
|
||||
auth: {
|
||||
secret: env('ADMIN_JWT_SECRET', 'example-token'),
|
||||
},
|
||||
});
|
||||
@ -28,7 +28,7 @@
|
||||
"uuid": "getstarted"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12.x.x <=16.x.x",
|
||||
"node": ">=12.22.0 <=16.x.x",
|
||||
"npm": ">=6.0.0"
|
||||
},
|
||||
"license": "SEE LICENSE IN LICENSE"
|
||||
|
||||
@ -57,4 +57,5 @@ module.exports = {
|
||||
},
|
||||
transformIgnorePatterns: ['node_modules/(?!(react-dnd|dnd-core|react-dnd-html5-backend)/)'],
|
||||
testMatch: ['/**/tests/**/?(*.)+(spec|test).[jt]s?(x)'],
|
||||
testURL: 'http://localhost:1337/admin',
|
||||
};
|
||||
|
||||
@ -121,7 +121,7 @@
|
||||
"yargs": "13.2.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12.x.x <=16.x.x",
|
||||
"node": ">=12.22.0 <=16.x.x",
|
||||
"npm": ">=6.0.0"
|
||||
}
|
||||
}
|
||||
|
||||
@ -43,7 +43,7 @@
|
||||
"inquirer": "8.2.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12.x.x <=16.x.x",
|
||||
"node": ">=12.22.0 <=16.x.x",
|
||||
"npm": ">=6.0.0"
|
||||
}
|
||||
}
|
||||
|
||||
@ -48,7 +48,7 @@
|
||||
"ora": "5.4.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12.x.x <=16.x.x",
|
||||
"node": ">=12.22.0 <=16.x.x",
|
||||
"npm": ">=6.0.0"
|
||||
}
|
||||
}
|
||||
|
||||
@ -149,7 +149,7 @@ const LeftMenu = ({ generalSectionLinks, pluginsSectionLinks }) => {
|
||||
>
|
||||
<FocusTrap onEscape={handleToggleUserLinks}>
|
||||
<Stack size={0}>
|
||||
<LinkUser onClick={handleToggleUserLinks} to="/me">
|
||||
<LinkUser tabIndex={0} onClick={handleToggleUserLinks} to="/me">
|
||||
<Typography>
|
||||
{formatMessage({
|
||||
id: 'app.components.LeftMenu.profile',
|
||||
@ -157,7 +157,7 @@ const LeftMenu = ({ generalSectionLinks, pluginsSectionLinks }) => {
|
||||
})}
|
||||
</Typography>
|
||||
</LinkUser>
|
||||
<LinkUser onClick={handleLogout} logout="logout" to="/auth/login">
|
||||
<LinkUser tabIndex={0} onClick={handleLogout} logout="logout" to="/auth/login">
|
||||
<Typography textColor="danger600">
|
||||
{formatMessage({
|
||||
id: 'app.components.LeftMenu.logout',
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
import PropTypes from 'prop-types';
|
||||
import { useIntl } from 'react-intl';
|
||||
import toString from 'lodash/toString';
|
||||
import { getNumberOfDecimals } from './utils/getNumberOfDecimals';
|
||||
|
||||
const CellValue = ({ type, value }) => {
|
||||
const { formatDate, formatTime, formatNumber } = useIntl();
|
||||
@ -27,8 +28,17 @@ const CellValue = ({ type, value }) => {
|
||||
});
|
||||
}
|
||||
|
||||
if (['float', 'integer', 'biginteger', 'decimal'].includes(type)) {
|
||||
formattedValue = formatNumber(value);
|
||||
if (['float', 'decimal'].includes(type)) {
|
||||
const numberOfDecimals = getNumberOfDecimals(value);
|
||||
|
||||
formattedValue = formatNumber(value, {
|
||||
minimumFractionDigits: numberOfDecimals,
|
||||
maximumFractionDigits: numberOfDecimals,
|
||||
});
|
||||
}
|
||||
|
||||
if (['integer', 'biginteger'].includes(type)) {
|
||||
formattedValue = formatNumber(value, { maximumFractionDigits: 0 });
|
||||
}
|
||||
|
||||
return toString(formattedValue);
|
||||
|
||||
@ -0,0 +1,63 @@
|
||||
import React from 'react';
|
||||
import { render } from '@testing-library/react';
|
||||
import { IntlProvider } from 'react-intl';
|
||||
|
||||
import CellValue from '../CellValue';
|
||||
|
||||
const CellValueWithProvider = (type, value) => {
|
||||
return (
|
||||
<IntlProvider messages={{}} defaultLocale="en" textComponent="span" locale="en">
|
||||
<CellValue type={type} value={value} />
|
||||
</IntlProvider>
|
||||
);
|
||||
};
|
||||
|
||||
describe('CellValue', () => {
|
||||
it('should return a number with 4 decimals', () => {
|
||||
const { getByText } = render(CellValueWithProvider('decimal', 3.1415));
|
||||
|
||||
expect(getByText('3.1415')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should return a number with 0 decimals', () => {
|
||||
const { getByText } = render(CellValueWithProvider('decimal', 3));
|
||||
|
||||
expect(getByText('3')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should return a number with 11 decimals', () => {
|
||||
const { getByText } = render(CellValueWithProvider('float', 3.14159265359));
|
||||
|
||||
expect(getByText('3.14159265359')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should return a number with 0 decimals', () => {
|
||||
const { getByText } = render(CellValueWithProvider('float', 3));
|
||||
|
||||
expect(getByText('3')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should return a number with 0 decimals', () => {
|
||||
const { getByText } = render(CellValueWithProvider('integer', 3));
|
||||
|
||||
expect(getByText('3')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should return a number with 0 decimals', () => {
|
||||
const { getByText } = render(CellValueWithProvider('integer', 314159265359));
|
||||
|
||||
expect(getByText('314,159,265,359')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should return a number with 0 decimals', () => {
|
||||
const { getByText } = render(CellValueWithProvider('biginteger', 3));
|
||||
|
||||
expect(getByText('3')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should return a number with 0 decimals', () => {
|
||||
const { getByText } = render(CellValueWithProvider('biginteger', 314159265359));
|
||||
|
||||
expect(getByText('314,159,265,359')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
@ -0,0 +1,8 @@
|
||||
export const getNumberOfDecimals = value => {
|
||||
if (value % 1 !== 0) {
|
||||
// value has decimals
|
||||
return value.toString().split('.')[1].length;
|
||||
}
|
||||
|
||||
return 0;
|
||||
};
|
||||
@ -0,0 +1,18 @@
|
||||
import { getNumberOfDecimals } from '../getNumberOfDecimals';
|
||||
|
||||
describe('getNumberOfDecimals', () => {
|
||||
it('should be 4 decimals', () => {
|
||||
const numberOfDecimals = getNumberOfDecimals(3.1415);
|
||||
expect(numberOfDecimals).toEqual(4);
|
||||
});
|
||||
|
||||
it('should be 1 decimals', () => {
|
||||
const numberOfDecimals = getNumberOfDecimals(3.1);
|
||||
expect(numberOfDecimals).toEqual(1);
|
||||
});
|
||||
|
||||
it('should be 0 decimals', () => {
|
||||
const numberOfDecimals = getNumberOfDecimals(3);
|
||||
expect(numberOfDecimals).toEqual(0);
|
||||
});
|
||||
});
|
||||
@ -32,6 +32,7 @@ const InputUID = ({
|
||||
onChange,
|
||||
value,
|
||||
placeholder,
|
||||
required,
|
||||
}) => {
|
||||
const { modifiedData, initialData, layout } = useCMEditViewDataManager();
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
@ -244,6 +245,7 @@ const InputUID = ({
|
||||
onChange={handleChange}
|
||||
placeholder={formattedPlaceholder}
|
||||
value={value || ''}
|
||||
required={required}
|
||||
/>
|
||||
);
|
||||
};
|
||||
@ -275,6 +277,7 @@ InputUID.propTypes = {
|
||||
defaultMessage: PropTypes.string.isRequired,
|
||||
values: PropTypes.object,
|
||||
}),
|
||||
required: PropTypes.bool,
|
||||
};
|
||||
|
||||
InputUID.defaultProps = {
|
||||
@ -284,6 +287,7 @@ InputUID.defaultProps = {
|
||||
labelAction: undefined,
|
||||
placeholder: undefined,
|
||||
value: '',
|
||||
required: false,
|
||||
};
|
||||
|
||||
export default InputUID;
|
||||
|
||||
@ -47,6 +47,10 @@ const Wrapper = styled.div`
|
||||
font-weight: 800;
|
||||
}
|
||||
|
||||
em {
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
blockquote {
|
||||
margin-top: 41px;
|
||||
margin-bottom: 34px;
|
||||
|
||||
@ -20,10 +20,6 @@ export const CustomIconButton = styled(IconButton)`
|
||||
}
|
||||
`;
|
||||
|
||||
export const DragHandleWrapper = styled(CustomIconButton)`
|
||||
cursor: move;
|
||||
`;
|
||||
|
||||
export const CustomIconButtonSibling = styled(IconButton)`
|
||||
background-color: transparent;
|
||||
|
||||
|
||||
@ -3,12 +3,14 @@ import React, { memo, useEffect, useRef, useState } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { useDrag, useDrop } from 'react-dnd';
|
||||
import { getEmptyImage } from 'react-dnd-html5-backend';
|
||||
import styled from 'styled-components';
|
||||
import { useIntl } from 'react-intl';
|
||||
import toString from 'lodash/toString';
|
||||
import { Accordion, AccordionToggle, AccordionContent } from '@strapi/design-system/Accordion';
|
||||
import { Grid, GridItem } from '@strapi/design-system/Grid';
|
||||
import { Stack } from '@strapi/design-system/Stack';
|
||||
import { Box } from '@strapi/design-system/Box';
|
||||
import { Tooltip } from '@strapi/design-system/Tooltip';
|
||||
import Trash from '@strapi/icons/Trash';
|
||||
import Drag from '@strapi/icons/Drag';
|
||||
import ItemTypes from '../../../utils/ItemTypes';
|
||||
@ -17,9 +19,23 @@ import Inputs from '../../Inputs';
|
||||
import FieldComponent from '../../FieldComponent';
|
||||
import Preview from './Preview';
|
||||
import DraggingSibling from './DraggingSibling';
|
||||
import { CustomIconButton, DragHandleWrapper } from './IconButtonCustoms';
|
||||
import { CustomIconButton } from './IconButtonCustoms';
|
||||
import { connect, select } from './utils';
|
||||
|
||||
const DragButton = styled.span`
|
||||
display: flex;
|
||||
align-items: center;
|
||||
height: ${({ theme }) => theme.spaces[7]};
|
||||
|
||||
padding: 0 ${({ theme }) => theme.spaces[3]};
|
||||
cursor: all-scroll;
|
||||
|
||||
svg {
|
||||
width: ${12 / 16}rem;
|
||||
height: ${12 / 16}rem;
|
||||
}
|
||||
`;
|
||||
|
||||
/* eslint-disable react/no-array-index-key */
|
||||
|
||||
// Issues:
|
||||
@ -40,7 +56,7 @@ const DraggedItem = ({
|
||||
// Retrieved from the select function
|
||||
moveComponentField,
|
||||
removeRepeatableField,
|
||||
setIsDraggingSiblig,
|
||||
setIsDraggingSibling,
|
||||
triggerFormValidation,
|
||||
// checkFormErrors,
|
||||
displayedValue,
|
||||
@ -125,7 +141,7 @@ const DraggedItem = ({
|
||||
end: () => {
|
||||
// Update the errors
|
||||
triggerFormValidation();
|
||||
setIsDraggingSiblig(false);
|
||||
setIsDraggingSibling(false);
|
||||
},
|
||||
collect: monitor => ({
|
||||
isDragging: monitor.isDragging(),
|
||||
@ -138,9 +154,9 @@ const DraggedItem = ({
|
||||
|
||||
useEffect(() => {
|
||||
if (isDragging) {
|
||||
setIsDraggingSiblig(true);
|
||||
setIsDraggingSibling(true);
|
||||
}
|
||||
}, [isDragging, setIsDraggingSiblig]);
|
||||
}, [isDragging, setIsDraggingSibling]);
|
||||
|
||||
// Effect in order to force a rerender after reordering the components
|
||||
// Since we are removing the Accordion when doing the DnD we are losing the dragRef, therefore the replaced element cannot be dragged
|
||||
@ -195,16 +211,22 @@ const DraggedItem = ({
|
||||
})}
|
||||
icon={<Trash />}
|
||||
/>
|
||||
<DragHandleWrapper
|
||||
expanded={isOpen}
|
||||
icon={<Drag />}
|
||||
label={formatMessage({
|
||||
{/* react-dnd is broken in firefox with our IconButton, maybe a ref issue */}
|
||||
<Tooltip
|
||||
description={formatMessage({
|
||||
id: getTrad('components.DragHandle-label'),
|
||||
defaultMessage: 'Drag',
|
||||
})}
|
||||
noBorder
|
||||
ref={refs.dragRef}
|
||||
/>
|
||||
>
|
||||
<DragButton
|
||||
role="button"
|
||||
tabIndex={-1}
|
||||
ref={refs.dragRef}
|
||||
onClick={e => e.stopPropagation()}
|
||||
>
|
||||
<Drag />
|
||||
</DragButton>
|
||||
</Tooltip>
|
||||
</Stack>
|
||||
)
|
||||
}
|
||||
@ -268,7 +290,7 @@ const DraggedItem = ({
|
||||
DraggedItem.defaultProps = {
|
||||
isDraggingSibling: false,
|
||||
isOpen: false,
|
||||
setIsDraggingSiblig: () => {},
|
||||
setIsDraggingSibling: () => {},
|
||||
toggleCollapses: () => {},
|
||||
};
|
||||
|
||||
@ -284,7 +306,7 @@ DraggedItem.propTypes = {
|
||||
toggleCollapses: PropTypes.func,
|
||||
moveComponentField: PropTypes.func.isRequired,
|
||||
removeRepeatableField: PropTypes.func.isRequired,
|
||||
setIsDraggingSiblig: PropTypes.func,
|
||||
setIsDraggingSibling: PropTypes.func,
|
||||
triggerFormValidation: PropTypes.func.isRequired,
|
||||
// checkFormErrors: PropTypes.func.isRequired,
|
||||
displayedValue: PropTypes.string.isRequired,
|
||||
|
||||
@ -47,7 +47,7 @@ const RepeatableComponent = ({
|
||||
const toggleNotification = useNotification();
|
||||
const { formatMessage } = useIntl();
|
||||
const [collapseToOpen, setCollapseToOpen] = useState('');
|
||||
const [isDraggingSibling, setIsDraggingSiblig] = useState(false);
|
||||
const [isDraggingSibling, setIsDraggingSibling] = useState(false);
|
||||
const [, drop] = useDrop({ accept: ItemTypes.COMPONENT });
|
||||
const { getComponentLayout } = useContentTypeLayout();
|
||||
const componentLayoutData = useMemo(() => getComponentLayout(componentUid), [
|
||||
@ -173,7 +173,7 @@ const RepeatableComponent = ({
|
||||
}}
|
||||
parentName={name}
|
||||
schema={componentLayoutData}
|
||||
setIsDraggingSiblig={setIsDraggingSiblig}
|
||||
setIsDraggingSibling={setIsDraggingSibling}
|
||||
toggleCollapses={toggleCollapses}
|
||||
/>
|
||||
);
|
||||
|
||||
@ -43,14 +43,20 @@ const App = () => {
|
||||
);
|
||||
}
|
||||
|
||||
// Array of models that are displayed in the content manager
|
||||
const supportedModelsToDisplay = models.filter(({ isDisplayed }) => isDisplayed);
|
||||
|
||||
// Redirect the user to the 403 page
|
||||
// FIXME when changing the routing
|
||||
if (authorisedModels.length === 0 && models.length > 0 && pathname !== '/content-manager/403') {
|
||||
if (
|
||||
authorisedModels.length === 0 &&
|
||||
supportedModelsToDisplay.length > 0 &&
|
||||
pathname !== '/content-manager/403'
|
||||
) {
|
||||
return <Redirect to="/content-manager/403" />;
|
||||
}
|
||||
|
||||
// Redirect the user to the create content type page
|
||||
if (models.length === 0 && pathname !== '/content-manager/no-content-types') {
|
||||
if (supportedModelsToDisplay.length === 0 && pathname !== '/content-manager/no-content-types') {
|
||||
return <Redirect to="/content-manager/no-content-types" />;
|
||||
}
|
||||
|
||||
|
||||
@ -72,9 +72,15 @@ describe('Content manager | App | main', () => {
|
||||
{
|
||||
kind: 'collectionType',
|
||||
uid: 'category',
|
||||
isDisplayed: true,
|
||||
info: { label: 'Categories', name: 'category' },
|
||||
},
|
||||
{ kind: 'singleType', uid: 'homepage', info: { label: 'Home page', name: 'homepage' } },
|
||||
{
|
||||
kind: 'singleType',
|
||||
isDisplayed: true,
|
||||
uid: 'homepage',
|
||||
info: { label: 'Home page', name: 'homepage' },
|
||||
},
|
||||
],
|
||||
components: [],
|
||||
status: 'resolved',
|
||||
@ -751,8 +757,14 @@ describe('Content manager | App | main', () => {
|
||||
kind: 'collectionType',
|
||||
uid: 'category',
|
||||
info: { label: 'Categories', name: 'category' },
|
||||
isDisplayed: true,
|
||||
},
|
||||
{
|
||||
kind: 'singleType',
|
||||
isDisplayed: true,
|
||||
uid: 'homepage',
|
||||
info: { label: 'Home page', name: 'homepage' },
|
||||
},
|
||||
{ kind: 'singleType', uid: 'homepage', info: { label: 'Home page', name: 'homepage' } },
|
||||
],
|
||||
components: [],
|
||||
status: 'resolved',
|
||||
@ -789,6 +801,7 @@ describe('Content manager | App | main', () => {
|
||||
{
|
||||
kind: 'collectionType',
|
||||
uid: 'category',
|
||||
isDisplayed: true,
|
||||
info: { label: 'Categories', name: 'category' },
|
||||
},
|
||||
{ kind: 'singleType', uid: 'homepage', info: { label: 'Home page', name: 'homepage' } },
|
||||
@ -828,7 +841,14 @@ describe('Content manager | App | main', () => {
|
||||
const contentManagerState = {
|
||||
collectionTypeLinks: [],
|
||||
singleTypeLinks: [],
|
||||
models: [],
|
||||
models: [
|
||||
{
|
||||
kind: 'collectionType',
|
||||
uid: 'category',
|
||||
info: { label: 'Categories', name: 'category' },
|
||||
isDisplayed: false,
|
||||
},
|
||||
],
|
||||
components: [],
|
||||
status: 'resolved',
|
||||
};
|
||||
|
||||
@ -341,7 +341,7 @@ const DisplayedFieldButton = ({
|
||||
isHidden={isHidden}
|
||||
>
|
||||
<DragButton
|
||||
as="button"
|
||||
as="span"
|
||||
type="button"
|
||||
ref={refs.dragRef}
|
||||
onClick={e => e.stopPropagation()}
|
||||
|
||||
@ -24,7 +24,7 @@ const FieldButtonContent = ({ attribute, onEditField, onDeleteField, children })
|
||||
|
||||
return (
|
||||
<Box overflow="hidden" width="100%">
|
||||
<Flex paddingLeft={3} alignItems="baseline" justifyContent="space-between">
|
||||
<Flex paddingLeft={3} alignItems="center" justifyContent="space-between">
|
||||
<Typography fontWeight="semiBold" textColor="neutral800" ellipsis>
|
||||
{children}
|
||||
</Typography>
|
||||
|
||||
@ -91,7 +91,7 @@ const RelationalFieldButton = ({
|
||||
isDragging={isDragging}
|
||||
>
|
||||
<DragButton
|
||||
as="button"
|
||||
as="span"
|
||||
type="button"
|
||||
ref={dragButtonRef}
|
||||
onClick={e => e.stopPropagation()}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@ -195,6 +195,7 @@ const DraggableCard = ({
|
||||
>
|
||||
<Stack horizontal size={3}>
|
||||
<DragButton
|
||||
as='span'
|
||||
aria-label={formatMessage(
|
||||
{
|
||||
id: getTrad('components.DraggableCard.move.field'),
|
||||
|
||||
@ -1530,7 +1530,7 @@ exports[`ADMIN | CM | LV | Configure the view renders and matches the snapshot 1
|
||||
<div
|
||||
class="c68 c69"
|
||||
>
|
||||
<button
|
||||
<span
|
||||
aria-label="Move id"
|
||||
class="c74 c75 c76"
|
||||
draggable="true"
|
||||
@ -1572,7 +1572,7 @@ exports[`ADMIN | CM | LV | Configure the view renders and matches the snapshot 1
|
||||
fill="#212134"
|
||||
/>
|
||||
</svg>
|
||||
</button>
|
||||
</span>
|
||||
<span
|
||||
class="c24 c77"
|
||||
>
|
||||
@ -1633,7 +1633,7 @@ exports[`ADMIN | CM | LV | Configure the view renders and matches the snapshot 1
|
||||
<div
|
||||
class="c68 c69"
|
||||
>
|
||||
<button
|
||||
<span
|
||||
aria-label="Move address"
|
||||
class="c74 c75 c76"
|
||||
draggable="true"
|
||||
@ -1675,7 +1675,7 @@ exports[`ADMIN | CM | LV | Configure the view renders and matches the snapshot 1
|
||||
fill="#212134"
|
||||
/>
|
||||
</svg>
|
||||
</button>
|
||||
</span>
|
||||
<span
|
||||
class="c24 c77"
|
||||
>
|
||||
@ -3417,7 +3417,7 @@ exports[`ADMIN | CM | LV | Configure the view should add field 1`] = `
|
||||
<div
|
||||
class="c68 c69"
|
||||
>
|
||||
<button
|
||||
<span
|
||||
aria-label="Move cover"
|
||||
class="c74 c75 c76"
|
||||
draggable="true"
|
||||
@ -3459,7 +3459,7 @@ exports[`ADMIN | CM | LV | Configure the view should add field 1`] = `
|
||||
fill="#212134"
|
||||
/>
|
||||
</svg>
|
||||
</button>
|
||||
</span>
|
||||
<span
|
||||
class="c24 c77"
|
||||
>
|
||||
@ -3520,7 +3520,7 @@ exports[`ADMIN | CM | LV | Configure the view should add field 1`] = `
|
||||
<div
|
||||
class="c68 c69"
|
||||
>
|
||||
<button
|
||||
<span
|
||||
aria-label="Move id"
|
||||
class="c74 c75 c76"
|
||||
draggable="true"
|
||||
@ -3562,7 +3562,7 @@ exports[`ADMIN | CM | LV | Configure the view should add field 1`] = `
|
||||
fill="#212134"
|
||||
/>
|
||||
</svg>
|
||||
</button>
|
||||
</span>
|
||||
<span
|
||||
class="c24 c77"
|
||||
>
|
||||
@ -3623,7 +3623,7 @@ exports[`ADMIN | CM | LV | Configure the view should add field 1`] = `
|
||||
<div
|
||||
class="c68 c69"
|
||||
>
|
||||
<button
|
||||
<span
|
||||
aria-label="Move address"
|
||||
class="c74 c75 c76"
|
||||
draggable="true"
|
||||
@ -3665,7 +3665,7 @@ exports[`ADMIN | CM | LV | Configure the view should add field 1`] = `
|
||||
fill="#212134"
|
||||
/>
|
||||
</svg>
|
||||
</button>
|
||||
</span>
|
||||
<span
|
||||
class="c24 c77"
|
||||
>
|
||||
|
||||
@ -178,7 +178,10 @@ const Register = ({ fieldsToDisable, noSignin, onSubmit, schema }) => {
|
||||
: undefined
|
||||
}
|
||||
required
|
||||
label={formatMessage({ id: 'Auth.form.email.label', defaultMessage: 'Email' })}
|
||||
label={formatMessage({
|
||||
id: 'Auth.form.email.label',
|
||||
defaultMessage: 'Email',
|
||||
})}
|
||||
type="email"
|
||||
/>
|
||||
<PasswordInput
|
||||
|
||||
@ -94,6 +94,8 @@ const forms = {
|
||||
email: yup
|
||||
.string()
|
||||
.email(translatedErrors.email)
|
||||
.strict()
|
||||
.lowercase(translatedErrors.lowercase)
|
||||
.required(translatedErrors.required),
|
||||
confirmPassword: yup
|
||||
.string()
|
||||
|
||||
@ -211,6 +211,7 @@ const ProfilePage = () => {
|
||||
value={values.firstname || ''}
|
||||
type="text"
|
||||
name="firstname"
|
||||
required
|
||||
/>
|
||||
</GridItem>
|
||||
<GridItem s={12} col={6}>
|
||||
@ -234,6 +235,7 @@ const ProfilePage = () => {
|
||||
value={values.email || ''}
|
||||
type="email"
|
||||
name="email"
|
||||
required
|
||||
/>
|
||||
</GridItem>
|
||||
<GridItem s={12} col={6}>
|
||||
|
||||
@ -213,7 +213,7 @@ describe('ADMIN | Pages | Profile page', () => {
|
||||
background: #4945ff;
|
||||
}
|
||||
|
||||
.c30 {
|
||||
.c32 {
|
||||
border: none;
|
||||
background: transparent;
|
||||
font-size: 1.6rem;
|
||||
@ -229,7 +229,7 @@ describe('ADMIN | Pages | Profile page', () => {
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.c38 {
|
||||
.c40 {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
right: 0;
|
||||
@ -240,22 +240,22 @@ describe('ADMIN | Pages | Profile page', () => {
|
||||
border: none;
|
||||
}
|
||||
|
||||
.c38:focus {
|
||||
.c40:focus {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.c38[aria-disabled='true'] {
|
||||
.c40[aria-disabled='true'] {
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.c35 {
|
||||
.c37 {
|
||||
font-weight: 600;
|
||||
color: #32324d;
|
||||
font-size: 0.75rem;
|
||||
line-height: 1.33;
|
||||
}
|
||||
|
||||
.c42 {
|
||||
.c44 {
|
||||
color: #32324d;
|
||||
display: block;
|
||||
white-space: nowrap;
|
||||
@ -265,22 +265,22 @@ describe('ADMIN | Pages | Profile page', () => {
|
||||
line-height: 1.43;
|
||||
}
|
||||
|
||||
.c46 {
|
||||
.c48 {
|
||||
color: #666687;
|
||||
font-size: 0.75rem;
|
||||
line-height: 1.33;
|
||||
}
|
||||
|
||||
.c41 {
|
||||
.c43 {
|
||||
padding-right: 16px;
|
||||
padding-left: 16px;
|
||||
}
|
||||
|
||||
.c44 {
|
||||
.c46 {
|
||||
padding-left: 12px;
|
||||
}
|
||||
|
||||
.c36 {
|
||||
.c38 {
|
||||
display: -webkit-box;
|
||||
display: -webkit-flex;
|
||||
display: -ms-flexbox;
|
||||
@ -294,7 +294,7 @@ describe('ADMIN | Pages | Profile page', () => {
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.c39 {
|
||||
.c41 {
|
||||
display: -webkit-box;
|
||||
display: -webkit-flex;
|
||||
display: -ms-flexbox;
|
||||
@ -312,7 +312,7 @@ describe('ADMIN | Pages | Profile page', () => {
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.c34 {
|
||||
.c36 {
|
||||
display: -webkit-box;
|
||||
display: -webkit-flex;
|
||||
display: -ms-flexbox;
|
||||
@ -322,16 +322,16 @@ describe('ADMIN | Pages | Profile page', () => {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.c34 > * {
|
||||
.c36 > * {
|
||||
margin-top: 0;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.c34 > * + * {
|
||||
.c36 > * + * {
|
||||
margin-top: 4px;
|
||||
}
|
||||
|
||||
.c37 {
|
||||
.c39 {
|
||||
position: relative;
|
||||
border: 1px solid #dcdce4;
|
||||
padding-right: 12px;
|
||||
@ -347,28 +347,28 @@ describe('ADMIN | Pages | Profile page', () => {
|
||||
transition-duration: 0.2s;
|
||||
}
|
||||
|
||||
.c37:focus-within {
|
||||
.c39:focus-within {
|
||||
border: 1px solid #4945ff;
|
||||
box-shadow: #4945ff 0px 0px 0px 2px;
|
||||
}
|
||||
|
||||
.c43 {
|
||||
.c45 {
|
||||
background: transparent;
|
||||
border: none;
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.c43 svg {
|
||||
.c45 svg {
|
||||
height: 0.6875rem;
|
||||
width: 0.6875rem;
|
||||
}
|
||||
|
||||
.c43 svg path {
|
||||
.c45 svg path {
|
||||
fill: #666687;
|
||||
}
|
||||
|
||||
.c45 {
|
||||
.c47 {
|
||||
display: -webkit-box;
|
||||
display: -webkit-flex;
|
||||
display: -ms-flexbox;
|
||||
@ -377,11 +377,11 @@ describe('ADMIN | Pages | Profile page', () => {
|
||||
border: none;
|
||||
}
|
||||
|
||||
.c45 svg {
|
||||
.c47 svg {
|
||||
width: 0.375rem;
|
||||
}
|
||||
|
||||
.c40 {
|
||||
.c42 {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
@ -423,7 +423,7 @@ describe('ADMIN | Pages | Profile page', () => {
|
||||
margin-top: 16px;
|
||||
}
|
||||
|
||||
.c32 {
|
||||
.c34 {
|
||||
display: -webkit-box;
|
||||
display: -webkit-flex;
|
||||
display: -ms-flexbox;
|
||||
@ -433,12 +433,12 @@ describe('ADMIN | Pages | Profile page', () => {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.c32 > * {
|
||||
.c34 > * {
|
||||
margin-top: 0;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.c32 > * + * {
|
||||
.c34 > * + * {
|
||||
margin-top: 4px;
|
||||
}
|
||||
|
||||
@ -449,7 +449,17 @@ describe('ADMIN | Pages | Profile page', () => {
|
||||
line-height: 1.33;
|
||||
}
|
||||
|
||||
.c29 {
|
||||
.c24 {
|
||||
color: #d02b20;
|
||||
font-size: 0.875rem;
|
||||
line-height: 1.43;
|
||||
}
|
||||
|
||||
.c25 {
|
||||
line-height: 0;
|
||||
}
|
||||
|
||||
.c31 {
|
||||
padding-right: 12px;
|
||||
padding-left: 8px;
|
||||
}
|
||||
@ -468,7 +478,7 @@ describe('ADMIN | Pages | Profile page', () => {
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.c24 {
|
||||
.c26 {
|
||||
display: -webkit-box;
|
||||
display: -webkit-flex;
|
||||
display: -ms-flexbox;
|
||||
@ -486,7 +496,7 @@ describe('ADMIN | Pages | Profile page', () => {
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.c26 {
|
||||
.c28 {
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
padding-left: 16px;
|
||||
@ -498,37 +508,37 @@ describe('ADMIN | Pages | Profile page', () => {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.c26::-webkit-input-placeholder {
|
||||
.c28::-webkit-input-placeholder {
|
||||
color: #8e8ea9;
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.c26::-moz-placeholder {
|
||||
.c28::-moz-placeholder {
|
||||
color: #8e8ea9;
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.c26:-ms-input-placeholder {
|
||||
.c28:-ms-input-placeholder {
|
||||
color: #8e8ea9;
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.c26::placeholder {
|
||||
.c28::placeholder {
|
||||
color: #8e8ea9;
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.c26[aria-disabled='true'] {
|
||||
.c28[aria-disabled='true'] {
|
||||
background: inherit;
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
.c26:focus {
|
||||
.c28:focus {
|
||||
outline: none;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
.c27 {
|
||||
.c29 {
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
padding-left: 16px;
|
||||
@ -540,37 +550,37 @@ describe('ADMIN | Pages | Profile page', () => {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.c27::-webkit-input-placeholder {
|
||||
.c29::-webkit-input-placeholder {
|
||||
color: #8e8ea9;
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.c27::-moz-placeholder {
|
||||
.c29::-moz-placeholder {
|
||||
color: #8e8ea9;
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.c27:-ms-input-placeholder {
|
||||
.c29:-ms-input-placeholder {
|
||||
color: #8e8ea9;
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.c27::placeholder {
|
||||
.c29::placeholder {
|
||||
color: #8e8ea9;
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.c27[aria-disabled='true'] {
|
||||
.c29[aria-disabled='true'] {
|
||||
background: inherit;
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
.c27:focus {
|
||||
.c29:focus {
|
||||
outline: none;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
.c25 {
|
||||
.c27 {
|
||||
border: 1px solid #dcdce4;
|
||||
border-radius: 4px;
|
||||
background: #ffffff;
|
||||
@ -583,7 +593,7 @@ describe('ADMIN | Pages | Profile page', () => {
|
||||
transition-duration: 0.2s;
|
||||
}
|
||||
|
||||
.c25:focus-within {
|
||||
.c27:focus-within {
|
||||
border: 1px solid #4945ff;
|
||||
box-shadow: #4945ff 0px 0px 0px 2px;
|
||||
}
|
||||
@ -614,7 +624,7 @@ describe('ADMIN | Pages | Profile page', () => {
|
||||
line-height: 1.25;
|
||||
}
|
||||
|
||||
.c33 {
|
||||
.c35 {
|
||||
color: #32324d;
|
||||
font-size: 0.875rem;
|
||||
line-height: 1.43;
|
||||
@ -692,16 +702,16 @@ describe('ADMIN | Pages | Profile page', () => {
|
||||
grid-column: span 6;
|
||||
}
|
||||
|
||||
.c28::-ms-reveal {
|
||||
.c30::-ms-reveal {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.c31 svg {
|
||||
.c33 svg {
|
||||
height: 1rem;
|
||||
width: 1rem;
|
||||
}
|
||||
|
||||
.c31 svg path {
|
||||
.c33 svg path {
|
||||
fill: #666687;
|
||||
}
|
||||
|
||||
@ -821,17 +831,23 @@ describe('ADMIN | Pages | Profile page', () => {
|
||||
<label
|
||||
class="c23"
|
||||
for="firstname"
|
||||
required=""
|
||||
>
|
||||
First name
|
||||
<span
|
||||
class="c24 c25"
|
||||
>
|
||||
*
|
||||
</span>
|
||||
</label>
|
||||
</div>
|
||||
<div
|
||||
class="c24 c25"
|
||||
class="c26 c27"
|
||||
>
|
||||
<input
|
||||
aria-disabled="false"
|
||||
aria-invalid="false"
|
||||
class="c26"
|
||||
class="c28"
|
||||
id="firstname"
|
||||
name="firstname"
|
||||
placeholder=""
|
||||
@ -866,12 +882,12 @@ describe('ADMIN | Pages | Profile page', () => {
|
||||
</label>
|
||||
</div>
|
||||
<div
|
||||
class="c24 c25"
|
||||
class="c26 c27"
|
||||
>
|
||||
<input
|
||||
aria-disabled="false"
|
||||
aria-invalid="false"
|
||||
class="c26"
|
||||
class="c28"
|
||||
id="lastname"
|
||||
name="lastname"
|
||||
placeholder=""
|
||||
@ -901,17 +917,23 @@ describe('ADMIN | Pages | Profile page', () => {
|
||||
<label
|
||||
class="c23"
|
||||
for="email"
|
||||
required=""
|
||||
>
|
||||
Email
|
||||
<span
|
||||
class="c24 c25"
|
||||
>
|
||||
*
|
||||
</span>
|
||||
</label>
|
||||
</div>
|
||||
<div
|
||||
class="c24 c25"
|
||||
class="c26 c27"
|
||||
>
|
||||
<input
|
||||
aria-disabled="false"
|
||||
aria-invalid="false"
|
||||
class="c26"
|
||||
class="c28"
|
||||
id="email"
|
||||
name="email"
|
||||
placeholder=""
|
||||
@ -946,12 +968,12 @@ describe('ADMIN | Pages | Profile page', () => {
|
||||
</label>
|
||||
</div>
|
||||
<div
|
||||
class="c24 c25"
|
||||
class="c26 c27"
|
||||
>
|
||||
<input
|
||||
aria-disabled="false"
|
||||
aria-invalid="false"
|
||||
class="c26"
|
||||
class="c28"
|
||||
id="username"
|
||||
name="username"
|
||||
placeholder=""
|
||||
@ -1003,23 +1025,23 @@ describe('ADMIN | Pages | Profile page', () => {
|
||||
</label>
|
||||
</div>
|
||||
<div
|
||||
class="c24 c25"
|
||||
class="c26 c27"
|
||||
>
|
||||
<input
|
||||
aria-disabled="false"
|
||||
aria-invalid="false"
|
||||
class="c27 c28"
|
||||
class="c29 c30"
|
||||
id="textinput-1"
|
||||
name="currentPassword"
|
||||
type="password"
|
||||
value=""
|
||||
/>
|
||||
<div
|
||||
class="c29"
|
||||
class="c31"
|
||||
>
|
||||
<button
|
||||
aria-label="Hide password"
|
||||
class="c30 c31"
|
||||
class="c32 c33"
|
||||
type="button"
|
||||
>
|
||||
<svg
|
||||
@ -1068,23 +1090,23 @@ describe('ADMIN | Pages | Profile page', () => {
|
||||
</label>
|
||||
</div>
|
||||
<div
|
||||
class="c24 c25"
|
||||
class="c26 c27"
|
||||
>
|
||||
<input
|
||||
aria-disabled="false"
|
||||
aria-invalid="false"
|
||||
class="c27 c28"
|
||||
class="c29 c30"
|
||||
id="textinput-2"
|
||||
name="password"
|
||||
type="password"
|
||||
value=""
|
||||
/>
|
||||
<div
|
||||
class="c29"
|
||||
class="c31"
|
||||
>
|
||||
<button
|
||||
aria-label="Hide password"
|
||||
class="c30 c31"
|
||||
class="c32 c33"
|
||||
type="button"
|
||||
>
|
||||
<svg
|
||||
@ -1129,23 +1151,23 @@ describe('ADMIN | Pages | Profile page', () => {
|
||||
</label>
|
||||
</div>
|
||||
<div
|
||||
class="c24 c25"
|
||||
class="c26 c27"
|
||||
>
|
||||
<input
|
||||
aria-disabled="false"
|
||||
aria-invalid="false"
|
||||
class="c27 c28"
|
||||
class="c29 c30"
|
||||
id="textinput-3"
|
||||
name="confirmPassword"
|
||||
type="password"
|
||||
value=""
|
||||
/>
|
||||
<div
|
||||
class="c29"
|
||||
class="c31"
|
||||
>
|
||||
<button
|
||||
aria-label="Hide password"
|
||||
class="c30 c31"
|
||||
class="c32 c33"
|
||||
type="button"
|
||||
>
|
||||
<svg
|
||||
@ -1178,7 +1200,7 @@ describe('ADMIN | Pages | Profile page', () => {
|
||||
class="c17"
|
||||
>
|
||||
<div
|
||||
class="c32"
|
||||
class="c34"
|
||||
>
|
||||
<h2
|
||||
class="c18"
|
||||
@ -1186,7 +1208,7 @@ describe('ADMIN | Pages | Profile page', () => {
|
||||
Experience
|
||||
</h2>
|
||||
<span
|
||||
class="c33"
|
||||
class="c35"
|
||||
>
|
||||
Selection will change the interface language only for you. Please refer to this
|
||||
<a
|
||||
@ -1210,17 +1232,17 @@ describe('ADMIN | Pages | Profile page', () => {
|
||||
>
|
||||
<div>
|
||||
<div
|
||||
class="c34"
|
||||
class="c36"
|
||||
>
|
||||
<span
|
||||
class="c35"
|
||||
class="c37"
|
||||
for="select-1"
|
||||
id="select-1-label"
|
||||
>
|
||||
Interface language
|
||||
</span>
|
||||
<div
|
||||
class="c36 c37"
|
||||
class="c38 c39"
|
||||
>
|
||||
<button
|
||||
aria-describedby="select-1-hint"
|
||||
@ -1228,21 +1250,21 @@ describe('ADMIN | Pages | Profile page', () => {
|
||||
aria-expanded="false"
|
||||
aria-haspopup="listbox"
|
||||
aria-labelledby="select-1-label select-1-content"
|
||||
class="c38"
|
||||
class="c40"
|
||||
id="select-1"
|
||||
type="button"
|
||||
/>
|
||||
<div
|
||||
class="c39 c40"
|
||||
class="c41 c42"
|
||||
>
|
||||
<div
|
||||
class="c36"
|
||||
class="c38"
|
||||
>
|
||||
<div
|
||||
class="c41"
|
||||
class="c43"
|
||||
>
|
||||
<span
|
||||
class="c42"
|
||||
class="c44"
|
||||
id="select-1-content"
|
||||
>
|
||||
Select
|
||||
@ -1250,12 +1272,12 @@ describe('ADMIN | Pages | Profile page', () => {
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="c36"
|
||||
class="c38"
|
||||
>
|
||||
<button
|
||||
aria-disabled="false"
|
||||
aria-label="Clear the interface language selected"
|
||||
class="c43"
|
||||
class="c45"
|
||||
>
|
||||
<svg
|
||||
fill="none"
|
||||
@ -1272,7 +1294,7 @@ describe('ADMIN | Pages | Profile page', () => {
|
||||
</button>
|
||||
<button
|
||||
aria-hidden="true"
|
||||
class="c44 c43 c45"
|
||||
class="c46 c45 c47"
|
||||
tabindex="-1"
|
||||
type="button"
|
||||
>
|
||||
@ -1295,7 +1317,7 @@ describe('ADMIN | Pages | Profile page', () => {
|
||||
</div>
|
||||
</div>
|
||||
<p
|
||||
class="c46"
|
||||
class="c48"
|
||||
id="select-1-hint"
|
||||
>
|
||||
This will only display your own interface in the chosen language.
|
||||
|
||||
@ -208,6 +208,7 @@ const ApiTokenCreateView = () => {
|
||||
})}
|
||||
onChange={handleChange}
|
||||
value={values.name}
|
||||
required
|
||||
/>
|
||||
</GridItem>
|
||||
<GridItem key="description" col={6} xs={12}>
|
||||
|
||||
@ -15,6 +15,7 @@ const layout = [
|
||||
col: 6,
|
||||
xs: 12,
|
||||
},
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
intlLabel: {
|
||||
@ -49,6 +50,7 @@ const layout = [
|
||||
col: 6,
|
||||
xs: 12,
|
||||
},
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
intlLabel: {
|
||||
|
||||
@ -15,6 +15,7 @@ const layout = [
|
||||
col: 6,
|
||||
xs: 12,
|
||||
},
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
intlLabel: {
|
||||
@ -49,6 +50,7 @@ const layout = [
|
||||
col: 6,
|
||||
xs: 12,
|
||||
},
|
||||
required: true,
|
||||
},
|
||||
],
|
||||
];
|
||||
|
||||
@ -68,6 +68,7 @@ const SelectRoles = ({ disabled, error, onChange, value }) => {
|
||||
startIcon={startIcon}
|
||||
value={value}
|
||||
withTags
|
||||
required
|
||||
>
|
||||
{(data || []).map(role => {
|
||||
return (
|
||||
|
||||
@ -100,7 +100,7 @@ const EventInput = ({ isDraftAndPublish }) => {
|
||||
|
||||
return (
|
||||
<Stack size={1}>
|
||||
<FieldLabel>
|
||||
<FieldLabel required>
|
||||
{formatMessage({
|
||||
id: 'Settings.webhooks.form.events',
|
||||
defaultMessage: 'Events',
|
||||
|
||||
@ -114,6 +114,7 @@ const WebhookForm = ({
|
||||
id: 'Settings.webhooks.form.name',
|
||||
defaultMessage: 'Name',
|
||||
})}
|
||||
required
|
||||
/>
|
||||
</GridItem>
|
||||
<GridItem col={12}>
|
||||
@ -125,6 +126,7 @@ const WebhookForm = ({
|
||||
id: 'Settings.roles.form.input.url',
|
||||
defaultMessage: 'Url',
|
||||
})}
|
||||
required
|
||||
/>
|
||||
</GridItem>
|
||||
</Grid>
|
||||
|
||||
@ -407,6 +407,7 @@
|
||||
"components.Input.error.password.noMatch": "Passwords do not match",
|
||||
"components.Input.error.validation.email": "This is an invalid email",
|
||||
"components.Input.error.validation.json": "This doesn't match the JSON format",
|
||||
"components.Input.error.validation.lowercase": "The value must be a lowercase string",
|
||||
"components.Input.error.validation.max": "The value is too high.",
|
||||
"components.Input.error.validation.maxLength": "The value is too long.",
|
||||
"components.Input.error.validation.min": "The value is too low.",
|
||||
|
||||
@ -1,323 +1,673 @@
|
||||
{
|
||||
"Analytics": "分析",
|
||||
"Auth.components.Oops.text": "你的帐号已经被停用。",
|
||||
"Auth.components.Oops.text.admin": "如果这是个错误,请联系你的管理员。",
|
||||
"Auth.components.Oops.title": "哎呀...",
|
||||
"Auth.form.button.forgot-password": "发送电子邮件",
|
||||
"Auth.form.button.go-home": "回到首页",
|
||||
"Auth.form.button.login": "登录",
|
||||
"Auth.form.button.login.providers.error": "无法通过选定的验证方式连接你的帐号。",
|
||||
"Auth.form.button.login.providers.see-more": "查看更多",
|
||||
"Auth.form.button.login.strapi": "i通过 Strapi 登录",
|
||||
"Auth.form.button.password-recovery": "找回密码",
|
||||
"Auth.form.button.register": "准备开始",
|
||||
"Auth.form.button.reset-password": "修改密码",
|
||||
"Auth.form.email.label": "邮箱",
|
||||
"Auth.form.email.placeholder": "kai@doe.com",
|
||||
"Auth.form.error.blocked": "您的帐户已被管理员禁用。",
|
||||
"Auth.form.error.code.provide": "提供代码的代码不正确。",
|
||||
"Auth.form.error.confirmed": "您的帐户电子邮件还未得到确认。",
|
||||
"Auth.form.error.email.invalid": "此电子邮件无效。",
|
||||
"Auth.form.confirmPassword.label": "确认密码",
|
||||
"Auth.form.currentPassword.label": "当前密码",
|
||||
"Auth.form.email.label": "电子邮件",
|
||||
"Auth.form.email.placeholder": "例如 kai@doe.com",
|
||||
"Auth.form.error.blocked": "你的帐号已经被管理员禁用。",
|
||||
"Auth.form.error.code.provide": "提供的代码不正确。",
|
||||
"Auth.form.error.confirmed": "您的帐号关联的电子邮件还未得到确认。",
|
||||
"Auth.form.error.email.invalid": "电子邮件格式不正确。",
|
||||
"Auth.form.error.email.provide": "请提供您的用户名或电子邮件。",
|
||||
"Auth.form.error.email.taken": "邮箱已被使用",
|
||||
"Auth.form.error.invalid": "标识符或密码无效。",
|
||||
"Auth.form.error.params.provide": "提供错误的参数。",
|
||||
"Auth.form.error.password.format": "您的密码不能包含符号 `$` 超过三次。",
|
||||
"Auth.form.error.password.local": "此用户从未设置本地密码,请通过帐户创建过程中使用的第三方供应商登录。",
|
||||
"Auth.form.error.email.taken": "此电子邮件地址已被占用",
|
||||
"Auth.form.error.invalid": "使用者名称或密码无效。",
|
||||
"Auth.form.error.params.provide": "密码不正确。",
|
||||
"Auth.form.error.password.format": "您的密码不能包含 `$` 符号超过三次。",
|
||||
"Auth.form.error.password.local": "这个用户从未设置本地密码,请使用账户创建时使用的验证方式登录。",
|
||||
"Auth.form.error.password.matching": "密码不匹配。",
|
||||
"Auth.form.error.password.provide": "请提供您的密码。",
|
||||
"Auth.form.error.ratelimit": "您尝试的次数过多,请稍后再试。",
|
||||
"Auth.form.error.user.not-exist": "此电子邮件不存在。",
|
||||
"Auth.form.error.username.taken": "用户名已被使用",
|
||||
"Auth.form.forgot-password.email.label": "输入你的电子邮件",
|
||||
"Auth.form.forgot-password.email.label.success": "电子邮件已成功发送到",
|
||||
"Auth.form.error.ratelimit": "尝试的次数过多,请稍后再试。",
|
||||
"Auth.form.error.user.not-exist": "这个电子邮件地址不存在。",
|
||||
"Auth.form.error.username.taken": "用户名已被占用。",
|
||||
"Auth.form.firstname.label": "名字",
|
||||
"Auth.form.firstname.placeholder": "例如 Kai",
|
||||
"Auth.form.forgot-password.email.label": "输入您的电子邮件地址",
|
||||
"Auth.form.forgot-password.email.label.success": "邮件成功发送",
|
||||
"Auth.form.lastname.label": "姓氏",
|
||||
"Auth.form.lastname.placeholder": "例如 Doe",
|
||||
"Auth.form.password.hide-password": "隐藏密码",
|
||||
"Auth.form.password.hint": "密码必须包含至少 8 个字符,1 个大写字母,1 个小写字母,1 个数字",
|
||||
"Auth.form.password.label": "密码",
|
||||
"Auth.form.register.news.label": "让我随时了解新功能和即将进行的改进(如果要这样做,您需要接受 {terms} 和 {policy} )。",
|
||||
"Auth.form.password.show-password": "显示密码",
|
||||
"Auth.form.register.news.label": "让我了解新的功能和即将到来的改进 (打勾表示你接受 {terms} 和 {policy}))。",
|
||||
"Auth.form.register.subtitle": "你的凭证只用于管理后台验证你的身份。所有保存的数据都将储存在你自己的数据库中。",
|
||||
"Auth.form.rememberMe.label": "记住我",
|
||||
"Auth.form.username.label": "用户名",
|
||||
"Auth.form.username.placeholder": "Kai Doe",
|
||||
"Auth.link.forgot-password": "忘记密码了吗?",
|
||||
"Auth.link.ready": "准备好登录?",
|
||||
"Auth.form.username.placeholder": "例如 Kai_Doe",
|
||||
"Auth.form.welcome.subtitle": "登录您的Strapi账户",
|
||||
"Auth.form.welcome.title": "欢迎!",
|
||||
"Auth.link.forgot-password": "忘记密码?",
|
||||
"Auth.link.ready": "准备好登录了吗?",
|
||||
"Auth.link.signin": "登录",
|
||||
"Auth.link.signin.account": "已经有了一个账户?",
|
||||
"Auth.login.sso.divider": "或用以下方式登录",
|
||||
"Auth.login.sso.loading": "正在加载SSO服务提供商...",
|
||||
"Auth.login.sso.subtitle": "通过SSO登录到你的账户",
|
||||
"Auth.privacy-policy-agreement.policy": "隐私政策",
|
||||
"Auth.privacy-policy-agreement.terms": "条款",
|
||||
"Auth.privacy-policy-agreement.terms": "使用条款",
|
||||
"Auth.reset-password.title": "重置密码",
|
||||
"admin.pages.MarketPlacePage.helmet": "市场 - 插件",
|
||||
"admin.pages.MarketPlacePage.illustration": "市场插图",
|
||||
"admin.pages.MarketPlacePage.title": "市场",
|
||||
"admin.pages.MarketPlacePage.subtitle": "从Strapi中获得更多",
|
||||
"admin.pages.MarketPlacePage.coming-soon.1": "让 Strapi 变更棒的方法。",
|
||||
"admin.pages.MarketPlacePage.coming-soon.2": "即将推出。",
|
||||
"admin.pages.MarketPlacePage.content.subtitle": "新的市场将帮助你从Strapi中获得更多。我们正在努力提供发现和安装插件的最佳体验。",
|
||||
"admin.pages.MarketPlacePage.submit.plugin.link": "提交您的插件",
|
||||
"admin.pages.MarketPlacePage.blog.link": "阅读我们的博文",
|
||||
"Content Manager": "内容管理",
|
||||
"Email": "邮件",
|
||||
"Content Type Builder": "内容类型构建器",
|
||||
"Documentation": "文档",
|
||||
"Email": "电子邮件",
|
||||
"Files Upload": "文件上传",
|
||||
"HomePage.helmet.title": "主页",
|
||||
"HomePage.helmet.title": "首页",
|
||||
"HomePage.roadmap": "查看我们的路线图",
|
||||
"HomePage.welcome.congrats": "恭喜!",
|
||||
"HomePage.welcome.congrats.content": "您是第一位以管理员身份登录的用户,去发现 Strapi 提供的强大功能吧,",
|
||||
"HomePage.welcome.congrats.content.bold": "我们建议您创建第一个 Content-Type。",
|
||||
"New entry": "新入口",
|
||||
"HomePage.welcome.congrats.content": "你登录成为第一个管理员,探索 Strapi 的強大功能,",
|
||||
"HomePage.welcome.congrats.content.bold": "我们建议你创建你的第一个集合类型。",
|
||||
"Media Library": "媒体库",
|
||||
"New entry": "新条目",
|
||||
"Password": "密码",
|
||||
"Provider": "供应商",
|
||||
"ResetPasswordToken": "密码重置",
|
||||
"Provider": "提供商",
|
||||
"ResetPasswordToken": "重置密码令牌",
|
||||
"Role": "角色",
|
||||
"Roles & Permissions": "角色和权限",
|
||||
"Roles.ListPage.notification.delete-all-not-allowed": "一些角色不能被删除,因为它们与用户相关。",
|
||||
"Roles.ListPage.notification.delete-not-allowed": "如果一个角色与用户相关联,则不能被删除",
|
||||
"Roles.RoleRow.select-all": "选择 {name} 进行批量操作",
|
||||
"Roles.RoleRow.user-count.plural": "{number} 个用户",
|
||||
"Roles.RoleRow.user-count.singular": "{number} 个用户",
|
||||
"Roles.components.List.empty.withSearch": "找不到你搜索的角色 ({search})...",
|
||||
"Settings.PageTitle": "设置 - {name}",
|
||||
"Settings.apiTokens.details": "详细信息",
|
||||
"Settings.apiTokens.addFirstToken": "添加你的第一个 API 令牌。",
|
||||
"Settings.apiTokens.addNewToken": "添加新的 API 令牌",
|
||||
"Settings.apiTokens.copy.editMessage": "出于安全原因,你只能看到你的令牌一次。",
|
||||
"Settings.apiTokens.copy.editTitle": "这个令牌已经无法使用了。",
|
||||
"Settings.apiTokens.copy.lastWarning": "请确保复制这个令牌,你将无法再看到它了!",
|
||||
"Settings.apiTokens.create": "创建",
|
||||
"Settings.apiTokens.description": "已添加的可使用 API 的令牌列表",
|
||||
"Settings.apiTokens.emptyStateLayout": "你还没有任何内容...",
|
||||
"Settings.apiTokens.notification.copied": "令牌已被复制至剪贴板。",
|
||||
"Settings.apiTokens.title": "API 令牌",
|
||||
"Settings.apiTokens.types.full-access": "完全访问",
|
||||
"Settings.apiTokens.types.read-only": "只读",
|
||||
"Settings.application.description": "查看项目的详细信息",
|
||||
"Settings.application.strapiVersion": "strapi 版本",
|
||||
"Settings.application.edition-title": "当前方案",
|
||||
"Settings.application.details": "详细信息",
|
||||
"Settings.application.link-pricing": "查看所有价格",
|
||||
"Settings.application.link-upgrade": "升级你的管理后台",
|
||||
"Settings.application.get-help": "获取帮助",
|
||||
"Settings.application.node-version": "node 版本",
|
||||
"Settings.application.strapi-version": "strapi 版本",
|
||||
"Settings.application.title": "应用程序",
|
||||
"Settings.error": "错误",
|
||||
"Settings.global": "全局设置",
|
||||
"Settings.webhooks.create": "创建一个 webhook",
|
||||
"Settings.webhooks.create.header": "添加",
|
||||
"Settings.permissions": "管理员权限",
|
||||
"Settings.permissions.category": "{category} 的权限设置",
|
||||
"Settings.permissions.category.plugins": "{category} 插件的权限设置",
|
||||
"Settings.permissions.conditions.anytime": "任何时候",
|
||||
"Settings.permissions.conditions.apply": "启用",
|
||||
"Settings.permissions.conditions.can": "可以",
|
||||
"Settings.permissions.conditions.define-conditions": "定义条件",
|
||||
"Settings.permissions.conditions.links": "链接",
|
||||
"Settings.permissions.conditions.no-actions": "你首先需要选择动作(创建、读取、更新......),然后再对其定义条件。",
|
||||
"Settings.permissions.conditions.none-selected": "任何时候",
|
||||
"Settings.permissions.conditions.or": "或",
|
||||
"Settings.permissions.conditions.selected.plural": "已选择 {number} 个条件",
|
||||
"Settings.permissions.conditions.selected.singular": "已选择 {number} 个条件",
|
||||
"Settings.permissions.conditions.when": "当",
|
||||
"Settings.permissions.menu.link.roles.label": "角色",
|
||||
"Settings.permissions.menu.link.users.label": "用户",
|
||||
"Settings.permissions.select-all-by-permission": "选择所有 {label} 权限",
|
||||
"Settings.permissions.select-by-permission": "选择 {label} 权限",
|
||||
"Settings.permissions.users.create": "邀请新用户",
|
||||
"Settings.permissions.users.form.email": "电子邮件",
|
||||
"Settings.permissions.users.form.firstname": "名字",
|
||||
"Settings.permissions.users.form.lastname": "姓氏",
|
||||
"Settings.permissions.users.form.sso": "通过 SSO 登录",
|
||||
"Settings.permissions.users.form.sso.description": "当启用这个选项(ON)时,用户可以通过SSO登录",
|
||||
"Settings.permissions.users.listview.header.subtitle": "所有能够访问 Strapi 管理后台的用户",
|
||||
"Settings.permissions.users.listview.header.title": "用户",
|
||||
"Settings.permissions.users.tabs.label": "标签页权限",
|
||||
"Settings.profile.form.notify.data.loaded": "你的个人数据已经加载完成",
|
||||
"Settings.profile.form.section.experience.clear.select": "清除已选择的界面语言",
|
||||
"Settings.profile.form.section.experience.interfaceLanguage": "界面语言",
|
||||
"Settings.profile.form.section.experience.interfaceLanguage.hint": "将会用所选择的语言显示你的界面",
|
||||
"Settings.profile.form.section.experience.interfaceLanguageHelp": "当前的语言选择只会更改你当前帐号界面语言。 请参考此 {documentation} 为你的团队提供其他语言。",
|
||||
"Settings.profile.form.section.experience.documentation": "文档",
|
||||
"Settings.profile.form.section.experience.title": "体验",
|
||||
"Settings.profile.form.section.helmet.title": "用户个人信息",
|
||||
"Settings.profile.form.section.password.title": "更改密码",
|
||||
"Settings.profile.form.section.profile.page.title": "个人信息页面",
|
||||
"Settings.profile.form.section.profile.title": "个人信息",
|
||||
"Settings.roles.create.description": "定义赋予角色的权限",
|
||||
"Settings.roles.create.title": "创建角色",
|
||||
"Settings.roles.created": "角色已创建",
|
||||
"Settings.roles.edit.title": "编辑角色",
|
||||
"Settings.roles.form.button.users-with-role": "{number, plural, =0 {# users} one {# user} other {# users}} 是这个角色",
|
||||
"Settings.roles.form.created": "已创建",
|
||||
"Settings.roles.form.description": "角色的名称与描述",
|
||||
"Settings.roles.form.input.description": "描述",
|
||||
"Settings.roles.form.input.name": "名称",
|
||||
"Settings.roles.form.permission.property-label": "{label} 权限",
|
||||
"Settings.roles.form.permissions.attributesPermissions": "属性权限",
|
||||
"Settings.roles.form.permissions.create": "创建",
|
||||
"Settings.roles.form.permissions.delete": "删除",
|
||||
"Settings.roles.form.permissions.publish": "发布",
|
||||
"Settings.roles.form.permissions.read": "读取",
|
||||
"Settings.roles.form.permissions.update": "更新",
|
||||
"Settings.roles.form.title": "详情",
|
||||
"Settings.roles.list.button.add": "添加角色",
|
||||
"Settings.roles.list.description": "角色列表",
|
||||
"Settings.roles.list.header.actions": "操作",
|
||||
"Settings.roles.list.header.description": "描述",
|
||||
"Settings.roles.list.header.name": "名称",
|
||||
"Settings.roles.list.header.users": "用户",
|
||||
"Settings.roles.list.title.plural": "{number} 个角色",
|
||||
"Settings.roles.list.title.singular": "{number} 个角色",
|
||||
"Settings.roles.title": "角色",
|
||||
"Settings.roles.title.singular": "角色",
|
||||
"Settings.sso.description": "配置 Single Sign-On 单点登录 功能的设置。",
|
||||
"Settings.sso.form.defaultRole.description": "它将把新的认证用户附加到选定的角色上。",
|
||||
"Settings.sso.form.defaultRole.description-not-allowed": "你需要有读取管理员角色的权限",
|
||||
"Settings.sso.form.defaultRole.label": "默认角色",
|
||||
"Settings.sso.form.registration.description": "如果没有账户存在,在SSO登录时创建新用户。",
|
||||
"Settings.sso.form.registration.label": "自动注册",
|
||||
"Settings.sso.form.settings.title": "设置",
|
||||
"Settings.sso.title": "单点登录",
|
||||
"Settings.webhooks.create": "创建 Webhook",
|
||||
"Settings.webhooks.create.header": "创建 Header",
|
||||
"Settings.webhooks.created": "Webhook 已创建",
|
||||
"Settings.webhooks.disabled": "已禁用",
|
||||
"Settings.webhooks.enabled": "已启用",
|
||||
"Settings.webhooks.event.publish-tooltip": "这个事件只适用于启用了草稿/发布系统的内容",
|
||||
"Settings.webhooks.events.create": "创建",
|
||||
"Settings.webhooks.events.delete": "删除",
|
||||
"Settings.webhooks.events.update": "更新",
|
||||
"Settings.webhooks.form.events": "事件",
|
||||
"Settings.webhooks.form.headers": "请求头",
|
||||
"Settings.webhooks.form.headers": "Headers",
|
||||
"Settings.webhooks.form.name": "名称",
|
||||
"Settings.webhooks.form.url": "请求地址",
|
||||
"Settings.webhooks.form.url": "Url",
|
||||
"Settings.webhooks.headers.remove": "移除第 {number} 行 header",
|
||||
"Settings.webhooks.key": "键",
|
||||
"Settings.webhooks.list.button.add": "添加新的 webhook",
|
||||
"Settings.webhooks.list.description": "获取 POST 更改通知。",
|
||||
"Settings.webhooks.list.empty.description": "添加你的第一个 webhook 到该列表吧!",
|
||||
"Settings.webhooks.list.empty.link": "查看我们的文档",
|
||||
"Settings.webhooks.list.empty.title": "目前还没有 webhook",
|
||||
"Settings.webhooks.list.all-entries.select": "选择所有条目",
|
||||
"Settings.webhooks.list.button.add": "添加 Webhook",
|
||||
"Settings.webhooks.list.description": "获得 POST 请求的更新通知",
|
||||
"Settings.webhooks.list.empty.description": "没有找到 Webhooks",
|
||||
"Settings.webhooks.list.empty.link": "察看们的文档",
|
||||
"Settings.webhooks.list.empty.title": "还没有 Webhooks",
|
||||
"Settings.webhooks.list.select": "选择",
|
||||
"Settings.webhooks.list.th.actions": "操作",
|
||||
"Settings.webhooks.list.th.status": "状态",
|
||||
"Settings.webhooks.singular": "webhook",
|
||||
"Settings.webhooks.title": "Webhooks",
|
||||
"Settings.webhooks.to.delete": "已选择{webhooksToDeleteLength, plural, one {# asset} other {# assets}}",
|
||||
"Settings.webhooks.trigger": "触发",
|
||||
"Settings.webhooks.trigger.cancel": "取消触发",
|
||||
"Settings.webhooks.trigger.pending": "等待中...",
|
||||
"Settings.webhooks.trigger.save": "请保存再触发",
|
||||
"Settings.webhooks.trigger.pending": "等待…",
|
||||
"Settings.webhooks.trigger.save": "请保存到触发",
|
||||
"Settings.webhooks.trigger.success": "成功!",
|
||||
"Settings.webhooks.trigger.success.label": "触发成功",
|
||||
"Settings.webhooks.trigger.test": "触发测试",
|
||||
"Settings.webhooks.trigger.title": "触发前保存",
|
||||
"Settings.webhooks.trigger.test": "测试触发",
|
||||
"Settings.webhooks.trigger.title": "触发前先保存",
|
||||
"Settings.webhooks.value": "值",
|
||||
"Username": "用户名",
|
||||
"Users": "用户",
|
||||
"Users & Permissions": "用户 & 权限",
|
||||
"Users & Permissions": "用户和权限",
|
||||
"Users.components.List.empty": "还没有用户...",
|
||||
"Users.components.List.empty.withFilters": "没有符合条件的用户...",
|
||||
"Users.components.List.empty.withSearch": "没有符合搜索条件 ({search}) 的用户...",
|
||||
"anErrorOccurred": "唉呀! 出错了。请再试一次。",
|
||||
"app.component.CopyToClipboard.label": "复制到剪贴板",
|
||||
"app.component.search.label": "搜索 {target}",
|
||||
"app.component.table.delete": "删除 {target}",
|
||||
"app.component.table.duplicate": "创建副本 {target}",
|
||||
"app.component.table.edit": "编辑 {target}",
|
||||
"app.component.table.select.all-entries": "选择所有条目",
|
||||
"app.component.table.select.one-entry": "选择 {target}",
|
||||
"app.components.BlockLink.blog": "博客",
|
||||
"app.components.BlockLink.blog.content": "阅读有关Strapi和生态系统的最新消息。",
|
||||
"app.components.BlockLink.code": "代码示例",
|
||||
"app.components.BlockLink.code.content": "通过试用社区开发的真实项目来学习。",
|
||||
"app.components.BlockLink.documentation": "文档",
|
||||
"app.components.BlockLink.documentation.content": "探索基本概念、指南和说明。",
|
||||
"app.components.BlockLink.tutorial": "教程",
|
||||
"app.components.BlockLink.tutorial.content": "按照手把手指引来使用和定制Strapi。",
|
||||
"app.components.Button.cancel": "取消",
|
||||
"app.components.Button.confirm": "确认",
|
||||
"app.components.Button.reset": "重置",
|
||||
"app.components.Button.save": "保存",
|
||||
"app.components.ComingSoonPage.comingSoon": "即将推出",
|
||||
"app.components.DownloadInfo.download": "正在下载...",
|
||||
"app.components.DownloadInfo.text": "这可能需要几分钟,谢谢你的耐心。",
|
||||
"app.components.EmptyAttributes.title": "还没有字段",
|
||||
"app.components.HomePage.button.blog": "在博客上查看更多",
|
||||
"app.components.HomePage.community": "在网络上找到社区",
|
||||
"app.components.HomePage.community.content": "与团队成员、贡献者和开发人员在不同的渠道进行讨论。",
|
||||
"app.components.HomePage.create": "创建第一个 Content Type",
|
||||
"app.components.HomePage.welcome": "欢迎回来",
|
||||
"app.components.HomePage.welcome.again": "欢迎 ",
|
||||
"app.components.HomePage.welcomeBlock.content": "我们很高兴有你成为社区成员之一。我们一直在寻找反馈,所以可以随时给我们发送消息到",
|
||||
"app.components.HomePage.welcomeBlock.content.again": "我们希望你在项目上取得进展。请随意阅读关于 Strapi 的最新消息。我们将尽最大努力根据您的反馈改进产品。",
|
||||
"app.components.HomePage.welcomeBlock.content.issues": "issues.",
|
||||
"app.components.HomePage.welcomeBlock.content.raise": " or raise ",
|
||||
"app.components.ImgPreview.hint": "将文件拖放到该区域或{browse}以供文件上载",
|
||||
"app.components.ConfirmDialog.title": "确认",
|
||||
"app.components.DownloadInfo.download": "下载中...",
|
||||
"app.components.DownloadInfo.text": "这可能需要几分钟时间。谢谢你的耐心。",
|
||||
"app.components.EmptyAttributes.title": "目前还没有任何字段",
|
||||
"app.components.EmptyStateLayout.content-document": "目前还没有任何内容。",
|
||||
"app.components.EmptyStateLayout.content-permissions": "你没有访问该内容的权限",
|
||||
"app.components.HeaderLayout.link.go-back": "返回",
|
||||
"app.components.HomePage.button.blog": "在博客上察看更多内容",
|
||||
"app.components.HomePage.community": "加入社区",
|
||||
"app.components.HomePage.community.content": "在不同的频道中与团队成员、贡献者和开发者进行讨论。",
|
||||
"app.components.HomePage.create": "创建你的第一个内容类型",
|
||||
"app.components.HomePage.roadmap": "查看我们的路线图",
|
||||
"app.components.HomePage.welcome": "欢迎加入 👋",
|
||||
"app.components.HomePage.welcome.again": "欢迎 👋",
|
||||
"app.components.HomePage.welcomeBlock.content": "恭喜! 您已登录为第一个管理员。为了探索Strapi提供的强大功能,我们建议你创建你的第一个内容类型",
|
||||
"app.components.HomePage.welcomeBlock.content.again": "我们希望你在你的项目上有所进展! 请随时阅读关于Strapi的最新消息。我们将根据您的反馈意见,尽力改进产品。",
|
||||
"app.components.HomePage.welcomeBlock.content.issues": "问题。",
|
||||
"app.components.HomePage.welcomeBlock.content.raise": "或鼓励",
|
||||
"app.components.ImgPreview.hint": "将你的文件拖放到这个区域,或{browse}一个要上传的文件",
|
||||
"app.components.ImgPreview.hint.browse": "浏览",
|
||||
"app.components.InputFile.newFile": "增加新文件",
|
||||
"app.components.InputFileDetails.open": "在新选项卡中打开",
|
||||
"app.components.InputFileDetails.originalName": "原名称:",
|
||||
"app.components.InputFileDetails.remove": "删除这个文件",
|
||||
"app.components.InputFile.newFile": "添加新文件",
|
||||
"app.components.InputFileDetails.open": "在新标签中打开",
|
||||
"app.components.InputFileDetails.originalName": "原文件名:",
|
||||
"app.components.InputFileDetails.remove": "移除此文件",
|
||||
"app.components.InputFileDetails.size": "大小:",
|
||||
"app.components.InstallPluginPage.Download.description": "下载并安装插件可能需要几秒钟的时间。",
|
||||
"app.components.InstallPluginPage.Download.description": "下载和安装该插件可能需要几秒钟。",
|
||||
"app.components.InstallPluginPage.Download.title": "下载中...",
|
||||
"app.components.InstallPluginPage.description": "轻松地扩展你的应用程序。",
|
||||
"app.components.InstallPluginPage.helmet": "市场 - 插件",
|
||||
"app.components.InstallPluginPage.title": "市场 - 插件",
|
||||
"app.components.LeftMenu.collapse": "收起导航栏",
|
||||
"app.components.LeftMenu.expand": "展开导航栏",
|
||||
"app.components.LeftMenu.logout": "退出",
|
||||
"app.components.LeftMenu.profile": "个人资料",
|
||||
"app.components.LeftMenuFooter.documentation": "文档",
|
||||
"app.components.LeftMenuFooter.help": "帮助",
|
||||
"app.components.LeftMenuFooter.poweredBy": "技术支持 ",
|
||||
"app.components.LeftMenuLinkContainer.collectionTypes": "Collection Types",
|
||||
"app.components.LeftMenuFooter.poweredBy": "由以下机构提供",
|
||||
"app.components.LeftMenuLinkContainer.collectionTypes": "集合类型",
|
||||
"app.components.LeftMenuLinkContainer.configuration": "配置",
|
||||
"app.components.LeftMenuLinkContainer.general": "常规",
|
||||
"app.components.LeftMenuLinkContainer.installNewPlugin": "市场",
|
||||
"app.components.LeftMenuLinkContainer.listPlugins": "插件",
|
||||
"app.components.LeftMenuLinkContainer.noPluginsInstalled": "还没有安装插件",
|
||||
"app.components.LeftMenuLinkContainer.general": "通用",
|
||||
"app.components.LeftMenuLinkContainer.installNewPlugin": "插件市场",
|
||||
"app.components.LeftMenuLinkContainer.listPlugins": "插件列表",
|
||||
"app.components.LeftMenuLinkContainer.noPluginsInstalled": "尚未安装任何插件",
|
||||
"app.components.LeftMenuLinkContainer.plugins": "插件",
|
||||
"app.components.LeftMenuLinkContainer.settings": "设置",
|
||||
"app.components.ListPluginsPage.description": "项目中已安装的插件列表",
|
||||
"app.components.LeftMenuLinkContainer.singleTypes": "单一类型",
|
||||
"app.components.ListPluginsPage.deletePlugin.description": "卸载插件可能需要几秒钟。",
|
||||
"app.components.ListPluginsPage.deletePlugin.title": "卸载中",
|
||||
"app.components.ListPluginsPage.description": "项目中已安装的插件的列表。",
|
||||
"app.components.ListPluginsPage.helmet.title": "插件列表",
|
||||
"app.components.ListPluginsPage.title": "插件",
|
||||
"app.components.Logout.logout": "登出",
|
||||
"app.components.Logout.logout": "退出",
|
||||
"app.components.Logout.profile": "个人资料",
|
||||
"app.components.NotFoundPage.back": "返回主页",
|
||||
"app.components.NotFoundPage.description": "没有找到",
|
||||
"app.components.MarketplaceBanner": "在 Strapi Awesome 上发现由社区开发的插件,以及更多可以启动你的项目的精彩内容。",
|
||||
"app.components.MarketplaceBanner.image.alt": "strapi 火箭标志",
|
||||
"app.components.MarketplaceBanner.link": "立即查看",
|
||||
"app.components.NotFoundPage.back": "返回首页",
|
||||
"app.components.NotFoundPage.description": "找不到此页面",
|
||||
"app.components.Official": "官方",
|
||||
"app.components.Onboarding.label.completed": "% 已完成",
|
||||
"app.components.Onboarding.title": "入门视频",
|
||||
"app.components.Onboarding.help.button": "帮助按钮",
|
||||
"app.components.Onboarding.label.completed": "完成 %",
|
||||
"app.components.Onboarding.title": "入门影片",
|
||||
"app.components.PluginCard.Button.label.download": "下载",
|
||||
"app.components.PluginCard.Button.label.install": "已下载",
|
||||
"app.components.PluginCard.PopUpWarning.install.impossible.autoReload.needed": "需要启用自动重载功能。请使用 `yarn develop` 来启动您的应用。",
|
||||
"app.components.PluginCard.PopUpWarning.install.impossible.confirm": "我明白!",
|
||||
"app.components.PluginCard.Button.label.install": "已安装",
|
||||
"app.components.PluginCard.PopUpWarning.install.impossible.autoReload.needed": "要启用autoReload功能。请用`yarn develop`启动你的应用程序。",
|
||||
"app.components.PluginCard.PopUpWarning.install.impossible.confirm": "我了解!",
|
||||
"app.components.PluginCard.PopUpWarning.install.impossible.environment": "出于安全原因,插件只能在开发环境中下载。",
|
||||
"app.components.PluginCard.PopUpWarning.install.impossible.title": "无法下载",
|
||||
"app.components.PluginCard.compatible": "与你的应用程序兼容",
|
||||
"app.components.PluginCard.compatible": "与你的项目兼容",
|
||||
"app.components.PluginCard.compatibleCommunity": "与社区兼容",
|
||||
"app.components.PluginCard.more-details": "更多细节",
|
||||
"app.components.listPlugins.button": "增加新插件",
|
||||
"app.components.PluginCard.more-details": "更多详情",
|
||||
"app.components.ToggleCheckbox.off-label": "关",
|
||||
"app.components.ToggleCheckbox.on-label": "开",
|
||||
"app.components.UpgradePlanModal.button": "了角更多",
|
||||
"app.components.UpgradePlanModal.limit-reached": "已经达到上限",
|
||||
"app.components.UpgradePlanModal.text-ce": "社区版",
|
||||
"app.components.UpgradePlanModal.text-ee": "企业版",
|
||||
"app.components.UpgradePlanModal.text-power": "通过将您的方案升级到企业版,开启 Strapi 的全部功能",
|
||||
"app.components.UpgradePlanModal.text-strapi": "通过将你的方案升级到 Strapi 的",
|
||||
"app.components.Users.MagicLink.connect": "复制并分享此链接给用户以开通账户",
|
||||
"app.components.Users.MagicLink.connect.sso": "将此链接发送给用户,第一次登录可以通过SSO供应商进行。",
|
||||
"app.components.Users.ModalCreateBody.block-title.details": "用户详情",
|
||||
"app.components.Users.ModalCreateBody.block-title.login": "角色",
|
||||
"app.components.Users.ModalCreateBody.block-title.roles": "用户角色",
|
||||
"app.components.Users.ModalCreateBody.block-title.roles.description": "一个用户可以拥有一个或多个角色",
|
||||
"app.components.Users.SortPicker.button-label": "排序",
|
||||
"app.components.Users.SortPicker.sortby.email_asc": "电子邮件 (A to Z)",
|
||||
"app.components.Users.SortPicker.sortby.email_desc": "电子邮件 (Z to A)",
|
||||
"app.components.Users.SortPicker.sortby.firstname_asc": "名字 (A to Z)",
|
||||
"app.components.Users.SortPicker.sortby.firstname_desc": "名字 (Z to A)",
|
||||
"app.components.Users.SortPicker.sortby.lastname_asc": "姓氏 (A to Z)",
|
||||
"app.components.Users.SortPicker.sortby.lastname_desc": "姓氏 (Z to A)",
|
||||
"app.components.Users.SortPicker.sortby.username_asc": "用户名 (A to Z)",
|
||||
"app.components.Users.SortPicker.sortby.username_desc": "用户名 (Z to A)",
|
||||
"app.components.go-back": "返回",
|
||||
"app.components.listPlugins.button": "添加插件",
|
||||
"app.components.listPlugins.title.none": "还没有安装插件",
|
||||
"app.components.listPlugins.title.plural": "{number} 个插件已安装",
|
||||
"app.components.listPlugins.title.singular": "{number} 个插件已安装",
|
||||
"app.components.listPluginsPage.deletePlugin.error": "卸载插件时出错",
|
||||
"app.components.listPlugins.title.plural": "已安装 {number} 个插件",
|
||||
"app.components.listPlugins.title.singular": "已安装 {number} 个插件",
|
||||
"app.components.listPluginsPage.deletePlugin.error": "卸载插件时发生错误",
|
||||
"app.containers.App.notification.error.init": "请求 API 时发生错误",
|
||||
"app.containers.AuthPage.ForgotPasswordSuccess.text.contact-admin": "如果你没有收到这个链接,请联系你的管理员。",
|
||||
"app.containers.AuthPage.ForgotPasswordSuccess.text.email": "收到你的密码恢复链接可能需要几分钟时间。",
|
||||
"app.containers.AuthPage.ForgotPasswordSuccess.title": "电子邮件已发送",
|
||||
"app.containers.Users.EditPage.form.active.label": "有效",
|
||||
"app.containers.Users.EditPage.header.label": "编辑 {name}",
|
||||
"app.containers.Users.EditPage.header.label-loading": "编辑用户",
|
||||
"app.containers.Users.EditPage.roles-bloc-title": "归属的角色",
|
||||
"app.containers.Users.ModalForm.footer.button-success": "邀请用户",
|
||||
"app.links.configure-view": "配置视图",
|
||||
"app.static.links.cheatsheet": "快速手册",
|
||||
"app.utils.SelectOption.defaultMessage": " ",
|
||||
"app.utils.add-filter": "添加过滤器",
|
||||
"app.utils.close-label": "关闭",
|
||||
"app.utils.defaultMessage": " ",
|
||||
"app.utils.delete": "删除",
|
||||
"app.utils.duplicate": "创建副本",
|
||||
"app.utils.edit": "编辑",
|
||||
"app.utils.errors.file-too-big.message": "文件太大了",
|
||||
"app.utils.filter-value": "过滤值",
|
||||
"app.utils.filters": "过滤器",
|
||||
"app.utils.notify.data-loaded": "{target} 已加载",
|
||||
"app.utils.placeholder.defaultMessage": " ",
|
||||
"component.Input.error.validation.integer": "该值必须是整数",
|
||||
"components.AutoReloadBlocker.description": "使用以下命令中的一个来运行 Strapi:",
|
||||
"components.AutoReloadBlocker.header": "这个插件需要重新加载特性。",
|
||||
"components.ErrorBoundary.title": "哪里出问题了…",
|
||||
"components.Input.error.attribute.key.taken": "此值已经存在",
|
||||
"components.Input.error.attribute.sameKeyAndName": "不能相等",
|
||||
"components.Input.error.attribute.taken": "此字段名称已经存在",
|
||||
"components.Input.error.contentTypeName.taken": "此名称已经存在",
|
||||
"app.utils.publish": "发布",
|
||||
"app.utils.select-all": "全选",
|
||||
"app.utils.select-field": "选择字段",
|
||||
"app.utils.select-filter": "选择过滤器",
|
||||
"app.utils.unpublish": "取消发布",
|
||||
"clearLabel": "清除",
|
||||
"coming.soon": "此内容目前正在建设中,将在几周后恢复!",
|
||||
"component.Input.error.validation.integer": "这个值必须是一个整数",
|
||||
"components.AutoReloadBlocker.description": "用以下命令之一运行Strapi:",
|
||||
"components.AutoReloadBlocker.header": "这个插件需要重新加载后才能载入",
|
||||
"components.ErrorBoundary.title": "出错了...",
|
||||
"components.FilterOptions.FILTER_TYPES.$contains": "包含 (区分大小写)",
|
||||
"components.FilterOptions.FILTER_TYPES.$endsWith": "结束于",
|
||||
"components.FilterOptions.FILTER_TYPES.$eq": "等于",
|
||||
"components.FilterOptions.FILTER_TYPES.$gt": "大于",
|
||||
"components.FilterOptions.FILTER_TYPES.$gte": "大于或等于",
|
||||
"components.FilterOptions.FILTER_TYPES.$lt": "小于",
|
||||
"components.FilterOptions.FILTER_TYPES.$lte": "小于或等于",
|
||||
"components.FilterOptions.FILTER_TYPES.$ne": "不等于",
|
||||
"components.FilterOptions.FILTER_TYPES.$notContains": "不包含 (区分大小写)",
|
||||
"components.FilterOptions.FILTER_TYPES.$notNull": "不为空",
|
||||
"components.FilterOptions.FILTER_TYPES.$null": "为空",
|
||||
"components.FilterOptions.FILTER_TYPES.$startsWith": "开始于",
|
||||
"components.Input.error.attribute.key.taken": "值已被占用",
|
||||
"components.Input.error.attribute.sameKeyAndName": "不能相同",
|
||||
"components.Input.error.attribute.taken": "字段名已被占用",
|
||||
"components.Input.error.contain.lowercase": "密码至少包含一个小写字母",
|
||||
"components.Input.error.contain.number": "密码至少包含一个数字",
|
||||
"components.Input.error.contain.uppercase": "密码至少包含一个大写字母",
|
||||
"components.Input.error.contentTypeName.taken": "名称已被占用",
|
||||
"components.Input.error.custom-error": "{errorMessage} ",
|
||||
"components.Input.error.password.noMatch": "密码不匹配",
|
||||
"components.Input.error.validation.email": "这不是电子邮件",
|
||||
"components.Input.error.validation.json": "JSON 格式匹配错误",
|
||||
"components.Input.error.validation.max": "超过最大值",
|
||||
"components.Input.error.validation.maxLength": "超过最大长度",
|
||||
"components.Input.error.validation.min": "低于最小值",
|
||||
"components.Input.error.validation.minLength": "低于最小长度",
|
||||
"components.Input.error.validation.minSupMax": "最小值超过最大值了",
|
||||
"components.Input.error.validation.regex": "格式不正确",
|
||||
"components.Input.error.validation.required": "这个值是必须的",
|
||||
"components.Input.error.validation.unique": "该值已经被使用。",
|
||||
"components.Input.error.validation.email": "邮箱格式不正确",
|
||||
"components.Input.error.validation.json": "JSON格式不正确",
|
||||
"components.Input.error.validation.max": "大于最大值",
|
||||
"components.Input.error.validation.maxLength": "长度太长",
|
||||
"components.Input.error.validation.min": "小于最小值",
|
||||
"components.Input.error.validation.minLength": "长度太短",
|
||||
"components.Input.error.validation.minSupMax": "最小值不能大于最大值",
|
||||
"components.Input.error.validation.regex": "正则表达式格式不正确。",
|
||||
"components.Input.error.validation.required": "必填项",
|
||||
"components.Input.error.validation.unique": "已被占用",
|
||||
"components.InputSelect.option.placeholder": "选择",
|
||||
"components.ListRow.empty": "没有要显示的数据",
|
||||
"components.OverlayBlocker.description": "您正在使用一个需要服务器重新启动的特性,请等待服务器启动。",
|
||||
"components.OverlayBlocker.description.serverError": "服务器应该已经重新启动,请在终端中检查您的日志。",
|
||||
"components.ListRow.empty": "没有数据可以显示",
|
||||
"components.NotAllowedInput.text": "没有权限显示此字段",
|
||||
"components.OverlayBlocker.description": "你正在使用一个需要服务器重新启动的功能。请等待,直到服务器启动。",
|
||||
"components.OverlayBlocker.description.serverError": "服务器已经重新启动,请在终端检查日志。",
|
||||
"components.OverlayBlocker.title": "等待重新启动...",
|
||||
"components.OverlayBlocker.title.serverError": "重启时间长于预期",
|
||||
"components.OverlayBlocker.title.serverError": "重新启动时间超过预期",
|
||||
"components.PageFooter.select": "每页条目",
|
||||
"components.ProductionBlocker.description": "为了安全起见,我们必须在其他环境中禁用这个插件。",
|
||||
"components.ProductionBlocker.header": "这个插件只能在开发中使用。",
|
||||
"components.Wysiwyg.collapse": "折叠",
|
||||
"components.Wysiwyg.selectOptions.H1": "H1 标题",
|
||||
"components.Wysiwyg.selectOptions.H2": "H2 标题",
|
||||
"components.Wysiwyg.selectOptions.H3": "H3 标题",
|
||||
"components.Wysiwyg.selectOptions.H4": "H4 标题",
|
||||
"components.Wysiwyg.selectOptions.H5": "H5 标题",
|
||||
"components.Wysiwyg.selectOptions.H6": "H6 标题",
|
||||
"components.Wysiwyg.selectOptions.title": "增加一个标题",
|
||||
"components.WysiwygBottomControls.charactersIndicators": "人物",
|
||||
"components.WysiwygBottomControls.fullscreen": "最大化",
|
||||
"components.ProductionBlocker.description": "为了安全起见,我们必须在其他环境下禁用这个插件。",
|
||||
"components.ProductionBlocker.header": "这个插件只在开发环境中可用。",
|
||||
"components.Search.placeholder": "搜索...",
|
||||
"components.Select.placeholder": "选择",
|
||||
"components.TableHeader.actions-label": "操作",
|
||||
"components.TableHeader.sort": "按 {label} 排序",
|
||||
"components.Wysiwyg.ToggleMode.markdown-mode": "Markdown 模式",
|
||||
"components.Wysiwyg.ToggleMode.preview-mode": "预览模式",
|
||||
"components.Wysiwyg.collapse": "收起",
|
||||
"components.Wysiwyg.selectOptions.H1": "标题1",
|
||||
"components.Wysiwyg.selectOptions.H2": "标题2",
|
||||
"components.Wysiwyg.selectOptions.H3": "标题3",
|
||||
"components.Wysiwyg.selectOptions.H4": "标题4",
|
||||
"components.Wysiwyg.selectOptions.H5": "标题5",
|
||||
"components.Wysiwyg.selectOptions.H6": "标题6",
|
||||
"components.Wysiwyg.selectOptions.title": "添加标题",
|
||||
"components.WysiwygBottomControls.charactersIndicators": "字符",
|
||||
"components.WysiwygBottomControls.fullscreen": "展开",
|
||||
"components.WysiwygBottomControls.uploadFiles": "拖放文件,从剪贴板粘贴或 {browse}.",
|
||||
"components.WysiwygBottomControls.uploadFiles.browse": "从文件夹选取",
|
||||
"components.popUpWarning.message": "确实要删除这个吗?",
|
||||
"components.WysiwygBottomControls.uploadFiles.browse": "选择文件",
|
||||
"components.pagination.go-to": "到 {page} 页",
|
||||
"components.pagination.go-to-next": "下一页",
|
||||
"components.pagination.go-to-previous": "上一页",
|
||||
"components.pagination.remaining-links": "及 {number} 个其他链接",
|
||||
"components.popUpWarning.button.cancel": "不, 取消",
|
||||
"components.popUpWarning.button.confirm": "是, 确认",
|
||||
"components.popUpWarning.message": "你确定要删除这个吗?",
|
||||
"components.popUpWarning.title": "请确认",
|
||||
"content-manager.App.schemas.data-loaded": "模式已经成功加载",
|
||||
"content-manager.DynamicTable.relation-loaded": "关联已经成功加载",
|
||||
"content-manager.EditRelations.title": "关联数据",
|
||||
"content-manager.HeaderLayout.button.label-add-entry": "添加条目",
|
||||
"content-manager.api.id": "API ID",
|
||||
"content-manager.components.AddFilterCTA.add": "过滤器",
|
||||
"content-manager.components.AddFilterCTA.hide": "过滤器",
|
||||
"content-manager.components.DragHandle-label": "拖曳",
|
||||
"content-manager.components.DraggableAttr.edit": "点击以编辑",
|
||||
"content-manager.components.DraggableCard.delete.field": "删除 {item}",
|
||||
"content-manager.components.DraggableCard.edit.field": "编辑 {item}",
|
||||
"content-manager.components.DraggableCard.move.field": "移动 {item}",
|
||||
"content-manager.components.DynamicTable.row-line": "项 行 {number}",
|
||||
"content-manager.components.DynamicZone.ComponentPicker-label": "选择一个组件",
|
||||
"content-manager.components.DynamicZone.add-component": "添加组件 {componentName}",
|
||||
"content-manager.components.DynamicZone.delete-label": "删除 {name}",
|
||||
"content-manager.components.DynamicZone.error-message": "这个组件有错误",
|
||||
"content-manager.components.DynamicZone.missing-components": "这里 {number, plural, =0 {有 # 遗失的组件} one {有 # 遗失的组件} other {有 # 遗失的组件}}",
|
||||
"content-manager.components.DynamicZone.move-down-label": "将组件向下移动",
|
||||
"content-manager.components.DynamicZone.move-up-label": "将组件向上移动",
|
||||
"content-manager.components.DynamicZone.pick-compo": "选择一个组件",
|
||||
"content-manager.components.DynamicZone.required": "必须有一个组件",
|
||||
"content-manager.components.EmptyAttributesBlock.button": "前往设置页面",
|
||||
"content-manager.components.EmptyAttributesBlock.description": "您可以更改设置",
|
||||
"content-manager.components.EmptyAttributesBlock.description": "你可以更改你的设置",
|
||||
"content-manager.components.FieldItem.linkToComponentLayout": "设置组件的布局",
|
||||
"content-manager.components.FieldSelect.label": "添加一个字段",
|
||||
"content-manager.components.FilterOptions.button.apply": "应用",
|
||||
"content-manager.components.FiltersPickWrapper.PluginHeader.actions.apply": "应用",
|
||||
"content-manager.components.FiltersPickWrapper.PluginHeader.actions.clearAll": "清除",
|
||||
"content-manager.components.FiltersPickWrapper.PluginHeader.description": "设置过滤条件",
|
||||
"content-manager.components.FiltersPickWrapper.PluginHeader.actions.clearAll": "清除所有",
|
||||
"content-manager.components.FiltersPickWrapper.PluginHeader.description": "设置过滤条目的条件",
|
||||
"content-manager.components.FiltersPickWrapper.PluginHeader.title.filter": "过滤器",
|
||||
"content-manager.components.FiltersPickWrapper.hide": "隐藏",
|
||||
"content-manager.components.LimitSelect.itemsPerPage": "每页显示数目",
|
||||
"content-manager.components.LeftMenu.Search.label": "搜索内容类型",
|
||||
"content-manager.components.LeftMenu.collection-types": "集合类型",
|
||||
"content-manager.components.LeftMenu.single-types": "单一类型",
|
||||
"content-manager.components.LimitSelect.itemsPerPage": "每页显示条目数",
|
||||
"content-manager.components.NotAllowedInput.text": "没有权限显示此字段",
|
||||
"content-manager.components.RepeatableComponent.error-message": "这个组件包含错误",
|
||||
"content-manager.components.Search.placeholder": "搜索...",
|
||||
"content-manager.components.Select.draft-info-title": "状态: 草稿",
|
||||
"content-manager.components.Select.publish-info-title": "状态: 已发布",
|
||||
"content-manager.components.SettingsViewWrapper.pluginHeader.description.edit-settings": "自定义编辑视图的外观。",
|
||||
"content-manager.components.SettingsViewWrapper.pluginHeader.description.list-settings": "定义列表视图的设置。",
|
||||
"content-manager.components.SettingsViewWrapper.pluginHeader.title": "配置视图 - {name}",
|
||||
"content-manager.components.TableDelete.delete": "刪除",
|
||||
"content-manager.components.TableDelete.deleteSelected": "删除所选",
|
||||
"content-manager.components.TableEmpty.withFilters": "找不到 {contentType} 按照过滤条件...",
|
||||
"content-manager.components.TableEmpty.withSearch": "找不到 {contentType} 按照过滤条件: ({search})...",
|
||||
"content-manager.components.TableDelete.delete": "删除所有",
|
||||
"content-manager.components.TableDelete.deleteSelected": "删除已选",
|
||||
"content-manager.components.TableDelete.label": "已选择 {number, plural, one {# 个条目} other {# 个条目}}",
|
||||
"content-manager.components.TableEmpty.withFilters": "按过滤条件找不到 {contentType}...",
|
||||
"content-manager.components.TableEmpty.withSearch": "按搜索条件 ({search}) 找不到相应的 {contentType}...",
|
||||
"content-manager.components.TableEmpty.withoutFilter": "找不到 {contentType}...",
|
||||
"content-manager.components.empty-repeatable": "还没有实体。可以点击下面的按钮来添加一个",
|
||||
"content-manager.components.notification.info.maximum-requirement": "您已经达到最大字段数限制",
|
||||
"content-manager.components.notification.info.minimum-requirement": "已在您的分组中添加一个字段,以满足最低要求",
|
||||
"content-manager.components.reset-entry": "重置实体",
|
||||
"content-manager.containers.Edit.Link.Layout": "编辑布局",
|
||||
"content-manager.containers.Edit.Link.Model": "编辑这个 Content Type",
|
||||
"content-manager.containers.Edit.addAnItem": "新增关联...",
|
||||
"content-manager.containers.Edit.clickToJump": "跳转到该项内容",
|
||||
"content-manager.components.empty-repeatable": "还没有条目。点击下面的按钮添加一个。",
|
||||
"content-manager.components.notification.info.maximum-requirement": "你已经达到了可定义字段数的最大数量",
|
||||
"content-manager.components.notification.info.minimum-requirement": "添加一个字段以符合定义字段数的最低要求",
|
||||
"content-manager.components.repeatable.reorder.error": "在重新排序您的组件字段时发生错误,请重试",
|
||||
"content-manager.components.reset-entry": "重置条目",
|
||||
"content-manager.components.uid.apply": "应用",
|
||||
"content-manager.components.uid.available": "可用",
|
||||
"content-manager.components.uid.regenerate": "重新生成",
|
||||
"content-manager.components.uid.suggested": "推荐",
|
||||
"content-manager.components.uid.unavailable": "不可用",
|
||||
"content-manager.containers.Edit.Link.Layout": "配置布局",
|
||||
"content-manager.containers.Edit.Link.Model": "编辑集合类型",
|
||||
"content-manager.containers.Edit.addAnItem": "添加关联...",
|
||||
"content-manager.containers.Edit.clickToJump": "点击跳转到该条目",
|
||||
"content-manager.containers.Edit.delete": "删除",
|
||||
"content-manager.containers.Edit.editing": "编辑...",
|
||||
"content-manager.containers.Edit.pluginHeader.title.new": "创建实体",
|
||||
"content-manager.containers.Edit.delete-entry": "删除该条目",
|
||||
"content-manager.containers.Edit.editing": "编辑中...",
|
||||
"content-manager.containers.Edit.information": "信息",
|
||||
"content-manager.containers.Edit.information.by": "由",
|
||||
"content-manager.containers.Edit.information.draftVersion": "草稿版本",
|
||||
"content-manager.containers.Edit.information.editing": "编辑中",
|
||||
"content-manager.containers.Edit.information.created": "已创建",
|
||||
"content-manager.containers.Edit.information.lastUpdate": "最后更新",
|
||||
"content-manager.containers.Edit.information.publishedVersion": "已发布版本",
|
||||
"content-manager.containers.Edit.pluginHeader.title.new": "创建新条目",
|
||||
"content-manager.containers.Edit.reset": "重置",
|
||||
"content-manager.containers.Edit.returnList": "返回列表",
|
||||
"content-manager.containers.Edit.seeDetails": "详细信息",
|
||||
"content-manager.containers.Edit.seeDetails": "详情",
|
||||
"content-manager.containers.Edit.submit": "保存",
|
||||
"content-manager.containers.EditSettingsView.modal-form.edit-field": "编辑字段",
|
||||
"content-manager.containers.EditView.components.missing.plural": "有 {count} 个缺少的组件",
|
||||
"content-manager.containers.EditView.components.missing.singular": "有 {count} 个缺少的组件",
|
||||
"content-manager.containers.EditView.notification.errors": "表单包含一些错误",
|
||||
"content-manager.containers.Home.introduction": "要编辑您的条目,请转到左边菜单中的特定链接。这个插件没有合适的方法来编辑设置,它仍然在积极的开发中。",
|
||||
"content-manager.containers.EditView.add.new-entry": "添加新条目",
|
||||
"content-manager.containers.EditView.components.missing.plural": "有 {count} 个组件缺失",
|
||||
"content-manager.containers.EditView.components.missing.singular": "有 {count} 个组件缺失",
|
||||
"content-manager.containers.EditView.notification.errors": "表单中包含一些错误",
|
||||
"content-manager.containers.Home.introduction": "要编辑你的条目,请到左边菜单中的特定链接。这个插件尚在积极开发中,没有适当的方法来编辑设置",
|
||||
"content-manager.containers.Home.pluginHeaderDescription": "通过一个强大而漂亮的界面管理你的条目。",
|
||||
"content-manager.containers.Home.pluginHeaderTitle": "内容管理器",
|
||||
"content-manager.containers.List.draft": "草稿",
|
||||
"content-manager.containers.List.errorFetchRecords": "错误",
|
||||
"content-manager.containers.List.published": "已发布",
|
||||
"content-manager.containers.ListPage.displayedFields": "显示字段",
|
||||
"content-manager.containers.ListSettingsView.modal-form.edit-label": "编辑标签",
|
||||
"content-manager.containers.SettingPage.add.field": "新增字段",
|
||||
"content-manager.containers.SettingPage.add.relational-field": "新增关联字段",
|
||||
"content-manager.containers.ListPage.items": "{number, plural, =0 {项} one {项} other {项}}",
|
||||
"content-manager.containers.ListPage.table-headers.published_at": "状态",
|
||||
"content-manager.containers.ListSettingsView.modal-form.edit-label": "编辑 {fieldName}",
|
||||
"content-manager.containers.SettingPage.add.field": "添加字段",
|
||||
"content-manager.containers.SettingPage.add.relational-field": "添加关联字段",
|
||||
"content-manager.containers.SettingPage.attributes": "属性",
|
||||
"content-manager.containers.SettingPage.attributes.description": "调整字段的顺序",
|
||||
"content-manager.containers.SettingPage.editSettings.description": "拖拽字段来布局",
|
||||
"content-manager.containers.SettingPage.editSettings.entry.title": "实体的标题",
|
||||
"content-manager.containers.SettingPage.editSettings.entry.title.description": "设置你的实体的显示字段",
|
||||
"content-manager.containers.SettingPage.editSettings.title": "编辑-设置",
|
||||
"content-manager.containers.SettingPage.attributes.description": "定义字段的排序",
|
||||
"content-manager.containers.SettingPage.editSettings.description": "拖放字段以建立布局",
|
||||
"content-manager.containers.SettingPage.editSettings.entry.title": "条目标题",
|
||||
"content-manager.containers.SettingPage.editSettings.entry.title.description": "设置可显示的字段",
|
||||
"content-manager.containers.SettingPage.editSettings.relation-field.description": "设置在编辑和列表视图中都可显示的字段",
|
||||
"content-manager.containers.SettingPage.editSettings.title": "编辑 (设置)",
|
||||
"content-manager.containers.SettingPage.layout": "布局",
|
||||
"content-manager.containers.SettingPage.listSettings.title": "列表 (设置)",
|
||||
"content-manager.containers.SettingPage.listSettings.description": "配置集合类型的选项",
|
||||
"content-manager.containers.SettingPage.listSettings.title": "列表视图 (设置)",
|
||||
"content-manager.containers.SettingPage.pluginHeaderDescription": "配置指定集合类型的设置",
|
||||
"content-manager.containers.SettingPage.relations": "关联字段",
|
||||
"content-manager.containers.SettingPage.settings": "设置",
|
||||
"content-manager.containers.SettingPage.view": "视图",
|
||||
"content-manager.containers.SettingViewModel.pluginHeader.title": "内容管理 - {name}",
|
||||
"content-manager.containers.SettingsPage.Block.contentType.description": "调整指定内容类型的的字段",
|
||||
"content-manager.containers.SettingsPage.Block.generalSettings.title": "总览",
|
||||
"content-manager.containers.SettingViewModel.pluginHeader.title": "内容管理器 - {名称}",
|
||||
"content-manager.containers.SettingsPage.Block.contentType.description": "配置指定内容类型的设置",
|
||||
"content-manager.containers.SettingsPage.Block.contentType.title": "集合类型",
|
||||
"content-manager.containers.SettingsPage.Block.generalSettings.description": "为你的集合类型配置默认选项",
|
||||
"content-manager.containers.SettingsPage.Block.generalSettings.title": "通用",
|
||||
"content-manager.containers.SettingsPage.pluginHeaderDescription": "为你所有的集合类型和组配置设置",
|
||||
"content-manager.containers.SettingsView.list.subtitle": "配置你的集合类型和组的布局和显示",
|
||||
"content-manager.containers.SettingsView.list.title": "显示配置",
|
||||
"content-manager.emptyAttributes.title": "还没有字段",
|
||||
"content-manager.edit-settings-view.link-to-ctb.components": "编辑组件",
|
||||
"content-manager.edit-settings-view.link-to-ctb.content-types": "编辑内容类型",
|
||||
"content-manager.emptyAttributes.button": "前往集合类型构建器",
|
||||
"content-manager.emptyAttributes.description": "为你的集合类型中添加你的第一个字段",
|
||||
"content-manager.emptyAttributes.title": "目前还没有任何字段",
|
||||
"content-manager.error.attribute.key.taken": "该值已存在",
|
||||
"content-manager.error.attribute.sameKeyAndName": "不能相等",
|
||||
"content-manager.error.attribute.taken": "该名称已被使用",
|
||||
"content-manager.error.contentTypeName.taken": "该名称已被使用",
|
||||
"content-manager.error.model.fetch": "获取models配置时发生错误",
|
||||
"content-manager.error.record.create": "创建记录时发生错误",
|
||||
"content-manager.error.record.delete": "删除记录时发生错误",
|
||||
"content-manager.error.record.fetch": "获取记录时发生错误。",
|
||||
"content-manager.error.record.update": "更新记录时发生错误",
|
||||
"content-manager.error.records.count": "获取计数记录期间发生错误。",
|
||||
"content-manager.error.records.fetch": "获取记录时发生错误。",
|
||||
"content-manager.error.schema.generation": "Schema 生成过程中发生错误。",
|
||||
"content-manager.error.validation.json": "非法的 JSON 格式",
|
||||
"content-manager.error.validation.max": "超过最大值",
|
||||
"content-manager.error.attribute.sameKeyAndName": "不能相同",
|
||||
"content-manager.error.attribute.taken": "该字段名已存在",
|
||||
"content-manager.error.contentTypeName.taken": "该名称已存在",
|
||||
"content-manager.error.model.fetch": "在获取模型配置时发生了一个错误。",
|
||||
"content-manager.error.record.create": "在创建记录时发生了一个错误。",
|
||||
"content-manager.error.record.delete": "在删除记录时发生了一个错误。",
|
||||
"content-manager.error.record.fetch": "在获取记录时发生了一个错误。",
|
||||
"content-manager.error.record.update": "在记录更新时发生了一个错误。",
|
||||
"content-manager.error.records.count": "在获取计数器记录时发生了一个错误。",
|
||||
"content-manager.error.records.fetch": "在获取记录时发生了一个错误。",
|
||||
"content-manager.error.schema.generation": "在生成模式时发生了一个错误。",
|
||||
"content-manager.error.validation.json": "无效的JSON格式",
|
||||
"content-manager.error.validation.max": "数值太大",
|
||||
"content-manager.error.validation.maxLength": "长度太长",
|
||||
"content-manager.error.validation.min": "小于最小值",
|
||||
"content-manager.error.validation.min": "数值太小",
|
||||
"content-manager.error.validation.minLength": "长度太短",
|
||||
"content-manager.error.validation.minSupMax": "最小值大于最大值。",
|
||||
"content-manager.error.validation.regex": "格式错误",
|
||||
"content-manager.error.validation.minSupMax": "最小值不能大于最大值",
|
||||
"content-manager.error.validation.regex": "正则表达式格式不正确",
|
||||
"content-manager.error.validation.required": "必填项",
|
||||
"content-manager.form.Input.bulkActions": "启用批量操作",
|
||||
"content-manager.form.Input.defaultSort": "默认排序设置",
|
||||
"content-manager.form.Input.bulkActions": "开启批量操作",
|
||||
"content-manager.form.Input.defaultSort": "默认排序字段",
|
||||
"content-manager.form.Input.description": "描述",
|
||||
"content-manager.form.Input.description.placeholder": "资料中的显示名称",
|
||||
"content-manager.form.Input.description.placeholder": "这个字段的描述",
|
||||
"content-manager.form.Input.editable": "可编辑字段",
|
||||
"content-manager.form.Input.filters": "应用过滤器",
|
||||
"content-manager.form.Input.filters": "开启过滤器",
|
||||
"content-manager.form.Input.label": "标签",
|
||||
"content-manager.form.Input.label.inputDescription": "这个标签会显示在表格的标题列名称",
|
||||
"content-manager.form.Input.pageEntries": "每页条数",
|
||||
"content-manager.form.Input.placeholder": "占位符",
|
||||
"content-manager.form.Input.placeholder.placeholder": "在文字框中显示提示信息",
|
||||
"content-manager.form.Input.search": "启用搜索功能",
|
||||
"content-manager.form.Input.search.field": "允许此字段可以被搜索",
|
||||
"content-manager.form.Input.sort.field": "允许此字段可以被排序",
|
||||
"content-manager.form.Input.wysiwyg": "显示为所见即所得模式",
|
||||
"content-manager.global.displayedFields": "显示的字段",
|
||||
"content-manager.groups": "分组",
|
||||
"content-manager.groups.numbered": "分组 ({number})",
|
||||
"content-manager.models": "Content Types",
|
||||
"content-manager.models.numbered": "Content Types ({number})",
|
||||
"content-manager.notification.error.displayedFields": "您至少需要显示一个字段",
|
||||
"content-manager.notification.error.relationship.fetch": "获取关联数据时发生错误",
|
||||
"content-manager.notification.info.SettingPage.disableSort": "您至少需要允许一个字段可以被用來排序",
|
||||
"content-manager.notification.info.minimumFields": "您至少需要显示一个字段",
|
||||
"content-manager.notification.upload.error": "上传文件时发生了错误",
|
||||
"content-manager.pageNotFound": "页面未找到",
|
||||
"content-manager.plugin.description.long": "快速查看、编辑和删除数据库中的数据。",
|
||||
"content-manager.plugin.description.short": "快速查看、编辑和删除数据库中的数据。",
|
||||
"content-manager.popUpWarning.bodyMessage.contentType.delete": "确实要删除此条目吗?",
|
||||
"content-manager.popUpWarning.bodyMessage.contentType.delete.all": "您确定要删除这些内容吗?",
|
||||
"content-manager.popUpWarning.warning.cancelAllSettings": "您确定要放弃修改?",
|
||||
"content-manager.popUpWarning.warning.updateAllSettings": "这会修改您的设置",
|
||||
"content-manager.form.Input.label.inputDescription": "该值会覆盖显示在表头的标签",
|
||||
"content-manager.form.Input.pageEntries": "每页条目数",
|
||||
"content-manager.form.Input.pageEntries.inputDescription": "注意:你可以在集合类型设置页面中覆盖该值",
|
||||
"content-manager.form.Input.placeholder": "提示信息",
|
||||
"content-manager.form.Input.placeholder.placeholder": "在文字输入框中显示提示信息",
|
||||
"content-manager.form.Input.search": "开启搜索",
|
||||
"content-manager.form.Input.search.field": "允许该字段被搜索",
|
||||
"content-manager.form.Input.sort.field": "允许按该字段排序",
|
||||
"content-manager.form.Input.sort.order": "默认排序顺序",
|
||||
"content-manager.form.Input.wysiwyg": "所见即所得编辑器",
|
||||
"content-manager.global.displayedFields": "已显示字段",
|
||||
"content-manager.groups": "群组",
|
||||
"content-manager.groups.numbered": "群组 ({number})",
|
||||
"content-manager.header.name": "内容",
|
||||
"content-manager.link-to-ctb": "编辑模型",
|
||||
"content-manager.models": "集合类型",
|
||||
"content-manager.models.numbered": "集合类型 ({number})",
|
||||
"content-manager.notification.error.displayedFields": "你至少需要显示一个字段",
|
||||
"content-manager.notification.error.relationship.fetch": "在获取关系时发生了一个错误",
|
||||
"content-manager.notification.info.SettingPage.disableSort": "你至少需要一个用来排序的字段",
|
||||
"content-manager.notification.info.minimumFields": "你至少需要一个用来显示的字段",
|
||||
"content-manager.notification.upload.error": "在上传文件时发生了一个错误",
|
||||
"content-manager.pageNotFound": "找不到页面",
|
||||
"content-manager.pages.ListView.header-subtitle": "找到 {number, plural, =0 {# 条目} one {# 条目} other {# 条目}}",
|
||||
"content-manager.pages.NoContentType.button": "创建你的第一个内容类型",
|
||||
"content-manager.pages.NoContentType.text": "还没有任何内容,我们建议您创建您的第一个内容类型。",
|
||||
"content-manager.permissions.not-allowed.create": "你不被允许创建文档",
|
||||
"content-manager.permissions.not-allowed.update": "你不被允许查看文件",
|
||||
"content-manager.plugin.description.long": "查看、编辑和删除数据库中的数据的快速方法。",
|
||||
"content-manager.plugin.description.short": "查看、编辑和删除数据库中的数据的快速方法。",
|
||||
"content-manager.plugin.name": "內容管理",
|
||||
"content-manager.popUpWarning.bodyMessage.contentType.delete": "你确定要删除这个条目吗?",
|
||||
"content-manager.popUpWarning.bodyMessage.contentType.delete.all": "你确定要删除这些条目吗?",
|
||||
"content-manager.popUpWarning.warning.cancelAllSettings": "你确定要取消这些修改吗?",
|
||||
"content-manager.popUpWarning.warning.publish-question": "你还想发布这个条目吗?",
|
||||
"content-manager.popUpWarning.warning.unpublish": "取消发布这个内容<br></br>将自动变成草稿.",
|
||||
"content-manager.popUpWarning.warning.unpublish-question": "你确定你要取消发布吗?",
|
||||
"content-manager.popUpWarning.warning.updateAllSettings": "这将修改你的所有设置",
|
||||
"content-manager.popUpwarning.warning.has-draft-relations.button-confirm": "是, 发布",
|
||||
"content-manager.popUpwarning.warning.has-draft-relations.message": "<b>{count, plural, =0 { 个关联的内容} one { 个关联的内容} other { 个关联的内容}}</b> 尚未发布。<br></br>它可能会在你的项目上产生关联失效和错误。",
|
||||
"content-manager.popover.display-relations.label": "显示关联",
|
||||
"content-manager.success.record.delete": "已删除",
|
||||
"content-manager.success.record.publish": "已发布",
|
||||
"content-manager.success.record.save": "已保存",
|
||||
"content-manager.success.record.unpublish": "已取消发布",
|
||||
"content-manager.utils.data-loaded": "这个 {number, plural, =1 {条目} other {条目}} 已成功加载",
|
||||
"form.button.continue": "继续",
|
||||
"form.button.done": "完成",
|
||||
"form.button.finish": "结束",
|
||||
"global.prompt.unsaved": "你确定要离开这个页面吗?你所有的修改都会丢失!",
|
||||
"notification.error": "发生了一个错误",
|
||||
"form.button.save": "保存",
|
||||
"global.prompt.unsaved": "您确定要离开这个页面吗?您所有的未保存的修改都将丢失",
|
||||
"notification.contentType.relations.conflict": "内容类型有关联冲突",
|
||||
"notification.default.title": "通知:",
|
||||
"notification.error": "发生错误",
|
||||
"notification.error.layout": "无法获取布局",
|
||||
"notification.form.error.fields": "表单包含一些错误",
|
||||
"notification.form.success.fields": "修改已保存",
|
||||
"request.error.model.unknown": "这个模型已不存在"
|
||||
"notification.link-copied": "链接已复制到剪贴板",
|
||||
"notification.permission.not-allowed-read": "你不被允许查看文件",
|
||||
"notification.success.delete": "条目已删除",
|
||||
"notification.success.saved": "已保存",
|
||||
"notification.success.title": "成功:",
|
||||
"notification.version.update.link": "查看更多",
|
||||
"notification.version.update.message": "有新版本的 Strapi 可用!",
|
||||
"notification.warning.title": "警告:",
|
||||
"or": "或",
|
||||
"request.error.model.unknown": "模型不存在",
|
||||
"skipToContent": "跳至内容",
|
||||
"submit": "提交"
|
||||
}
|
||||
|
||||
@ -10,7 +10,7 @@
|
||||
</head>
|
||||
<body>
|
||||
<!-- The app hooks into this div -->
|
||||
<div style="min-height:100%;background-color:#f6f6f9;" id="app"></div>
|
||||
<div style="min-height:100%" id="app"></div>
|
||||
<!-- A lot of magic happens in this file. HtmlWebpackPlugin automatically includes all assets (e.g. bundle.js, main.css) with the correct HTML tags, which is why they are missing in this HTML file. Don't add any assets here! (Check out webpackconfig.js if you want to know more) -->
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@ -36,15 +36,15 @@
|
||||
"test:front:watch:ce": "cross-env IS_EE=false jest --config ./jest.config.front.js --watchAll"
|
||||
},
|
||||
"dependencies": {
|
||||
"@babel/core": "7.14.0",
|
||||
"@babel/plugin-proposal-async-generator-functions": "7.13.15",
|
||||
"@babel/plugin-proposal-class-properties": "7.13.0",
|
||||
"@babel/core": "7.16.7",
|
||||
"@babel/plugin-proposal-async-generator-functions": "7.16.7",
|
||||
"@babel/plugin-proposal-class-properties": "7.16.7",
|
||||
"@babel/plugin-syntax-dynamic-import": "7.8.3",
|
||||
"@babel/plugin-transform-modules-commonjs": "7.14.0",
|
||||
"@babel/plugin-transform-runtime": "7.13.15",
|
||||
"@babel/preset-env": "7.14.0",
|
||||
"@babel/preset-react": "7.13.13",
|
||||
"@babel/runtime": "7.14.0",
|
||||
"@babel/plugin-transform-modules-commonjs": "7.16.7",
|
||||
"@babel/plugin-transform-runtime": "7.16.7",
|
||||
"@babel/preset-env": "7.16.7",
|
||||
"@babel/preset-react": "7.16.7",
|
||||
"@babel/runtime": "7.16.7",
|
||||
"@casl/ability": "^4.1.5",
|
||||
"@fingerprintjs/fingerprintjs": "3.1.1",
|
||||
"@fortawesome/fontawesome-free": "^5.15.3",
|
||||
@ -53,20 +53,19 @@
|
||||
"@fortawesome/free-solid-svg-icons": "^5.15.3",
|
||||
"@fortawesome/react-fontawesome": "^0.1.14",
|
||||
"@strapi/babel-plugin-switch-ee-ce": "4.0.2",
|
||||
"@strapi/design-system": "0.0.1-alpha.70",
|
||||
"@strapi/design-system": "0.0.1-alpha.71",
|
||||
"@strapi/helper-plugin": "4.0.2",
|
||||
"@strapi/icons": "0.0.1-alpha.70",
|
||||
"@strapi/icons": "0.0.1-alpha.71",
|
||||
"@strapi/utils": "4.0.2",
|
||||
"axios": "0.24.0",
|
||||
"babel-loader": "8.2.2",
|
||||
"babel-plugin-styled-components": "1.12.0",
|
||||
"babel-loader": "8.2.3",
|
||||
"babel-plugin-styled-components": "2.0.2",
|
||||
"bcryptjs": "2.4.3",
|
||||
"chalk": "^4.1.1",
|
||||
"chokidar": "^3.5.1",
|
||||
"classnames": "^2.3.1",
|
||||
"codemirror": "^5.61.0",
|
||||
"cross-env": "^7.0.3",
|
||||
"css-loader": "5.2.4",
|
||||
"css-loader": "6.5.1",
|
||||
"date-fns": "2.22.1",
|
||||
"dotenv": "8.5.1",
|
||||
"execa": "^1.0.0",
|
||||
@ -77,10 +76,9 @@
|
||||
"highlight.js": "^10.4.1",
|
||||
"history": "^4.9.0",
|
||||
"hoist-non-react-statics": "^3.3.0",
|
||||
"html-loader": "2.1.2",
|
||||
"html-webpack-plugin": "5.3.1",
|
||||
"html-loader": "3.0.1",
|
||||
"html-webpack-plugin": "5.5.0",
|
||||
"immer": "9.0.6",
|
||||
"immutable": "^3.8.2",
|
||||
"invariant": "^2.2.4",
|
||||
"is-wsl": "2.2.0",
|
||||
"js-cookie": "2.2.1",
|
||||
@ -102,7 +100,7 @@
|
||||
"match-sorter": "^4.0.2",
|
||||
"mini-css-extract-plugin": "2.4.4",
|
||||
"moment": "^2.29.1",
|
||||
"node-polyfill-webpack-plugin": "1.1.0",
|
||||
"node-polyfill-webpack-plugin": "1.1.4",
|
||||
"p-map": "4.0.0",
|
||||
"passport-local": "1.0.0",
|
||||
"prop-types": "^15.7.2",
|
||||
@ -116,30 +114,25 @@
|
||||
"react-fast-compare": "^3.2.0",
|
||||
"react-helmet": "^6.1.0",
|
||||
"react-intl": "5.20.2",
|
||||
"react-loadable": "^5.5.0",
|
||||
"react-query": "3.24.3",
|
||||
"react-redux": "7.2.3",
|
||||
"react-router": "5.2.0",
|
||||
"react-router-dom": "5.2.0",
|
||||
"react-select": "^4.0.2",
|
||||
"react-tooltip": "4.2.18",
|
||||
"react-transition-group": "4.4.1",
|
||||
"react-virtualized": "^9.22.3",
|
||||
"redux": "^4.0.1",
|
||||
"redux-immutable": "^4.0.0",
|
||||
"redux-saga": "^0.16.0",
|
||||
"reselect": "^4.0.0",
|
||||
"rimraf": "3.0.2",
|
||||
"sanitize-html": "2.4.0",
|
||||
"sanitize.css": "^4.1.0",
|
||||
"semver": "7.3.5",
|
||||
"sift": "13.5.0",
|
||||
"style-loader": "2.0.0",
|
||||
"style-loader": "3.3.1",
|
||||
"styled-components": "^5.2.3",
|
||||
"terser-webpack-plugin": "4.2.3",
|
||||
"webpack": "5.36.2",
|
||||
"terser-webpack-plugin": "5.3.0",
|
||||
"webpack": "5.65.0",
|
||||
"webpack-cli": "4.9.1",
|
||||
"webpack-dev-server": "4.5.0",
|
||||
"webpack-dev-server": "4.7.2",
|
||||
"webpackbar": "5.0.0-3",
|
||||
"yup": "^0.32.9"
|
||||
},
|
||||
@ -148,7 +141,7 @@
|
||||
"webpack-bundle-analyzer": "4.4.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12.x.x <=16.x.x",
|
||||
"node": ">=12.22.0 <=16.x.x",
|
||||
"npm": ">=6.0.0"
|
||||
}
|
||||
}
|
||||
|
||||
@ -7,11 +7,9 @@ const alias = [
|
||||
'whatwg-fetch',
|
||||
'@fortawesome/fontawesome-svg-core',
|
||||
'@fortawesome/free-solid-svg-icons',
|
||||
'classnames',
|
||||
'history',
|
||||
'hoist-non-react-statics',
|
||||
'immer',
|
||||
'immutable',
|
||||
'invariant',
|
||||
'lodash',
|
||||
'moment',
|
||||
@ -26,16 +24,12 @@ const alias = [
|
||||
'react-helmet',
|
||||
'react-is',
|
||||
'react-intl',
|
||||
'react-loadable',
|
||||
'react-redux',
|
||||
'react-router',
|
||||
'react-router-dom',
|
||||
'react-transition-group',
|
||||
'react-tooltip',
|
||||
'react-virtualized',
|
||||
'react-select',
|
||||
'redux',
|
||||
'redux-immutable',
|
||||
'reselect',
|
||||
'styled-components',
|
||||
'yup',
|
||||
|
||||
@ -88,9 +88,6 @@ module.exports = ({
|
||||
},
|
||||
},
|
||||
parallel: !isWsl,
|
||||
// Enable file caching
|
||||
cache: true,
|
||||
sourceMap: false,
|
||||
}),
|
||||
],
|
||||
runtimeChunk: true,
|
||||
@ -176,6 +173,7 @@ module.exports = ({
|
||||
symlinks: false,
|
||||
extensions: ['.js', '.jsx', '.react.js'],
|
||||
mainFields: ['browser', 'jsnext:main', 'main'],
|
||||
modules: ['node_modules', path.resolve(__dirname, 'node_modules')],
|
||||
},
|
||||
plugins: [
|
||||
new HtmlWebpackPlugin({
|
||||
|
||||
@ -28,7 +28,7 @@
|
||||
"lodash": "4.17.21"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12.x.x <=16.x.x",
|
||||
"node": ">=12.22.0 <=16.x.x",
|
||||
"npm": ">=6.0.0"
|
||||
},
|
||||
"strapi": {
|
||||
|
||||
@ -45,7 +45,7 @@
|
||||
"yup": "^0.32.9"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12.x.x <=16.x.x",
|
||||
"node": ">=12.22.0 <=16.x.x",
|
||||
"npm": ">=6.0.0"
|
||||
},
|
||||
"strapi": {
|
||||
|
||||
@ -9,7 +9,7 @@ const getDialectClass = client => {
|
||||
case 'sqlite':
|
||||
return require('./sqlite');
|
||||
default:
|
||||
throw new Error(`Unknow dialect ${client}`);
|
||||
throw new Error(`Unknown dialect ${client}`);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@ -390,7 +390,7 @@ const createJoinTable = (metadata, { attributeName, attribute, meta }) => {
|
||||
const targetMeta = metadata.get(attribute.target);
|
||||
|
||||
if (!targetMeta) {
|
||||
throw new Error(`Unknow target ${attribute.target}`);
|
||||
throw new Error(`Unknown target ${attribute.target}`);
|
||||
}
|
||||
|
||||
const joinTableName = _.snakeCase(`${meta.tableName}_${attributeName}_links`);
|
||||
|
||||
@ -177,7 +177,7 @@ const getColumnType = attribute => {
|
||||
return { type: 'boolean' };
|
||||
}
|
||||
default: {
|
||||
throw new Error(`Unknow type ${attribute.type}`);
|
||||
throw new Error(`Unknown type ${attribute.type}`);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@ -39,7 +39,7 @@
|
||||
"umzug": "2.3.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12.x.x <=16.x.x",
|
||||
"node": ">=12.22.0 <=16.x.x",
|
||||
"npm": ">=6.0.0"
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { axiosInstance } from '../../../../../../admin/admin/src/core/utils';
|
||||
import axiosInstance from '../../../utils/axiosInstance';
|
||||
|
||||
const fetchEmailSettings = async () => {
|
||||
const { data } = await axiosInstance.get('/email/settings');
|
||||
|
||||
36
packages/core/email/admin/src/utils/axiosInstance.js
Normal file
36
packages/core/email/admin/src/utils/axiosInstance.js
Normal file
@ -0,0 +1,36 @@
|
||||
import axios from 'axios';
|
||||
import { auth } from '@strapi/helper-plugin';
|
||||
|
||||
const instance = axios.create({
|
||||
baseURL: process.env.STRAPI_ADMIN_BACKEND_URL,
|
||||
});
|
||||
|
||||
instance.interceptors.request.use(
|
||||
async config => {
|
||||
config.headers = {
|
||||
Authorization: `Bearer ${auth.getToken()}`,
|
||||
Accept: 'application/json',
|
||||
'Content-Type': 'application/json',
|
||||
};
|
||||
|
||||
return config;
|
||||
},
|
||||
error => {
|
||||
Promise.reject(error);
|
||||
}
|
||||
);
|
||||
|
||||
instance.interceptors.response.use(
|
||||
response => response,
|
||||
error => {
|
||||
// whatever you want to do with the error
|
||||
if (error.response?.status === 401) {
|
||||
auth.clearAppStorage();
|
||||
window.location.reload();
|
||||
}
|
||||
|
||||
throw error;
|
||||
}
|
||||
);
|
||||
|
||||
export default instance;
|
||||
@ -34,7 +34,7 @@
|
||||
"@strapi/helper-plugin": "4.0.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12.x.x <=16.x.x",
|
||||
"node": ">=12.22.0 <=16.x.x",
|
||||
"npm": ">=6.0.0"
|
||||
},
|
||||
"strapi": {
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
const errorsTrads = {
|
||||
email: 'components.Input.error.validation.email',
|
||||
json: 'components.Input.error.validation.json',
|
||||
lowercase: 'components.Input.error.validation.lowercase',
|
||||
max: 'components.Input.error.validation.max',
|
||||
maxLength: 'components.Input.error.validation.maxLength',
|
||||
min: 'components.Input.error.validation.min',
|
||||
|
||||
@ -45,10 +45,8 @@
|
||||
"@fortawesome/free-solid-svg-icons": "^5.15.3",
|
||||
"@fortawesome/react-fontawesome": "^0.1.14",
|
||||
"axios": "0.24.0",
|
||||
"babel-plugin-styled-components": "1.12.0",
|
||||
"classnames": "^2.3.1",
|
||||
"babel-plugin-styled-components": "2.0.2",
|
||||
"formik": "2.2.9",
|
||||
"immutable": "^3.8.2",
|
||||
"invariant": "^2.2.1",
|
||||
"lodash": "4.17.21",
|
||||
"match-sorter": "^4.0.2",
|
||||
@ -64,22 +62,22 @@
|
||||
"whatwg-fetch": "^3.6.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "7.14.0",
|
||||
"@babel/plugin-proposal-class-properties": "7.13.0",
|
||||
"@babel/plugin-proposal-export-default-from": "7.14.5",
|
||||
"@babel/plugin-proposal-function-bind": "7.14.5",
|
||||
"@babel/plugin-transform-runtime": "7.13.15",
|
||||
"@babel/preset-env": "7.14.0",
|
||||
"@babel/preset-react": "7.13.13",
|
||||
"@babel/runtime": "7.14.0",
|
||||
"@babel/core": "7.16.7",
|
||||
"@babel/plugin-proposal-class-properties": "7.16.7",
|
||||
"@babel/plugin-proposal-export-default-from": "7.16.7",
|
||||
"@babel/plugin-proposal-function-bind": "7.16.7",
|
||||
"@babel/plugin-transform-runtime": "7.16.7",
|
||||
"@babel/preset-env": "7.16.7",
|
||||
"@babel/preset-react": "7.16.7",
|
||||
"@babel/runtime": "7.16.7",
|
||||
"@storybook/addon-actions": "^6.3.7",
|
||||
"@storybook/addon-essentials": "^6.3.7",
|
||||
"@storybook/addon-links": "^6.3.7",
|
||||
"@storybook/builder-webpack5": "^6.3.7",
|
||||
"@storybook/manager-webpack5": "^6.3.7",
|
||||
"@storybook/react": "^6.3.7",
|
||||
"@strapi/design-system": "0.0.1-alpha.70",
|
||||
"@strapi/icons": "0.0.1-alpha.70",
|
||||
"@strapi/design-system": "0.0.1-alpha.71",
|
||||
"@strapi/icons": "0.0.1-alpha.71",
|
||||
"babel-loader": "^8.2.2",
|
||||
"cross-env": "^7.0.3",
|
||||
"date-fns": "2.22.1",
|
||||
@ -94,7 +92,7 @@
|
||||
"react-select": "^4.0.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12.x.x <=16.x.x",
|
||||
"node": ">=12.22.0 <=16.x.x",
|
||||
"npm": ">=6.0.0"
|
||||
}
|
||||
}
|
||||
|
||||
@ -133,7 +133,6 @@ program
|
||||
|
||||
program
|
||||
.command('build')
|
||||
.option('--clean', 'Remove the build and .cache folders', false)
|
||||
.option('--no-optimization', 'Build the Administration without assets optimization')
|
||||
.description('Builds the strapi admin app')
|
||||
.action(getLocalScript('build'));
|
||||
|
||||
@ -12,7 +12,7 @@ const getEnabledPlugins = require('../core/loaders/plugins/get-enabled-plugins')
|
||||
/**
|
||||
* `$ strapi build`
|
||||
*/
|
||||
module.exports = async ({ clean, optimization, forceBuild = true }) => {
|
||||
module.exports = async ({ optimization, forceBuild = true }) => {
|
||||
const dir = process.cwd();
|
||||
|
||||
const strapiInstance = strapi({
|
||||
@ -28,9 +28,8 @@ module.exports = async ({ clean, optimization, forceBuild = true }) => {
|
||||
|
||||
console.log(`Building your admin UI with ${green(env)} configuration ...`);
|
||||
|
||||
if (clean) {
|
||||
await strapiAdmin.clean({ dir });
|
||||
}
|
||||
// Always remove the .cache and build folders
|
||||
await strapiAdmin.clean({ dir });
|
||||
|
||||
ee({ dir });
|
||||
|
||||
|
||||
@ -29,7 +29,7 @@ module.exports = async function({ build, watchAdmin, polling, browser }) {
|
||||
// Don't run the build process if the admin is in watch mode
|
||||
if (build && !watchAdmin && serveAdminPanel && !buildExists) {
|
||||
try {
|
||||
await buildAdmin({ clean: false, optimization: false, forceBuild: false });
|
||||
await buildAdmin({ optimization: false, forceBuild: false });
|
||||
} catch (err) {
|
||||
process.exit(1);
|
||||
}
|
||||
@ -126,6 +126,8 @@ function watchFileChanges({ dir, strapiInstance, watchIgnoreFiles, polling }) {
|
||||
'**/node_modules',
|
||||
'**/node_modules/**',
|
||||
'**/plugins.json',
|
||||
'**/build',
|
||||
'**/build/**',
|
||||
'**/index.html',
|
||||
'**/public',
|
||||
'**/public/**',
|
||||
|
||||
@ -132,7 +132,7 @@
|
||||
"supertest": "^6.1.6"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12.x.x <=16.x.x",
|
||||
"node": ">=12.22.0 <=16.x.x",
|
||||
"npm": ">=6.0.0"
|
||||
}
|
||||
}
|
||||
|
||||
@ -27,26 +27,18 @@ const Extension = styled.span`
|
||||
text-transform: uppercase;
|
||||
`;
|
||||
|
||||
export const UploadingAssetCard = ({
|
||||
name,
|
||||
extension,
|
||||
assetType,
|
||||
file,
|
||||
onCancel,
|
||||
onStatusChange,
|
||||
addUploadedFiles,
|
||||
}) => {
|
||||
const { upload, cancel, error, progress, status } = useUpload();
|
||||
export const UploadingAssetCard = ({ asset, onCancel, onStatusChange, addUploadedFiles }) => {
|
||||
const { upload, cancel, error, progress, status } = useUpload(asset);
|
||||
const { formatMessage } = useIntl();
|
||||
|
||||
let badgeContent;
|
||||
|
||||
if (assetType === AssetType.Image) {
|
||||
if (asset.type === AssetType.Image) {
|
||||
badgeContent = formatMessage({
|
||||
id: getTrad('settings.section.image.label'),
|
||||
defaultMessage: 'Image',
|
||||
});
|
||||
} else if (assetType === AssetType.Video) {
|
||||
} else if (asset.type === AssetType.Video) {
|
||||
badgeContent = formatMessage({
|
||||
id: getTrad('settings.section.video.label'),
|
||||
defaultMessage: 'Video',
|
||||
@ -60,7 +52,7 @@ export const UploadingAssetCard = ({
|
||||
|
||||
useEffect(() => {
|
||||
const uploadFile = async () => {
|
||||
const files = await upload(file);
|
||||
const files = await upload(asset);
|
||||
|
||||
if (addUploadedFiles) {
|
||||
addUploadedFiles(files);
|
||||
@ -77,7 +69,7 @@ export const UploadingAssetCard = ({
|
||||
|
||||
const handleCancel = () => {
|
||||
cancel();
|
||||
onCancel(file);
|
||||
onCancel(asset.rawFile);
|
||||
};
|
||||
|
||||
return (
|
||||
@ -90,9 +82,9 @@ export const UploadingAssetCard = ({
|
||||
</CardHeader>
|
||||
<CardBody>
|
||||
<CardContent>
|
||||
<CardTitle as="h2">{name}</CardTitle>
|
||||
<CardTitle as="h2">{asset.name}</CardTitle>
|
||||
<CardSubtitle>
|
||||
<Extension>{extension}</Extension>
|
||||
<Extension>{asset.ext}</Extension>
|
||||
</CardSubtitle>
|
||||
</CardContent>
|
||||
<CardBadge>{badgeContent}</CardBadge>
|
||||
@ -115,10 +107,12 @@ UploadingAssetCard.defaultProps = {
|
||||
|
||||
UploadingAssetCard.propTypes = {
|
||||
addUploadedFiles: PropTypes.func,
|
||||
assetType: PropTypes.oneOf(Object.values(AssetType)).isRequired,
|
||||
extension: PropTypes.string.isRequired,
|
||||
name: PropTypes.string.isRequired,
|
||||
file: PropTypes.instanceOf(File).isRequired,
|
||||
asset: PropTypes.shape({
|
||||
name: PropTypes.string,
|
||||
ext: PropTypes.string,
|
||||
rawFile: PropTypes.instanceOf(File),
|
||||
type: PropTypes.oneOf(Object.values(AssetType)),
|
||||
}).isRequired,
|
||||
onCancel: PropTypes.func.isRequired,
|
||||
onStatusChange: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
@ -7,6 +7,7 @@
|
||||
import PropTypes from 'prop-types';
|
||||
import React, { useRef, useState } from 'react';
|
||||
import { useIntl } from 'react-intl';
|
||||
import isEqual from 'lodash/isEqual';
|
||||
import {
|
||||
ModalLayout,
|
||||
ModalHeader,
|
||||
@ -76,141 +77,162 @@ export const EditAssetDialog = ({
|
||||
|
||||
const formDisabled = !canUpdate || isCropping;
|
||||
|
||||
const handleConfirmClose = () => {
|
||||
// eslint-disable-next-line no-alert
|
||||
const confirm = window.confirm(
|
||||
formatMessage({
|
||||
id: 'window.confirm.close-modal.file',
|
||||
defaultMessage: 'Are you sure? Your changes will be lost.',
|
||||
})
|
||||
);
|
||||
|
||||
if (confirm) {
|
||||
onClose();
|
||||
}
|
||||
};
|
||||
|
||||
const initialFormData = {
|
||||
name: asset.name,
|
||||
alternativeText: asset.alternativeText || '',
|
||||
caption: asset.caption || '',
|
||||
};
|
||||
|
||||
const handleClose = values => {
|
||||
if (!isEqual(initialFormData, values)) {
|
||||
handleConfirmClose();
|
||||
} else {
|
||||
onClose();
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<ModalLayout onClose={() => onClose()} labelledBy="title">
|
||||
<ModalHeader>
|
||||
<Typography fontWeight="bold" textColor="neutral800" as="h2" id="title">
|
||||
{formatMessage({ id: getTrad('modal.edit.title'), defaultMessage: 'Details' })}
|
||||
</Typography>
|
||||
</ModalHeader>
|
||||
<ModalBody>
|
||||
<Grid gap={4}>
|
||||
<GridItem xs={12} col={6}>
|
||||
<PreviewBox
|
||||
asset={asset}
|
||||
canUpdate={canUpdate}
|
||||
canCopyLink={canCopyLink}
|
||||
canDownload={canDownload}
|
||||
onDelete={onClose}
|
||||
onCropFinish={handleFinishCropping}
|
||||
onCropStart={handleStartCropping}
|
||||
onCropCancel={handleCancelCropping}
|
||||
replacementFile={replacementFile}
|
||||
trackedLocation={trackedLocation}
|
||||
/>
|
||||
</GridItem>
|
||||
<GridItem xs={12} col={6}>
|
||||
<Formik
|
||||
validationSchema={fileInfoSchema}
|
||||
validateOnChange={false}
|
||||
onSubmit={handleSubmit}
|
||||
initialValues={{
|
||||
name: asset.name,
|
||||
alternativeText: asset.alternativeText || '',
|
||||
caption: asset.caption || '',
|
||||
}}
|
||||
>
|
||||
{({ values, errors, handleChange }) => (
|
||||
<Form noValidate>
|
||||
<Stack size={3}>
|
||||
<AssetMeta
|
||||
size={formatBytes(asset.size)}
|
||||
dimension={
|
||||
asset.height && asset.width ? `${asset.height}✕${asset.width}` : ''
|
||||
}
|
||||
date={formatDate(new Date(asset.createdAt))}
|
||||
extension={getFileExtension(asset.ext)}
|
||||
/>
|
||||
<Formik
|
||||
validationSchema={fileInfoSchema}
|
||||
validateOnChange={false}
|
||||
onSubmit={handleSubmit}
|
||||
initialValues={initialFormData}
|
||||
>
|
||||
{({ values, errors, handleChange }) => (
|
||||
<ModalLayout onClose={() => handleClose(values)} labelledBy="title">
|
||||
<ModalHeader>
|
||||
<Typography fontWeight="bold" textColor="neutral800" as="h2" id="title">
|
||||
{formatMessage({ id: getTrad('modal.edit.title'), defaultMessage: 'Details' })}
|
||||
</Typography>
|
||||
</ModalHeader>
|
||||
<ModalBody>
|
||||
<Grid gap={4}>
|
||||
<GridItem xs={12} col={6}>
|
||||
<PreviewBox
|
||||
asset={asset}
|
||||
canUpdate={canUpdate}
|
||||
canCopyLink={canCopyLink}
|
||||
canDownload={canDownload}
|
||||
onDelete={onClose}
|
||||
onCropFinish={handleFinishCropping}
|
||||
onCropStart={handleStartCropping}
|
||||
onCropCancel={handleCancelCropping}
|
||||
replacementFile={replacementFile}
|
||||
trackedLocation={trackedLocation}
|
||||
/>
|
||||
</GridItem>
|
||||
<GridItem xs={12} col={6}>
|
||||
<Form noValidate>
|
||||
<Stack size={3}>
|
||||
<AssetMeta
|
||||
size={formatBytes(asset.size)}
|
||||
dimension={
|
||||
asset.height && asset.width ? `${asset.height}✕${asset.width}` : ''
|
||||
}
|
||||
date={formatDate(new Date(asset.createdAt))}
|
||||
extension={getFileExtension(asset.ext)}
|
||||
/>
|
||||
|
||||
<TextInput
|
||||
size="S"
|
||||
label={formatMessage({
|
||||
id: getTrad('form.input.label.file-name'),
|
||||
defaultMessage: 'File name',
|
||||
})}
|
||||
name="name"
|
||||
value={values.name}
|
||||
error={errors.name}
|
||||
onChange={handleChange}
|
||||
disabled={formDisabled}
|
||||
/>
|
||||
<TextInput
|
||||
size="S"
|
||||
label={formatMessage({
|
||||
id: getTrad('form.input.label.file-name'),
|
||||
defaultMessage: 'File name',
|
||||
})}
|
||||
name="name"
|
||||
value={values.name}
|
||||
error={errors.name}
|
||||
onChange={handleChange}
|
||||
disabled={formDisabled}
|
||||
/>
|
||||
|
||||
<TextInput
|
||||
size="S"
|
||||
label={formatMessage({
|
||||
id: getTrad('form.input.label.file-alt'),
|
||||
defaultMessage: 'Alternative text',
|
||||
})}
|
||||
name="alternativeText"
|
||||
hint={formatMessage({
|
||||
id: getTrad({ id: getTrad('form.input.decription.file-alt') }),
|
||||
defaultMessage:
|
||||
'This text will be displayed if the asset can’t be shown.',
|
||||
})}
|
||||
value={values.alternativeText}
|
||||
error={errors.alternativeText}
|
||||
onChange={handleChange}
|
||||
disabled={formDisabled}
|
||||
/>
|
||||
<TextInput
|
||||
size="S"
|
||||
label={formatMessage({
|
||||
id: getTrad('form.input.label.file-alt'),
|
||||
defaultMessage: 'Alternative text',
|
||||
})}
|
||||
name="alternativeText"
|
||||
hint={formatMessage({
|
||||
id: getTrad({ id: getTrad('form.input.decription.file-alt') }),
|
||||
defaultMessage: 'This text will be displayed if the asset can’t be shown.',
|
||||
})}
|
||||
value={values.alternativeText}
|
||||
error={errors.alternativeText}
|
||||
onChange={handleChange}
|
||||
disabled={formDisabled}
|
||||
/>
|
||||
|
||||
<TextInput
|
||||
size="S"
|
||||
label={formatMessage({
|
||||
id: getTrad('form.input.label.file-caption'),
|
||||
defaultMessage: 'Caption',
|
||||
})}
|
||||
name="caption"
|
||||
value={values.caption}
|
||||
error={errors.caption}
|
||||
onChange={handleChange}
|
||||
disabled={formDisabled}
|
||||
/>
|
||||
</Stack>
|
||||
<TextInput
|
||||
size="S"
|
||||
label={formatMessage({
|
||||
id: getTrad('form.input.label.file-caption'),
|
||||
defaultMessage: 'Caption',
|
||||
})}
|
||||
name="caption"
|
||||
value={values.caption}
|
||||
error={errors.caption}
|
||||
onChange={handleChange}
|
||||
disabled={formDisabled}
|
||||
/>
|
||||
</Stack>
|
||||
|
||||
<VisuallyHidden>
|
||||
<button
|
||||
type="submit"
|
||||
tabIndex={-1}
|
||||
ref={submitButtonRef}
|
||||
disabled={formDisabled}
|
||||
>
|
||||
{formatMessage({ id: 'submit', defaultMessage: 'Submit' })}
|
||||
</button>
|
||||
</VisuallyHidden>
|
||||
</Form>
|
||||
)}
|
||||
</Formik>
|
||||
</GridItem>
|
||||
</Grid>
|
||||
</ModalBody>
|
||||
<ModalFooter
|
||||
startActions={
|
||||
<Button onClick={() => onClose()} variant="tertiary">
|
||||
{formatMessage({ id: 'cancel', defaultMessage: 'Cancel' })}
|
||||
</Button>
|
||||
}
|
||||
endActions={
|
||||
<>
|
||||
<ReplaceMediaButton
|
||||
onSelectMedia={setReplacementFile}
|
||||
acceptedMime={asset.mime}
|
||||
disabled={formDisabled}
|
||||
trackedLocation={trackedLocation}
|
||||
/>
|
||||
|
||||
<Button
|
||||
onClick={() => submitButtonRef.current.click()}
|
||||
loading={isLoading}
|
||||
disabled={formDisabled}
|
||||
>
|
||||
{formatMessage({ id: 'form.button.finish', defaultMessage: 'Finish' })}
|
||||
<VisuallyHidden>
|
||||
<button
|
||||
type="submit"
|
||||
tabIndex={-1}
|
||||
ref={submitButtonRef}
|
||||
disabled={formDisabled}
|
||||
>
|
||||
{formatMessage({ id: 'submit', defaultMessage: 'Submit' })}
|
||||
</button>
|
||||
</VisuallyHidden>
|
||||
</Form>
|
||||
</GridItem>
|
||||
</Grid>
|
||||
</ModalBody>
|
||||
<ModalFooter
|
||||
startActions={
|
||||
<Button onClick={() => handleClose(values)} variant="tertiary">
|
||||
{formatMessage({ id: 'cancel', defaultMessage: 'Cancel' })}
|
||||
</Button>
|
||||
</>
|
||||
}
|
||||
/>
|
||||
</ModalLayout>
|
||||
</>
|
||||
}
|
||||
endActions={
|
||||
<>
|
||||
<ReplaceMediaButton
|
||||
onSelectMedia={setReplacementFile}
|
||||
acceptedMime={asset.mime}
|
||||
disabled={formDisabled}
|
||||
trackedLocation={trackedLocation}
|
||||
/>
|
||||
|
||||
<Button
|
||||
onClick={() => submitButtonRef.current.click()}
|
||||
loading={isLoading}
|
||||
disabled={formDisabled}
|
||||
>
|
||||
{formatMessage({ id: 'form.button.finish', defaultMessage: 'Finish' })}
|
||||
</Button>
|
||||
</>
|
||||
}
|
||||
/>
|
||||
</ModalLayout>
|
||||
)}
|
||||
</Formik>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
import React from 'react';
|
||||
import { render, screen, fireEvent, waitFor } from '@testing-library/react';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
import { ThemeProvider, lightTheme } from '@strapi/design-system';
|
||||
import { IntlProvider } from 'react-intl';
|
||||
import { QueryClient, QueryClientProvider } from 'react-query';
|
||||
@ -108,12 +109,16 @@ const renderCompo = (
|
||||
|
||||
describe('<EditAssetDialog />', () => {
|
||||
const RealNow = Date.now;
|
||||
let confirmSpy;
|
||||
|
||||
beforeAll(() => {
|
||||
confirmSpy = jest.spyOn(window, 'confirm');
|
||||
confirmSpy.mockImplementation(jest.fn(() => true));
|
||||
global.Date.now = jest.fn(() => new Date('2021-09-20').getTime());
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
confirmSpy.mockRestore();
|
||||
global.Date.now = RealNow;
|
||||
});
|
||||
|
||||
@ -132,6 +137,15 @@ describe('<EditAssetDialog />', () => {
|
||||
expect(screen.getByLabelText('Caption')).toHaveValue('');
|
||||
});
|
||||
|
||||
it('open confirm box on close if data has changed', () => {
|
||||
renderCompo();
|
||||
|
||||
userEvent.type(screen.getByLabelText('Alternative text'), 'Test');
|
||||
fireEvent.click(screen.getByText('Cancel'));
|
||||
|
||||
expect(window.confirm).toBeCalled();
|
||||
});
|
||||
|
||||
it('disables all the actions and field when the user is not allowed to update', () => {
|
||||
renderCompo({ canUpdate: false, canCopyLink: false, canDownload: false });
|
||||
|
||||
|
||||
@ -107,11 +107,8 @@ export const PendingAssetStep = ({
|
||||
<UploadingAssetCard
|
||||
// Props used to store the newly uploaded files
|
||||
addUploadedFiles={addUploadedFiles}
|
||||
assetType={asset.type}
|
||||
extension={asset.ext}
|
||||
file={asset.rawFile}
|
||||
asset={asset}
|
||||
id={assetKey}
|
||||
name={asset.name}
|
||||
onCancel={onCancelUpload}
|
||||
onStatusChange={status => handleStatusChange(status, asset.rawFile)}
|
||||
size="S"
|
||||
@ -148,7 +145,9 @@ export const PendingAssetStep = ({
|
||||
<Button type="submit" loading={uploadStatus === Status.Uploading}>
|
||||
{formatMessage(
|
||||
{
|
||||
id: getTrad('modal.upload-list.footer.button.singular'),
|
||||
id: getTrad(
|
||||
`modal.upload-list.footer.button.${assets.length > 1 ? 'plural' : 'singular'}`
|
||||
),
|
||||
defaultMessage: 'Upload assets',
|
||||
},
|
||||
{ number: assets.length }
|
||||
|
||||
@ -1041,7 +1041,7 @@ exports[`PendingAssetStep snapshots the component with valid cards 1`] = `
|
||||
<span
|
||||
class="c15 c16"
|
||||
>
|
||||
Upload {number} asset to the library
|
||||
Upload {number} assets to the library
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
import React, { useState } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { ModalLayout } from '@strapi/design-system/ModalLayout';
|
||||
import { useIntl } from 'react-intl';
|
||||
import { AddAssetStep } from './AddAssetStep/AddAssetStep';
|
||||
import { PendingAssetStep } from './PendingAssetStep/PendingAssetStep';
|
||||
import { EditAssetDialog } from '../EditAssetDialog';
|
||||
@ -17,6 +18,7 @@ export const UploadAssetDialog = ({
|
||||
addUploadedFiles,
|
||||
trackedLocation,
|
||||
}) => {
|
||||
const { formatMessage } = useIntl();
|
||||
const [step, setStep] = useState(initialAssetsToAdd ? Steps.PendingAsset : Steps.AddAsset);
|
||||
const [assets, setAssets] = useState(initialAssetsToAdd || []);
|
||||
const [assetToEdit, setAssetToEdit] = useState(undefined);
|
||||
@ -58,8 +60,26 @@ export const UploadAssetDialog = ({
|
||||
setAssetToEdit(undefined);
|
||||
};
|
||||
|
||||
const handleClose = () => {
|
||||
if (step === Steps.PendingAsset && assets.length > 0) {
|
||||
// eslint-disable-next-line no-alert
|
||||
const confirm = window.confirm(
|
||||
formatMessage({
|
||||
id: 'window.confirm.close-modal.files',
|
||||
defaultMessage: 'Are you sure? You have some files that have not been uploaded yet.',
|
||||
})
|
||||
);
|
||||
|
||||
if (confirm) {
|
||||
onClose();
|
||||
}
|
||||
} else {
|
||||
onClose();
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<ModalLayout onClose={onClose} labelledBy="title">
|
||||
<ModalLayout onClose={handleClose} labelledBy="title">
|
||||
{step === Steps.AddAsset && (
|
||||
<AddAssetStep
|
||||
onClose={onClose}
|
||||
@ -70,7 +90,7 @@ export const UploadAssetDialog = ({
|
||||
|
||||
{step === Steps.PendingAsset && (
|
||||
<PendingAssetStep
|
||||
onClose={onClose}
|
||||
onClose={handleClose}
|
||||
assets={assets}
|
||||
onEditAsset={setAssetToEdit}
|
||||
onClickAddAsset={moveToAddAsset}
|
||||
|
||||
@ -31,9 +31,17 @@ const render = (props = { onClose: () => {} }) =>
|
||||
);
|
||||
|
||||
describe('UploadAssetDialog', () => {
|
||||
beforeAll(() => server.listen());
|
||||
let confirmSpy;
|
||||
beforeAll(() => {
|
||||
confirmSpy = jest.spyOn(window, 'confirm');
|
||||
confirmSpy.mockImplementation(jest.fn(() => true));
|
||||
server.listen();
|
||||
});
|
||||
afterEach(() => server.resetHandlers());
|
||||
afterAll(() => server.close());
|
||||
afterAll(() => {
|
||||
confirmSpy.mockRestore();
|
||||
server.close();
|
||||
});
|
||||
|
||||
describe('from computer', () => {
|
||||
it('snapshots the component', () => {
|
||||
@ -51,7 +59,7 @@ describe('UploadAssetDialog', () => {
|
||||
expect(onCloseSpy).toBeCalled();
|
||||
});
|
||||
|
||||
it('closes the dialog when clicking on cancel on the pending asset step', () => {
|
||||
it('open confirm box when clicking on cancel on the pending asset step', () => {
|
||||
const file = new File(['Some stuff'], 'test.png', { type: 'image/png' });
|
||||
const onCloseSpy = jest.fn();
|
||||
|
||||
@ -63,7 +71,7 @@ describe('UploadAssetDialog', () => {
|
||||
fireEvent.change(container.querySelector('[type="file"]'), { target: { files: fileList } });
|
||||
fireEvent.click(screen.getByText('app.components.Button.cancel'));
|
||||
|
||||
expect(onCloseSpy).toBeCalled();
|
||||
expect(window.confirm).toBeCalled();
|
||||
});
|
||||
|
||||
[
|
||||
|
||||
@ -7,17 +7,18 @@ import pluginId from '../pluginId';
|
||||
|
||||
const endpoint = `/${pluginId}`;
|
||||
|
||||
const uploadAsset = (file, cancelToken, onProgress) => {
|
||||
const uploadAsset = (asset, cancelToken, onProgress) => {
|
||||
const { rawFile, caption, name, alternativeText } = asset;
|
||||
const formData = new FormData();
|
||||
|
||||
formData.append('files', file);
|
||||
formData.append('files', rawFile);
|
||||
|
||||
formData.append(
|
||||
'fileInfo',
|
||||
JSON.stringify({
|
||||
alternativeText: file.name,
|
||||
caption: file.name,
|
||||
name: file.name,
|
||||
name,
|
||||
caption: caption || name,
|
||||
alternativeText: alternativeText || name,
|
||||
})
|
||||
);
|
||||
|
||||
|
||||
@ -42,7 +42,7 @@
|
||||
"sharp": "0.29.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12.x.x <=16.x.x",
|
||||
"node": ">=12.22.0 <=16.x.x",
|
||||
"npm": ">=6.0.0"
|
||||
},
|
||||
"strapi": {
|
||||
|
||||
@ -42,7 +42,7 @@
|
||||
"yup": "0.32.9"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12.x.x <=16.x.x",
|
||||
"node": ">=12.22.0 <=16.x.x",
|
||||
"npm": ">=6.0.0"
|
||||
}
|
||||
}
|
||||
|
||||
@ -143,7 +143,7 @@ async function checkTemplateContentsStructure(templateContentsPath) {
|
||||
await checkPathContents(itemPath, nextParents);
|
||||
} else {
|
||||
throw Error(
|
||||
`Illegal template structure, unknow file ${chalk.green(nextParents.join('/'))}`
|
||||
`Illegal template structure, unknown file ${chalk.green(nextParents.join('/'))}`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ -49,7 +49,7 @@
|
||||
"uuid": "^3.3.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12.x.x <=16.x.x",
|
||||
"node": ">=12.22.0 <=16.x.x",
|
||||
"npm": ">=6.0.0"
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,25 +0,0 @@
|
||||
/**
|
||||
*
|
||||
* This component is the skeleton around the actual pages, and should only
|
||||
* contain code that should be seen on all pages. (e.g. navigation bar)
|
||||
*
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { Switch, Route } from 'react-router-dom';
|
||||
import { NotFound } from '@strapi/helper-plugin';
|
||||
import pluginId from '../../pluginId';
|
||||
import HomePage from '../HomePage';
|
||||
|
||||
const App = () => {
|
||||
return (
|
||||
<div>
|
||||
<Switch>
|
||||
<Route path={`/plugins/${pluginId}`} component={HomePage} exact />
|
||||
<Route component={NotFound} />
|
||||
</Switch>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default App;
|
||||
@ -1,20 +0,0 @@
|
||||
/*
|
||||
*
|
||||
* HomePage
|
||||
*
|
||||
*/
|
||||
|
||||
import React, { memo } from 'react';
|
||||
// import PropTypes from 'prop-types';
|
||||
import pluginId from '../../pluginId';
|
||||
|
||||
const HomePage = () => {
|
||||
return (
|
||||
<div>
|
||||
<h1>{pluginId}'s HomePage</h1>
|
||||
<p>Happy coding</p>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default memo(HomePage);
|
||||
@ -1,26 +0,0 @@
|
||||
/**
|
||||
*
|
||||
* Initializer
|
||||
*
|
||||
*/
|
||||
|
||||
import { useEffect, useRef } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import pluginId from '../../pluginId';
|
||||
|
||||
const Initializer = ({ setPlugin }) => {
|
||||
const ref = useRef();
|
||||
ref.current = setPlugin;
|
||||
|
||||
useEffect(() => {
|
||||
ref.current(pluginId);
|
||||
}, []);
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
Initializer.propTypes = {
|
||||
setPlugin: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
export default Initializer;
|
||||
@ -0,0 +1,40 @@
|
||||
/**
|
||||
* axios with a custom config.
|
||||
*/
|
||||
|
||||
import axios from 'axios';
|
||||
import { auth } from '@strapi/helper-plugin';
|
||||
|
||||
const instance = axios.create({
|
||||
baseURL: process.env.STRAPI_ADMIN_BACKEND_URL,
|
||||
});
|
||||
|
||||
instance.interceptors.request.use(
|
||||
async config => {
|
||||
config.headers = {
|
||||
Authorization: `Bearer ${auth.getToken()}`,
|
||||
Accept: 'application/json',
|
||||
'Content-Type': 'application/json',
|
||||
};
|
||||
|
||||
return config;
|
||||
},
|
||||
error => {
|
||||
Promise.reject(error);
|
||||
}
|
||||
);
|
||||
|
||||
instance.interceptors.response.use(
|
||||
response => response,
|
||||
error => {
|
||||
// whatever you want to do with the error
|
||||
if (error.response?.status === 401) {
|
||||
auth.clearAppStorage();
|
||||
window.location.reload();
|
||||
}
|
||||
|
||||
throw error;
|
||||
}
|
||||
);
|
||||
|
||||
export default instance;
|
||||
@ -38,7 +38,7 @@
|
||||
"pluralize": "8.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12.x.x <=16.x.x",
|
||||
"node": ">=12.22.0 <=16.x.x",
|
||||
"npm": ">=6.0.0"
|
||||
}
|
||||
}
|
||||
|
||||
@ -46,7 +46,7 @@
|
||||
"swagger-ui-dist": "3.47.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12.x.x <=16.x.x",
|
||||
"node": ">=12.22.0 <=16.x.x",
|
||||
"npm": ">=6.0.0"
|
||||
},
|
||||
"strapi": {
|
||||
|
||||
@ -52,7 +52,7 @@
|
||||
"koa": "^2.13.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12.x.x <=16.x.x",
|
||||
"node": ">=12.22.0 <=16.x.x",
|
||||
"npm": ">=6.0.0"
|
||||
},
|
||||
"strapi": {
|
||||
|
||||
2
packages/plugins/graphql/server/bootstrap.js
vendored
2
packages/plugins/graphql/server/bootstrap.js
vendored
@ -57,7 +57,7 @@ module.exports = async ({ strapi }) => {
|
||||
bodyParserConfig: true,
|
||||
|
||||
plugins: [
|
||||
process.env.NODE_ENV === 'production'
|
||||
process.env.NODE_ENV === 'production' && !config('playgroundAlways')
|
||||
? ApolloServerPluginLandingPageDisabled()
|
||||
: ApolloServerPluginLandingPageGraphQLPlayground(),
|
||||
],
|
||||
|
||||
@ -28,7 +28,7 @@
|
||||
"lodash": "4.17.21"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12.x.x <=16.x.x",
|
||||
"node": ">=12.22.0 <=16.x.x",
|
||||
"npm": ">=6.0.0"
|
||||
},
|
||||
"strapi": {
|
||||
|
||||
@ -27,7 +27,7 @@
|
||||
"@sentry/node": "6.7.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12.x.x <=16.x.x",
|
||||
"node": ">=12.22.0 <=16.x.x",
|
||||
"npm": ">=6.0.0"
|
||||
},
|
||||
"strapi": {
|
||||
|
||||
@ -51,7 +51,7 @@
|
||||
"koa": "^2.13.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12.x.x <=16.x.x",
|
||||
"node": ">=12.22.0 <=16.x.x",
|
||||
"npm": ">=6.0.0"
|
||||
},
|
||||
"strapi": {
|
||||
|
||||
@ -386,7 +386,7 @@ module.exports = {
|
||||
throw new ValidationError('token.invalid');
|
||||
}
|
||||
|
||||
await userService.edit({ id: user.id }, { confirmed: true, confirmationToken: null });
|
||||
await userService.edit(user.id, { confirmed: true, confirmationToken: null });
|
||||
|
||||
if (returnUser) {
|
||||
ctx.send({
|
||||
|
||||
@ -123,7 +123,7 @@ module.exports = {
|
||||
...ctx.request.body,
|
||||
};
|
||||
|
||||
const data = await getService('user').edit({ id }, updateData);
|
||||
const data = await getService('user').edit(user.id, updateData);
|
||||
const sanitizedData = await sanitizeOutput(data, ctx);
|
||||
|
||||
ctx.send(sanitizedData);
|
||||
|
||||
@ -45,16 +45,23 @@ module.exports = ({ strapi }) => ({
|
||||
|
||||
/**
|
||||
* Promise to edit a/an user.
|
||||
* @param {string} userId
|
||||
* @param {object} params
|
||||
* @return {Promise}
|
||||
*/
|
||||
async edit(params, values) {
|
||||
if (values.password) {
|
||||
values.password = await getService('user').hashPassword(values);
|
||||
async edit(userId, params = {}) {
|
||||
if (params.password) {
|
||||
params.password = await getService('user').hashPassword(params);
|
||||
}
|
||||
|
||||
return strapi
|
||||
.query('plugin::users-permissions.user')
|
||||
.update({ where: params, data: values, populate: ['role'] });
|
||||
return strapi.entityService.update(
|
||||
'plugin::users-permissions.user',
|
||||
userId,
|
||||
{
|
||||
data: params,
|
||||
populate: ['role']
|
||||
}
|
||||
);
|
||||
},
|
||||
|
||||
/**
|
||||
@ -132,7 +139,7 @@ module.exports = ({ strapi }) => ({
|
||||
|
||||
const confirmationToken = crypto.randomBytes(20).toString('hex');
|
||||
|
||||
await this.edit({ id: user.id }, { confirmationToken });
|
||||
await this.edit(user.id, { confirmationToken });
|
||||
|
||||
settings.message = await userPermissionService.template(settings.message, {
|
||||
URL: `${getAbsoluteServerUrl(strapi.config)}/auth/email-confirmation`,
|
||||
|
||||
@ -1,22 +1,21 @@
|
||||
'use strict';
|
||||
|
||||
// Test a simple default API with no relations
|
||||
|
||||
const { createStrapiInstance } = require('../../../../test/helpers/strapi');
|
||||
const { createRequest } = require('../../../../test/helpers/request');
|
||||
const { createRequest, createAuthRequest } = require('../../../../test/helpers/request');
|
||||
const { createTestBuilder } = require('../../../../test/helpers/builder');
|
||||
|
||||
let strapi;
|
||||
let rq;
|
||||
let graphqlQuery;
|
||||
let data = {};
|
||||
// Test a simple default API with no relations
|
||||
describe('Simple Test GraphQL Users API End to End', () => {
|
||||
let strapi;
|
||||
let rq;
|
||||
let graphqlQuery;
|
||||
const user = {
|
||||
username: 'User 1',
|
||||
email: 'user1@strapi.io',
|
||||
password: 'test1234',
|
||||
};
|
||||
const data = {};
|
||||
|
||||
const user = {
|
||||
username: 'User 1',
|
||||
email: 'user1@strapi.io',
|
||||
password: 'test1234',
|
||||
};
|
||||
|
||||
describe('Test Graphql Users API End to End', () => {
|
||||
beforeAll(async () => {
|
||||
strapi = await createStrapiInstance();
|
||||
rq = await createRequest({ strapi });
|
||||
@ -114,6 +113,43 @@ describe('Test Graphql Users API End to End', () => {
|
||||
data.user = res.body.data.login.user;
|
||||
});
|
||||
|
||||
test('Update a user', async () => {
|
||||
const res = await graphqlQuery({
|
||||
query: /* GraphQL */ `
|
||||
mutation updateUser($id: ID!, $data: UsersPermissionsUserInput!) {
|
||||
updateUsersPermissionsUser(id: $id, data: $data) {
|
||||
data {
|
||||
attributes {
|
||||
username
|
||||
email
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`,
|
||||
variables: {
|
||||
id: data.user.id,
|
||||
data: { username: 'User Test' },
|
||||
},
|
||||
});
|
||||
|
||||
const { body } = res;
|
||||
|
||||
expect(res.statusCode).toBe(200);
|
||||
expect(body).toMatchObject({
|
||||
data: {
|
||||
updateUsersPermissionsUser: {
|
||||
data: {
|
||||
attributes: {
|
||||
username: 'User Test',
|
||||
email: data.user.email,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
test('Delete a user', async () => {
|
||||
const res = await graphqlQuery({
|
||||
query: /* GraphQL */ `
|
||||
@ -121,6 +157,7 @@ describe('Test Graphql Users API End to End', () => {
|
||||
deleteUsersPermissionsUser(id: $id) {
|
||||
data {
|
||||
attributes {
|
||||
username
|
||||
email
|
||||
}
|
||||
}
|
||||
@ -140,6 +177,7 @@ describe('Test Graphql Users API End to End', () => {
|
||||
deleteUsersPermissionsUser: {
|
||||
data: {
|
||||
attributes: {
|
||||
username: 'User Test',
|
||||
email: data.user.email,
|
||||
},
|
||||
},
|
||||
@ -149,3 +187,322 @@ describe('Test Graphql Users API End to End', () => {
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// Test with attributes such as components, relations..
|
||||
describe('Advanced Test GraphQL Users API End to End', () => {
|
||||
const builder = createTestBuilder();
|
||||
|
||||
let strapi;
|
||||
let rq;
|
||||
let authReq;
|
||||
let graphqlQuery;
|
||||
const user = {
|
||||
username: 'User 2',
|
||||
email: 'user2@strapi.io',
|
||||
password: 'test1234',
|
||||
};
|
||||
const component = {
|
||||
displayName: 'somecomponent',
|
||||
attributes: {
|
||||
name: {
|
||||
type: 'string',
|
||||
},
|
||||
isTesting: {
|
||||
type: 'boolean',
|
||||
},
|
||||
},
|
||||
};
|
||||
const data = {};
|
||||
|
||||
const restart = async () => {
|
||||
await strapi.destroy();
|
||||
strapi = await createStrapiInstance();
|
||||
rq = await createAuthRequest({ strapi });
|
||||
authReq = await createAuthRequest({ strapi });
|
||||
|
||||
graphqlQuery = body => {
|
||||
return rq({
|
||||
url: '/graphql',
|
||||
method: 'POST',
|
||||
body,
|
||||
});
|
||||
};
|
||||
};
|
||||
|
||||
beforeAll(async () => {
|
||||
await builder.addComponent(component).build();
|
||||
|
||||
strapi = await createStrapiInstance();
|
||||
rq = await createRequest({ strapi });
|
||||
authReq = await createAuthRequest({ strapi });
|
||||
|
||||
graphqlQuery = body => {
|
||||
return rq({
|
||||
url: '/graphql',
|
||||
method: 'POST',
|
||||
body,
|
||||
});
|
||||
};
|
||||
});
|
||||
|
||||
afterAll(async () => {
|
||||
await strapi.destroy();
|
||||
await builder.cleanup();
|
||||
});
|
||||
|
||||
test('Update user to add component attribute', async () => {
|
||||
const uid = 'plugin::users-permissions.user';
|
||||
|
||||
const res = await authReq({
|
||||
method: 'PUT',
|
||||
url: `/content-type-builder/content-types/${uid}`,
|
||||
body: {
|
||||
contentType: {
|
||||
displayName: 'User',
|
||||
singularName: 'user',
|
||||
pluralName: 'users',
|
||||
description: '',
|
||||
draftAndPublish: false,
|
||||
kind: 'collectionType',
|
||||
collectionName: 'up_users',
|
||||
attributes: {
|
||||
username: {
|
||||
type: 'string',
|
||||
minLength: 3,
|
||||
unique: true,
|
||||
configurable: false,
|
||||
required: true,
|
||||
},
|
||||
email: {
|
||||
type: 'email',
|
||||
minLength: 6,
|
||||
configurable: false,
|
||||
required: true,
|
||||
},
|
||||
provider: {
|
||||
type: 'string',
|
||||
configurable: false,
|
||||
},
|
||||
password: {
|
||||
type: 'password',
|
||||
minLength: 6,
|
||||
configurable: false,
|
||||
private: true,
|
||||
},
|
||||
resetPasswordToken: {
|
||||
type: 'string',
|
||||
configurable: false,
|
||||
private: true,
|
||||
},
|
||||
confirmationToken: {
|
||||
type: 'string',
|
||||
configurable: false,
|
||||
private: true,
|
||||
},
|
||||
confirmed: {
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
configurable: false,
|
||||
},
|
||||
blocked: {
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
configurable: false,
|
||||
},
|
||||
role: {
|
||||
type: 'relation',
|
||||
relation: 'manyToOne',
|
||||
target: 'plugin::users-permissions.role',
|
||||
inversedBy: 'users',
|
||||
configurable: false,
|
||||
},
|
||||
someComponent: {
|
||||
type: 'component',
|
||||
repeatable: false,
|
||||
component: 'default.somecomponent',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
expect(res.statusCode).toBe(201);
|
||||
expect(res.body).toEqual({
|
||||
data: {
|
||||
uid,
|
||||
},
|
||||
});
|
||||
|
||||
await restart();
|
||||
});
|
||||
|
||||
describe('Test register and login with component', () => {
|
||||
test('Register a user', async () => {
|
||||
const res = await graphqlQuery({
|
||||
query: /* GraphQL */ `
|
||||
mutation register($input: UsersPermissionsRegisterInput!) {
|
||||
register(input: $input) {
|
||||
jwt
|
||||
user {
|
||||
id
|
||||
email
|
||||
}
|
||||
}
|
||||
}
|
||||
`,
|
||||
variables: {
|
||||
input: user,
|
||||
},
|
||||
});
|
||||
|
||||
const { body } = res;
|
||||
|
||||
expect(res.statusCode).toBe(200);
|
||||
expect(body).toMatchObject({
|
||||
data: {
|
||||
register: {
|
||||
jwt: expect.any(String),
|
||||
user: {
|
||||
id: expect.any(String),
|
||||
email: user.email,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
data.user = res.body.data.register.user;
|
||||
});
|
||||
|
||||
test('Log in a user', async () => {
|
||||
const res = await graphqlQuery({
|
||||
query: /* GraphQL */ `
|
||||
mutation login($input: UsersPermissionsLoginInput!) {
|
||||
login(input: $input) {
|
||||
jwt
|
||||
user {
|
||||
id
|
||||
email
|
||||
}
|
||||
}
|
||||
}
|
||||
`,
|
||||
variables: {
|
||||
input: {
|
||||
identifier: user.username,
|
||||
password: user.password,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const { body } = res;
|
||||
|
||||
expect(res.statusCode).toBe(200);
|
||||
expect(body).toMatchObject({
|
||||
data: {
|
||||
login: {
|
||||
jwt: expect.any(String),
|
||||
user: {
|
||||
id: expect.any(String),
|
||||
email: user.email,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
// Use the JWT returned by the login request to
|
||||
// authentify the next queries or mutations
|
||||
rq.setLoggedUser(user).setToken(res.body.data.login.jwt);
|
||||
|
||||
data.user = res.body.data.login.user;
|
||||
});
|
||||
|
||||
test('Update a user', async () => {
|
||||
const res = await graphqlQuery({
|
||||
query: /* GraphQL */ `
|
||||
mutation updateUser($id: ID!, $data: UsersPermissionsUserInput!) {
|
||||
updateUsersPermissionsUser(id: $id, data: $data) {
|
||||
data {
|
||||
attributes {
|
||||
username
|
||||
email
|
||||
someComponent {
|
||||
name
|
||||
isTesting
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`,
|
||||
variables: {
|
||||
id: data.user.id,
|
||||
data: {
|
||||
username: 'User Test',
|
||||
someComponent: { name: 'Changed Name', isTesting: false },
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const { body } = res;
|
||||
|
||||
expect(res.statusCode).toBe(200);
|
||||
expect(body).toMatchObject({
|
||||
data: {
|
||||
updateUsersPermissionsUser: {
|
||||
data: {
|
||||
attributes: {
|
||||
username: 'User Test',
|
||||
email: data.user.email,
|
||||
someComponent: {
|
||||
name: 'Changed Name',
|
||||
isTesting: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
test('Delete a user', async () => {
|
||||
const res = await graphqlQuery({
|
||||
query: /* GraphQL */ `
|
||||
mutation deleteUser($id: ID!) {
|
||||
deleteUsersPermissionsUser(id: $id) {
|
||||
data {
|
||||
attributes {
|
||||
username
|
||||
email
|
||||
someComponent {
|
||||
name
|
||||
isTesting
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`,
|
||||
variables: {
|
||||
id: data.user.id,
|
||||
},
|
||||
});
|
||||
|
||||
const { body } = res;
|
||||
|
||||
expect(res.statusCode).toBe(200);
|
||||
expect(body).toMatchObject({
|
||||
data: {
|
||||
deleteUsersPermissionsUser: {
|
||||
data: {
|
||||
attributes: {
|
||||
username: 'User Test',
|
||||
email: data.user.email,
|
||||
someComponent: null,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@ -40,7 +40,7 @@
|
||||
"node-ses": "^3.0.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12.x.x <=16.x.x",
|
||||
"node": ">=12.22.0 <=16.x.x",
|
||||
"npm": ">=6.0.0"
|
||||
}
|
||||
}
|
||||
|
||||
@ -40,7 +40,7 @@
|
||||
"mailgun-js": "0.22.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12.x.x <=16.x.x",
|
||||
"node": ">=12.22.0 <=16.x.x",
|
||||
"npm": ">=6.0.0"
|
||||
}
|
||||
}
|
||||
|
||||
@ -53,7 +53,7 @@
|
||||
"nodemailer": "6.5.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12.x.x <=16.x.x",
|
||||
"node": ">=12.22.0 <=16.x.x",
|
||||
"npm": ">=6.0.0"
|
||||
}
|
||||
}
|
||||
|
||||
@ -40,7 +40,7 @@
|
||||
"@strapi/utils": "4.0.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12.x.x <=16.x.x",
|
||||
"node": ">=12.22.0 <=16.x.x",
|
||||
"npm": ">=6.0.0"
|
||||
}
|
||||
}
|
||||
|
||||
@ -39,7 +39,7 @@
|
||||
"sendmail": "^1.6.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12.x.x <=16.x.x",
|
||||
"node": ">=12.22.0 <=16.x.x",
|
||||
"npm": ">=6.0.0"
|
||||
}
|
||||
}
|
||||
|
||||
@ -41,7 +41,7 @@
|
||||
"lodash": "4.17.21"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12.x.x <=16.x.x",
|
||||
"node": ">=12.22.0 <=16.x.x",
|
||||
"npm": ">=6.0.0"
|
||||
}
|
||||
}
|
||||
|
||||
@ -41,7 +41,7 @@
|
||||
"into-stream": "^5.1.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12.x.x <=16.x.x",
|
||||
"node": ">=12.22.0 <=16.x.x",
|
||||
"npm": ">=6.0.0"
|
||||
}
|
||||
}
|
||||
|
||||
@ -38,7 +38,7 @@
|
||||
"@strapi/utils": "4.0.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12.x.x <=16.x.x",
|
||||
"node": ">=12.22.0 <=16.x.x",
|
||||
"npm": ">=6.0.0"
|
||||
}
|
||||
}
|
||||
|
||||
@ -29,7 +29,7 @@
|
||||
"streamifier": "0.1.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12.x.x <=16.x.x",
|
||||
"node": ">=12.22.0 <=16.x.x",
|
||||
"npm": ">=6.0.0"
|
||||
}
|
||||
}
|
||||
|
||||
@ -19,15 +19,15 @@
|
||||
],
|
||||
"main": "lib/index.js",
|
||||
"devDependencies": {
|
||||
"@babel/cli": "7.14.5",
|
||||
"@babel/core": "7.14.6",
|
||||
"@babel/generator": "7.14.5",
|
||||
"@babel/parser": "7.14.7",
|
||||
"@babel/cli": "7.16.7",
|
||||
"@babel/core": "7.16.7",
|
||||
"@babel/generator": "7.16.7",
|
||||
"@babel/parser": "7.16.7",
|
||||
"@babel/plugin-syntax-dynamic-import": "7.8.3",
|
||||
"@babel/plugin-transform-modules-commonjs": "7.14.5",
|
||||
"@babel/plugin-transform-runtime": "7.14.5",
|
||||
"@babel/preset-env": "7.14.7",
|
||||
"@babel/template": "7.14.5",
|
||||
"@babel/plugin-transform-modules-commonjs": "7.16.7",
|
||||
"@babel/plugin-transform-runtime": "7.16.7",
|
||||
"@babel/preset-env": "7.16.7",
|
||||
"@babel/template": "7.16.7",
|
||||
"reselect": "4.0.0",
|
||||
"resolve": "1.20.0"
|
||||
}
|
||||
|
||||
@ -35,7 +35,7 @@
|
||||
"winston": "3.3.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12.x.x <=16.x.x",
|
||||
"node": ">=12.22.0 <=16.x.x",
|
||||
"npm": ">=6.0.0"
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user