Merge branch 'master' into docs-client

This commit is contained in:
Alexandre BODIN 2019-12-20 12:14:09 +01:00 committed by GitHub
commit d44b88a0d2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
61 changed files with 482 additions and 143 deletions

View File

@ -132,6 +132,16 @@ Run the following statement in your database:
::: :::
:::: ::::
## Date type changes
We introduced new types in the admin panel: `date`, `datetime` and `time`. Before all of those types where saved as `datetime`.
You will need to change the type of your fields from `date` to `datetime` if you don't want to migrate your data.
- To migrate yout old `date` to `datetime`, change the field type from `date` to `datetime`. NO data migration is required.
- To migrate your old `date` to new `date`, you will need to migrate yout data to be of the format: `YYYY-MM-DD`
- To migrate your old `date` to the new `time`, change the field type from `date` to `time`. You will also need to transform them to be of the format: `HH:mm:ss.SSS`
## Groups become Components ## Groups become Components
If you were using the groups feature you will need to apply some changes: If you were using the groups feature you will need to apply some changes:
@ -370,10 +380,19 @@ _Repeat this query for every join table where you are using this component._
```sql ```sql
UPDATE restaurant_components UPDATE restaurant_components
SET component_type = 'groups_old_table_name' SET component_type = 'components_new_table_name'
WHERE component_type = 'components_new_table_name'; WHERE component_type = 'groups_old_table_name';
``` ```
**4. If you store files in groups, update the `related_type` values**
```sql
UPDATE upload_file_morph
SET related_type = 'components_new_table_name'
WHERE related_type = 'groups_old_table_name';
```
#### Mongo #### Mongo
In `mongo` the relation between a content type and its components is held in an array of references. To know which component type it referes to, the array also contains a `kind` attribute containing the component Schema name. In `mongo` the relation between a content type and its components is held in an array of references. To know which component type it referes to, the array also contains a `kind` attribute containing the component Schema name.
@ -450,6 +469,45 @@ We created new home pages when your go to your api url.
You will need to copy `index.html` and `production.html` into your `public` folder. You will need to copy `index.html` and `production.html` into your `public` folder.
You can find those two files [here](https://github.com/strapi/strapi/tree/master/packages/strapi-generate-new/lib/resources/files/public). You can find those two files [here](https://github.com/strapi/strapi/tree/master/packages/strapi-generate-new/lib/resources/files/public).
## Updating `csp` options
The admin panel contains certain assets that use `data:img;base64` images. To allow rendering of those assets you can update the files `./config/environments/{env}/security.json` as follows:
**Before**
```json
{
"csp": {
"enabled": true,
"policy": [
{
"img-src": "'self' http:"
},
"block-all-mixed-content"
]
}
//....
}
```
**After**
```json
{
"csp": {
"enabled": true,
"policy": ["block-all-mixed-content"]
}
//....
}
```
If you need more fine control you can also simply add the `data:` option to the `img-src` option.
## Rebuilding your administration panel ## Rebuilding your administration panel
Now delete the `.cache` and `build` folders. Then run `yarn develop`. Now delete the `.cache` and `build` folders. Then run `yarn develop`.
```
```

View File

@ -43,11 +43,6 @@
"full_name": { "full_name": {
"type": "string", "type": "string",
"required": true "required": true
},
"dz": {
"type": "dynamiczone",
"components": [],
"min": 2
} }
} }
} }

View File

