diff --git a/.eslintignore b/.eslintignore
index 5cdf069c80..e16b59bb01 100644
--- a/.eslintignore
+++ b/.eslintignore
@@ -1,6 +1,7 @@
**/node_modules/**
**/build/**
**/dist/**
+**/OLD/**
testApp/**
examples/**
packages/strapi-generate-plugin/files/admin/src/**
diff --git a/.eslintrc.front.js b/.eslintrc.front.js
index ffa0c91cbb..ceb64d7a5c 100644
--- a/.eslintrc.front.js
+++ b/.eslintrc.front.js
@@ -47,7 +47,7 @@ module.exports = {
'no-console': 0,
'require-atomic-updates': 0,
'react-hooks/rules-of-hooks': 'error',
- 'react-hooks/exhaustive-deps': 'warn',
+ 'react-hooks/exhaustive-deps': 'error',
'arrow-body-style': 0,
'arrow-parens': 0,
camelcase: 0,
@@ -64,10 +64,7 @@ module.exports = {
{
flatTernaryExpressions: false,
SwitchCase: 1,
- ignoredNodes: [
- 'ConditionalExpression',
- "VariableDeclarator[kind='const']",
- ],
+ ignoredNodes: ['ConditionalExpression', "VariableDeclarator[kind='const']"],
},
],
'func-names': ['error', 'never'],
@@ -97,10 +94,7 @@ module.exports = {
'no-plusplus': 0,
'no-shadow': 0,
'no-underscore-dangle': 0,
- 'no-use-before-define': [
- 'error',
- { functions: false, classes: false, variables: false },
- ],
+ 'no-use-before-define': ['error', { functions: false, classes: false, variables: false }],
'object-curly-newline': [2, { multiline: true, consistent: true }],
'operator-linebreak': 0,
'padding-line-between-statements': [
diff --git a/.github/ISSUE_TEMPLATE/BUG_REPORT.md b/.github/ISSUE_TEMPLATE/BUG_REPORT.md
index b2637866a1..bc6da3ab2e 100644
--- a/.github/ISSUE_TEMPLATE/BUG_REPORT.md
+++ b/.github/ISSUE_TEMPLATE/BUG_REPORT.md
@@ -16,6 +16,7 @@ https://guides.github.com/features/mastering-markdown/
A clear and concise description of what the bug is.
**Steps to reproduce the behavior**
+
1. Go to '...'
2. Click on '....'
3. Scroll down to '....'
@@ -31,6 +32,7 @@ If applicable, add screenshots to help explain your problem.
If applicable, add code samples to help explain your problem.
**System**
+
- Node.js version:
- NPM version:
- Strapi version:
diff --git a/.travis.yml b/.travis.yml
index 85760cab6b..55e3be7390 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -3,17 +3,13 @@ services:
- mysql
addons:
- postgresql: '10'
- apt:
- packages:
- - postgresql-10
- - postgresql-client-10
+ postgresql: '11.2'
+
env:
global:
- PGPORT=5433
-sudo: required
-dist: trusty
+dist: xenial
language: node_js
@@ -33,14 +29,6 @@ e2e_tests: &e2e_tests
- yarn run -s test:start-app & wait-on http://localhost:1337
- yarn run -s test:e2e
-install_mongo: &install_mongo
- before_install:
- - wget http://fastdl.mongodb.org/linux/mongodb-linux-x86_64-3.6.6.tgz
- - tar -zxvf mongodb-linux-x86_64-3.6.6.tgz
- - mkdir -p ./data/db/27017
- - mkdir -p ./data/db/27000
- - ./mongodb-linux-x86_64-3.6.6/bin/mongod --fork --dbpath ./data/db/27017 --syslog --port 27017
-
before_script:
- yarn build
- yarn global add -g wait-on
@@ -50,11 +38,17 @@ jobs:
include:
- stage: test
name: Snyk
+ before_install:
+ - curl -o- -L https://yarnpkg.com/install.sh | bash
+ - export PATH="$HOME/.yarn/bin:$PATH"
script: yarn run -s test:snyk
if: fork = false
- stage: test
name: 'Lint / Unit Tests '
+ before_install:
+ - curl -o- -L https://yarnpkg.com/install.sh | bash
+ - export PATH="$HOME/.yarn/bin:$PATH"
script:
- yarn run -s lint
- yarn run -s test:unit --coverage && codecov -C -F unit
@@ -63,8 +57,13 @@ jobs:
- <<: *e2e_tests
name: 'E2E Postgresql'
before_install:
- - sudo cp /etc/postgresql/{9.6,10}/main/pg_hba.conf
- - sudo /etc/init.d/postgresql restart
+ - curl -o- -L https://yarnpkg.com/install.sh | bash
+ - export PATH="$HOME/.yarn/bin:$PATH"
+ - sudo apt-get update
+ - sudo apt-get --yes remove postgresql-*
+ - sudo apt-get install -y postgresql-11 postgresql-client-11
+ - sudo cp /etc/postgresql/{9.6,11}/main/pg_hba.conf
+ - sudo service postgresql restart 11
- psql -c 'create database strapi_test;' -U postgres
env:
- DB_STRING='--dbclient=postgres --dbhost=localhost --dbport=5433 --dbname=strapi_test --dbusername=postgres --dbpassword='
@@ -72,6 +71,8 @@ jobs:
- <<: *e2e_tests
name: 'E2E Mysql'
before_install:
+ - curl -o- -L https://yarnpkg.com/install.sh | bash
+ - export PATH="$HOME/.yarn/bin:$PATH"
- sudo cp $TRAVIS_BUILD_DIR/_travis/mysql.cnf /etc/mysql/conf.d/
- sudo service mysql restart
- mysql -e 'CREATE DATABASE strapi_test;'
@@ -80,21 +81,21 @@ jobs:
- <<: *e2e_tests
name: 'E2E Sqlite'
+ before_install:
+ - curl -o- -L https://yarnpkg.com/install.sh | bash
+ - export PATH="$HOME/.yarn/bin:$PATH"
env:
- DB_STRING='--dbclient=sqlite --dbfile=./tmp/data.db'
- <<: *e2e_tests
name: 'E2E MongoDB'
- <<: *install_mongo
+ before_install:
+ - curl -o- -L https://yarnpkg.com/install.sh | bash
+ - export PATH="$HOME/.yarn/bin:$PATH"
+ - wget http://fastdl.mongodb.org/linux/mongodb-linux-x86_64-3.6.6.tgz
+ - tar -zxvf mongodb-linux-x86_64-3.6.6.tgz
+ - mkdir -p ./data/db/27017
+ - mkdir -p ./data/db/27000
+ - ./mongodb-linux-x86_64-3.6.6/bin/mongod --fork --dbpath ./data/db/27017 --syslog --port 27017
env:
- DB_STRING='--dbclient=mongo --dbhost=localhost --dbport=27017 --dbname=strapi_test --dbusername= --dbpassword='
- # - name: 'Cypress tests'
- # <<: *install_mongo
- # install:
- # - yarn run -s bootstrap
- # - yarn global add -g wait-on
- # - cypress install
- # script:
- # - yarn run -s test:generate-app -- --dbclient=mongo --dbhost=localhost --dbport=27017 --dbname=strapi_test --dbusername= --dbpassword=
- # - yarn run -s test:start-app & wait-on http://localhost:1337
- # - node test/cypress.js
diff --git a/docs/.vuepress/config.js b/docs/.vuepress/config.js
index e10ce3aaf0..7ef7ea0e03 100644
--- a/docs/.vuepress/config.js
+++ b/docs/.vuepress/config.js
@@ -213,6 +213,8 @@ module.exports = {
'/3.0.0-beta.x/guides/scheduled-publication',
'/3.0.0-beta.x/guides/secure-your-app',
'/3.0.0-beta.x/guides/send-email',
+ '/3.0.0-beta.x/guides/registering-a-field-in-admin',
+ '/3.0.0-beta.x/guides/count-graphql',
'/3.0.0-beta.x/guides/client',
'/3.0.0-beta.x/guides/update-version',
],
@@ -220,7 +222,11 @@ module.exports = {
{
collapsable: true,
title: '⚙️️ Admin Panel',
- children: ['/3.0.0-beta.x/admin-panel/customization', '/3.0.0-beta.x/admin-panel/deploy'],
+ children: [
+ '/3.0.0-beta.x/admin-panel/customization',
+ '/3.0.0-beta.x/admin-panel/custom-webpack-config',
+ '/3.0.0-beta.x/admin-panel/deploy',
+ ],
},
{
collapsable: true,
@@ -241,6 +247,7 @@ module.exports = {
'/3.0.0-beta.x/plugin-development/plugin-architecture',
'/3.0.0-beta.x/plugin-development/backend-development',
'/3.0.0-beta.x/plugin-development/frontend-development',
+ '/3.0.0-beta.x/plugin-development/frontend-field-api',
'/3.0.0-beta.x/plugin-development/frontend-settings-api',
],
},
diff --git a/docs/3.0.0-beta.x/admin-panel/custom-webpack-config.md b/docs/3.0.0-beta.x/admin-panel/custom-webpack-config.md
new file mode 100644
index 0000000000..f840ace0a9
--- /dev/null
+++ b/docs/3.0.0-beta.x/admin-panel/custom-webpack-config.md
@@ -0,0 +1,16 @@
+# Custom Webpack Config
+
+In order to extend the usage of webpack, you can define a function that extends its config inside `admin/admin.config.js`, like so:
+
+```js
+module.exports = {
+ webpack: (config, webpack) => {
+ // Note: we provide webpack above so you should not `require` it
+ // Perform customizations to webpack config
+ // Important: return the modified config
+ config.plugins.push(new webpack.IgnorePlugin(/\/__tests__\//));
+
+ return config;
+ },
+};
+```
diff --git a/docs/3.0.0-beta.x/cli/CLI.md b/docs/3.0.0-beta.x/cli/CLI.md
index 8812773a60..d44054627f 100644
--- a/docs/3.0.0-beta.x/cli/CLI.md
+++ b/docs/3.0.0-beta.x/cli/CLI.md
@@ -78,6 +78,8 @@ options: [--no-optimization]
- **strapi build**
Builds the administration panel and minimizing the assets
+- **strapi build --clean**
+ Builds the administration panel and delete the previous build and .cache folders
- **strapi build --no-optimization**
Builds the administration panel without minimizing the assets. The build duration is faster.
diff --git a/docs/3.0.0-beta.x/concepts/models.md b/docs/3.0.0-beta.x/concepts/models.md
index 767c0073c3..61420edf26 100644
--- a/docs/3.0.0-beta.x/concepts/models.md
+++ b/docs/3.0.0-beta.x/concepts/models.md
@@ -132,6 +132,12 @@ Additional settings can be set on models:
In this example, the model `Restaurant` will be accessible through the `Restaurants` global variable. The data will be stored in the `Restaurants_v1` collection or table and the model will use the `mongo` connection defined in `./config/environments/**/database.json`
+::: warning
+If not set manually in the JSON file, Strapi will adopt the filename as `globalId`.
+The `globalId` serves as a reference to your model within relations and Strapi APIs. If you chose to rename it (either by renaming your file or by changing the value of the `globalId`), you'd have to migrate your tables manually and update the references.
+Please note that you should not alter Strapi's models `globalId` (plugins and core ones) since it is used directly within Strapi APIs and other models' relations.
+:::
+
::: tip
The `connection` value can be changed whenever you want, but you should be aware that there is no automatic data migration process. Also if the new connection doesn't use the same ORM you will have to rewrite your queries.
:::
@@ -159,10 +165,7 @@ The info key on the model-json states information about the model. This informat
The options key on the model-json states.
-- `idAttribute`: This tells the model which attribute to expect as the unique identifier for each database row (typically an auto-incrementing primary key named 'id'). _Only valid for bookshelf._
-- `idAttributeType`: Data type of `idAttribute`, accepted list of value below. _Only valid for bookshelf._
- `timestamps`: This tells the model which attributes to use for timestamps. Accepts either `boolean` or `Array` of strings where first element is create date and second element is update date. Default value when set to `true` for Bookshelf is `["created_at", "updated_at"]` and for MongoDB is `["createdAt", "updatedAt"]`.
-- `uuid` : Boolean to enable UUID support on MySQL, you will need to set the `idAttributeType` to `uuid` as well and install the `bookshelf-uuid` package. To load the package you can see [this example](./configurations.md#bookshelf-mongoose).
**Path —** `User.settings.json`.
diff --git a/docs/3.0.0-beta.x/concepts/services.md b/docs/3.0.0-beta.x/concepts/services.md
index ee7d2da7c2..7d67016bde 100644
--- a/docs/3.0.0-beta.x/concepts/services.md
+++ b/docs/3.0.0-beta.x/concepts/services.md
@@ -137,12 +137,13 @@ module.exports = {
*/
async create(data, { files } = {}) {
- const entry = await strapi.query(model).create(data);
+ const entry = await strapi.query('restaurant').create(data);
if (files) {
// automatically uploads the files based on the entry and the model
await strapi.entityService.uploadFiles(entry, files, {
- model: strapi.models.restaurant,
+ model: 'restaurant',
+ // if you are using a plugin's model you will have to add the `plugin` key (plugin: 'users-permissions')
});
return this.findOne({ id: entry.id });
}
@@ -167,12 +168,13 @@ module.exports = {
*/
async update(params, data, { files } = {}) {
- const entry = await strapi.query(model).update(params, data);
+ const entry = await strapi.query('restaurant').update(params, data);
if (files) {
// automatically uploads the files based on the entry and the model
await strapi.entityService.uploadFiles(entry, files, {
- model: strapi.models.restaurant,
+ model: 'restaurant',
+ // if you are using a plugin's model you will have to add the `plugin` key (plugin: 'users-permissions')
});
return this.findOne({ id: entry.id });
}
diff --git a/docs/3.0.0-beta.x/deployment/google-app-engine.md b/docs/3.0.0-beta.x/deployment/google-app-engine.md
new file mode 100644
index 0000000000..b4b5de3fdd
--- /dev/null
+++ b/docs/3.0.0-beta.x/deployment/google-app-engine.md
@@ -0,0 +1,307 @@
+# Google App Engine
+
+In this guide we are going to:
+
+- Create a new Strapi project
+- Configure PostgreSQL for the production enviroment
+- Deploy the app to Google App Engine
+- Add the [Google Cloud Storage file uploading plugin](https://github.com/Lith/strapi-provider-upload-google-cloud-storage) by [@Lith](https://github.com/Lith)
+
+### New Strapi project
+
+:::: tabs
+
+::: tab yarn
+
+Use **yarn** to install the Strapi project (**recommended**). [Install yarn with these docs](https://yarnpkg.com/lang/en/docs/install/)
+
+```bash
+yarn create strapi-app my-project --quickstart
+```
+
+:::
+
+::: tab npx
+
+Use **npm/npx** to install the Strapi project
+
+```bash
+npx create-strapi-app my-project --quickstart
+```
+
+:::
+
+::::
+
+When the setup completes, register an admin user using the form which opens in the browser. This user will be only relevant in local development.
+
+The `sqlite` database is created at `.tmp/data.db`.
+
+Login, but don't add content types yet. Close the browser. Quit the running app.
+
+### Initial commit
+
+This may be a good point to commit the files in their initial state.
+
+```bash
+cd my-project
+git init
+git add .
+git commit -m first
+```
+
+### Install the Cloud SDK CLI tool
+
+[Cloud SDK: Command Line Interface](https://cloud.google.com/sdk/)
+
+### New App Engine project
+
+Create a new [App Engine](https://console.cloud.google.com/appengine/) project.
+
+Select the region, such as `europe-west`.
+
+- Language: Node JS
+- Environment: Standard (or Flexible)
+
+(_A note on performance and cost_: the `Standard Environment` is sufficient for development, but it may not be for production. Review the resources your application will need to determine the cost. When you sign up for Google App Engine, it offers a certain amount of free credits which will not be billed.)
+
+Create the project. Take note of the instance identifier, which is in the form of `::`.
+
+Check if `gcloud` lists the project:
+
+```bash
+gcloud projects list
+```
+
+Run `init` to authenticate the cli, and select current cloud project.
+
+```bash
+gcloud init
+```
+
+### Create the database (PostgreSQL)
+
+Create the [Cloud SQL database](https://cloud.google.com/sql/docs/postgres/create-manage-databases) which the app is going to use.
+
+Take note of the user name (default is `postgres`) and password.
+
+The first database will be created with the name `postgres`. This cannot be deleted.
+
+Create another database, named `strapi` for example. It may be useful to delete and and re-create this while you are experimenting with the application setup.
+
+### Create app.yaml and .gcloudignore
+
+Create the `app.yaml` file in the project root.
+
+Add `app.yaml` to `.gitignore`.
+
+The instance identifier looks like `myapi-123456:europe-west1:myapi`.
+
+The `myapi-123456` part is the project identifier. (The number is automatically added to short project names).
+
+The following is an example config for `Standard Environment` or `Flexible Environment`.
+
+:::: tabs
+
+::: tab Standard Environment
+
+```yaml
+runtime: nodejs10
+
+instance_class: F2
+
+env_variables:
+ HOST: '.appspot.com'
+ NODE_ENV: 'production'
+ DATABASE_NAME: 'strapi'
+ DATABASE_USERNAME: 'postgres'
+ DATABASE_PASSWORD: ''
+ INSTANCE_CONNECTION_NAME: ''
+
+beta_settings:
+ cloud_sql_instances: ''
+```
+
+:::
+
+::: tab Flexible Environment
+
+```yaml
+runtime: nodejs10
+
+env: flex
+
+env_variables:
+ HOST: '.appspot.com'
+ NODE_ENV: 'production'
+ DATABASE_NAME: 'strapi'
+ DATABASE_USERNAME: 'postgres'
+ DATABASE_PASSWORD: ''
+ INSTANCE_CONNECTION_NAME: ''
+
+beta_settings:
+ cloud_sql_instances: ''
+```
+
+:::
+
+::::
+
+Create `.gcloudignore` in the project root, include `app.yaml` here as well.
+
+```
+app.yaml
+.gcloudignore
+.git
+.gitignore
+node_modules/
+#!include:.gitignore
+```
+
+In the case of Strapi, the admin UI will have to be re-built after every deploy,
+and so we don't deploy local build artifacts, cache files and so on by including
+the `.gitignore` entries.
+
+### Configure the database
+
+The `PostgreSQL` database will need the `pg` package.
+
+```bash
+yarn add pg
+```
+
+[Google App Engine requires](https://cloud.google.com/sql/docs/postgres/connect-app-engine) to connect to the database using the unix socket path, not an IP and port.
+
+Edit `database.json`, and use the socket path as `host`.
+
+```
+config/environments/production/database.json
+```
+
+```json
+{
+ "defaultConnection": "default",
+ "connections": {
+ "default": {
+ "connector": "bookshelf",
+ "settings": {
+ "client": "postgres",
+ "host": "/cloudsql/${process.env.INSTANCE_CONNECTION_NAME}",
+ "database": "${process.env.DATABASE_NAME}",
+ "username": "${process.env.DATABASE_USERNAME}",
+ "password": "${process.env.DATABASE_PASSWORD}"
+ },
+ "options": {}
+ }
+ }
+}
+```
+
+Edit `server.json` to pick up the deployed hostname from the `HOST` variable in `app.yaml`.
+
+```
+config/environments/production/server.json
+```
+
+```json
+{
+ "host": "${process.env.HOST}",
+ "port": "${process.env.PORT || 1337}",
+ "production": true,
+ "proxy": {
+ "enabled": false
+ },
+ "cron": {
+ "enabled": false
+ },
+ "admin": {
+ "autoOpen": false
+ }
+}
+```
+
+### Auto-build after deploy
+
+After deployment, the admin UI has to be re-built. This generates the contents of the `build` folder on the server.
+
+In `package.json`, add the `gcp-build` command to `scripts`:
+
+```json
+{
+ "scripts": {
+ "gcp-build": "strapi build"
+ }
+}
+```
+
+### Deploy
+
+```bash
+gcloud app deploy app.yaml --project myapi-123456
+```
+
+Watch the logs:
+
+```bash
+gcloud app logs tail --project=myapi-123456 -s default
+```
+
+Open the admin page and register and admin user.
+
+```
+https://myapp-123456.appspot.com/admin/
+```
+
+### File uploading to Google Cloud Storage
+
+[Lith/strapi-provider-upload-google-cloud-storage](https://github.com/Lith/strapi-provider-upload-google-cloud-storage)
+
+```bash
+yarn add strapi-provider-upload-google-cloud-storage
+```
+
+Deploy so that the server app includes the dependency from `package.json`.
+
+Create a Google service account key.
+
+
+
+Save the JSON credentials file.
+
+Plugins > File Upload > Settings > Production tab
+
+By default `localhost` is selected. Select the `Google Cloud Storage` plugin.
+
+Copy the JSON key and set the regions.
+
+Open the `Cloud Console > Storage > Browser` menu.
+
+Copy the bucket name to the plugin settings, the default is the app ID, such as `myapi-123456.appspot.com`.
+
+(Note that the `Access control` setting of the bucket has to be `Fine-grained`, which is the default.)
+
+Click `Save`, and it's ready to go!
+
+### Post-setup configuration
+
+**CORS**
+
+CORS is enabled by default, allowing `*` origin. You may want to limit the allowed origins.
+
+```
+config/environments/production/security.json
+```
+
+**Changing the admin url**
+
+```
+config/environments/production/server.json
+```
+
+```json
+{
+ "admin": {
+ "path": "/dashboard"
+ }
+}
+```
diff --git a/docs/3.0.0-beta.x/getting-started/deployment.md b/docs/3.0.0-beta.x/getting-started/deployment.md
index 1bad0bd4e7..bb284520fd 100644
--- a/docs/3.0.0-beta.x/getting-started/deployment.md
+++ b/docs/3.0.0-beta.x/getting-started/deployment.md
@@ -46,6 +46,18 @@ Manual guides for deployment on various platforms, for One-click and docker plea
+
+
+
+
+
+ Google App Engine
+
+ Manual step by step guide for deploying on GCP's App Engine
+
+
+
+
diff --git a/docs/3.0.0-beta.x/guides/custom-admin.md b/docs/3.0.0-beta.x/guides/custom-admin.md
index 20ad8fa35f..5dd8b49e9e 100644
--- a/docs/3.0.0-beta.x/guides/custom-admin.md
+++ b/docs/3.0.0-beta.x/guides/custom-admin.md
@@ -74,15 +74,15 @@ To be able to see the update, you will need to have a Content Type that has a `d
Then you will have to investigate into the [`strapi-plugin-content-manager`](https://github.com/strapi/strapi/tree/master/packages/strapi-plugin-content-manager) package to find the file that is used to format the date for the list view.
-Here is the [Row component](https://github.com/strapi/strapi/blob/master/packages/strapi-plugin-content-manager/admin/src/components/CustomTable/Row.js) you will have to update.
+Here is the [Row component](https://github.com/strapi/strapi/blob/master/packages/strapi-plugin-content-manager/admin/src/components/CustomTable/Row.js) which requires a [dedicated file](https://github.com/strapi/strapi/blob/master/packages/strapi-plugin-content-manager/admin/src/utils/dateFormats.js) to modify the date display.
### Eject the file
Let's eject the file to be able to customize it.
-**Path —** `./extensions/content-manager/admin/src/components/CustomTable/Row.js`
+**Path —** `./extensions/content-manager/admin/src/utils/dateFormats.js`
-In this new file, paste the current [Row component](https://github.com/strapi/strapi/blob/master/packages/strapi-plugin-content-manager/admin/src/components/CustomTable/Row.js) code.
+In this new file, paste the current [dateFormats](https://github.com/strapi/strapi/blob/master/packages/strapi-plugin-content-manager/admin/src/utils/dateFormats.js) code.
To run your application, you will have to run the `yarn develop --watch-admin` command.
@@ -95,13 +95,17 @@ In our example, we want to change the format of the date. We have to find in thi
Here is the code you have to find:
```js
-return moment
- .parseZone(date)
- .utc()
- .format('dddd, MMMM Do YYYY');
+const dateFormats = {
+ ...defaultDateFormats,
+ // Customise the format by uncommenting the one you wan to override it corresponds to the type of your field
+ // date: 'dddd, MMMM Do YYYY',
+ // datetime: 'dddd, MMMM Do YYYY HH:mm',
+ // time: 'HH:mm A',
+ // timestamp: 'dddd, MMMM Do YYYY HH:mm',
+};
```
-Now let's replace `.format('dddd, MMMM Do YYYY');` by `.format('YYYY/MM/DD');`
+Now let's replace `date: 'dddd, MMMM Do YYYY'` by `date: 'YYYY/MM/DD';`
And tada, the date will now display with the new format.
diff --git a/docs/3.0.0-beta.x/guides/draft.md b/docs/3.0.0-beta.x/guides/draft.md
index ce89660cdf..b9833932df 100644
--- a/docs/3.0.0-beta.x/guides/draft.md
+++ b/docs/3.0.0-beta.x/guides/draft.md
@@ -72,9 +72,7 @@ module.exports = {
entities = await strapi.services.article.find(ctx.query);
}
- return entities.map(entity =>
- sanitizeEntity(entity, { model: strapi.models.article })
- );
+ return entities.map(entity => sanitizeEntity(entity, { model: strapi.models.article }));
},
};
```
@@ -99,7 +97,7 @@ module.exports = {
ctx.query = {
...ctx.query,
- status: 'published'
+ status: 'published',
};
if (ctx.query._q) {
@@ -108,9 +106,7 @@ module.exports = {
entities = await strapi.services.article.find(ctx.query);
}
- return entities.map(entity =>
- sanitizeEntity(entity, { model: strapi.models.article })
- );
+ return entities.map(entity => sanitizeEntity(entity, { model: strapi.models.article }));
},
};
```
@@ -120,4 +116,3 @@ And tada! Draft and archived articles disappeared.
::: tip
This guide can be applied to any other controller action.
:::
-
diff --git a/docs/3.0.0-beta.x/guides/registering-a-field-in-admin.md b/docs/3.0.0-beta.x/guides/registering-a-field-in-admin.md
new file mode 100644
index 0000000000..a4a55a4015
--- /dev/null
+++ b/docs/3.0.0-beta.x/guides/registering-a-field-in-admin.md
@@ -0,0 +1,335 @@
+# Creating a new Field in the administration panel
+
+In this guide we will see how you can create a new Field for your administration panel.
+
+## Introduction
+
+For this example, we will see how to change the WYSIWYG with [CKEditor](https://ckeditor.com/ckeditor-5/) in the **`Content Manager`** plugin by creating a new plugin which will add a new **Field** in your application.
+
+## Setup
+
+1. Create a new project:
+
+```bash
+# Create an application using SQLite and prevent the server from starting automatically as we will create a plugin
+# right after the project generation
+yarn create strapi-app my-app --quickstart --no-run
+```
+
+2. Generate a plugin:
+
+```bash
+yarn run strapi generate:plugin wysiwyg
+```
+
+3. Install the needed dependencies:
+
+```bash
+cd my-app/plugins/wysiwyg
+yarn add @ckeditor/ckeditor5-react @ckeditor/ckeditor5-build-classic
+```
+
+4. Start your application with the front-end development mode:
+
+```bash
+cd my-app
+yarn develop --watch-admin
+```
+
+Once this step is over all we need to do is to create our new WYSIWYG which will replace the default one in the **Content Manager** plugin.
+
+### Creating the WYSIWYG
+
+In this part we will create three components:
+
+- MediaLib which will be used to insert media in the editor
+- Wysiwyg which will wrap the CKEditor with a label and the errors
+- CKEditor which will be the implementation of the new WYSIWYG
+
+### Creating the MediaLib
+
+**Path —** `./plugins/wysiwyg/admin/src/components/MediaLib/index.js`
+
+```js
+import React, { useEffect, useState } from 'react';
+import { useStrapi, prefixFileUrlWithBackendUrl } from 'strapi-helper-plugin';
+import PropTypes from 'prop-types';
+
+const MediaLib = ({ isOpen, onChange, onToggle }) => {
+ const {
+ strapi: {
+ componentApi: { getComponent },
+ },
+ } = useStrapi();
+ const [data, setData] = useState(null);
+ const [isDisplayed, setIsDisplayed] = useState(false);
+
+ useEffect(() => {
+ if (isOpen) {
+ setIsDisplayed(true);
+ }
+ }, [isOpen]);
+
+ const Component = getComponent('media-library').Component;
+
+ const handleInputChange = data => {
+ if (data) {
+ const { url } = data;
+
+ setData({ ...data, url: prefixFileUrlWithBackendUrl(url) });
+ }
+ };
+
+ const handleClosed = () => {
+ if (data) {
+ onChange(data);
+ }
+
+ setData(null);
+ setIsDisplayed(false);
+ };
+
+ if (Component && isDisplayed) {
+ return (
+
+ );
+ }
+
+ return null;
+};
+
+MediaLib.defaultProps = {
+ isOpen: false,
+ onChange: () => {},
+ onToggle: () => {},
+};
+
+MediaLib.propTypes = {
+ isOpen: PropTypes.bool,
+ onChange: PropTypes.func,
+ onToggle: PropTypes.func,
+};
+
+export default MediaLib;
+```
+
+#### Creating the WYSIWYG Wrapper
+
+**Path —** `./plugins/wysiwyg/admin/src/components/Wysiwyg/index.js`
+
+```js
+import React, { useState } from 'react';
+import PropTypes from 'prop-types';
+import { isEmpty } from 'lodash';
+import { Button } from '@buffetjs/core';
+import { Label, InputDescription, InputErrors } from 'strapi-helper-plugin';
+import Editor from '../CKEditor';
+import MediaLib from '../MediaLib';
+
+const Wysiwyg = ({
+ inputDescription,
+ errors,
+ label,
+ name,
+ noErrorsDescription,
+ onChange,
+ value,
+}) => {
+ const [isOpen, setIsOpen] = useState(false);
+ let spacer = !isEmpty(inputDescription) ? : ;
+
+ if (!noErrorsDescription && !isEmpty(errors)) {
+ spacer = ;
+ }
+
+ const handleChange = data => {
+ if (data.mime.includes('image')) {
+ const imgTag = `
`;
+ const newValue = value ? `${value}${imgTag}` : imgTag;
+
+ onChange({ target: { name, value: newValue } });
+ }
+
+ // Handle videos and other type of files by adding some code
+ };
+
+ const handleToggle = () => setIsOpen(prev => !prev);
+
+ return (
+
+
+
+
+
+
+
+
+ {spacer}
+
+
+ );
+};
+
+Wysiwyg.defaultProps = {
+ errors: [],
+ inputDescription: null,
+ label: '',
+ noErrorsDescription: false,
+ value: '',
+};
+
+Wysiwyg.propTypes = {
+ errors: PropTypes.array,
+ inputDescription: PropTypes.oneOfType([
+ PropTypes.string,
+ PropTypes.func,
+ PropTypes.shape({
+ id: PropTypes.string,
+ params: PropTypes.object,
+ }),
+ ]),
+ label: PropTypes.oneOfType([
+ PropTypes.string,
+ PropTypes.func,
+ PropTypes.shape({
+ id: PropTypes.string,
+ params: PropTypes.object,
+ }),
+ ]),
+ name: PropTypes.string.isRequired,
+ noErrorsDescription: PropTypes.bool,
+ onChange: PropTypes.func.isRequired,
+ value: PropTypes.string,
+};
+
+export default Wysiwyg;
+
+```
+
+#### Implementing CKEditor
+
+**Path —** `./plugins/wysiwyg/admin/src/components/CKEditor/index.js`
+
+```js
+import React from 'react';
+import PropTypes from 'prop-types';
+import CKEditor from '@ckeditor/ckeditor5-react';
+import ClassicEditor from '@ckeditor/ckeditor5-build-classic';
+import styled from 'styled-components';
+
+const Wrapper = styled.div`
+ .ck-editor__main {
+ min-height: 200px;
+ > div {
+ min-height: 200px;
+ }
+ }
+`;
+
+const configuration = {
+ toolbar: [
+ 'heading',
+ '|',
+ 'bold',
+ 'italic',
+ 'link',
+ 'bulletedList',
+ 'numberedList',
+ '|',
+ 'indent',
+ 'outdent',
+ '|',
+ 'blockQuote',
+ 'insertTable',
+ 'mediaEmbed',
+ 'undo',
+ 'redo',
+ ],
+};
+
+const Editor = ({ onChange, name, value }) => {
+ return (
+
+ {
+ const data = editor.getData();
+ onChange({ target: { name, value: data } });
+ }}
+ />
+
+ );
+};
+
+Editor.propTypes = {
+ onChange: PropTypes.func.isRequired,
+ name: PropTypes.string.isRequired,
+ value: PropTypes.string,
+};
+
+export default Editor;
+```
+
+At this point we have simply created a new plugin which is mounted in our project but our custom **Field** has not been registered yet.
+
+### Registering a our new Field
+
+Since the goal of our plugin is to override the current WYSIWYG we don't want it to be displayed in the administration panel but we need it to register our new **Field**. In order to do so, we will simply modify the front-end entry point of our plugin:
+
+**Path —** `./plugins/wysiwyg/admin/src/index.js`
+
+```js
+import pluginPkg from '../../package.json';
+import Wysiwyg from './components/Wysiwyg';
+import pluginId from './pluginId';
+
+export default strapi => {
+ const pluginDescription = pluginPkg.strapi.description || pluginPkg.description;
+
+ const plugin = {
+ blockerComponent: null,
+ blockerComponentProps: {},
+ description: pluginDescription,
+ icon: pluginPkg.strapi.icon,
+ id: pluginId,
+ initializer: () => null,
+ injectedComponents: [],
+ isReady: true,
+ isRequired: pluginPkg.strapi.required || false,
+ leftMenuLinks: [],
+ leftMenuSections: [],
+ mainComponent: null,
+ name: pluginPkg.strapi.name,
+ preventComponentRendering: false,
+ settings: null,
+ trads: {},
+ };
+
+ strapi.registerField({ type: 'wysiwyg', Component: Wysiwyg });
+
+ return strapi.registerPlugin(plugin);
+};
+```
+
+And VOILA, if you create a new `collectionType` or a `singleType` with a `richtext` field you will see the implementation of [CKEditor](https://ckeditor.com/ckeditor-5/) instead of the default WYSIWYG.
diff --git a/docs/3.0.0-beta.x/guides/secure-your-app.md b/docs/3.0.0-beta.x/guides/secure-your-app.md
index 4daeb26cbf..27bd5437e0 100644
--- a/docs/3.0.0-beta.x/guides/secure-your-app.md
+++ b/docs/3.0.0-beta.x/guides/secure-your-app.md
@@ -46,7 +46,7 @@ Now you can run `node server.js` and it will start your application.
By following their Node.js onboarding, we need to require the Sqreen node_module where the server is started.
Also, Sqreen has to be required just before Strapi to work!
-*This is the reason why we have created a `server.js` file.*
+_This is the reason why we have created a `server.js` file._
To do so, you will have to update this file.
diff --git a/docs/3.0.0-beta.x/migration-guide/README.md b/docs/3.0.0-beta.x/migration-guide/README.md
index cc29fcd06b..fd1c41c91f 100644
--- a/docs/3.0.0-beta.x/migration-guide/README.md
+++ b/docs/3.0.0-beta.x/migration-guide/README.md
@@ -11,6 +11,7 @@ Read the [Migration guide from alpha.26 to beta](migration-guide-alpha.26-to-bet
- [Migration guide from beta.17+ to beta.18](migration-guide-beta.17-to-beta.18.md)
- [Migration guide from beta.18 to beta.19](migration-guide-beta.18-to-beta.19.md)
- [Migration guide from beta.19+ to beta.19.4](migration-guide-beta.19-to-beta.19.4.md)
+- [Migration guide from beta.19.4+ to beta.20](migration-guide-beta.19-to-beta.20.md)
## Alpha guides
diff --git a/docs/3.0.0-beta.x/migration-guide/migration-guide-beta.19-to-beta.19.4.md b/docs/3.0.0-beta.x/migration-guide/migration-guide-beta.19-to-beta.19.4.md
index 333ae446b1..6f54560683 100644
--- a/docs/3.0.0-beta.x/migration-guide/migration-guide-beta.19-to-beta.19.4.md
+++ b/docs/3.0.0-beta.x/migration-guide/migration-guide-beta.19-to-beta.19.4.md
@@ -74,7 +74,7 @@ In some cases (with Heroku, Docker...), listening to `localhost` won't work. In
### Example
-**After -** `./config/environments/**/server.js`
+**Before -** `./config/environments/**/server.js`
```json
{
diff --git a/docs/3.0.0-beta.x/migration-guide/migration-guide-beta.19-to-beta.20.md b/docs/3.0.0-beta.x/migration-guide/migration-guide-beta.19-to-beta.20.md
new file mode 100644
index 0000000000..e171494367
--- /dev/null
+++ b/docs/3.0.0-beta.x/migration-guide/migration-guide-beta.19-to-beta.20.md
@@ -0,0 +1,217 @@
+# Migration guide from beta.19.x to beta.20
+
+Upgrading your Strapi application to `v3.0.0-beta.20`.
+
+**Make sure your server is not running until the end of the migration**
+
+## Upgrading your dependencies
+
+Start by upgrading your dependencies. Make sure to use exact versions.
+
+Update your package.json accordingly:
+
+```json
+{
+ //...
+ "dependencies": {
+ "strapi": "3.0.0-beta.20",
+ "strapi-admin": "3.0.0-beta.20",
+ "strapi-connector-bookshelf": "3.0.0-beta.20",
+ "strapi-plugin-content-manager": "3.0.0-beta.20",
+ "strapi-plugin-content-type-builder": "3.0.0-beta.20",
+ "strapi-plugin-email": "3.0.0-beta.20",
+ "strapi-plugin-graphql": "3.0.0-beta.20",
+ "strapi-plugin-upload": "3.0.0-beta.20",
+ "strapi-plugin-users-permissions": "3.0.0-beta.20",
+ "strapi-utils": "3.0.0-beta.20"
+ }
+}
+```
+
+Then run either `yarn install` or `npm install`.
+
+## Index page
+
+Some users have been asking to make the `/` route customizable and to be able to disable it.
+
+To allow customizations, the server will now serve the files in your `./public` folder as is. To migrate, you must delete the `index.html` and `production.html` files in the `./public` directory.
+
+From now on, if you don't have any `index.html` file in your `./public` folder, the server will render the default Strapi homepage.
+
+You can now also disable this behavior with the `public.defaultIndex` option. Read the documentation [here](../concepts/configurations.md#application).
+
+## Upload plugin settings
+
+A lot of our users have been requesting that we move some back-end specific configurations to files. While implementing the media library feature, we decided to move the upload plugin settings to files.
+
+This means that you now have to configure your provider directly in the files. You can read the documentation [here](../plugins/upload.md#using-a-provider) to update.
+
+## MongoDB Media relation changes
+
+::: tip
+The guide below only applies if you are using MongoDB/Mongoose, if you are using a Bookshelf database you can simply skip to the [rebuilding step](#rebuilding-your-administration-panel)
+:::
+
+In the media library features, We wanted to make sure media would keep their ordering. To implement this in mongo we had to change the way the media relation was built.
+
+Previously, the `upload_file` collection was the one keeping track of the relations and the entity related to the file had not reference to it.
+Implementing ordering without changes the relations proved unfeasible. Finally we decided to add the reverse reference in the entities so it would make accessing the files really easy.
+
+You will hence need to migrate your `mongo` database to avoid losing references to your files.
+
+### Backup your database
+
+When running in production, you should always backup your database before running migrations. To backup a `mongo` database, look at the documentation [here](https://docs.mongodb.com/manual/core/backups/)
+
+### Export model metadatas
+
+First create a `export.js` file at the root of your project with the following content:
+
+```js
+const fs = require('fs');
+
+require('strapi')()
+ .load()
+ .then(() => {
+ const models = {};
+
+ Object.keys(strapi.api).forEach(apiName => {
+ Object.values(strapi.api[apiName].models || {}).forEach(model => {
+ models[model.globalId] = formatModel(model);
+ });
+ });
+
+ Object.keys(strapi.plugins).forEach(pluginName => {
+ Object.values(strapi.plugins[pluginName].models || {}).forEach(model => {
+ models[model.globalId] = formatModel(model);
+ });
+ });
+
+ Object.values(strapi.components).forEach(model => {
+ models[model.globalId] = formatModel(model);
+ });
+
+ fs.writeFileSync('models.json', JSON.stringify(models, null, 2));
+ process.exit(0);
+ });
+
+function formatModel(model) {
+ return {
+ collection: model.collectionName,
+ files: Object.keys(model.attributes).reduce((acc, key) => {
+ const attr = model.attributes[key];
+ if (attr.model === 'file' && attr.plugin === 'upload') {
+ acc[key] = 'single';
+ }
+
+ if (attr.collection === 'file' && attr.plugin === 'upload') {
+ acc[key] = 'multiple';
+ }
+ return acc;
+ }, {}),
+ };
+}
+```
+
+then run the script from the root of your project
+
+```sh
+node export.js
+```
+
+This script will create a models.json file at the root of your project that looks like:
+
+```json
+{
+ "ModelName": {
+ "collection": "collectionName",
+ "files": {
+ "image": "single",
+ "images": "multiple"
+ }
+ }
+}
+```
+
+### Create migration script
+
+You then need to create the mongo shell script to run to migrate your data strucutre. First create a `migration.js` file with the following code:
+
+```js
+var models = {
+ /* paste the object model.json here */
+};
+
+for (var i in models) {
+ var model = models[i];
+ var update = {};
+ var keyCount = 0;
+
+ for (var key in model.files) {
+ keyCount += 1;
+ update[key] = '';
+ }
+
+ if (keyCount > 0) {
+ db.getCollection(model.collection).update({}, { $unset: update }, { multi: true });
+ }
+}
+
+var fileCursor = db.getCollection('upload_file').find({});
+
+while (fileCursor.hasNext()) {
+ var el = fileCursor.next();
+ el.related.forEach(function(fileRef) {
+ var model = models[fileRef.kind];
+
+ if (!model) {
+ return;
+ }
+
+ var fieldType = model.files && model.files[fileRef.field];
+
+ // stop if the file points to a field the user didn't specify
+ if (!fieldType) {
+ return;
+ }
+
+ if (fieldType === 'single') {
+ db.getCollection(model.collection).updateOne(
+ { _id: fileRef.ref },
+ { $set: { [fileRef.field]: el._id } }
+ );
+ } else if (fieldType === 'multiple') {
+ db.getCollection(model.collection).updateOne(
+ { _id: fileRef.ref },
+ { $push: { [fileRef.field]: el._id } }
+ );
+ }
+ });
+}
+```
+
+Then you will need to copy the content of `models.json` to the `models` variable at the top of this script:
+
+```js
+var models = {
+ ModelName: {
+ collection: 'collectionName',
+ files: {
+ image: 'single',
+ images: 'multiple',
+ },
+ },
+};
+
+// rest of the script
+```
+
+Finally you can load this script in your mongo shell and run it.
+
+Once your migration is done you can delete the `export.js` and `models.json` files from your project. You are all set !
+
+## Rebuilding your administration panel
+
+You can run `yarn build --clean` or `npm run build -- --clean` to rebuild your admin panel with the newly installed version of strapi.
+
+Finally restart your server: `yarn develop` or `npm run develop`.
diff --git a/docs/3.0.0-beta.x/plugin-development/frontend-components-api.md b/docs/3.0.0-beta.x/plugin-development/frontend-components-api.md
new file mode 100644
index 0000000000..73092d30cb
--- /dev/null
+++ b/docs/3.0.0-beta.x/plugin-development/frontend-components-api.md
@@ -0,0 +1,160 @@
+# Plugin's front-end Component API
+
+As plugins developer you may need to add custom components in your application so other plugin may use them. To do so, a **Component API** is available in order for a plugin to register a component which will be available for all plugins.
+
+## Registering a new component
+
+Registering a component can be made in two different ways:
+
+1. During the load phase of a plugin
+2. Using the provided `react-hook` in a component.
+
+### Registering a component during the load of a plugin
+
+Registering a component during the load phase of a plugin can be done as follows:
+
+1. Create a new Field type (in this example a **`media`** field type):
+
+**Path —** `plugins/my-plugin/admin/src/components/MyComponent/index.js`.
+
+```js
+import React from 'react';
+
+const MyComponent = () => {
+ return
MyComponent
;
+};
+
+export default MyComponent;
+```
+
+2. Register the field into the application:
+
+**Path —** `plugins/my-plugin/admin/src/index.js`.
+
+```js
+import pluginPkg from '../../package.json';
+import MyComponent from './components/MyComponent';
+import pluginId from './pluginId';
+
+export default strapi => {
+ const pluginDescription = pluginPkg.strapi.description || pluginPkg.description;
+
+ const plugin = {
+ blockerComponent: null,
+ blockerComponentProps: {},
+ description: pluginDescription,
+ icon: pluginPkg.strapi.icon,
+ id: pluginId,
+ initializer: () => null,
+ injectedComponents: [],
+ isReady: true,
+ leftMenuLinks: [],
+ leftMenuSections: [],
+ mainComponent: null,
+ name: pluginPkg.strapi.name,
+ preventComponentRendering: false,
+ trads: {},
+ };
+
+ strapi.registerComponent({ name: 'my-component', Component: MyComponent });
+
+ return strapi.registerPlugin(plugin);
+};
+```
+
+By doing so, all the plugins from your project will be able to use the newly registered **Component**.
+
+### Registering a component inside a React Component
+
+The other way to register a **Component** is to use the provided `react-hook`: **`useStrapi`** it can be done in the `Initializer` Component so it is accessible directly when the user is logged in, if you decide to register your plugin in another component than the `Initializer` the **Component** will only be registered in the administration panel once the component is mounted (the user has navigated to the view where the **Component** is registered).
+
+1. Register the **Component** in the `Initializer` Component:
+
+**Path —** `plugins/my-plugin/admin/src/containers/Initializer/index.js`.
+
+```js
+/**
+ *
+ * Initializer
+ *
+ */
+
+import { useEffect, useRef } from 'react';
+import PropTypes from 'prop-types';
+import { useStrapi } from 'strapi-helper-plugin';
+import pluginId from '../../pluginId';
+import MyComponent from './components/MyComponent';
+
+const Initializer = ({ updatePlugin }) => {
+ const {
+ strapi: { componentApi },
+ } = useStrapi();
+ const ref = useRef();
+ ref.current = updatePlugin;
+
+ useEffect(() => {
+ // Register the new component
+ strapi.componentApi.registerComponent({ name: 'my-component', Component: MyComponent });
+
+ ref.current(pluginId, 'isReady', true);
+ }, []);
+
+ return null;
+};
+
+Initializer.propTypes = {
+ updatePlugin: PropTypes.func.isRequired,
+};
+
+export default Initializer;
+```
+
+2. Add the `Initializer` component to your plugin so it is mounted in the administration panel once the user is logged in:
+
+```js
+import pluginPkg from '../../package.json';
+import pluginLogo from './assets/images/logo.svg';
+import App from './containers/App';
+import Initializer from './containers/Initializer';
+import lifecycles from './lifecycles';
+import trads from './translations';
+import pluginId from './pluginId';
+
+export default strapi => {
+ const pluginDescription = pluginPkg.strapi.description || pluginPkg.description;
+ const plugin = {
+ blockerComponent: null,
+ blockerComponentProps: {},
+ description: pluginDescription,
+ icon: pluginPkg.strapi.icon,
+ id: pluginId,
+ initializer: Initializer,
+ injectedComponents: [],
+ isRequired: pluginPkg.strapi.required || false,
+ layout: null,
+ lifecycles,
+ leftMenuLinks: [],
+ leftMenuSections: [],
+ mainComponent: App,
+ name: pluginPkg.strapi.name,
+ pluginLogo,
+ preventComponentRendering: false,
+ trads,
+ };
+
+ return strapi.registerPlugin(plugin);
+};
+```
+
+## Consuming the Component API
+
+Consuming the **Component** API can only be done by using the provided `react-hook` **`useStrapi`**.
+
+## Component API definition
+
+| Method | Param | Description |
+| :---------------- | :------------ | :----------------------------------------- |
+| getComponent | {String} name | Retrieve a Component depending on the name |
+| getComponents | | Retrieve all the Components |
+| registerComponent | {Object} | Register a Component |
+| removeComponent | | Remove a Component |
diff --git a/docs/3.0.0-beta.x/plugin-development/frontend-field-api.md b/docs/3.0.0-beta.x/plugin-development/frontend-field-api.md
new file mode 100644
index 0000000000..82b0a4a894
--- /dev/null
+++ b/docs/3.0.0-beta.x/plugin-development/frontend-field-api.md
@@ -0,0 +1,234 @@
+# Plugin's front-end Field API
+
+As plugins developer you may need to add custom fields in your application. To do so, a **Field API** is available in order for a plugin to register a field which will be available for all plugins.
+
+::: warning NOTE
+
+Currently, only the content manager uses this API to extend its current fields.
+
+:::
+
+## Registering a new field
+
+Registering a field can be made in two different ways:
+
+1. During the load phase of a plugin
+2. Using the provided `react-hook` in a component.
+
+### Registering a field during the load of a plugin
+
+Registering a field during the load phase of a plugin can be done as follows:
+
+1. Create a new Field type (in this example a **`media`** field type):
+
+**Path —** `plugins/my-plugin/admin/src/components/InputMedia/index.js`.
+
+```js
+import React from 'react';
+const InputMedia = props => {
+ // Check out the provided props
+ console.log(props);
+
+ return
InputMedia
;
+};
+
+export default InputMedia;
+```
+
+2. Register the field into the application:
+
+**Path —** `plugins/my-plugin/admin/src/index.js`.
+
+```js
+import pluginPkg from '../../package.json';
+import InputMedia from './components/InputMedia';
+import pluginId from './pluginId';
+
+export default strapi => {
+ const pluginDescription = pluginPkg.strapi.description || pluginPkg.description;
+
+ const plugin = {
+ blockerComponent: null,
+ blockerComponentProps: {},
+ description: pluginDescription,
+ icon: pluginPkg.strapi.icon,
+ id: pluginId,
+ initializer: () => null,
+ injectedComponents: [],
+ isReady: true,
+ leftMenuLinks: [],
+ leftMenuSections: [],
+ mainComponent: null,
+ name: pluginPkg.strapi.name,
+ preventComponentRendering: false,
+ trads: {},
+ };
+
+ strapi.registerField({ type: 'media', Component: InputMedia });
+
+ return strapi.registerPlugin(plugin);
+};
+```
+
+By doing so, all the plugins from your project will be able to use the newly registered **Field** type.
+
+### Registering a field inside a React Component
+
+The other way to register a **Field** is to use the provided `react-hook`: **`useStrapi`** it can be done in the `Initializer` Component so it is accessible directly when the user is logged in, if you decide to register your plugin in another component than the `Initializer` the **Field** will only be registered in the administration panel once the component is mounted (the user has navigated to the view where the **Field** is registered).
+
+1. Register the **Field** in the `Initializer` Component:
+
+**Path —** `plugins/my-plugin/admin/src/containers/Initializer/index.js`.
+
+```js
+/**
+ *
+ * Initializer
+ *
+ */
+
+import { useEffect, useRef } from 'react';
+import PropTypes from 'prop-types';
+import { useStrapi } from 'strapi-helper-plugin';
+import pluginId from '../../pluginId';
+import InputMedia from './components/InputMedia';
+
+const Initializer = ({ updatePlugin }) => {
+ const {
+ strapi: { fieldApi },
+ } = useStrapi();
+ const ref = useRef();
+ ref.current = updatePlugin;
+
+ useEffect(() => {
+ // Register the new field
+ strapi.fieldApi.registerField({ type: 'media', Component: InputMedia });
+
+ ref.current(pluginId, 'isReady', true);
+ }, []);
+
+ return null;
+};
+
+Initializer.propTypes = {
+ updatePlugin: PropTypes.func.isRequired,
+};
+
+export default Initializer;
+```
+
+2. Add the `Initializer` component to your plugin so it is mounted in the administration panel once the user is logged in:
+
+```js
+import pluginPkg from '../../package.json';
+import pluginLogo from './assets/images/logo.svg';
+import App from './containers/App';
+import Initializer from './containers/Initializer';
+import lifecycles from './lifecycles';
+import trads from './translations';
+import pluginId from './pluginId';
+
+export default strapi => {
+ const pluginDescription = pluginPkg.strapi.description || pluginPkg.description;
+ const plugin = {
+ blockerComponent: null,
+ blockerComponentProps: {},
+ description: pluginDescription,
+ icon: pluginPkg.strapi.icon,
+ id: pluginId,
+ initializer: Initializer,
+ injectedComponents: [],
+ isRequired: pluginPkg.strapi.required || false,
+ layout: null,
+ lifecycles,
+ leftMenuLinks: [],
+ leftMenuSections: [],
+ mainComponent: App,
+ name: pluginPkg.strapi.name,
+ pluginLogo,
+ preventComponentRendering: false,
+ trads,
+ };
+
+ return strapi.registerPlugin(plugin);
+};
+```
+
+## Consuming the Field API
+
+Consuming the **Field** API can only be done by using the provided `react-hook` **`useStrapi`**. Here's an example from the **content-manager** plugin:
+
+**Path —** `~/strapi-plugin-content-manager/admin/src/components/Inputs/index.js`.
+
+```js
+import React, { memo, useMemo } from 'react';
+// Other imports
+// ...
+// Import the Inputs component from our component library Buffet.js
+import { Inputs as InputsIndex } from '@buffetjs/custom';
+
+// Import the Hook with which you can access the Field API
+import { useStrapi } from 'strapi-helper-plugin';
+
+function Inputs({ autoFocus, keys, layout, name, onBlur }) {
+ // This is where you will access the field API
+ const {
+ strapi: { fieldApi },
+ } = useStrapi();
+
+ // Other boilerplate code
+ // ...
+
+ return (
+
+ {error => {
+ return (
+
+ );
+ }}
+
+ );
+}
+```
+
+## Field API definition
+
+| Method | Param | Description |
+| :------------ | :------------ | :------------------------------------- |
+| getField | {String} type | Retrieve a Field depending on the type |
+| getFields | | Retrieve all the Fields |
+| registerField | {Object} | Register a Field |
+| removeField | | Remove a Field |
diff --git a/docs/3.0.0-beta.x/plugin-development/frontend-settings-api.md b/docs/3.0.0-beta.x/plugin-development/frontend-settings-api.md
index 66764c01e6..0f9c1419a9 100644
--- a/docs/3.0.0-beta.x/plugin-development/frontend-settings-api.md
+++ b/docs/3.0.0-beta.x/plugin-development/frontend-settings-api.md
@@ -19,13 +19,12 @@ The menu section can be declared as follows:
**Path —** `plugins/my-plugin/admin/src/index.js`.
-```
+```js
import pluginPkg from '../../package.json';
import pluginId from './pluginId';
export default strapi => {
- const pluginDescription =
- pluginPkg.strapi.description || pluginPkg.description;
+ const pluginDescription = pluginPkg.strapi.description || pluginPkg.description;
// Declare the links that will be injected into the settings menu
const menuSection = {
@@ -90,7 +89,7 @@ With the configuration from above we could easily create our plugin Settings vie
**Path —** `plugins/my-plugin/admin/src/containers/Settings/index.js`.
-```
+```js
import React from 'react';
import PropTypes from 'prop-types';
import { Switch, Route } from 'react-router-dom';
@@ -110,14 +109,8 @@ const SettingPage2 = () => (
const Settings = ({ settingsBaseURL }) => {
return (
-
-
+
+
);
};
@@ -133,15 +126,14 @@ Now that the `Settings` component is declared in your plugin the only thing left
**Path —** `plugins/my-plugin/admin/src/index.js`.
-```
+```js
import pluginPkg from '../../package.json';
// Import the component
import Settings from './containers/Settings';
import pluginId from './pluginId';
export default strapi => {
- const pluginDescription =
- pluginPkg.strapi.description || pluginPkg.description;
+ const pluginDescription = pluginPkg.strapi.description || pluginPkg.description;
// Declare the links that will be injected into the settings menu
const menuSection = {
@@ -191,3 +183,83 @@ export default strapi => {
return strapi.registerPlugin(plugin);
};
```
+
+## Adding a setting into the global section
+
+In order to add a link into the global section of the settings view you need to create a global array containing the links you want to add:
+
+**Path —** `plugins/my-plugin/admin/src/index.js`.
+
+```js
+import pluginPkg from '../../package.json';
+// Import the component
+import Settings from './containers/Settings';
+import SettingLink from './components/SettingLink';
+import pluginId from './pluginId';
+
+export default strapi => {
+ const pluginDescription = pluginPkg.strapi.description || pluginPkg.description;
+
+ // Declare the links that will be injected into the settings menu
+ const menuSection = {
+ id: pluginId,
+ title: {
+ id: `${pluginId}.foo`,
+ defaultMessage: 'Super cool setting',
+ },
+ links: [
+ {
+ title: 'Setting page 1',
+ to: `${strapi.settingsBaseURL}/${pluginId}/setting1`,
+ name: 'setting1',
+ },
+ {
+ title: {
+ id: `${pluginId}.bar`,
+ defaultMessage: 'Setting page 2',
+ },
+ to: `${strapi.settingsBaseURL}/${pluginId}/setting2`,
+ name: 'setting2',
+ },
+ ],
+ };
+
+ const plugin = {
+ blockerComponent: null,
+ blockerComponentProps: {},
+ description: pluginDescription,
+ icon: pluginPkg.strapi.icon,
+ id: pluginId,
+ initializer: () => null,
+ injectedComponents: [],
+ isReady: true,
+ leftMenuLinks: [],
+ leftMenuSections: [],
+ mainComponent: null,
+ name: pluginPkg.strapi.name,
+ preventComponentRendering: false,
+ settings: {
+ // Add a link into the global section of the settings view
+ global: [
+ {
+ title: 'Setting link 1',
+ to: `${strapi.settingsBaseURL}/setting-link-1`,
+ name: 'settingLink1',
+ Component: SettingLink,
+ // Bool : https://reacttraining.com/react-router/web/api/Route/exact-bool
+ exact: false,
+ },
+ ],
+ mainComponent: Settings,
+ menuSection,
+ },
+ trads: {},
+ };
+
+ return strapi.registerPlugin(plugin);
+};
+```
+
+::: danger
+It is currently not possible to add a link into another plugin's setting section
+:::
diff --git a/docs/3.0.0-beta.x/plugins/upload.md b/docs/3.0.0-beta.x/plugins/upload.md
index 2eb8f93a11..07bc1b65db 100644
--- a/docs/3.0.0-beta.x/plugins/upload.md
+++ b/docs/3.0.0-beta.x/plugins/upload.md
@@ -2,6 +2,26 @@
Thanks to the plugin `Upload`, you can upload any kind of file on your server or external providers such as **AWS S3**.
+## Configuration
+
+Currently the strapi middleware in charge of parsing request needs to be configured to support bigger file sizes if you need to upload file with a size greater than 200MB.
+
+The library we use is [`koa-body`](https://github.com/dlau/koa-body), and itself uses the [`node-formidable`](https://github.com/felixge/node-formidable) library to process files.
+
+You can pass configuration to the middleware directly by setting it in the `parser` middleware configuration:
+
+```json
+{
+ "parser": {
+ "enabled": true,
+ "multipart": true,
+ "formidable": {
+ "maxFileSize": 20000000 // defaults to 200mb
+ }
+ }
+}
+```
+
## Endpoints