@ -1,7 +1,7 @@
{ {
"name": "getstarted", "name": "getstarted",
"private": true, "private": true,
"version": "3.0.0-beta.18.1", "version": "3.0.0-beta.18.2",
"description": "A Strapi application.", "description": "A Strapi application.",
"scripts": { "scripts": {
"develop": "strapi develop", "develop": "strapi develop",
@ -15,21 +15,21 @@
"mysql": "^2.17.1", "mysql": "^2.17.1",
"pg": "^7.10.0", "pg": "^7.10.0",
"sqlite3": "^4.0.6", "sqlite3": "^4.0.6",
"strapi": "3.0.0-beta.18.1", "strapi": "3.0.0-beta.18.2",
"strapi-admin": "3.0.0-beta.18.1", "strapi-admin": "3.0.0-beta.18.2",
"strapi-connector-bookshelf": "3.0.0-beta.18.1", "strapi-connector-bookshelf": "3.0.0-beta.18.2",
"strapi-connector-mongoose": "3.0.0-beta.18.1", "strapi-connector-mongoose": "3.0.0-beta.18.2",
"strapi-middleware-views": "3.0.0-beta.18.1", "strapi-middleware-views": "3.0.0-beta.18.2",
"strapi-plugin-content-manager": "3.0.0-beta.18.1", "strapi-plugin-content-manager": "3.0.0-beta.18.2",
"strapi-plugin-content-type-builder": "3.0.0-beta.18.1", "strapi-plugin-content-type-builder": "3.0.0-beta.18.2",
"strapi-plugin-documentation": "3.0.0-beta.18.1", "strapi-plugin-documentation": "3.0.0-beta.18.2",
"strapi-plugin-email": "3.0.0-beta.18.1", "strapi-plugin-email": "3.0.0-beta.18.2",
"strapi-plugin-graphql": "3.0.0-beta.18.1", "strapi-plugin-graphql": "3.0.0-beta.18.2",
"strapi-plugin-upload": "3.0.0-beta.18.1", "strapi-plugin-upload": "3.0.0-beta.18.2",
"strapi-plugin-users-permissions": "3.0.0-beta.18.1", "strapi-plugin-users-permissions": "3.0.0-beta.18.2",
"strapi-provider-email-mailgun": "3.0.0-beta.18.1", "strapi-provider-email-mailgun": "3.0.0-beta.18.2",
"strapi-provider-upload-aws-s3": "3.0.0-beta.18.1", "strapi-provider-upload-aws-s3": "3.0.0-beta.18.2",
"strapi-utils": "3.0.0-beta.18.1" "strapi-utils": "3.0.0-beta.18.2"
}, },
"strapi": { "strapi": {
"uuid": "getstarted" "uuid": "getstarted"

View File

@ -1,5 +1,5 @@
{ {
"version": "3.0.0-beta.18.1", "version": "3.0.0-beta.18.2",
"packages": [ "packages": [
"packages/*", "packages/*",
"examples/*" "examples/*"

View File

@ -1,6 +1,6 @@
{ {
"name": "create-strapi-app", "name": "create-strapi-app",
"version": "3.0.0-beta.18.1", "version": "3.0.0-beta.18.2",
"description": "Generate a new Strapi application.", "description": "Generate a new Strapi application.",
"license": "MIT", "license": "MIT",
"homepage": "http://strapi.io", "homepage": "http://strapi.io",
@ -21,7 +21,7 @@
], ],
"dependencies": { "dependencies": {
"commander": "^2.20.0", "commander": "^2.20.0",
"strapi-generate-new": "3.0.0-beta.18.1" "strapi-generate-new": "3.0.0-beta.18.2"
}, },
"scripts": { "scripts": {
"test": "echo \"no tests yet\"" "test": "echo \"no tests yet\""

View File

@ -26,9 +26,9 @@ const Logout = ({ history: { push } }) => {
const id = get(auth.getUserInfo(), 'id'); const id = get(auth.getUserInfo(), 'id');
push({ push({
pathname: `/plugins/content-manager/administrator/${id}`, pathname: `/plugins/content-manager/strapi::administrator/${id}`,
search: search:
'?redirectUrl=/plugins/content-manager/administrator/?page=0&limit=0&sort=id&source=admin', '?redirectUrl=/plugins/content-manager/strapi::administrator/?_page=0&_limit=0&_sort=id',
}); });
}; };
const handleGoToAdministrator = () => { const handleGoToAdministrator = () => {

View File

@ -48,7 +48,7 @@ function App(props) {
if (uuid) { if (uuid) {
try { try {
await fetch('https://analytics.strapi.io/track', { fetch('https://analytics.strapi.io/track', {
method: 'POST', method: 'POST',
body: JSON.stringify({ body: JSON.stringify({
event: 'didInitializeAdministration', event: 'didInitializeAdministration',

View File

@ -130,6 +130,7 @@
"components.Input.error.validation.required": "This value is required.", "components.Input.error.validation.required": "This value is required.",
"components.Input.error.validation.unique": "This value is already used.", "components.Input.error.validation.unique": "This value is already used.",
"components.InputSelect.option.placeholder": "Choose here", "components.InputSelect.option.placeholder": "Choose here",
"component.Input.error.validation.integer": "The value must be an integer",
"components.ListRow.empty": "There is no data to be shown.", "components.ListRow.empty": "There is no data to be shown.",
"components.OverlayBlocker.description": "You're using a feature that needs the server to restart. Please wait until the server is up.", "components.OverlayBlocker.description": "You're using a feature that needs the server to restart. Please wait until the server is up.",
"components.OverlayBlocker.description.serverError": "The server should have restarted, please check your logs in the terminal.", "components.OverlayBlocker.description.serverError": "The server should have restarted, please check your logs in the terminal.",

View File

@ -1,6 +1,6 @@
{ {
"name": "strapi-admin", "name": "strapi-admin",
"version": "3.0.0-beta.18.1", "version": "3.0.0-beta.18.2",
"description": "Strapi Admin", "description": "Strapi Admin",
"repository": { "repository": {
"type": "git", "type": "git",
@ -80,8 +80,8 @@
"reselect": "^3.0.1", "reselect": "^3.0.1",
"sanitize.css": "^4.1.0", "sanitize.css": "^4.1.0",
"shelljs": "^0.7.8", "shelljs": "^0.7.8",
"strapi-helper-plugin": "3.0.0-beta.18.1", "strapi-helper-plugin": "3.0.0-beta.18.2",
"strapi-utils": "3.0.0-beta.18.1", "strapi-utils": "3.0.0-beta.18.2",
"style-loader": "^0.23.1", "style-loader": "^0.23.1",
"styled-components": "^4.2.0", "styled-components": "^4.2.0",
"terser-webpack-plugin": "^1.2.3", "terser-webpack-plugin": "^1.2.3",

View File

@ -1,6 +1,6 @@
{ {
"name": "strapi-connector-bookshelf", "name": "strapi-connector-bookshelf",
"version": "3.0.0-beta.18.1", "version": "3.0.0-beta.18.2",
"description": "Bookshelf hook for the Strapi framework", "description": "Bookshelf hook for the Strapi framework",
"homepage": "http://strapi.io", "homepage": "http://strapi.io",
"keywords": [ "keywords": [
@ -22,7 +22,7 @@
"lodash": "^4.17.11", "lodash": "^4.17.11",
"pluralize": "^7.0.0", "pluralize": "^7.0.0",
"rimraf": "^2.6.3", "rimraf": "^2.6.3",
"strapi-utils": "3.0.0-beta.18.1" "strapi-utils": "3.0.0-beta.18.2"
}, },
"strapi": { "strapi": {
"dependencies": [ "dependencies": [

View File

@ -364,6 +364,14 @@ const createOnFetchPopulateFn = ({
} }
}); });
if (definition.modelType === 'component') {
definition.associations
.filter(ast => ast.autoPopulate !== false)
.forEach(ast => {
this.populate({ path: ast.alias });
});
}
componentAttributes.forEach(key => { componentAttributes.forEach(key => {
this.populate({ path: `${key}.ref` }); this.populate({ path: `${key}.ref` });
}); });

View File

@ -1,6 +1,6 @@
{ {
"name": "strapi-connector-mongoose", "name": "strapi-connector-mongoose",
"version": "3.0.0-beta.18.1", "version": "3.0.0-beta.18.2",
"description": "Mongoose hook for the Strapi framework", "description": "Mongoose hook for the Strapi framework",
"homepage": "http://strapi.io", "homepage": "http://strapi.io",
"keywords": [ "keywords": [
@ -20,7 +20,7 @@
"mongoose-float": "^1.0.4", "mongoose-float": "^1.0.4",
"mongoose-long": "^0.2.1", "mongoose-long": "^0.2.1",
"pluralize": "^7.0.0", "pluralize": "^7.0.0",
"strapi-utils": "3.0.0-beta.18.1" "strapi-utils": "3.0.0-beta.18.2"
}, },
"author": { "author": {
"email": "hi@strapi.io", "email": "hi@strapi.io",

View File

@ -1,6 +1,6 @@
{ {
"name": "strapi-database", "name": "strapi-database",
"version": "3.0.0-beta.18.1", "version": "3.0.0-beta.18.2",
"description": "Strapi's database layer", "description": "Strapi's database layer",
"homepage": "http://strapi.io", "homepage": "http://strapi.io",
"main": "./lib/index.js", "main": "./lib/index.js",

View File

@ -1,6 +1,6 @@
{ {
"name": "strapi-generate-api", "name": "strapi-generate-api",
"version": "3.0.0-beta.18.1", "version": "3.0.0-beta.18.2",
"description": "Generate an API for a Strapi application.", "description": "Generate an API for a Strapi application.",
"homepage": "http://strapi.io", "homepage": "http://strapi.io",
"keywords": [ "keywords": [

View File

@ -1,6 +1,6 @@
{ {
"name": "strapi-generate-controller", "name": "strapi-generate-controller",
"version": "3.0.0-beta.18.1", "version": "3.0.0-beta.18.2",
"description": "Generate a controller for a Strapi API.", "description": "Generate a controller for a Strapi API.",
"homepage": "http://strapi.io", "homepage": "http://strapi.io",
"keywords": [ "keywords": [

View File

@ -1,6 +1,6 @@
{ {
"name": "strapi-generate-model", "name": "strapi-generate-model",
"version": "3.0.0-beta.18.1", "version": "3.0.0-beta.18.2",
"description": "Generate a model for a Strapi API.", "description": "Generate a model for a Strapi API.",
"homepage": "http://strapi.io", "homepage": "http://strapi.io",
"keywords": [ "keywords": [

View File

@ -1,6 +1,6 @@
{ {
"name": "strapi-generate-new", "name": "strapi-generate-new",
"version": "3.0.0-beta.18.1", "version": "3.0.0-beta.18.2",
"description": "Generate a new Strapi application.", "description": "Generate a new Strapi application.",
"homepage": "http://strapi.io", "homepage": "http://strapi.io",
"keywords": [ "keywords": [

View File

@ -1,6 +1,6 @@
{ {
"name": "strapi-generate-plugin", "name": "strapi-generate-plugin",
"version": "3.0.0-beta.18.1", "version": "3.0.0-beta.18.2",
"description": "Generate an plugin for a Strapi application.", "description": "Generate an plugin for a Strapi application.",
"homepage": "http://strapi.io", "homepage": "http://strapi.io",
"keywords": [ "keywords": [

View File

@ -1,6 +1,6 @@
{ {
"name": "strapi-generate-policy", "name": "strapi-generate-policy",
"version": "3.0.0-beta.18.1", "version": "3.0.0-beta.18.2",
"description": "Generate a policy for a Strapi API.", "description": "Generate a policy for a Strapi API.",
"homepage": "http://strapi.io", "homepage": "http://strapi.io",
"keywords": [ "keywords": [

View File

@ -1,6 +1,6 @@
{ {
"name": "strapi-generate-service", "name": "strapi-generate-service",
"version": "3.0.0-beta.18.1", "version": "3.0.0-beta.18.2",
"description": "Generate a service for a Strapi API.", "description": "Generate a service for a Strapi API.",
"homepage": "http://strapi.io", "homepage": "http://strapi.io",
"keywords": [ "keywords": [

View File

@ -1,6 +1,6 @@
{ {
"name": "strapi-generate", "name": "strapi-generate",
"version": "3.0.0-beta.18.1", "version": "3.0.0-beta.18.2",
"description": "Master of ceremonies for the Strapi generators.", "description": "Master of ceremonies for the Strapi generators.",
"homepage": "http://strapi.io", "homepage": "http://strapi.io",
"keywords": [ "keywords": [
@ -20,7 +20,7 @@
"fs-extra": "^8.0.1", "fs-extra": "^8.0.1",
"lodash": "^4.17.11", "lodash": "^4.17.11",
"reportback": "^2.0.2", "reportback": "^2.0.2",
"strapi-utils": "3.0.0-beta.18.1" "strapi-utils": "3.0.0-beta.18.2"
}, },
"author": { "author": {
"name": "Strapi team", "name": "Strapi team",

View File

@ -8,6 +8,7 @@ const errorsTrads = {
regex: 'components.Input.error.validation.regex', regex: 'components.Input.error.validation.regex',
required: 'components.Input.error.validation.required', required: 'components.Input.error.validation.required',
unique: 'components.Input.error.validation.unique', unique: 'components.Input.error.validation.unique',
integer: 'component.Input.error.validation.integer',
}; };
export default errorsTrads; export default errorsTrads;

View File

@ -1,6 +1,6 @@
{ {
"name": "strapi-helper-plugin", "name": "strapi-helper-plugin",
"version": "3.0.0-beta.18.1", "version": "3.0.0-beta.18.2",
"description": "Helper for Strapi plugins development", "description": "Helper for Strapi plugins development",
"files": [ "files": [
"dist" "dist"

View File

@ -1,6 +1,6 @@
{ {
"name": "strapi-hook-ejs", "name": "strapi-hook-ejs",
"version": "3.0.0-beta.18.1", "version": "3.0.0-beta.18.2",
"description": "EJS hook for the Strapi framework", "description": "EJS hook for the Strapi framework",
"homepage": "http://strapi.io", "homepage": "http://strapi.io",
"keywords": [ "keywords": [

View File

@ -1,6 +1,6 @@
{ {
"name": "strapi-hook-redis", "name": "strapi-hook-redis",
"version": "3.0.0-beta.18.1", "version": "3.0.0-beta.18.2",
"description": "Redis hook for the Strapi framework", "description": "Redis hook for the Strapi framework",
"homepage": "http://strapi.io", "homepage": "http://strapi.io",
"keywords": [ "keywords": [
@ -19,7 +19,7 @@
"lodash": "^4.17.11", "lodash": "^4.17.11",
"rimraf": "^2.6.3", "rimraf": "^2.6.3",
"stack-trace": "0.0.10", "stack-trace": "0.0.10",
"strapi-utils": "3.0.0-beta.18.1" "strapi-utils": "3.0.0-beta.18.2"
}, },
"author": { "author": {
"email": "hi@strapi.io", "email": "hi@strapi.io",

View File

@ -1,6 +1,6 @@
{ {
"name": "strapi-middleware-views", "name": "strapi-middleware-views",
"version": "3.0.0-beta.18.1", "version": "3.0.0-beta.18.2",
"description": "Views middleware to enable server-side rendering for the Strapi framework", "description": "Views middleware to enable server-side rendering for the Strapi framework",
"homepage": "http://strapi.io", "homepage": "http://strapi.io",
"keywords": [ "keywords": [

View File

@ -8,6 +8,7 @@ import Tooltip from './Tooltip';
const DynamicComponent = ({ const DynamicComponent = ({
componentUid, componentUid,
friendlyName, friendlyName,
icon,
setIsOverDynamicZone, setIsOverDynamicZone,
}) => { }) => {
const [state, setState] = useState(false); const [state, setState] = useState(false);
@ -22,6 +23,7 @@ const DynamicComponent = ({
<DynamicComponentCard <DynamicComponentCard
componentUid={componentUid} componentUid={componentUid}
friendlyName={friendlyName} friendlyName={friendlyName}
icon={icon}
isOver={state} isOver={state}
onClick={() => { onClick={() => {
push( push(
@ -38,11 +40,13 @@ const DynamicComponent = ({
DynamicComponent.defaultProps = { DynamicComponent.defaultProps = {
friendlyName: '', friendlyName: '',
icon: 'smile',
}; };
DynamicComponent.propTypes = { DynamicComponent.propTypes = {
componentUid: PropTypes.string.isRequired, componentUid: PropTypes.string.isRequired,
friendlyName: PropTypes.string, friendlyName: PropTypes.string,
icon: PropTypes.string,
setIsOverDynamicZone: PropTypes.func.isRequired, setIsOverDynamicZone: PropTypes.func.isRequired,
}; };

View File

@ -46,8 +46,8 @@ const DraggedFieldWithPreview = forwardRef(
'dynamiczone', 'dynamiczone',
]; ];
const withLongerHeight = higherFields.includes(type) && !dragStart; const withLongerHeight = higherFields.includes(type) && !dragStart;
const getCompoFriendlyName = uid => const getCompoInfos = uid =>
get(componentLayouts, [uid, 'schema', 'info', 'name'], ''); get(componentLayouts, [uid, 'schema', 'info'], { name: '', icon: '' });
const componentData = get(componentLayouts, [componentUid], {}); const componentData = get(componentLayouts, [componentUid], {});
const componentLayout = get(componentData, ['layouts', 'edit'], []); const componentLayout = get(componentData, ['layouts', 'edit'], []);
@ -146,11 +146,14 @@ const DraggedFieldWithPreview = forwardRef(
{type === 'dynamiczone' && ( {type === 'dynamiczone' && (
<DynamicZoneWrapper> <DynamicZoneWrapper>
{dynamicZoneComponents.map(compo => { {dynamicZoneComponents.map(compo => {
const { name, icon } = getCompoInfos(compo);
return ( return (
<DynamicComponent <DynamicComponent
key={compo} key={compo}
componentUid={compo} componentUid={compo}
friendlyName={getCompoFriendlyName(compo)} friendlyName={name}
icon={icon}
setIsOverDynamicZone={setIsOverDynamicZone} setIsOverDynamicZone={setIsOverDynamicZone}
/> />
); );

View File

@ -14,7 +14,6 @@ const getInputType = (type = '') => {
switch (toLower(type)) { switch (toLower(type)) {
case 'boolean': case 'boolean':
return 'bool'; return 'bool';
case 'biginteger':
case 'decimal': case 'decimal':
case 'float': case 'float':
case 'integer': case 'integer':
@ -114,6 +113,14 @@ function Inputs({ autoFocus, keys, layout, name, onBlur }) {
inputValue = []; inputValue = [];
} }
let step;
if (type === 'float' || type === 'decimal') {
step = 'any';
} else {
step = '1';
}
return ( return (
<FormattedMessage id={errorId}> <FormattedMessage id={errorId}>
{error => { {error => {
@ -141,6 +148,7 @@ function Inputs({ autoFocus, keys, layout, name, onBlur }) {
onBlur={onBlur} onBlur={onBlur}
onChange={onChange} onChange={onChange}
options={get(attribute, 'enum', [])} options={get(attribute, 'enum', [])}
step={step}
type={getInputType(type)} type={getInputType(type)}
validations={validations} validations={validations}
value={inputValue} value={inputValue}

View File

@ -52,6 +52,7 @@ const RepeatableComponent = ({
const errorsArray = componentErrorKeys.map(key => const errorsArray = componentErrorKeys.map(key =>
get(formErrors, [key, 'id'], '') get(formErrors, [key, 'id'], '')
); );
const hasMinError = const hasMinError =
get(errorsArray, [0], '').includes('min') && get(errorsArray, [0], '').includes('min') &&
!collapses.some(obj => obj.isOpen === true); !collapses.some(obj => obj.isOpen === true);

View File

@ -196,6 +196,10 @@ const EditViewDataManagerProvider = ({
inputValue = null; inputValue = null;
} }
if (type === 'biginteger' && value === '') {
inputValue = null;
}
dispatch({ dispatch({
type: 'ON_CHANGE', type: 'ON_CHANGE',
keys: name.split('.'), keys: name.split('.'),
@ -262,7 +266,7 @@ const EditViewDataManagerProvider = ({
}); });
redirectToPreviousPage(); redirectToPreviousPage();
} catch (err) { } catch (err) {
console.log({ err }); console.error({ err });
const error = get( const error = get(
err, err,
['response', 'payload', 'message', '0', 'messages', '0', 'id'], ['response', 'payload', 'message', '0', 'messages', '0', 'id'],
@ -277,6 +281,7 @@ const EditViewDataManagerProvider = ({
} }
} catch (err) { } catch (err) {
const errors = getYupInnerErrors(err); const errors = getYupInnerErrors(err);
console.error({ err, errors });
dispatch({ dispatch({
type: 'SUBMIT_ERRORS', type: 'SUBMIT_ERRORS',

View File

@ -7,6 +7,7 @@ import {
isArray, isArray,
isEmpty, isEmpty,
isNaN, isNaN,
toNumber,
} from 'lodash'; } from 'lodash';
import * as yup from 'yup'; import * as yup from 'yup';
import { translatedErrors as errorsTrads } from 'strapi-helper-plugin'; import { translatedErrors as errorsTrads } from 'strapi-helper-plugin';
@ -28,6 +29,34 @@ yup.addMethod(yup.array, 'notEmptyMin', function(min) {
}); });
}); });
yup.addMethod(yup.string, 'isInferior', function(message, max) {
return this.test('isInferior', message, function(value) {
if (!value) {
return true;
}
if (Number.isNaN(toNumber(value))) {
return true;
}
return toNumber(max) >= toNumber(value);
});
});
yup.addMethod(yup.string, 'isSuperior', function(message, min) {
return this.test('isSuperior', message, function(value) {
if (!value) {
return true;
}
if (Number.isNaN(toNumber(value))) {
return true;
}
return toNumber(value) >= toNumber(min);
});
});
const getAttributes = data => get(data, ['schema', 'attributes'], {}); const getAttributes = data => get(data, ['schema', 'attributes'], {});
const createYupSchema = (model, { components }) => { const createYupSchema = (model, { components }) => {
@ -80,7 +109,26 @@ const createYupSchema = (model, { components }) => {
.nullable(); .nullable();
if (min) { if (min) {
componentSchema = componentSchema.min(min, errorsTrads.min); componentSchema = yup.lazy(array => {
if (attribute.required) {
return yup
.array()
.of(componentFieldSchema)
.defined()
.min(min, errorsTrads.min);
}
let schema = yup
.array()
.of(componentFieldSchema)
.nullable();
if (array && !isEmpty(array)) {
schema = schema.min(min, errorsTrads.min);
}
return schema;
});
} }
if (max) { if (max) {
@ -146,6 +194,7 @@ const createYupSchema = (model, { components }) => {
const createYupSchemaAttribute = (type, validations) => { const createYupSchemaAttribute = (type, validations) => {
let schema = yup.mixed(); let schema = yup.mixed();
if ( if (
['string', 'text', 'richtext', 'email', 'password', 'enumeration'].includes( ['string', 'text', 'richtext', 'email', 'password', 'enumeration'].includes(
type type
@ -153,6 +202,7 @@ const createYupSchemaAttribute = (type, validations) => {
) { ) {
schema = yup.string(); schema = yup.string();
} }
if (type === 'json') { if (type === 'json') {
schema = yup schema = yup
.mixed(errorsTrads.json) .mixed(errorsTrads.json)
@ -183,16 +233,22 @@ const createYupSchemaAttribute = (type, validations) => {
if (type === 'email') { if (type === 'email') {
schema = schema.email(errorsTrads.email); schema = schema.email(errorsTrads.email);
} }
if (['number', 'integer', 'biginteger', 'float', 'decimal'].includes(type)) { if (['number', 'integer', 'biginteger', 'float', 'decimal'].includes(type)) {
schema = yup schema = yup
.number() .number()
.transform(cv => (isNaN(cv) ? undefined : cv)) .transform(cv => (isNaN(cv) ? undefined : cv))
.typeError(); .typeError();
} }
if (['date', 'datetime'].includes(type)) { if (['date', 'datetime'].includes(type)) {
schema = yup.date(); schema = yup.date();
} }
if (type === 'biginteger') {
schema = yup.string().matches(/^\d*$/);
}
Object.keys(validations).forEach(validation => { Object.keys(validations).forEach(validation => {
const validationValue = validations[validation]; const validationValue = validations[validation];
if ( if (
@ -205,15 +261,25 @@ const createYupSchemaAttribute = (type, validations) => {
case 'required': case 'required':
schema = schema.required(errorsTrads.required); schema = schema.required(errorsTrads.required);
break; break;
case 'max': case 'max': {
schema = schema.max(validationValue, errorsTrads.max); if (type === 'biginteger') {
schema = schema.isInferior(errorsTrads.max, validationValue);
} else {
schema = schema.max(validationValue, errorsTrads.max);
}
break; break;
}
case 'maxLength': case 'maxLength':
schema = schema.max(validationValue, errorsTrads.maxLength); schema = schema.max(validationValue, errorsTrads.maxLength);
break; break;
case 'min': case 'min': {
schema = schema.min(validationValue, errorsTrads.min); if (type === 'biginteger') {
schema = schema.isSuperior(errorsTrads.min, validationValue);
} else {
schema = schema.min(validationValue, errorsTrads.min);
}
break; break;
}
case 'minLength': case 'minLength':
schema = schema.min(validationValue, errorsTrads.minLength); schema = schema.min(validationValue, errorsTrads.minLength);
break; break;
@ -249,6 +315,7 @@ const createYupSchemaAttribute = (type, validations) => {
} }
} }
}); });
return schema; return schema;
}; };

View File

@ -1,6 +1,6 @@
{ {
"name": "strapi-plugin-content-manager", "name": "strapi-plugin-content-manager",
"version": "3.0.0-beta.18.1", "version": "3.0.0-beta.18.2",
"description": "A powerful UI to easily manage your data.", "description": "A powerful UI to easily manage your data.",
"strapi": { "strapi": {
"name": "Content Manager", "name": "Content Manager",
@ -32,8 +32,8 @@
"redux-immutable": "^4.0.0", "redux-immutable": "^4.0.0",
"reselect": "^3.0.1", "reselect": "^3.0.1",
"showdown": "^1.9.0", "showdown": "^1.9.0",
"strapi-helper-plugin": "3.0.0-beta.18.1", "strapi-helper-plugin": "3.0.0-beta.18.2",
"strapi-utils": "3.0.0-beta.18.1", "strapi-utils": "3.0.0-beta.18.2",
"styled-components": "^4.2.0", "styled-components": "^4.2.0",
"yup": "^0.27.0" "yup": "^0.27.0"
}, },

View File

@ -0,0 +1 @@
<svg width="30" height="30" xmlns="http://www.w3.org/2000/svg"><text transform="translate(-19 -4)" fill="#4B515A" fill-rule="evenodd" font-size="30" font-family="AppleColorEmoji, Apple Color Emoji"><tspan x="19" y="32">🏭</tspan></text></svg>

After

Width:  |  Height:  |  Size: 244 B

View File

@ -1,29 +1,98 @@
import React, { useState } from 'react'; import React, { useEffect, useState } from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { components } from 'react-select'; import { components } from 'react-select';
import { FormattedMessage } from 'react-intl';
import { get } from 'lodash'; import { get } from 'lodash';
import { Checkbox, CheckboxWrapper, Label } from '@buffetjs/styles'; import { Checkbox, CheckboxWrapper, Label } from '@buffetjs/styles';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import useDataManager from '../../hooks/useDataManager'; import useDataManager from '../../hooks/useDataManager';
import useQuery from '../../hooks/useQuery';
import getTrad from '../../utils/getTrad';
import UpperFirst from '../UpperFirst'; import UpperFirst from '../UpperFirst';
import SubUl from './SubUl'; import SubUl from './SubUl';
import Ul from './Ul'; import Ul from './Ul';
import hasSubArray from './utils/hasSubArray'; import hasSubArray from './utils/hasSubArray';
const MultipleMenuList = ({ const MultipleMenuList = ({
selectProps: { name, addComponentsToDynamicZone, /*refState, */ value }, selectProps: { name, addComponentsToDynamicZone, inputValue, value },
...rest ...rest
}) => { }) => {
const { componentsGroupedByCategory } = useDataManager(); const { componentsGroupedByCategory, modifiedData } = useDataManager();
const collapsesObject = Object.keys(componentsGroupedByCategory).reduce( const query = useQuery();
(acc, current) => { const dzName = query.get('dynamicZoneTarget');
acc[current] = false; const alreadyUsedComponents = get(
modifiedData,
['contentType', 'schema', 'attributes', dzName, 'components'],
[]
);
const filteredComponentsGroupedByCategory = Object.keys(
componentsGroupedByCategory
).reduce((acc, current) => {
const filteredComponents = componentsGroupedByCategory[current].filter(
({ uid }) => {
return !alreadyUsedComponents.includes(uid);
}
);
if (filteredComponents.length > 0) {
acc[current] = filteredComponents;
}
return acc;
}, {});
const collapsesObject = Object.keys(
filteredComponentsGroupedByCategory
).reduce((acc, current) => {
acc[current] = false;
return acc;
}, {});
const [collapses, setCollapses] = useState(collapsesObject);
const [options, setOptions] = useState(filteredComponentsGroupedByCategory);
// Search for component
useEffect(() => {
const formattedOptions = Object.keys(
filteredComponentsGroupedByCategory
).reduce((acc, current) => {
const filteredComponents = filteredComponentsGroupedByCategory[
current
].filter(({ schema: { name } }) => {
return name.includes(inputValue);
});
if (filteredComponents.length > 0) {
acc[current] = filteredComponents;
}
return acc; return acc;
}, }, {});
{}
); setOptions(formattedOptions);
const [collapses, setCollapses] = useState(collapsesObject);
const categoriesToOpen = Object.keys(formattedOptions);
if (inputValue !== '') {
// Close collapses
Object.keys(filteredComponentsGroupedByCategory)
.filter(cat => categoriesToOpen.indexOf(cat) === -1)
.forEach(catName => {
setCollapses(prevState => ({ ...prevState, [catName]: false }));
});
categoriesToOpen.forEach(catName => {
setCollapses(prevState => ({ ...prevState, [catName]: true }));
});
} else {
// Close all collapses
categoriesToOpen.forEach(catName => {
setCollapses(prevState => ({ ...prevState, [catName]: false }));
});
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [inputValue]);
const toggleCollapse = catName => { const toggleCollapse = catName => {
setCollapses(prevState => ({ setCollapses(prevState => ({
...prevState, ...prevState,
@ -33,18 +102,15 @@ const MultipleMenuList = ({
const Component = components.MenuList; const Component = components.MenuList;
const allComponentsCategory = Object.keys(componentsGroupedByCategory).reduce( const allComponentsCategory = Object.keys(options).reduce((acc, current) => {
(acc, current) => { const categoryCompos = options[current].map(compo => {
const categoryCompos = componentsGroupedByCategory[current].map(compo => { return compo.uid;
return compo.uid; });
});
acc[current] = categoryCompos; acc[current] = categoryCompos;
return acc; return acc;
}, }, {});
{}
);
const getCategoryValue = categoryName => { const getCategoryValue = categoryName => {
const componentsCategory = allComponentsCategory[categoryName]; const componentsCategory = allComponentsCategory[categoryName];
@ -79,7 +145,18 @@ const MultipleMenuList = ({
maxHeight: 150, maxHeight: 150,
}} }}
> >
{Object.keys(componentsGroupedByCategory).map(categoryName => { {Object.keys(options).length === 0 && (
<FormattedMessage
id={getTrad(
`components.componentSelect.no-component-available${
inputValue === '' ? '' : '.with-search'
}`
)}
>
{msg => <li style={{ paddingTop: 11 }}>{msg}</li>}
</FormattedMessage>
)}
{Object.keys(options).map(categoryName => {
const isChecked = getCategoryValue(categoryName); const isChecked = getCategoryValue(categoryName);
const target = { name: categoryName, value: !isChecked }; const target = { name: categoryName, value: !isChecked };
@ -124,7 +201,7 @@ const MultipleMenuList = ({
</CheckboxWrapper> </CheckboxWrapper>
</div> </div>
<SubUl tag="ul" isOpen={collapses[categoryName]}> <SubUl tag="ul" isOpen={collapses[categoryName]}>
{componentsGroupedByCategory[categoryName].map(component => { {options[categoryName].map(component => {
const isChecked = get(value, 'value', []).includes( const isChecked = get(value, 'value', []).includes(
component.uid component.uid
); );
@ -165,6 +242,7 @@ const MultipleMenuList = ({
MultipleMenuList.defaultProps = { MultipleMenuList.defaultProps = {
selectProps: { selectProps: {
inputValue: '',
refState: { refState: {
current: { current: {
select: { select: {
@ -179,6 +257,7 @@ MultipleMenuList.defaultProps = {
MultipleMenuList.propTypes = { MultipleMenuList.propTypes = {
selectProps: PropTypes.shape({ selectProps: PropTypes.shape({
addComponentsToDynamicZone: PropTypes.func.isRequired, addComponentsToDynamicZone: PropTypes.func.isRequired,
inputValue: PropTypes.string,
name: PropTypes.string.isRequired, name: PropTypes.string.isRequired,
refState: PropTypes.object, refState: PropTypes.object,
value: PropTypes.object, value: PropTypes.object,

View File

@ -10,8 +10,18 @@ import { isEmpty, isNumber } from 'lodash';
import { Inputs } from '@buffetjs/custom'; import { Inputs } from '@buffetjs/custom';
import StyledCustomCheckbox from './StyledCustomCheckbox'; import StyledCustomCheckbox from './StyledCustomCheckbox';
const CustomCheckbox = ({ label, name, onChange, value, ...rest }) => { const CustomCheckbox = ({
label,
modifiedData,
name,
onChange,
value,
...rest
}) => {
const [checked, setChecked] = useState(isNumber(value) || !isEmpty(value)); const [checked, setChecked] = useState(isNumber(value) || !isEmpty(value));
const type = modifiedData.type === 'biginteger' ? 'text' : 'number';
const step = ['decimal', 'float'].includes(modifiedData.type) ? 'any' : '1';
const disabled = !modifiedData.type;
return ( return (
<StyledCustomCheckbox> <StyledCustomCheckbox>
@ -38,8 +48,10 @@ const CustomCheckbox = ({ label, name, onChange, value, ...rest }) => {
{...rest} {...rest}
name={name} name={name}
onChange={onChange} onChange={onChange}
step={step}
disabled={disabled}
value={value} value={value}
type="number" type={type}
/> />
</div> </div>
)} )}
@ -49,12 +61,14 @@ const CustomCheckbox = ({ label, name, onChange, value, ...rest }) => {
CustomCheckbox.defaultProps = { CustomCheckbox.defaultProps = {
label: null, label: null,
modifiedData: {},
name: '', name: '',
value: null, value: null,
}; };
CustomCheckbox.propTypes = { CustomCheckbox.propTypes = {
label: PropTypes.string, label: PropTypes.string,
modifiedData: PropTypes.object,
name: PropTypes.string, name: PropTypes.string,
onChange: PropTypes.func.isRequired, onChange: PropTypes.func.isRequired,
value: PropTypes.oneOfType([PropTypes.number, PropTypes.string]), value: PropTypes.oneOfType([PropTypes.number, PropTypes.string]),

View File

@ -132,7 +132,9 @@ const reducer = (state, action) => {
dynamicZoneTarget, dynamicZoneTarget,
'components', 'components',
], ],
() => fromJS(makeUnique(newComponents)) list => {
return fromJS(makeUnique([...list.toJS(), ...newComponents]));
}
) )
.updateIn(['modifiedData', 'components'], old => { .updateIn(['modifiedData', 'components'], old => {
const componentsSchema = newComponents.reduce((acc, current) => { const componentsSchema = newComponents.reduce((acc, current) => {

View File

@ -213,8 +213,10 @@ const FormModal = () => {
); );
const attributeToEdit = { const attributeToEdit = {
...attributeToEditNotFormatted, ...attributeToEditNotFormatted,
// We filter the available components
// Because this modal is only used for adding components
components: [],
name: dynamicZoneTarget, name: dynamicZoneTarget,
// createComponent: true,
createComponent: false, createComponent: false,
componentToCreate: { type: 'component' }, componentToCreate: { type: 'component' },
}; };
@ -1238,6 +1240,7 @@ const FormModal = () => {
> >
<Inputs <Inputs
{...input} {...input}
modifiedData={modifiedData}
addComponentsToDynamicZone={ addComponentsToDynamicZone={
handleClickAddComponentsToDynamicZone handleClickAddComponentsToDynamicZone
} }

View File

@ -1,4 +1,5 @@
const NAME_REGEX = new RegExp('^[A-Za-z][_0-9A-Za-z]*$'); const CATEGORY_NAME_REGEX = new RegExp('^[A-Za-z][-_0-9A-Za-z]*$');
const ENUM_REGEX = new RegExp('^[_A-Za-z][_0-9A-Za-z]*$'); const ENUM_REGEX = new RegExp('^[_A-Za-z][_0-9A-Za-z]*$');
const NAME_REGEX = new RegExp('^[A-Za-z][_0-9A-Za-z]*$');
export { ENUM_REGEX, NAME_REGEX }; export { CATEGORY_NAME_REGEX, ENUM_REGEX, NAME_REGEX };

View File

@ -1,6 +1,6 @@
import React from 'react'; import React from 'react';
import * as yup from 'yup'; import * as yup from 'yup';
import { get, isEmpty, toLower, trim } from 'lodash'; import { get, isEmpty, toLower, trim, toNumber } from 'lodash';
import { translatedErrors as errorsTrads } from 'strapi-helper-plugin'; import { translatedErrors as errorsTrads } from 'strapi-helper-plugin';
import { FormattedMessage } from 'react-intl'; import { FormattedMessage } from 'react-intl';
import pluginId from '../../../pluginId'; import pluginId from '../../../pluginId';
@ -8,7 +8,11 @@ import getTrad from '../../../utils/getTrad';
import { createComponentUid, createUid, nameToSlug } from './createUid'; import { createComponentUid, createUid, nameToSlug } from './createUid';
import componentForm from './componentForm'; import componentForm from './componentForm';
import fields from './staticFields'; import fields from './staticFields';
import { NAME_REGEX, ENUM_REGEX } from './attributesRegexes'; import {
NAME_REGEX,
ENUM_REGEX,
CATEGORY_NAME_REGEX,
} from './attributesRegexes';
import RESERVED_NAMES from './reservedNames'; import RESERVED_NAMES from './reservedNames';
yup.addMethod(yup.mixed, 'defined', function() { yup.addMethod(yup.mixed, 'defined', function() {
@ -52,6 +56,20 @@ yup.addMethod(yup.string, 'isAllowed', function(message) {
}); });
}); });
yup.addMethod(yup.string, 'isInferior', function(message, max) {
return this.test('isInferior', message, function(min) {
if (!min) {
return false;
}
if (Number.isNaN(toNumber(min))) {
return true;
}
return toNumber(max) >= toNumber(min);
});
});
const ATTRIBUTES_THAT_DONT_HAVE_MIN_MAX_SETTINGS = [ const ATTRIBUTES_THAT_DONT_HAVE_MIN_MAX_SETTINGS = [
'boolean', 'boolean',
'date', 'date',
@ -210,11 +228,50 @@ const forms = {
case 'integer': case 'integer':
case 'biginteger': case 'biginteger':
case 'float': case 'float':
case 'decimal': case 'decimal': {
if (dataToValidate.type === 'biginteger') {
return yup.object().shape({
...commonShape,
default: yup
.string()
.nullable()
.matches(/^\d*$/),
min: yup
.string()
.nullable()
.matches(/^\d*$/)
.when('max', (max, schema) => {
if (max) {
return schema.isInferior(
getTrad('error.validation.minSupMax'),
max
);
} else {
return schema;
}
}),
max: yup
.string()
.nullable()
.matches(/^\d*$/),
});
}
let defaultType = yup.number();
if (dataToValidate.type === 'integer') {
defaultType = yup
.number()
.integer('component.Input.error.validation.integer');
}
return yup.object().shape({ return yup.object().shape({
...commonShape, ...commonShape,
default: defaultType.nullable(),
...numberTypeShape, ...numberTypeShape,
}); });
}
case 'relation': case 'relation':
return yup.object().shape({ return yup.object().shape({
name: yup name: yup
@ -312,6 +369,24 @@ const forms = {
const items = defaultItems.slice(); const items = defaultItems.slice();
if (type === 'number' && data.type !== 'biginteger') {
const step =
data.type === 'decimal' || data.type === 'float' ? 'any' : '1';
items.splice(0, 1, [
{
autoFocus: true,
name: 'default',
type: 'number',
step,
label: {
id: getTrad('form.attribute.settings.default'),
},
validations: {},
},
]);
}
if (type === 'media') { if (type === 'media') {
items.splice(0, 1); items.splice(0, 1);
} else if (type === 'boolean') { } else if (type === 'boolean') {
@ -758,7 +833,7 @@ const forms = {
.required(errorsTrads.required), .required(errorsTrads.required),
category: yup category: yup
.string() .string()
.matches(NAME_REGEX, errorsTrads.regex) .matches(CATEGORY_NAME_REGEX, errorsTrads.regex)
.required(errorsTrads.required), .required(errorsTrads.required),
icon: yup.string().required(errorsTrads.required), icon: yup.string().required(errorsTrads.required),
collectionName: yup.string().nullable(), collectionName: yup.string().nullable(),
@ -821,6 +896,7 @@ const forms = {
return yup.object().shape({ return yup.object().shape({
name: yup name: yup
.string() .string()
.matches(CATEGORY_NAME_REGEX, errorsTrads.regex)
.unique(errorsTrads.unique, allowedCategories, toLower) .unique(errorsTrads.unique, allowedCategories, toLower)
.required(errorsTrads.required), .required(errorsTrads.required),
}); });

View File

@ -34,7 +34,9 @@
"button.component.add": "Add a component", "button.component.add": "Add a component",
"button.component.create": "Create new component", "button.component.create": "Create new component",
"button.model.create": "Create new content-type", "button.model.create": "Create new content-type",
"components.componentSelect.value-component": "{number} component selected", "components.componentSelect.no-component-available": "You have already added all your components",
"components.componentSelect.no-component-available.with-search": "There is no component matching your search",
"components.componentSelect.value-component": "{number} component selected (type to search for a component)",
"components.componentSelect.value-components": "{number} components selected", "components.componentSelect.value-components": "{number} components selected",
"component.repeatable": "(repeatable)", "component.repeatable": "(repeatable)",
"configurations": "configurations", "configurations": "configurations",

View File

@ -17,6 +17,7 @@ const validators = {
const NAME_REGEX = new RegExp('^[A-Za-z][_0-9A-Za-z]*$'); const NAME_REGEX = new RegExp('^[A-Za-z][_0-9A-Za-z]*$');
const COLLECTION_NAME_REGEX = new RegExp('^[A-Za-z][-_0-9A-Za-z]*$'); const COLLECTION_NAME_REGEX = new RegExp('^[A-Za-z][-_0-9A-Za-z]*$');
const CATEGORY_NAME_REGEX = new RegExp('^[A-Za-z][-_0-9A-Za-z]*$');
const ENUM_REGEX = new RegExp('^[_A-Za-z][_0-9A-Za-z]*$'); const ENUM_REGEX = new RegExp('^[_A-Za-z][_0-9A-Za-z]*$');
const ICON_REGEX = new RegExp('^[A-Za-z0-9][-A-Za-z0-9]*$'); const ICON_REGEX = new RegExp('^[A-Za-z0-9][-A-Za-z0-9]*$');
@ -26,6 +27,12 @@ const isValidName = {
test: val => val === '' || NAME_REGEX.test(val), test: val => val === '' || NAME_REGEX.test(val),
}; };
const isValidCategoryName = {
name: 'isValidCategoryName',
message: '${path} must match the following regex: /^[A-Za-z][_-0-9A-Za-z]*$/',
test: val => val === '' || CATEGORY_NAME_REGEX.test(val),
};
const isValidCollectionName = { const isValidCollectionName = {
name: 'isValidCollectionName', name: 'isValidCollectionName',
message: '${path} must match the following regex: /^[A-Za-z][-_0-9A-Za-z]*$/', message: '${path} must match the following regex: /^[A-Za-z][-_0-9A-Za-z]*$/',
@ -55,6 +62,7 @@ module.exports = {
validators, validators,
isValidCollectionName, isValidCollectionName,
isValidCategoryName,
isValidName, isValidName,
isValidIcon, isValidIcon,
isValidKey, isValidKey,

View File

@ -3,7 +3,7 @@
const yup = require('yup'); const yup = require('yup');
const formatYupErrors = require('./yup-formatter'); const formatYupErrors = require('./yup-formatter');
const { isValidName } = require('./common'); const { isValidCategoryName } = require('./common');
module.exports = data => { module.exports = data => {
return componentCategorySchema return componentCategorySchema
@ -19,7 +19,7 @@ const componentCategorySchema = yup
name: yup name: yup
.string() .string()
.min(3) .min(3)
.test(isValidName) .test(isValidCategoryName)
.required('name.required'), .required('name.required'),
}) })
.noUnknown(); .noUnknown();

View File

@ -3,7 +3,7 @@
const _ = require('lodash'); const _ = require('lodash');
const yup = require('yup'); const yup = require('yup');
const { isValidName, isValidIcon } = require('./common'); const { isValidCategoryName, isValidIcon } = require('./common');
const formatYupErrors = require('./yup-formatter'); const formatYupErrors = require('./yup-formatter');
const createSchema = require('./model-schema'); const createSchema = require('./model-schema');
const { modelTypes, DEFAULT_TYPES } = require('./constants'); const { modelTypes, DEFAULT_TYPES } = require('./constants');
@ -23,7 +23,7 @@ const componentSchema = createSchema(VALID_TYPES, VALID_RELATIONS, {
category: yup category: yup
.string() .string()
.nullable() .nullable()
.test(isValidName) .test(isValidCategoryName)
.required('category.required'), .required('category.required'),
}) })
.required() .required()

View File

@ -1,6 +1,6 @@
{ {
"name": "strapi-plugin-content-type-builder", "name": "strapi-plugin-content-type-builder",
"version": "3.0.0-beta.18.1", "version": "3.0.0-beta.18.2",
"description": "Strapi plugin to create content type (API).", "description": "Strapi plugin to create content type (API).",
"strapi": { "strapi": {
"name": "Content Type Builder", "name": "Content Type Builder",
@ -29,9 +29,9 @@
"redux": "^4.0.1", "redux": "^4.0.1",
"redux-immutable": "^4.0.0", "redux-immutable": "^4.0.0",
"reselect": "^3.0.1", "reselect": "^3.0.1",
"strapi-generate": "3.0.0-beta.18.1", "strapi-generate": "3.0.0-beta.18.2",
"strapi-generate-api": "3.0.0-beta.18.1", "strapi-generate-api": "3.0.0-beta.18.2",
"strapi-helper-plugin": "3.0.0-beta.18.1", "strapi-helper-plugin": "3.0.0-beta.18.2",
"yup": "^0.27.0" "yup": "^0.27.0"
}, },
"author": { "author": {

View File

@ -1,6 +1,6 @@
{ {
"name": "strapi-plugin-documentation", "name": "strapi-plugin-documentation",
"version": "3.0.0-beta.18.1", "version": "3.0.0-beta.18.2",
"description": "This is the description of the plugin.", "description": "This is the description of the plugin.",
"strapi": { "strapi": {
"name": "Documentation", "name": "Documentation",
@ -32,7 +32,7 @@
"redux": "^4.0.1", "redux": "^4.0.1",
"redux-immutable": "^4.0.0", "redux-immutable": "^4.0.0",
"reselect": "^4.0.0", "reselect": "^4.0.0",
"strapi-helper-plugin": "3.0.0-beta.18.1", "strapi-helper-plugin": "3.0.0-beta.18.2",
"swagger-ui-dist": "3.24.3" "swagger-ui-dist": "3.24.3"
}, },
"author": { "author": {

View File

@ -115,7 +115,8 @@
"required": false, "required": false,
"description": "Get records that matches any value in the array of values", "description": "Get records that matches any value in the array of values",
"schema": { "schema": {
"type": "array" "type": "array",
"items": { "type": "string" }
}, },
"deprecated": false "deprecated": false
}, },
@ -125,8 +126,9 @@
"required": false, "required": false,
"description": "Get records that doesn't match any value in the array of values", "description": "Get records that doesn't match any value in the array of values",
"schema": { "schema": {
"type": "array" "type": "array",
"items": { "type": "string" }
}, },
"deprecated": false "deprecated": false
} }
] ]

View File

@ -1,6 +1,6 @@
{ {
"name": "strapi-plugin-email", "name": "strapi-plugin-email",
"version": "3.0.0-beta.18.1", "version": "3.0.0-beta.18.2",
"description": "This is the description of the plugin.", "description": "This is the description of the plugin.",
"strapi": { "strapi": {
"name": "Email", "name": "Email",
@ -12,13 +12,13 @@
"test": "echo \"no tests yet\"" "test": "echo \"no tests yet\""
}, },
"dependencies": { "dependencies": {
"strapi-provider-email-sendmail": "3.0.0-beta.18.1", "strapi-provider-email-sendmail": "3.0.0-beta.18.2",
"strapi-utils": "3.0.0-beta.18.1" "strapi-utils": "3.0.0-beta.18.2"
}, },
"devDependencies": { "devDependencies": {
"react-copy-to-clipboard": "5.0.1", "react-copy-to-clipboard": "5.0.1",
"rimraf": "^2.6.3", "rimraf": "^2.6.3",
"strapi-helper-plugin": "3.0.0-beta.18.1" "strapi-helper-plugin": "3.0.0-beta.18.2"
}, },
"author": { "author": {
"name": "Strapi team", "name": "Strapi team",

View File

@ -1,6 +1,6 @@
{ {
"name": "strapi-plugin-graphql", "name": "strapi-plugin-graphql",
"version": "3.0.0-beta.18.1", "version": "3.0.0-beta.18.2",
"description": "This is the description of the plugin.", "description": "This is the description of the plugin.",
"strapi": { "strapi": {
"name": "graphql", "name": "graphql",
@ -23,7 +23,7 @@
"graphql-type-long": "^0.1.1", "graphql-type-long": "^0.1.1",
"koa-compose": "^4.1.0", "koa-compose": "^4.1.0",
"pluralize": "^7.0.0", "pluralize": "^7.0.0",
"strapi-utils": "3.0.0-beta.18.1" "strapi-utils": "3.0.0-beta.18.2"
}, },
"devDependencies": { "devDependencies": {
"cross-env": "^5.2.0", "cross-env": "^5.2.0",

View File

@ -1,6 +1,6 @@
{ {
"name": "strapi-plugin-upload", "name": "strapi-plugin-upload",
"version": "3.0.0-beta.18.1", "version": "3.0.0-beta.18.2",
"description": "This is the description of the plugin.", "description": "This is the description of the plugin.",
"strapi": { "strapi": {
"name": "Files Upload", "name": "Files Upload",
@ -23,9 +23,9 @@
"react-router-dom": "^5.0.0", "react-router-dom": "^5.0.0",
"react-transition-group": "^2.5.0", "react-transition-group": "^2.5.0",
"reactstrap": "^5.0.0", "reactstrap": "^5.0.0",
"strapi-helper-plugin": "3.0.0-beta.18.1", "strapi-helper-plugin": "3.0.0-beta.18.2",
"strapi-provider-upload-local": "3.0.0-beta.18.1", "strapi-provider-upload-local": "3.0.0-beta.18.2",
"strapi-utils": "3.0.0-beta.18.1", "strapi-utils": "3.0.0-beta.18.2",
"stream-to-array": "^2.3.0", "stream-to-array": "^2.3.0",
"uuid": "^3.2.1" "uuid": "^3.2.1"
}, },

View File

@ -1,6 +1,6 @@
{ {
"name": "strapi-plugin-users-permissions", "name": "strapi-plugin-users-permissions",
"version": "3.0.0-beta.18.1", "version": "3.0.0-beta.18.2",
"description": "Protect your API with a full-authentication process based on JWT", "description": "Protect your API with a full-authentication process based on JWT",
"strapi": { "strapi": {
"name": "Roles & Permissions", "name": "Roles & Permissions",
@ -31,8 +31,8 @@
"reactstrap": "^5.0.0", "reactstrap": "^5.0.0",
"redux-saga": "^0.16.0", "redux-saga": "^0.16.0",
"request": "^2.83.0", "request": "^2.83.0",
"strapi-helper-plugin": "3.0.0-beta.18.1", "strapi-helper-plugin": "3.0.0-beta.18.2",
"strapi-utils": "3.0.0-beta.18.1", "strapi-utils": "3.0.0-beta.18.2",
"uuid": "^3.1.0" "uuid": "^3.1.0"
}, },
"devDependencies": { "devDependencies": {

View File

@ -1,6 +1,6 @@
{ {
"name": "strapi-provider-email-amazon-ses", "name": "strapi-provider-email-amazon-ses",
"version": "3.0.0-beta.18.1", "version": "3.0.0-beta.18.2",
"description": "Amazon SES provider for strapi email", "description": "Amazon SES provider for strapi email",
"homepage": "http://strapi.io", "homepage": "http://strapi.io",
"keywords": [ "keywords": [

View File

@ -1,6 +1,6 @@
{ {
"name": "strapi-provider-email-mailgun", "name": "strapi-provider-email-mailgun",
"version": "3.0.0-beta.18.1", "version": "3.0.0-beta.18.2",
"description": "Mailgun provider for strapi email plugin", "description": "Mailgun provider for strapi email plugin",
"homepage": "http://strapi.io", "homepage": "http://strapi.io",
"keywords": [ "keywords": [

View File

@ -1,6 +1,6 @@
{ {
"name": "strapi-provider-email-sendgrid", "name": "strapi-provider-email-sendgrid",
"version": "3.0.0-beta.18.1", "version": "3.0.0-beta.18.2",
"description": "Sendgrid provider for strapi email", "description": "Sendgrid provider for strapi email",
"homepage": "http://strapi.io", "homepage": "http://strapi.io",
"keywords": [ "keywords": [

View File

@ -1,6 +1,6 @@
{ {
"name": "strapi-provider-email-sendmail", "name": "strapi-provider-email-sendmail",
"version": "3.0.0-beta.18.1", "version": "3.0.0-beta.18.2",
"description": "Sendmail provider for strapi email", "description": "Sendmail provider for strapi email",
"homepage": "http://strapi.io", "homepage": "http://strapi.io",
"keywords": [ "keywords": [

View File

@ -1,6 +1,6 @@
{ {
"name": "strapi-provider-upload-aws-s3", "name": "strapi-provider-upload-aws-s3",
"version": "3.0.0-beta.18.1", "version": "3.0.0-beta.18.2",
"description": "AWS S3 provider for strapi upload", "description": "AWS S3 provider for strapi upload",
"homepage": "http://strapi.io", "homepage": "http://strapi.io",
"keywords": [ "keywords": [

View File

@ -1,6 +1,6 @@
{ {
"name": "strapi-provider-upload-cloudinary", "name": "strapi-provider-upload-cloudinary",
"version": "3.0.0-beta.18.1", "version": "3.0.0-beta.18.2",
"description": "Cloudinary provider for strapi upload", "description": "Cloudinary provider for strapi upload",
"homepage": "http://strapi.io", "homepage": "http://strapi.io",
"keywords": [ "keywords": [

View File

@ -1,6 +1,6 @@
{ {
"name": "strapi-provider-upload-local", "name": "strapi-provider-upload-local",
"version": "3.0.0-beta.18.1", "version": "3.0.0-beta.18.2",
"description": "Local provider for strapi upload", "description": "Local provider for strapi upload",
"homepage": "http://strapi.io", "homepage": "http://strapi.io",
"keywords": [ "keywords": [

View File

@ -1,6 +1,6 @@
{ {
"name": "strapi-provider-upload-rackspace", "name": "strapi-provider-upload-rackspace",
"version": "3.0.0-beta.18.1", "version": "3.0.0-beta.18.2",
"description": "Rackspace provider for strapi upload", "description": "Rackspace provider for strapi upload",
"main": "./lib", "main": "./lib",
"keywords": [], "keywords": [],

View File

@ -1,6 +1,6 @@
{ {
"name": "strapi-utils", "name": "strapi-utils",
"version": "3.0.0-beta.18.1", "version": "3.0.0-beta.18.2",
"description": "Shared utilities for the Strapi packages", "description": "Shared utilities for the Strapi packages",
"homepage": "http://strapi.io", "homepage": "http://strapi.io",
"keywords": [ "keywords": [

View File

@ -1,6 +1,6 @@
{ {
"name": "strapi", "name": "strapi",
"version": "3.0.0-beta.18.1", "version": "3.0.0-beta.18.2",
"description": "An open source headless CMS solution to create and manage your own API. It provides a powerful dashboard and features to make your life easier. Databases supported: MongoDB, MySQL, MariaDB, PostgreSQL, SQLite", "description": "An open source headless CMS solution to create and manage your own API. It provides a powerful dashboard and features to make your life easier. Databases supported: MongoDB, MySQL, MariaDB, PostgreSQL, SQLite",
"homepage": "http://strapi.io", "homepage": "http://strapi.io",
"directories": { "directories": {
@ -49,16 +49,16 @@
"resolve-cwd": "^3.0.0", "resolve-cwd": "^3.0.0",
"rimraf": "^2.6.2", "rimraf": "^2.6.2",
"shelljs": "^0.8.3", "shelljs": "^0.8.3",
"strapi-database": "3.0.0-beta.18.1", "strapi-database": "3.0.0-beta.18.2",
"strapi-generate": "3.0.0-beta.18.1", "strapi-generate": "3.0.0-beta.18.2",
"strapi-generate-api": "3.0.0-beta.18.1", "strapi-generate-api": "3.0.0-beta.18.2",
"strapi-generate-controller": "3.0.0-beta.18.1", "strapi-generate-controller": "3.0.0-beta.18.2",
"strapi-generate-model": "3.0.0-beta.18.1", "strapi-generate-model": "3.0.0-beta.18.2",
"strapi-generate-new": "3.0.0-beta.18.1", "strapi-generate-new": "3.0.0-beta.18.2",
"strapi-generate-plugin": "3.0.0-beta.18.1", "strapi-generate-plugin": "3.0.0-beta.18.2",
"strapi-generate-policy": "3.0.0-beta.18.1", "strapi-generate-policy": "3.0.0-beta.18.2",
"strapi-generate-service": "3.0.0-beta.18.1", "strapi-generate-service": "3.0.0-beta.18.2",
"strapi-utils": "3.0.0-beta.18.1" "strapi-utils": "3.0.0-beta.18.2"
}, },
"scripts": { "scripts": {
"test": "jest --verbose", "test": "jest --verbose",