diff --git a/.gitignore b/.gitignore index 051c482f88..43d2a4ca80 100755 --- a/.gitignore +++ b/.gitignore @@ -78,6 +78,7 @@ $RECYCLE.BIN/ *# .idea nbproject +.vscode/ ############################ diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 5d9ab64699..47723e14fb 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,6 +1,6 @@ # Contribute to Strapi -👍🎉 First off, thanks for taking the time to contribute! 🎉👍 +First off, thanks for taking the time to contribute! 🎉👍 The following is a set of guidelines for contributing to Strapi and its packages. @@ -21,51 +21,126 @@ Every user can send a feature request using the [issues](https://github.com/stra ## Repository Organization We made the choice to use a monorepo design such as [React](https://github.com/facebook/react/tree/master/packages), [Babel](https://github.com/babel/babel/tree/master/packages), [Meteor](https://github.com/meteor/meteor/tree/devel/packages) or [Ember](https://github.com/emberjs/ember.js/tree/master/packages) do. It allows the community to easily maintain the whole ecosystem up-to-date and consistent. -The Babel team wrotes an excellent short post about [the pros and cons of the monorepo design](https://github.com/babel/babel/blob/master/doc/design/monorepo.md). +The Babel team wrote an excellent short post about [the pros and cons of the monorepo design](https://github.com/babel/babel/blob/master/doc/design/monorepo.md). -We will do our best to keep the master branch clean as possible, with tests passing all the times. However, it can happen that the master branch moves faster than the release cycle. To ensure to use the latest stable version, please refers to the [release on npm](https://www.npmjs.com/package/strapi). +We will do our best to keep the master branch as clean as possible, with tests passing all the times. However, it can happen that the master branch moves faster than the release cycle. To ensure you have the latest stable version, please refer to the [release on npm](https://www.npmjs.com/package/strapi). -If you send a pull request, please do it again the `master` branch. We are developing upcoming versions separately to ensure non-breaking changes from master to the latest stable major version. +If you send a pull request, please do it against the `master` branch. We are developing upcoming versions separately to ensure non-breaking changes from master to the latest stable major version. *** ## Setup Development Environment To facilitate the contribution, we drastically reduce the amount of commands necessary to install the entire development environment. First of all, you need to check if you're using the recommended versions of Node.js (v8) and npm (v5). -**Then, please follow the instructions below:** +Then, please follow the instructions below: -1. [Fork the repository](https://github.com/strapi/strapi) to your own GitHub account. -2. Clone it to your computer `git clone git@github.com:strapi/strapi.git`. -3. Run `npm run setup` at the root of the directory. +#### 1. ▪️ Fork the repository -> Note: If the installation failed, please remove the global packages related to Strapi. The command `npm ls strapi` will help you to find where your packages are installed globally. +[Go to the repository](https://github.com/strapi/strapi) and fork it to your own GitHub account. -> Note: You can run `npm run setup:build` to build the plugins' admin (the setup time will be longer). +#### 2. 💿 Clone the repository +```bash +git clone git@github.com:strapi/strapi.git +``` -The development environment has been installed. Now, you have to create a development project to live-test your updates. +#### 3. ⏳ Installation + +Go to the root of the repository. +```bash +cd strapi +``` -1. Go to a folder on your computer `cd /path/to/my/folder`. -2. Create a new project `strapi new myDevelopmentProject --dev`. -3. Start your app with `strapi start`. +**Two setup are available... with or without the front-end builds.** -Awesome! You are now able to make bug fixes or enhancements in the framework layer of Strapi. **To make updates in the administration panel, you need to go a little bit further.** +Without the front-end builds, you won't be able to access the administration panel via http://localhost:1337/admin. You'll have to run the administration separately and access it through http://localhost:4000/admin. -4. Open a new tab or new terminal window. -5. Go to the `my-app/admin` folder of your currently running app. -6. Run `npm start` and go to the following url [http://localhost:4000/admin](http://localhost:4000/admin) +
+ +Without the front-end builds (recommended) +```bash +npm run setup +``` +or with the front-end builds +```bash +npm run setup:build +``` + +> ⚠️  If the installation failed, please remove the global packages related to Strapi. The command `npm ls strapi` will help you to find where your packages are installed globally. + +#### 4. 🏗 Create a new project + +You can open a new terminal window and go into any folder you want for the next steps. +```bash +cd /.../workspace/ +``` + +The command to generate a project is the same, except you have to add the `--dev` argument at the end of line. +```bash +strapi new my-project --dev +``` + +#### 5. 🚀 Start the project + +First, you have to start the server. +```bash +cd ./my-project +strapi start +``` + +The server (API) is available at http://localhost:1337 + +> ⚠️  If you've followed the recommended setup, you should not be able to reach the administration panel at http://localhost:1337/admin. + +Then, you have to start the Webpack server to build and run the administration. +```bash +cd ./my-project/admin +npm run start +``` + +The administration panel is available at http://localhost:4000/admin + +**Awesome! You are now able to contribute to Strapi.** + +--- ## Plugin Development Setup -To create a new plugin, you'll have to run the following commands +To create a new plugin, you'll have to run the following commands: -1. In your project folder `cd myDevelopmentProject && strapi generate:plugin my-plugin`. -2. Make sure that the `strapi-helper-plugin` is linked to your plugin - - In the folder where strapi is cloned `cd pathToStrapiRepo/strapi/packages/strapi-helper-plugin && npm link`. - - In your project folder `cd pathToMyProject/myDevelopmentProject/plugins/my-plugin && npm link strapi-helper-plugin`. -3. Start the server in the admin folder `cd pathToMyProject/myDevelopmentProject/admin && npm start` and go to the following url [http://localhost:4000/admin](http://localhost:4000/admin). +#### 1. 🏗 Generate a new plugin -*** +```bash +cd ./my-project +strapi generate:plugin my-plugin +``` + +#### 2. ✅ Verify the symlink + +Make you that the `strapi-helper-plugin` is linked to your project. + +Please run this command in the repository folder where Strapi is cloned: +```bash +cd /repository/strapi/packages/strapi-helper-plugin +npm link +``` + +Link the `strapi-helper-plugin` node_modules in the plugin folder: +```bash +cd ./my-project/plugins/my-plugin +npm link strapi-helper-plugin +``` + +#### 3. 🚀 Start the project + +```bash +cd ./my-project/admin +npm run start +``` + +The administration panel is available at http://localhost:4000/admin + +--- ## Reporting an issue diff --git a/README.md b/README.md index d08a19d2ab..97d8a823be 100755 --- a/README.md +++ b/README.md @@ -1,51 +1,95 @@ -

+

+ We're hiring! Located in Paris 🇫🇷 and dreaming of being full-time on Strapi? + Join us! +

+ +--- + +

+ + Strapi logo + +

API creation made simple, secure and fast.

-

The most advanced open-source Content Management Framework to build powerful API with no effort.

+

The most advanced open-source Content Management Framework (headless-CMS) to build powerful API with no effort.


- Dependency Status + NPM Version - Dependency Status + Monthly download on NPM - Dependency Status + Travis Build Status - Dependency Status + Strapi on Slack


-

+

+ + + +


-## Quick start +## Getting Started -We've been working on a major update to Strapi for several months now, rewriting the core framework and the administration panel. Performances has been increased, Developer eXperience has been improved and a brand new plugins -ecosystem has been introduced. **Both versions are available, we still recommend you to use v1 for production usage.**. +Read the Getting Started tutorial or follow the steps below: + +#### 🖐 Requirements + +Node: + * NodeJS >= 10.x + * NPM >= 6.x + +Database: + * MongoDB >= 3.x + * MySQL >= 5.6 + * MariaDB >= 10.1 + * PostgreSQL >= 10 + +#### ⏳ Installation + +```bash +npm install strapi@alpha -g +```` + +**We recommend to use the latest version of Strapi to start your new project**. +Some breaking changes might happen, new releases are shipped every two weeks to fix/enhance the product. + +#### 🏗 Create a new project + +```bash +strapi new my-project +``` + +It will generate a brand new project with the default features (authentication, permissions, content management, content type builder & file upload). + +#### 🚀 Start your project + +```bash +cd my-project +strapi start +``` + +Congratulations, you made it! Enjoy 🎉 + +
+ +You can also give it a try using Heroku! Be aware that one of the content type builder won't work due to the writing files restriction on the Heroku servers. Deploy -#### Alpha +
-The alpha has support for the latest version of Node.js (v9) and npm (v5). -```bash -npm install strapi@alpha -g -``` - -#### Stable -This is the production-ready version of Strapi (v1). You should also consider that the migration to v3 will not be easy due to many breaking changes. -```bash -npm install strapi -g -``` - -Read the [Getting started](https://strapi.io/getting-started) page to create your first project using Strapi. ## Features @@ -58,19 +102,17 @@ Read the [Getting started](https://strapi.io/getting-started) page to create you - **Powerful CLI:** Scaffold projects and APIs on the fly. - **SQL & NoSQL databases:** Work with Mongo as a main database, also supports Postgres, MySQL, etc. -## Philosophy ? +**[See more on our website](https://strapi.io/overview)** -> At [Strapi](https://strapi.io), everything we do we believe in changing the status quo of web development. Our products are simple to use, user friendly and production-ready. +## Contributing -Web and mobile applications needed a powerful, simple to use and production-ready API-driven solution. That's why we created Strapi, an open-source Content Management Framework (CMF) for exposing your content (data, media) accross multi-devices. - -Halfway between a CMS and a framework, Strapi takes advantages of both worlds. A powerful dashboard to easily manage your content with a flexible framework layer to develop and integrate specific features. +Please read our [Contributing Guide](./CONTRIBUTING.md) before submitting a Pull Request to the project. ## Support For more information on the upcoming version, please take a look to our [ROADMAP](https://github.com/strapi/strapi/projects). -### Community support +#### Community support For general help using Strapi, please refer to [the official Strapi documentation](https://strapi.io/documentation/). For additional help, you can use one of this channel to ask question: @@ -80,13 +122,15 @@ For general help using Strapi, please refer to [the official Strapi documentatio - [Twitter](https://twitter.com/strapijs) - [Facebook](https://www.facebook.com/Strapi-616063331867161). -### Professional support +#### Professional support -[Strapi Solutions](https://strapi.io), the company behind Strapi, provides a full range of solutions to get better results, faster. We're always looking for the next challenge: coaching, consulting, training, customization, etc. [Drop us an email](mailto:support@strapi.io) to see how we can help you. +[Strapi Solutions](https://strapi.io), the company behind Strapi, provides a full range of solutions to get better results, faster. We're always looking for the next challenge: coaching, consulting, training, customization, etc. -### Migration +[Drop us an email](mailto:support@strapi.io) to see how we can help you. -Follow our [migration guides](https://github.com/strapi/strapi/wiki) on the wiki to keep your Strapi projects updated. +## Migration + +Follow our [migration guides](https://github.com/strapi/strapi/wiki) on the wiki to keep your projects up-to-date. ## License diff --git a/docs/3.x.x/en/README.md b/docs/3.x.x/en/README.md index 8747a3ae9f..24bed369b4 100644 --- a/docs/3.x.x/en/README.md +++ b/docs/3.x.x/en/README.md @@ -15,10 +15,10 @@ The most advanced open-source Content Management Framework to build powerful API {% endcenter %} -## v3@alpha.12 is available! +## v3@alpha.14 is available! We've been working on a major update for Strapi during the past months, rewriting the core framework and the dashboard. -This documentation is only related to Strapi v3@alpha.12 ([v1 documentation is still available](http://strapi.io/documentation/1.x.x)). +This documentation is only related to Strapi v3@alpha.14 ([v1 documentation is still available](http://strapi.io/documentation/1.x.x)). **[Get Started](getting-started/installation.md)**
Learn how to install Strapi and start developing your API. diff --git a/docs/3.x.x/en/advanced/customize-admin.md b/docs/3.x.x/en/advanced/customize-admin.md index a819fa90d5..efe380e7b4 100644 --- a/docs/3.x.x/en/advanced/customize-admin.md +++ b/docs/3.x.x/en/advanced/customize-admin.md @@ -62,6 +62,8 @@ The panel will be available through [http://localhost:1337/dashboard](http://loc ### Development mode +Note that to modify the administration panel, your project needs to be created with using the `dev` flag, an example of such would be: `strapi new strapi --dev`. + **#1 — Install its own dependencies** Run `npm install` from the `./admin` folder. @@ -99,14 +101,16 @@ Note: make sure the size of your image is the same as the existing one (434px x ## Build -To build the administration, run the following command from the `./admin` folder: +To build the administration, run the following command from the root directory of your project. ``` -npm run build +npm run setup ``` This will replace the folder's content located at `./admin/admin/build`. Visit http://localhost:1337/admin/ to make sure your updates have been taken in account. +After you have built the admininistration you can now create a new project to develop your API with the changes implemented. **Note:** You should now create a project without `--dev` + *** ## Deployment diff --git a/docs/3.x.x/en/advanced/hooks.md b/docs/3.x.x/en/advanced/hooks.md index 972ade67f0..a624aaeca9 100644 --- a/docs/3.x.x/en/advanced/hooks.md +++ b/docs/3.x.x/en/advanced/hooks.md @@ -1,6 +1,6 @@ # Hooks -The hooks are modules that add functionality to the core. They are loaded during the server boot. For example, if your project needs to work with a SQL database, your will have to add the hook `strapi-bookshelf` to be able to connect your app with your database. +The hooks are modules that add functionality to the core. They are loaded during the server boot. For example, if your project needs to work with a SQL database, your will have to add the hook `strapi-hook-bookshelf` to be able to connect your app with your database. **Path —** `./hooks/documentation/lib/index.js`. ```js @@ -76,13 +76,13 @@ The `index.js` is the entry point to your hook. It should look like the example ## Dependencies -It happens that a hook has a dependency to another one. For example, the `strapi-bookshelf` has a dependency to `strapi-knex`. Without it, the `strapi-bookshelf` can't work correctly. It also means that the `strapi-knex` hook has to be loaded before. +It happens that a hook has a dependency to another one. For example, the `strapi-hook-bookshelf` has a dependency to `strapi-hook-knex`. Without it, the `strapi-hook-bookshelf` can't work correctly. It also means that the `strapi-hook-knex` hook has to be loaded before. To handle this case, you need to update the `package.json` at the root of your hook. ```json { - "name": "strapi-bookshelf", + "name": "strapi-hook-bookshelf", "version": "x.x.x", "description": "Bookshelf hook for the Strapi framework", "dependencies": { @@ -90,10 +90,10 @@ To handle this case, you need to update the `package.json` at the root of your h }, "strapi": { "dependencies": [ - "strapi-knex" + "strapi-hook-knex" ] } -} +} ``` ## Custom hooks @@ -107,7 +107,9 @@ The framework allows to load hooks from the project directly without having to i └─── config └─── hooks │ └─── strapi-documentation +│ - index.js │ └─── strapi-server-side-rendering +│ - index.js └─── plugins └─── public - favicon.ico diff --git a/docs/3.x.x/en/advanced/middlewares.md b/docs/3.x.x/en/advanced/middlewares.md index 9adc669171..46e2bd8890 100644 --- a/docs/3.x.x/en/advanced/middlewares.md +++ b/docs/3.x.x/en/advanced/middlewares.md @@ -65,8 +65,12 @@ The core of Strapi embraces a small list of middlewares for performances, securi A middleware needs to follow the structure below: ``` -/lib -- index.js +/middleware +└─── lib + - index.js +- LICENSE.md +- package.json +- README.md ``` The `index.js` is the entry point to your middleware. It should look like the example above. @@ -82,7 +86,9 @@ The framework allows the application to override the default middlewares and add └─── config └─── middlewares │ └─── responseTime // It will override the core default responseTime middleware +│ - index.js │ └─── views // It will be added into the stack of middleware +│ - index.js └─── plugins └─── public - favicon.ico diff --git a/docs/3.x.x/en/advanced/usage-tracking.md b/docs/3.x.x/en/advanced/usage-tracking.md index b9a84d0e58..b6b445700f 100644 --- a/docs/3.x.x/en/advanced/usage-tracking.md +++ b/docs/3.x.x/en/advanced/usage-tracking.md @@ -13,7 +13,7 @@ Here is the list of the collected data and why we need them. *Understand how the developers are using the different configurations? How many projects are started in production mode?* - **Node modules names** *Are developers integrating Strapi with Stripe? It means that we should develop a plugin to simplify the development process with Stripe. - Are developers using Strapi with strapi-bookshelf or strapi-mongoose? It helps us prioritize the issues.* + Are developers using Strapi with strapi-hook-bookshelf or strapi-hook-mongoose? It helps us prioritize the issues.* - **OS** *Is the community using Windows, Linux or Mac? It helps us prioritize the issues.* - **Build configurations** diff --git a/docs/3.x.x/en/cli/CLI.md b/docs/3.x.x/en/cli/CLI.md index daac90433d..9759038fef 100644 --- a/docs/3.x.x/en/cli/CLI.md +++ b/docs/3.x.x/en/cli/CLI.md @@ -11,7 +11,7 @@ Create a new project ```bash strapi new -options: [--dev|--dbclient= --dbhost= --dbport= --dbname= --dbusername= --dbpassword=] +options: [--dev|--dbclient= --dbhost= --dbport= --dbname= --dbusername= --dbpassword= --dbssl= --dbauth=] ``` - **strapi new <name>**
@@ -20,8 +20,8 @@ options: [--dev|--dbclient= --dbhost= --dbport= --dbna - **strapi new <name> --dev**
Generates a new project called **<name>** and creates symlinks for the `./admin` folder and each plugin inside the `./plugin` folder. It means that the Strapi's development workflow has been set up on the machine earlier. -- **strapi new <name> --dbclient=<dbclient> --dbhost=<dbhost> --dbport=<dbport> --dbname=<dbname> --dbusername=<dbusername> --dbpassword=<dbpassword>**
- Generates a new project called **<name>** and skip the interactive database configuration and initilize with these options. **<dbclient>** can be `mongo`, `postgres`, `mysql`, `sqlite3` or `redis`. **<dbusername>** and **<dbpassword>** are optional. +- **strapi new <name> --dbclient=<dbclient> --dbhost=<dbhost> --dbport=<dbport> --dbname=<dbname> --dbusername=<dbusername> --dbpassword=<dbpassword> --dbssl=<dbssl> --dbauth=<dbauth>**
+ Generates a new project called **<name>** and skip the interactive database configuration and initilize with these options. **<dbclient>** can be `mongo`, `postgres`, `mysql`, `sqlite3` or `redis`. **<dbssl>** and **<dbauth>** are optional. See the [CONTRIBUTING guide](https://github.com/strapi/strapi/blob/master/CONTRIBUTING.md) for more details. diff --git a/docs/3.x.x/en/concepts/concepts.md b/docs/3.x.x/en/concepts/concepts.md index 56611c491c..966482fdf4 100644 --- a/docs/3.x.x/en/concepts/concepts.md +++ b/docs/3.x.x/en/concepts/concepts.md @@ -146,7 +146,7 @@ In this example, there is a `User` model which contains two attributes `firstnam ### Where are the models defined? -The models are defined in each `./api/**/models/` folder. Every JavaScript or JSON file in these folders will be loaded as a model. They are also available through the `strapi.models` and `strapi.api.**.models` global variables. Usable every where in the project, they contain the ORM model object that they are refer to. By convention, models' names should be Pascal-cased, so that every word in the file (including the first one) is capitalized `User.js`, `User.settings.json`, `LegalEntity.js`, `LegalEntity.settings.json`. +The models are defined in each `./api/**/models/` folder. Every JavaScript or JSON file in these folders will be loaded as a model. They are also available through the `strapi.models` and `strapi.api.**.models` global variables. Usable every where in the project, they contain the ORM model object that they are refer to. By convention, models' names should be written in lowercase. ### Attributes diff --git a/docs/3.x.x/en/configurations/configurations.md b/docs/3.x.x/en/configurations/configurations.md index d038fc99ec..3e4ccb8556 100644 --- a/docs/3.x.x/en/configurations/configurations.md +++ b/docs/3.x.x/en/configurations/configurations.md @@ -1,6 +1,6 @@ # Configurations -The main configurations of the project are located in the `./config` directory. Additional configs can be added in the `./api/**/config` folder of each APIs and plugins by creating JavaScript or JSON files. +The main configurations of the project are located in the `./config` directory. Additional configs can be added in the `./api/**/config` folder of each API and plugin by creating JavaScript or JSON files. ## Application @@ -153,7 +153,7 @@ Each JSON file located in the folder must have the name of its corresponding tra Most of the application's configurations are defined by environment. It means that you can specify settings for each environment (`development`, `production`, `test`, etc.). -> Note: You can access to the config of the current environment through `strapi.config.currentEnvironment`. +> Note: You can access the config of the current environment through `strapi.config.currentEnvironment`. *** @@ -164,14 +164,9 @@ Most of the application's configurations are defined by environment. It means th - `defaultConnection` (string): Connection by default for models which are not related to a specific `connection`. Default value: `default`. - `connections` List of all available connections. - `default` - - `connector` (string): Connector used by the current connection. Default value: `strapi-mongoose`. - - `client` (string): Client used to store session. Default value: `cookie`. - - `key` (string): Cookie key name. Default value: `strapi.sid` - - `maxAge` (integer): Time in milliseconds before the session expire. Default value: `86400000`. - - `rolling` (boolean): Force a session identifier cookie to be set on every response. Default value: `false`. - - `signed` (boolean): httpOnly or not. Default value: `true`. - - `overwrite` (boolean): Can overwrite or not. Default value: `true`. + - `connector` (string): Connector used by the current connection. Default value: `strapi-hook-mongoose`. - `settings` Useful for external session stores such as Redis. + - `client` (string): Database client to create the connection. Default value: `mongo`. - `host` (string): Database host name. Default value: `localhost`. - `port` (integer): Database port. Default value: `27017`. - `database` (string): Database name. Default value: `development`. @@ -192,7 +187,7 @@ Most of the application's configurations are defined by environment. It means th "defaultConnection": "default", "connections": { "default": { - "connector": "strapi-mongoose", + "connector": "strapi-hook-mongoose", "settings": { "client": "mongo", "host": "localhost", @@ -208,14 +203,14 @@ Most of the application's configurations are defined by environment. It means th } }, "postgres": { - "connector": "strapi-bookshelf", + "connector": "strapi-hook-bookshelf", "settings": { "client": "postgres", "host": "localhost", "port": 5432, - "username": "aureliengeorget", - "password": "${process.env.USERNAME}", - "database": "${process.env.PWD}", + "username": "${process.env.USERNAME}", + "password": "${process.env.PWD}", + "database": "strapi", "schema": "public" }, "options": { @@ -223,12 +218,12 @@ Most of the application's configurations are defined by environment. It means th } }, "mysql": { - "connector": "strapi-bookshelf", + "connector": "strapi-hook-bookshelf", "settings": { "client": "mysql", "host": "localhost", "port": 5432, - "username": "aureliengeorget", + "username": "strapi", "password": "root", "database": "" }, @@ -335,10 +330,18 @@ Most of the application's configurations are defined by environment. It means th - `host` (string): Host name. Default value: `localhost`. - `port` (integer): Port on which the server should be running. Default value: `1337`. - - `autoReload` (boolean): Enable or disabled server reload on files update. Default value: depends on the environment. + - `autoReload` + - `enabled` (boolean): Enable or disabled server reload on files update. Default value: depends on the environment. + - `emitErrors` (boolean): Enable errors to be emited to `koa` when they happen in order to attach custom logic or use error reporting services. + - `proxy` + - `enabled` (boolean): Enable proxy support such as Apache or Nginx. Default value: `false`. + - `ssl` (boolean): Enable proxy SSL support + - `host` (string): Host name your proxy service uses for Strapi. + - `port` (integer): Port that your proxy service accepts connections on. - [`cron`](https://en.wikipedia.org/wiki/Cron) - `enabled` (boolean): Enable or disable CRON tasks to schedule jobs at specific dates. Default value: `false`. - `admin` + - `autoOpen` (boolean): Enable or disabled administration opening on start (default: `true`) - `path` (string): Allow to change the URL to access the admin (default: `/admin`). - `build` - `host` (string): URL to access the admin panel (default: `http://localhost:1337/admin`). @@ -347,6 +350,33 @@ Most of the application's configurations are defined by environment. It means th - `source` (string): Define the source mode (origin, host, custom). - `folder` (string): Indicate what the plugins folder in `host` source mode. +#### Example + +**Path —** `./config/environments/**/server.json`. + +As an example using this configuration with Nginx your server would respond to `https://example.com:8443` instead of `http://localhost:1337` + +**Note:** you will need to configure Nginx or Apache as a proxy before configuring this example. + +```json +{ + "host": "localhost", + "port": 1337, + "proxy": { + "enabled": true, + "ssl": true, + "host": "example.com", + "port": 8443 + }, + "autoReload": { + "enabled": true + }, + "cron": { + "enabled": true + } +} +``` + *** ## Dynamic configurations @@ -367,7 +397,7 @@ In any JSON configurations files in your project, you can inject dynamic values "defaultConnection": "default", "connections": { "default": { - "connector": "strapi-mongoose", + "connector": "strapi-hook-mongoose", "settings": { "client": "mongo", "uri": "${process.env.DATABASE_URI || ''}", diff --git a/docs/3.x.x/en/getting-started/quick-start.md b/docs/3.x.x/en/getting-started/quick-start.md index 6fe28aac6e..1a26062328 100644 --- a/docs/3.x.x/en/getting-started/quick-start.md +++ b/docs/3.x.x/en/getting-started/quick-start.md @@ -77,7 +77,7 @@ At this point, your application is empty. To create your first API is to use the ![Content Type Builder - Home](../assets/getting-started_no_content_type.png) -**#2 —** Create a Content Type name `Product` and submit the form. +**#2 —** Create a Content Type named `Product` and submit the form. ![Content Type Builder - Create a new Content Type](../assets/getting-started_create_content_type.png) @@ -92,7 +92,7 @@ At this point, your application is empty. To create your first API is to use the **#4 —** Save. That's it! -> Note: See the [CLI documentation](../cli/CLI.md#strapi-generateapi) for informations about how to do it the hacker way. +> Note: See the [CLI documentation](../cli/CLI.md#strapi-generateapi) for more information on how to do it the hacker way. ### Files structure diff --git a/docs/3.x.x/en/guides/authentication.md b/docs/3.x.x/en/guides/authentication.md index e93962f27c..1e74f59b0f 100644 --- a/docs/3.x.x/en/guides/authentication.md +++ b/docs/3.x.x/en/guides/authentication.md @@ -59,7 +59,8 @@ $.ajax({ Thanks to [Grant](https://github.com/simov/grant) and [Purest](https://github.com/simov/purest), you can easily use OAuth and OAuth2 providers to enable authentication in your application. By default, -Strapi comes with four providers: +Strapi comes with the following providers: +- [Discord](https://github.com/strapi/strapi-examples/blob/master/login-react/doc/discord_setup.md) - [Facebook](https://github.com/strapi/strapi-examples/blob/master/login-react/doc/fb_setup.md) - [Google](https://github.com/strapi/strapi-examples/blob/master/login-react/doc/google_setup.md) - [Github](https://github.com/strapi/strapi-examples/blob/master/login-react/doc/github_setup.md) @@ -189,6 +190,140 @@ The User object is available to successfully authenticated requests. ``` +## Add a new provider + +To add a new provider on strapi, you will need to perform changes onto the following files: + +``` +packages/strapi-plugin-users-permissions/services/Providers.js +packages/strapi-plugin-users-permissions/config/functions/bootstrap.js +packages/strapi-plugin-users-permissions/admin/src/components/PopUpForm/index.js +packages/strapi-plugin-users-permissions/admin/src/translations/en.json +``` + +We will go step by step. + +### Configure your Provider request +First, we need to configure our new provider onto `Provider.js` file. + +Jump onto the `getProfile` function, you will see the list of currently available providers in the form of a `switch...case`. + +As you can see, `getProfile` take three params: + +1. provider :: The name of the used provider as a string. +2. query :: The query is the result of the provider callback. +3. callback :: The callback function who will continue the internal strapi login logic. + +Let's take the `discord` one as an example since it's not the easier, it should cover most of the case you may encounter trying to implement your own provider. + +#### Configure your oauth generic information + +```js + case 'discord': { + const discord = new Purest({ + provider: 'discord', + config: { + 'discord': { + 'https://discordapp.com/api/': { + '__domain': { + 'auth': { + 'auth': {'bearer': '[0]'} + } + }, + '{endpoint}': { + '__path': { + 'alias': '__default' + } + } + } + } + } + }); +``` + +So here, you can see that we use a module called `Purest`. This module gives us with a generic way to interact with the REST API. + +To understand each value usage, and the templating syntax, I invite you to read the [Official Purest Documentation](https://github.com/simov/purest/tree/2.x) + +You may also want to take a look onto the numerous already made configurations [here](https://github.com/simov/purest-providers/blob/master/config/providers.json). + +#### Retrieve your user informations: +```js + discord.query().get('users/@me').auth(access_token).request((err, res, body) => { + if (err) { + callback(err); + } else { + // Combine username and discriminator because discord username is not unique + var username = `${body.username}#${body.discriminator}`; + callback(null, { + username: username, + email: body.email + }); + } + }); + break; + } +``` + +Here is the next part of our switch. Now that we have properly configured our provider, we want to use it to retrieve user information. + +Here you see the real power of `purest`, you can simply make a get request on the desired URL, using the `access_token` from the `query` parameter to authenticate. + +That way, you should be able to retrieve the user info you need. + +Now, you can simply call the `callback` function with the username and email of your user. That way, strapi will be able to retrieve your user from the database and log you in. + +#### Configure the new provider model onto database + +Now, we need to configure our 'model' for our new provider. That way, our settings can be stored in the database, and managed from the admin panel. + +Into: `packages/strapi-plugin-users-permissions/config/functions/bootstrap.js` + +Simply add the fields your provider need into the `grantConfig` object. +For our discord provider it will look like: + +```js + discord: { + enabled: false, // make this provider disabled by default + icon: 'comments', // The icon to use on the UI + key: '', // our provider app id (leave it blank, you will fill it with the content manager) + secret: '', // our provider secret key (leave it blank, you will fill it with the content manager) + callback: '/auth/discord/callback', // the callback endpoint of our provider + scope: [ // the scope that we need from our user to retrieve infos + 'identify', + 'email' + ] + }, +``` + + +You have already done the hard part, now, we simply need to make our new provider available from the front side of our application. So let's do it! + + + + + +### Configure frontend for your new provider + +First, let's edit: `packages/strapi-plugin-users-permissions/admin/src/components/PopUpForm/index.js` +As for backend, we have a `switch...case` where we need to put our new provider info. + +```js + case 'discord': + return `${strapi.backendURL}/connect/discord/callback`; +``` + +Add the corresponding translation into: `packages/strapi-plugin-users-permissions/admin/src/translations/en.json` + +```js + "PopUpForm.Providers.discord.providerConfig.redirectURL": "The redirect URL to add in your Discord application configurations", +```` + +These two change will set up the popup message who appear on the UI when we will configure our new provider. + +That's it, now you should be able to use your new provider. + + ## Email templates [See the documentation on GitHub](https://github.com/strapi/strapi/blob/master/packages/strapi-plugin-users-permissions/docs/email-templates.md) diff --git a/docs/3.x.x/en/guides/deployment.md b/docs/3.x.x/en/guides/deployment.md index a082b1d2a2..0c698834e7 100644 --- a/docs/3.x.x/en/guides/deployment.md +++ b/docs/3.x.x/en/guides/deployment.md @@ -18,6 +18,9 @@ Update the `production` settings with the IP and domain name where the project w } ``` +In case your database is not running on the same server, make sure that the environment of your production +database (`./config/environments/production/database.json`) is set properly. + **⚠️ If you changed the path to access to the administration, the step #2 is required.** #### #2 - Setup (optional) diff --git a/docs/3.x.x/en/guides/email.md b/docs/3.x.x/en/guides/email.md index b9edddd7f8..f852e57929 100644 --- a/docs/3.x.x/en/guides/email.md +++ b/docs/3.x.x/en/guides/email.md @@ -27,8 +27,8 @@ To install a new provider run: $ npm install strapi-email-sendgrid@alpha --save ``` -We have two providers available `strapi-email-sendgrid` and `strapi-upload-mailgun`, use the alpha tag to install one of them. Then, visit `/admin/plugins/email/configurations/development` and configure the provider. +We have two providers available `strapi-email-sendgrid` and `strapi-upload-mailgun`, use the alpha tag to install one of them. Then, visit `/admin/plugins/email/configurations/development` on your web browser and configure the provider. If you want to create your own, make sure the name starts with `strapi-email-` (duplicating an existing one will be easier to create), modify the `auth` config object and customize the `send` functions. -Check all community providers available on npmjs.org - [Providers list](https://www.npmjs.com/search?q=strapi-email-) \ No newline at end of file +Check all community providers available on npmjs.org - [Providers list](https://www.npmjs.com/search?q=strapi-email-) diff --git a/docs/3.x.x/en/guides/graphql.md b/docs/3.x.x/en/guides/graphql.md index 6449538a25..3f67c612f1 100644 --- a/docs/3.x.x/en/guides/graphql.md +++ b/docs/3.x.x/en/guides/graphql.md @@ -146,6 +146,149 @@ type Query { The query will use the generated controller's actions as resolvers. It means that the `posts` query will execute the `Post.find` action and the `post` query will use the `Post.findOne` action. +## Aggregation & Grouping +> This feature is only available on Mongoose ORM. + +Strapi now supports Aggregation & Grouping. +Let's consider again the model mentioned above: +``` +type Post { + _id: ID + createdAt: String + updatedAt: String + title: String + content: String + nb_likes: Int, + published: Boolean +} + +``` +Strapi will generate automatically for you the following queries & types: + +### Aggregation +``` +type PostConnection { + values: [Post] + groupBy: PostGroupBy + aggregate: PostAggregator +} + +type PostGroupBy { + _id: [PostConnection_id] + createdAt: [PostConnectionCreatedAt] + updatedAt: [PostConnectionUpdatedAt] + title: [PostConnectionTitle] + content: [PostConnectionContent] + nb_likes: [PostConnectionNbLikes], + published: [PostConnectionPublished] +} + +type PostConnectionPublished { + key: Boolean + connection: PostConnection +} + +type PostAggregator { + count: Int + sum: PostAggregatorSum + avg: PostAggregatorAvg + min: PostAggregatorMin + max: PostAggregatorMax +} + +type PostAggregatorAvg { + nb_likes: Float +} + +type PostAggregatorMin { // Same for max and sum + nb_likes: Int +} + +type Query { + postsConnection(sort: String, limit: Int, start: Int, where: JSON): PostConnection +} +``` + +Getting the total count and the average likes of posts: + +``` +postsConnection { + aggregate { + count + avg { + nb_likes + } + } + +} +``` + +Let's say we want to do the same query but for only published posts +``` +postsConnection(where: { published: true }) { + aggregate { + count + avg { + nb_likes + } + } + +} +``` + +Gettings the average likes of published and unpublished posts + +``` +postsConnection { + groupBy { + published: { + key + connection { + aggregate { + avg { + nb_likes + } + } + } + } + } +} +``` + +Result +```JSON +{ + data: { + postsConnection: { + groupBy: { + published: [ + { + key: true, + connection: { + aggregate: { + avg { + nb_likes: 10 + } + } + } + }, + { + key: false, + connection: { + aggregate: { + avg { + nb_likes: 0 + } + } + } + } + ] + } + } + } +} +``` + ## Customise the GraphQL schema If you want to define a new scalar, input or enum types, this section is for you. To do so, you will have to create a `schema.graphql` file. This file has to be placed into the config folder of each API `./api/*/config/schema.graphql` or plugin `./plugins/*/config/schema.graphql`. diff --git a/docs/3.x.x/en/guides/models.md b/docs/3.x.x/en/guides/models.md index 91f0a517bc..de58ff2c6d 100644 --- a/docs/3.x.x/en/guides/models.md +++ b/docs/3.x.x/en/guides/models.md @@ -19,6 +19,11 @@ The info key on the model-json states information about the model. This informat - `description`: The description of the model. - `mainField`: Determines which model-attribute is shown when displaying the model. +## Model options +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'). + - `idAttributeType`: Data type of `idAttribute`, accepted list of value bellow: + ## Define the attributes The following types are currently available: diff --git a/docs/3.x.x/en/guides/policies.md b/docs/3.x.x/en/guides/policies.md index a4ee3c25dc..e0553f1659 100644 --- a/docs/3.x.x/en/guides/policies.md +++ b/docs/3.x.x/en/guides/policies.md @@ -11,7 +11,7 @@ There are several ways to create a policy. **Path —** `./config/policies/isAuthenticated.js`. ```js module.exports = async (ctx, next) => { - if (ctx.session.isAuthenticated === true) { + if (ctx.state.user) { // Go to next policy or will reach the controller's action. return await next(); } @@ -22,7 +22,7 @@ module.exports = async (ctx, next) => { In this example, we are verifying that a session is open. If it is the case, we call the `next()` method that will execute the next policy or controller's action. Otherwise, a 401 error is returned. -> Note: You can access to any controllers, services or models thanks to the global variable `strapi` in a policy. +> Note: You can access any controllers, services, or models by using the global variable `strapi` in a policy. ## Usage @@ -87,7 +87,7 @@ The scoped policies can only be associated to the routes defined in the API wher **Path —** `./api/car/config/policies/isAdmin.js`. ```js module.exports = async (ctx, next) => { - if (ctx.session.user.role === 'administrator') { + if (ctx.state.user.role.name === 'Administrator') { // Go to next policy or will reach the controller's action. return await next(); } diff --git a/docs/3.x.x/en/guides/upload.md b/docs/3.x.x/en/guides/upload.md index eae41c75f4..96dfc72a64 100644 --- a/docs/3.x.x/en/guides/upload.md +++ b/docs/3.x.x/en/guides/upload.md @@ -13,6 +13,7 @@ The plugin exposes a single route `POST /upload` to upload one or multiple files **Parameters** - `files`: The file(s) to upload. The value(s) can be a Buffer or Stream. +- `path`: (optional): The folder where the file(s) will be uploaded to (only supported on strapi-upload-aws-s3 now). - `refId`: (optional): The ID of the entry which the file(s) will be linked to. - `ref`: (optional): The name of the model which the file(s) will be linked to (see more below). - `source`: (optional): The name of the plugin where the model is located. @@ -111,6 +112,7 @@ Let's say that you want to have a `User` model provided by the plugin `Users & P ```js { "files": "...", // Buffer or stream of file(s) + "path": "user/avatar", // Uploading folder of file(s). "refId": "5a993616b8e66660e8baf45c", // User's Id. "ref": "user", // Model name. "source": "users-permissions", // Plugin name. @@ -133,7 +135,7 @@ To install a new provider run: $ npm install strapi-upload-aws-s3@alpha --save ``` -We have two providers available `strapi-upload-aws-s3` and `strapi-upload-cloudinary`, use the alpha tag to install one of them. Then, visit `/admin/plugins/upload/configurations/development` and configure the provider. +We have two providers available `strapi-upload-aws-s3` and `strapi-upload-cloudinary`, use the alpha tag to install one of them. Then, visit `/admin/plugins/upload/configurations/development` on your web browser and configure the provider. If you want to create your own, make sure the name starts with `strapi-upload-` (duplicating an existing one will be easier to create), modify the `auth` config object and customize the `upload` and `delete` functions. diff --git a/docs/3.x.x/en/plugin-development/frontend-use-cases.md b/docs/3.x.x/en/plugin-development/frontend-use-cases.md index 74d65ad409..2b6f4e723a 100644 --- a/docs/3.x.x/en/plugin-development/frontend-use-cases.md +++ b/docs/3.x.x/en/plugin-development/frontend-use-cases.md @@ -346,7 +346,7 @@ const send = require('koa-send'); module.exports = { autoReload: async ctx => { - ctx.send({ autoReload: _.get(strapi.config.environments, 'development.server.autoReload', false) }); + ctx.send({ autoReload: _.get(strapi.config.currentEnvironment, 'server.autoReload', { enabled: false }) }); } } ``` @@ -374,8 +374,8 @@ import request from 'utils/request'; const shouldRenderCompo = (plugin) => new Promise((resolve, request) => { request('/my-plugin/autoReload') .then(response => { - // If autoReload is enabled the response is `{ autoReload: true }` - plugin.preventComponentRendering = !response.autoReload; + // If autoReload is enabled the response is `{ autoReload: { enabled: true } }` + plugin.preventComponentRendering = !response.autoReload.enabled; // Set the BlockerComponent props plugin.blockerComponentProps = { blockerComponentTitle: 'my-plugin.blocker.title', @@ -407,8 +407,8 @@ import MyCustomBlockerComponent from 'components/MyCustomBlockerComponent'; const shouldRenderCompo = (plugin) => new Promise((resolve, request) => { request('/my-plugin/autoReload') .then(response => { - // If autoReload is enabled the response is `{ autoReload: true }` - plugin.preventComponentRendering = !response.autoReload; + // If autoReload is enabled the response is `{ autoReload: { enabled: true } }` + plugin.preventComponentRendering = !response.autoReload.enabled; // Tell which component to be rendered instead plugin.blockerComponent = MyCustomBlockerComponent; diff --git a/package.json b/package.json index dc4f8cb9d2..fb77993c64 100755 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "private": true, - "version": "3.0.0-alpha.12.7.1", + "version": "3.0.0-alpha.14", "dependencies": {}, "devDependencies": { "assert": "~1.3.0", diff --git a/packages/strapi-admin/.gitignore b/packages/strapi-admin/.gitignore index afe256bf30..2e39eb54ca 100644 --- a/packages/strapi-admin/.gitignore +++ b/packages/strapi-admin/.gitignore @@ -3,6 +3,7 @@ coverage node_modules stats.json package-lock.json +yarn.lock # Cruft .DS_Store diff --git a/packages/strapi-admin/admin/.gitignore b/packages/strapi-admin/admin/.gitignore index ad2318a4a0..d14656e5b9 100644 --- a/packages/strapi-admin/admin/.gitignore +++ b/packages/strapi-admin/admin/.gitignore @@ -5,6 +5,7 @@ manifest.json plugins.json stats.json package-lock.json +yarn.lock # Cruft .DS_Store diff --git a/packages/strapi-admin/admin/src/app.js b/packages/strapi-admin/admin/src/app.js index a7f4cff8d0..83a6e8debd 100755 --- a/packages/strapi-admin/admin/src/app.js +++ b/packages/strapi-admin/admin/src/app.js @@ -129,7 +129,7 @@ if (window.location.port !== '4000') { }); }) .catch(err => { - console.log(err); + console.log(err); // eslint-disable-line no-console }); } else if (findIndex(plugins, ['id', 'users-permissions']) === -1) { store.dispatch(unsetHasUserPlugin()); diff --git a/packages/strapi-admin/admin/src/components/CtaWrapper/index.js b/packages/strapi-admin/admin/src/components/CtaWrapper/index.js index cc21587510..53d6b9c370 100644 --- a/packages/strapi-admin/admin/src/components/CtaWrapper/index.js +++ b/packages/strapi-admin/admin/src/components/CtaWrapper/index.js @@ -15,7 +15,7 @@ const style = { top: '0', right: '0', display: 'flex', - zIndex: '999', + zIndex: '1050', }; CTAWrapper.propTypes = { diff --git a/packages/strapi-admin/admin/src/components/Header/index.js b/packages/strapi-admin/admin/src/components/Header/index.js index 1d254ca65f..c8ca11a470 100755 --- a/packages/strapi-admin/admin/src/components/Header/index.js +++ b/packages/strapi-admin/admin/src/components/Header/index.js @@ -7,14 +7,6 @@ import React from 'react'; import styles from './styles.scss'; -class Header extends React.Component { // eslint-disable-line react/prefer-stateless-function - render() { - return ( -
- -
- ); - } +export default function Header() { + return
; } - -export default Header; diff --git a/packages/strapi-admin/admin/src/components/Header/styles.scss b/packages/strapi-admin/admin/src/components/Header/styles.scss index 1756c3bafa..9c49284a6f 100755 --- a/packages/strapi-admin/admin/src/components/Header/styles.scss +++ b/packages/strapi-admin/admin/src/components/Header/styles.scss @@ -5,7 +5,7 @@ width: 100%; height: $header-height; position: fixed; - z-index: 100; + z-index: 1040; left: $left-menu-width; box-shadow: 0 1px 2px 0 rgba(40, 42, 49, 0.16); diff --git a/packages/strapi-admin/admin/src/components/LeftMenuLink/index.js b/packages/strapi-admin/admin/src/components/LeftMenuLink/index.js index 5a36b8e12b..d37f84e0bc 100755 --- a/packages/strapi-admin/admin/src/components/LeftMenuLink/index.js +++ b/packages/strapi-admin/admin/src/components/LeftMenuLink/index.js @@ -20,9 +20,10 @@ class LeftMenuLink extends React.Component { // We need to create our own active url checker, // because of the two levels router. const isLinkActive = startsWith( - window.location.pathname.replace('/admin', ''), - this.props.destination, + window.location.pathname.replace('/admin', '').concat('/'), + this.props.destination.concat('/'), ); + const plugin = this.props.source !== 'content-manager' && this.props.source !== '' ? (
diff --git a/packages/strapi-admin/admin/src/components/LeftMenuLinkContainer/index.js b/packages/strapi-admin/admin/src/components/LeftMenuLinkContainer/index.js index 5ea571e596..ce156f2cbe 100755 --- a/packages/strapi-admin/admin/src/components/LeftMenuLinkContainer/index.js +++ b/packages/strapi-admin/admin/src/components/LeftMenuLinkContainer/index.js @@ -69,13 +69,17 @@ function LeftMenuLinkContainer({ layout, plugins }) { // Check if the plugins list is empty or not and display plugins by name const pluginsLinks = !isEmpty(pluginsObject) ? ( map(sortBy(pluginsObject, 'name'), plugin => { - if (plugin.id !== 'email' && plugin.id !== 'content-manager' && plugin.id !== 'settings-manager') { + if (plugin.id !== 'email' && plugin.id !== 'settings-manager') { + const basePath = `/plugins/${get(plugin, 'id')}`; + // NOTE: this should be dynamic + const destination = plugin.id === 'content-manager' ? `${basePath}/ctm-configurations` : basePath; + return ( ); } diff --git a/packages/strapi-admin/admin/src/components/ListPlugins/index.js b/packages/strapi-admin/admin/src/components/ListPlugins/index.js index 6a448a878a..64a6ead46d 100644 --- a/packages/strapi-admin/admin/src/components/ListPlugins/index.js +++ b/packages/strapi-admin/admin/src/components/ListPlugins/index.js @@ -16,7 +16,7 @@ import Row from 'components/Row'; import styles from './styles.scss'; -class ListPlugins extends React.Component { +class ListPlugins extends React.PureComponent { render() { const listSize = size(this.props.plugins); let titleType = listSize === 1 ? 'singular' : 'plural'; diff --git a/packages/strapi-admin/admin/src/components/Notification/styles.scss b/packages/strapi-admin/admin/src/components/Notification/styles.scss index 01e7a1de4a..9d0a8fb748 100644 --- a/packages/strapi-admin/admin/src/components/Notification/styles.scss +++ b/packages/strapi-admin/admin/src/components/Notification/styles.scss @@ -14,6 +14,13 @@ color: #333740; transition: all 0.15s ease; overflow: hidden; + z-index: 10; + + // The last notification must appear from + // the background of the previous one. + &:last-child { + z-index: 1; + } } .notification:hover { diff --git a/packages/strapi-admin/admin/src/components/NotificationsContainer/styles.scss b/packages/strapi-admin/admin/src/components/NotificationsContainer/styles.scss index 7f9216f413..3f1e7b5041 100755 --- a/packages/strapi-admin/admin/src/components/NotificationsContainer/styles.scss +++ b/packages/strapi-admin/admin/src/components/NotificationsContainer/styles.scss @@ -4,7 +4,11 @@ .notificationsContainer { /* stylelint-disable */ position: absolute; top: 72px; - right: 15px; + left: 240px; + right: 0; z-index: 1000; list-style: none; + width: 300px; + margin: 0 auto; + overflow-y: hidden; } diff --git a/packages/strapi-admin/admin/src/components/Row/index.js b/packages/strapi-admin/admin/src/components/Row/index.js index 8ad6128e6c..47b5ef279b 100644 --- a/packages/strapi-admin/admin/src/components/Row/index.js +++ b/packages/strapi-admin/admin/src/components/Row/index.js @@ -8,7 +8,7 @@ import React from 'react'; import PropTypes from 'prop-types'; import cn from 'classnames'; import { FormattedMessage } from 'react-intl'; -import { isEmpty } from 'lodash'; +import { includes, isEmpty } from 'lodash'; // Design import IcoContainer from 'components/IcoContainer'; @@ -17,6 +17,8 @@ import PopUpWarning from 'components/PopUpWarning'; import styles from './styles.scss'; +const PLUGINS_WITH_CONFIG = ['content-manager', 'email', 'upload']; + class Row extends React.Component { state = { showModal: false }; @@ -33,8 +35,10 @@ class Row extends React.Component { render() { // const uploadPath = `/plugins/upload/configurations/${this.context.currentEnvironment}`; - const settingsPath = `/plugins/${this.props.name}/configurations/${this.context.currentEnvironment}`; - const icons = this.props.name === 'upload' || this.props.name === 'email' ? [ + // Make sure to match the ctm config URI instead of content-type view URI + const settingsPath = this.props.name === 'content-manager' ? '/plugins/content-manager/ctm-configurations' : `/plugins/${this.props.name}/configurations/${this.context.currentEnvironment}`; + // const icons = this.props.name === 'upload' || this.props.name === 'email' ? [ + const icons = includes(PLUGINS_WITH_CONFIG, this.props.name) ? [ { icoType: 'cog', onClick: (e) => { diff --git a/packages/strapi-admin/admin/src/config/languages.json b/packages/strapi-admin/admin/src/config/languages.json index 44c5547803..1fd3d36fcd 100644 --- a/packages/strapi-admin/admin/src/config/languages.json +++ b/packages/strapi-admin/admin/src/config/languages.json @@ -1,3 +1,3 @@ { - "languages": ["ar", "en", "fr", "de", "it", "ko", "pl", "pt", "pt-BR", "tr", "zh", "zh-Hans"] + "languages": ["ar", "en", "es", "fr", "de", "it", "ko", "nl", "pl", "pt", "pt-BR", "tr", "zh", "zh-Hans"] } diff --git a/packages/strapi-admin/admin/src/config/manifest.json b/packages/strapi-admin/admin/src/config/manifest.json index d3a433dcf0..18c20e7b12 100644 --- a/packages/strapi-admin/admin/src/config/manifest.json +++ b/packages/strapi-admin/admin/src/config/manifest.json @@ -1 +1 @@ -{"name":"vendor_lib","content":{"./strapi-admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/_export.js":{"id":0,"meta":{}},"./strapi-admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/_an-object.js":{"id":1,"meta":{}},"./strapi-admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/_global.js":{"id":2,"meta":{}},"./strapi-admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/_fails.js":{"id":3,"meta":{}},"./strapi-admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/_is-object.js":{"id":4,"meta":{}},"./strapi-admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/_wks.js":{"id":5,"meta":{}},"./strapi-admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/_descriptors.js":{"id":6,"meta":{}},"./strapi-admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/_object-dp.js":{"id":7,"meta":{}},"./strapi-admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/_to-length.js":{"id":8,"meta":{}},"./strapi-admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/_to-object.js":{"id":9,"meta":{}},"./strapi-admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/_a-function.js":{"id":10,"meta":{}},"./strapi-admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/_hide.js":{"id":11,"meta":{}},"./strapi-admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/_redefine.js":{"id":12,"meta":{}},"./strapi-admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/_string-html.js":{"id":13,"meta":{}},"./strapi-admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/_has.js":{"id":14,"meta":{}},"./strapi-admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/_to-iobject.js":{"id":15,"meta":{}},"./strapi-admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/_object-gopd.js":{"id":16,"meta":{}},"./strapi-admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/_object-gpo.js":{"id":17,"meta":{}},"./strapi-admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/_core.js":{"id":18,"meta":{}},"./strapi-admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/_ctx.js":{"id":19,"meta":{}},"./strapi-admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/_cof.js":{"id":20,"meta":{}},"./strapi-admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/_strict-method.js":{"id":21,"meta":{}},"./strapi-admin/node_modules/strapi-helper-plugin/node_modules/react/index.js":{"id":22,"meta":{}},"./strapi-admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/_to-primitive.js":{"id":23,"meta":{}},"./strapi-admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/_defined.js":{"id":24,"meta":{}},"./strapi-admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/_to-integer.js":{"id":25,"meta":{}},"./strapi-admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/_object-sap.js":{"id":26,"meta":{}},"./strapi-admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/_array-methods.js":{"id":27,"meta":{}},"./strapi-admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/_typed-array.js":{"id":28,"meta":{}},"./strapi-admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/_metadata.js":{"id":29,"meta":{}},"./strapi-admin/node_modules/strapi-helper-plugin/node_modules/prop-types/index.js":{"id":30,"meta":{}},"./strapi-admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/_meta.js":{"id":31,"meta":{}},"./strapi-admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/_library.js":{"id":32,"meta":{}},"./strapi-admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/_add-to-unscopables.js":{"id":33,"meta":{}},"./strapi-admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/_property-desc.js":{"id":34,"meta":{}},"./strapi-admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/_uid.js":{"id":35,"meta":{}},"./strapi-admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/_object-keys.js":{"id":36,"meta":{}},"./strapi-admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/_to-absolute-index.js":{"id":37,"meta":{}},"./strapi-admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/_object-create.js":{"id":38,"meta":{}},"./strapi-admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/_object-gopn.js":{"id":39,"meta":{}},"./strapi-admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/_set-species.js":{"id":40,"meta":{}},"./strapi-admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/_an-instance.js":{"id":41,"meta":{}},"./strapi-admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/_for-of.js":{"id":42,"meta":{}},"./strapi-admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/_redefine-all.js":{"id":43,"meta":{}},"./strapi-admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/_set-to-string-tag.js":{"id":44,"meta":{}},"./strapi-admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/_string-trim.js":{"id":45,"meta":{}},"./strapi-admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/_iterators.js":{"id":46,"meta":{}},"./strapi-admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/_validate-collection.js":{"id":47,"meta":{}},"./strapi-admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/_iobject.js":{"id":48,"meta":{}},"./strapi-admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/_object-pie.js":{"id":49,"meta":{}},"./strapi-admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/_classof.js":{"id":50,"meta":{}},"./strapi-admin/node_modules/strapi-helper-plugin/node_modules/react-dom/index.js":{"id":51,"meta":{}},"./strapi-helper-plugin/node_modules/webpack/buildin/global.js":{"id":52,"meta":{}},"./strapi-admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/_shared.js":{"id":53,"meta":{}},"./strapi-admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/_array-includes.js":{"id":54,"meta":{}},"./strapi-admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/_object-gops.js":{"id":55,"meta":{}},"./strapi-admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/_is-array.js":{"id":56,"meta":{}},"./strapi-admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/_is-regexp.js":{"id":57,"meta":{}},"./strapi-admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/_iter-detect.js":{"id":58,"meta":{}},"./strapi-admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/_flags.js":{"id":59,"meta":{}},"./strapi-admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/_fix-re-wks.js":{"id":60,"meta":{}},"./strapi-admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/_species-constructor.js":{"id":61,"meta":{}},"./strapi-admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/_user-agent.js":{"id":62,"meta":{}},"./strapi-admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/_collection.js":{"id":63,"meta":{}},"./strapi-admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/_typed.js":{"id":64,"meta":{}},"./strapi-admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/_object-forced-pam.js":{"id":65,"meta":{}},"./strapi-admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/_set-collection-of.js":{"id":66,"meta":{}},"./strapi-admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/_set-collection-from.js":{"id":67,"meta":{}},"./strapi-admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/_dom-create.js":{"id":68,"meta":{}},"./strapi-admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/_wks-define.js":{"id":69,"meta":{}},"./strapi-admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/_shared-key.js":{"id":70,"meta":{}},"./strapi-admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/_enum-bug-keys.js":{"id":71,"meta":{}},"./strapi-admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/_html.js":{"id":72,"meta":{}},"./strapi-admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/_set-proto.js":{"id":73,"meta":{}},"./strapi-admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/_string-ws.js":{"id":74,"meta":{}},"./strapi-admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/_inherit-if-required.js":{"id":75,"meta":{}},"./strapi-admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/_string-repeat.js":{"id":76,"meta":{}},"./strapi-admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/_math-sign.js":{"id":77,"meta":{}},"./strapi-admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/_math-expm1.js":{"id":78,"meta":{}},"./strapi-admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/_string-at.js":{"id":79,"meta":{}},"./strapi-admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/_iter-define.js":{"id":80,"meta":{}},"./strapi-admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/_iter-create.js":{"id":81,"meta":{}},"./strapi-admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/_string-context.js":{"id":82,"meta":{}},"./strapi-admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/_fails-is-regexp.js":{"id":83,"meta":{}},"./strapi-admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/_is-array-iter.js":{"id":84,"meta":{}},"./strapi-admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/_create-property.js":{"id":85,"meta":{}},"./strapi-admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/core.get-iterator-method.js":{"id":86,"meta":{}},"./strapi-admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/_array-species-create.js":{"id":87,"meta":{}},"./strapi-admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/_array-fill.js":{"id":88,"meta":{}},"./strapi-admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/es6.array.iterator.js":{"id":89,"meta":{}},"./strapi-admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/_task.js":{"id":90,"meta":{}},"./strapi-admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/_microtask.js":{"id":91,"meta":{}},"./strapi-admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/_new-promise-capability.js":{"id":92,"meta":{}},"./strapi-admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/_typed-buffer.js":{"id":93,"meta":{}},"./strapi-admin/node_modules/strapi-helper-plugin/node_modules/object-assign/index.js":{"id":94,"meta":{}},"./strapi-admin/node_modules/strapi-helper-plugin/node_modules/fbjs/lib/invariant.js":{"id":95,"meta":{}},"./strapi-admin/node_modules/strapi-helper-plugin/node_modules/fbjs/lib/emptyObject.js":{"id":96,"meta":{}},"./strapi-admin/node_modules/strapi-helper-plugin/node_modules/fbjs/lib/emptyFunction.js":{"id":97,"meta":{}},"./strapi-admin/node_modules/strapi-helper-plugin/node_modules/intl-messageformat/src/main.js":{"id":98,"meta":{"harmonyModule":true},"exports":["default"]},"./strapi-admin/node_modules/strapi-helper-plugin/node_modules/intl-messageformat/src/utils.js":{"id":99,"meta":{"harmonyModule":true},"exports":["hop","extend"]},"./strapi-admin/node_modules/strapi-helper-plugin/node_modules/react-transition-group/Transition.js":{"id":100,"meta":{}},"./strapi-admin/node_modules/strapi-helper-plugin/node_modules/react-lifecycles-compat/react-lifecycles-compat.cjs.js":{"id":101,"meta":{}},"./strapi-admin/node_modules/strapi-helper-plugin/node_modules/react-transition-group/utils/PropTypes.js":{"id":102,"meta":{}},"./strapi-admin/node_modules/strapi-helper-plugin/node_modules/react-transition-group/TransitionGroup.js":{"id":103,"meta":{}},"./strapi-admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/_ie8-dom-define.js":{"id":104,"meta":{}},"./strapi-admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/_wks-ext.js":{"id":105,"meta":{}},"./strapi-admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/_object-keys-internal.js":{"id":106,"meta":{}},"./strapi-admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/_object-dps.js":{"id":107,"meta":{}},"./strapi-admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/_object-gopn-ext.js":{"id":108,"meta":{}},"./strapi-admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/_object-assign.js":{"id":109,"meta":{}},"./strapi-admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/_bind.js":{"id":110,"meta":{}},"./strapi-admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/_invoke.js":{"id":111,"meta":{}},"./strapi-admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/_parse-int.js":{"id":112,"meta":{}},"./strapi-admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/_parse-float.js":{"id":113,"meta":{}},"./strapi-admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/_a-number-value.js":{"id":114,"meta":{}},"./strapi-admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/_is-integer.js":{"id":115,"meta":{}},"./strapi-admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/_math-log1p.js":{"id":116,"meta":{}},"./strapi-admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/_math-fround.js":{"id":117,"meta":{}},"./strapi-admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/_iter-call.js":{"id":118,"meta":{}},"./strapi-admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/_array-reduce.js":{"id":119,"meta":{}},"./strapi-admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/_array-copy-within.js":{"id":120,"meta":{}},"./strapi-admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/_iter-step.js":{"id":121,"meta":{}},"./strapi-admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/es6.regexp.flags.js":{"id":122,"meta":{}},"./strapi-admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/_perform.js":{"id":123,"meta":{}},"./strapi-admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/_promise-resolve.js":{"id":124,"meta":{}},"./strapi-admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/es6.map.js":{"id":125,"meta":{}},"./strapi-admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/_collection-strong.js":{"id":126,"meta":{}},"./strapi-admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/es6.set.js":{"id":127,"meta":{}},"./strapi-admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/es6.weak-map.js":{"id":128,"meta":{}},"./strapi-admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/_collection-weak.js":{"id":129,"meta":{}},"./strapi-admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/_to-index.js":{"id":130,"meta":{}},"./strapi-admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/_own-keys.js":{"id":131,"meta":{}},"./strapi-admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/_flatten-into-array.js":{"id":132,"meta":{}},"./strapi-admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/_string-pad.js":{"id":133,"meta":{}},"./strapi-admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/_object-to-array.js":{"id":134,"meta":{}},"./strapi-admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/_collection-to-json.js":{"id":135,"meta":{}},"./strapi-admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/_array-from-iterable.js":{"id":136,"meta":{}},"./strapi-admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/_math-scale.js":{"id":137,"meta":{}},"./strapi-admin/node_modules/strapi-helper-plugin/node_modules/react/cjs/react.production.min.js":{"id":139,"meta":{}},"./strapi-admin/node_modules/strapi-helper-plugin/node_modules/react-dom/cjs/react-dom.production.min.js":{"id":140,"meta":{}},"./strapi-admin/node_modules/strapi-helper-plugin/node_modules/fbjs/lib/ExecutionEnvironment.js":{"id":141,"meta":{}},"./strapi-admin/node_modules/strapi-helper-plugin/node_modules/fbjs/lib/getActiveElement.js":{"id":142,"meta":{}},"./strapi-admin/node_modules/strapi-helper-plugin/node_modules/fbjs/lib/shallowEqual.js":{"id":143,"meta":{}},"./strapi-admin/node_modules/strapi-helper-plugin/node_modules/fbjs/lib/containsNode.js":{"id":144,"meta":{}},"./strapi-admin/node_modules/strapi-helper-plugin/node_modules/fbjs/lib/isTextNode.js":{"id":145,"meta":{}},"./strapi-admin/node_modules/strapi-helper-plugin/node_modules/fbjs/lib/isNode.js":{"id":146,"meta":{}},"./strapi-admin/node_modules/strapi-helper-plugin/node_modules/react-intl/lib/index.es.js":{"id":147,"meta":{"harmonyModule":true},"exports":["addLocaleData","intlShape","injectIntl","defineMessages","IntlProvider","FormattedDate","FormattedTime","FormattedRelative","FormattedNumber","FormattedPlural","FormattedMessage","FormattedHTMLMessage"]},"./strapi-admin/node_modules/strapi-helper-plugin/node_modules/intl-messageformat/src/core.js":{"id":149,"meta":{"harmonyModule":true},"exports":["default"]},"./strapi-admin/node_modules/strapi-helper-plugin/node_modules/intl-messageformat/src/es5.js":{"id":150,"meta":{"harmonyModule":true},"exports":["defineProperty","objCreate"]},"./strapi-admin/node_modules/strapi-helper-plugin/node_modules/intl-messageformat/src/compiler.js":{"id":151,"meta":{"harmonyModule":true},"exports":["default"]},"./strapi-admin/node_modules/strapi-helper-plugin/node_modules/intl-messageformat-parser/src/parser.js":{"id":152,"meta":{"harmonyModule":true},"exports":["default"]},"./strapi-admin/node_modules/strapi-helper-plugin/node_modules/intl-messageformat/src/en.js":{"id":153,"meta":{"harmonyModule":true},"exports":["default"]},"./strapi-admin/node_modules/strapi-helper-plugin/node_modules/intl-relativeformat/src/main.js":{"id":154,"meta":{"harmonyModule":true},"exports":["default"]},"./strapi-admin/node_modules/strapi-helper-plugin/node_modules/intl-relativeformat/src/core.js":{"id":155,"meta":{"harmonyModule":true},"exports":["default"]},"./strapi-admin/node_modules/strapi-helper-plugin/node_modules/intl-relativeformat/src/diff.js":{"id":156,"meta":{"harmonyModule":true},"exports":["default"]},"./strapi-admin/node_modules/strapi-helper-plugin/node_modules/intl-relativeformat/src/es5.js":{"id":157,"meta":{"harmonyModule":true},"exports":["defineProperty","objCreate","arrIndexOf","isArray","dateNow"]},"./strapi-admin/node_modules/strapi-helper-plugin/node_modules/intl-relativeformat/src/en.js":{"id":158,"meta":{"harmonyModule":true},"exports":["default"]},"./strapi-admin/node_modules/strapi-helper-plugin/node_modules/prop-types/factoryWithThrowingShims.js":{"id":159,"meta":{}},"./strapi-admin/node_modules/strapi-helper-plugin/node_modules/prop-types/lib/ReactPropTypesSecret.js":{"id":160,"meta":{}},"./strapi-admin/node_modules/strapi-helper-plugin/node_modules/invariant/browser.js":{"id":161,"meta":{}},"./strapi-admin/node_modules/strapi-helper-plugin/node_modules/intl-format-cache/src/memoizer.js":{"id":162,"meta":{"harmonyModule":true},"exports":["default"]},"./strapi-admin/node_modules/strapi-helper-plugin/node_modules/intl-format-cache/src/es5.js":{"id":163,"meta":{"harmonyModule":true},"exports":["bind","defineProperty","objCreate"]},"./strapi-admin/node_modules/strapi-helper-plugin/node_modules/reactstrap/dist/reactstrap.es.js":{"id":164,"meta":{"harmonyModule":true},"exports":["Alert","Container","Row","Col","Navbar","NavbarBrand","NavbarToggler","Nav","NavItem","NavDropdown","NavLink","Breadcrumb","BreadcrumbItem","Button","ButtonDropdown","ButtonGroup","ButtonToolbar","Dropdown","DropdownItem","DropdownMenu","DropdownToggle","Fade","Badge","Card","CardLink","CardGroup","CardDeck","CardColumns","CardBody","CardBlock","CardFooter","CardHeader","CardImg","CardImgOverlay","Carousel","UncontrolledCarousel","CarouselControl","CarouselItem","CarouselIndicators","CarouselCaption","CardSubtitle","CardText","CardTitle","Popover","PopoverContent","PopoverBody","PopoverTitle","PopoverHeader","Progress","Modal","ModalHeader","ModalBody","ModalFooter","PopperContent","PopperTargetHelper","Tooltip","Table","ListGroup","Form","FormFeedback","FormGroup","FormText","Input","InputGroup","InputGroupAddon","InputGroupButton","InputGroupButtonDropdown","InputGroupText","Label","Media","Pagination","PaginationItem","PaginationLink","TabContent","TabPane","Jumbotron","Collapse","ListGroupItem","ListGroupItemText","ListGroupItemHeading","UncontrolledAlert","UncontrolledButtonDropdown","UncontrolledDropdown","UncontrolledNavDropdown","UncontrolledTooltip","Util"]},"./strapi-admin/node_modules/strapi-helper-plugin/node_modules/classnames/index.js":{"id":165,"meta":{}},"./strapi-admin/node_modules/strapi-helper-plugin/node_modules/lodash.isfunction/index.js":{"id":166,"meta":{}},"./strapi-admin/node_modules/strapi-helper-plugin/node_modules/lodash.isobject/index.js":{"id":167,"meta":{}},"./strapi-admin/node_modules/strapi-helper-plugin/node_modules/react-popper/dist/react-popper.js":{"id":168,"meta":{}},"./strapi-admin/node_modules/strapi-helper-plugin/node_modules/lodash.tonumber/index.js":{"id":169,"meta":{}},"./strapi-admin/node_modules/strapi-helper-plugin/node_modules/react-transition-group/index.js":{"id":170,"meta":{}},"./strapi-admin/node_modules/strapi-helper-plugin/node_modules/react-transition-group/CSSTransition.js":{"id":171,"meta":{}},"./strapi-admin/node_modules/strapi-helper-plugin/node_modules/dom-helpers/class/addClass.js":{"id":172,"meta":{}},"./strapi-admin/node_modules/strapi-helper-plugin/node_modules/dom-helpers/class/hasClass.js":{"id":173,"meta":{}},"./strapi-admin/node_modules/strapi-helper-plugin/node_modules/dom-helpers/class/removeClass.js":{"id":174,"meta":{}},"./strapi-admin/node_modules/strapi-helper-plugin/node_modules/react-transition-group/ReplaceTransition.js":{"id":175,"meta":{}},"./strapi-admin/node_modules/strapi-helper-plugin/node_modules/react-transition-group/utils/ChildMapping.js":{"id":176,"meta":{}},"./strapi-admin/node_modules/strapi-helper-plugin/node_modules/immutable/dist/immutable.js":{"id":177,"meta":{}},"./strapi-admin/node_modules/strapi-helper-plugin/node_modules/lodash/lodash.js":{"id":178,"meta":{}},"./strapi-helper-plugin/node_modules/webpack/buildin/module.js":{"id":179,"meta":{}},"./strapi-admin/node_modules/strapi-helper-plugin/node_modules/babel-polyfill/lib/index.js":{"id":180,"meta":{}},"./strapi-admin/node_modules/strapi-helper-plugin/node_modules/core-js/shim.js":{"id":181,"meta":{}},"./strapi-admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/es6.symbol.js":{"id":182,"meta":{}},"./strapi-admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/_enum-keys.js":{"id":183,"meta":{}},"./strapi-admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/es6.object.create.js":{"id":184,"meta":{}},"./strapi-admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/es6.object.define-property.js":{"id":185,"meta":{}},"./strapi-admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/es6.object.define-properties.js":{"id":186,"meta":{}},"./strapi-admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/es6.object.get-own-property-descriptor.js":{"id":187,"meta":{}},"./strapi-admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/es6.object.get-prototype-of.js":{"id":188,"meta":{}},"./strapi-admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/es6.object.keys.js":{"id":189,"meta":{}},"./strapi-admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/es6.object.get-own-property-names.js":{"id":190,"meta":{}},"./strapi-admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/es6.object.freeze.js":{"id":191,"meta":{}},"./strapi-admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/es6.object.seal.js":{"id":192,"meta":{}},"./strapi-admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/es6.object.prevent-extensions.js":{"id":193,"meta":{}},"./strapi-admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/es6.object.is-frozen.js":{"id":194,"meta":{}},"./strapi-admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/es6.object.is-sealed.js":{"id":195,"meta":{}},"./strapi-admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/es6.object.is-extensible.js":{"id":196,"meta":{}},"./strapi-admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/es6.object.assign.js":{"id":197,"meta":{}},"./strapi-admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/es6.object.is.js":{"id":198,"meta":{}},"./strapi-admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/_same-value.js":{"id":199,"meta":{}},"./strapi-admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/es6.object.set-prototype-of.js":{"id":200,"meta":{}},"./strapi-admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/es6.object.to-string.js":{"id":201,"meta":{}},"./strapi-admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/es6.function.bind.js":{"id":202,"meta":{}},"./strapi-admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/es6.function.name.js":{"id":203,"meta":{}},"./strapi-admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/es6.function.has-instance.js":{"id":204,"meta":{}},"./strapi-admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/es6.parse-int.js":{"id":205,"meta":{}},"./strapi-admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/es6.parse-float.js":{"id":206,"meta":{}},"./strapi-admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/es6.number.constructor.js":{"id":207,"meta":{}},"./strapi-admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/es6.number.to-fixed.js":{"id":208,"meta":{}},"./strapi-admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/es6.number.to-precision.js":{"id":209,"meta":{}},"./strapi-admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/es6.number.epsilon.js":{"id":210,"meta":{}},"./strapi-admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/es6.number.is-finite.js":{"id":211,"meta":{}},"./strapi-admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/es6.number.is-integer.js":{"id":212,"meta":{}},"./strapi-admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/es6.number.is-nan.js":{"id":213,"meta":{}},"./strapi-admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/es6.number.is-safe-integer.js":{"id":214,"meta":{}},"./strapi-admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/es6.number.max-safe-integer.js":{"id":215,"meta":{}},"./strapi-admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/es6.number.min-safe-integer.js":{"id":216,"meta":{}},"./strapi-admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/es6.number.parse-float.js":{"id":217,"meta":{}},"./strapi-admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/es6.number.parse-int.js":{"id":218,"meta":{}},"./strapi-admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/es6.math.acosh.js":{"id":219,"meta":{}},"./strapi-admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/es6.math.asinh.js":{"id":220,"meta":{}},"./strapi-admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/es6.math.atanh.js":{"id":221,"meta":{}},"./strapi-admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/es6.math.cbrt.js":{"id":222,"meta":{}},"./strapi-admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/es6.math.clz32.js":{"id":223,"meta":{}},"./strapi-admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/es6.math.cosh.js":{"id":224,"meta":{}},"./strapi-admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/es6.math.expm1.js":{"id":225,"meta":{}},"./strapi-admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/es6.math.fround.js":{"id":226,"meta":{}},"./strapi-admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/es6.math.hypot.js":{"id":227,"meta":{}},"./strapi-admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/es6.math.imul.js":{"id":228,"meta":{}},"./strapi-admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/es6.math.log10.js":{"id":229,"meta":{}},"./strapi-admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/es6.math.log1p.js":{"id":230,"meta":{}},"./strapi-admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/es6.math.log2.js":{"id":231,"meta":{}},"./strapi-admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/es6.math.sign.js":{"id":232,"meta":{}},"./strapi-admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/es6.math.sinh.js":{"id":233,"meta":{}},"./strapi-admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/es6.math.tanh.js":{"id":234,"meta":{}},"./strapi-admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/es6.math.trunc.js":{"id":235,"meta":{}},"./strapi-admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/es6.string.from-code-point.js":{"id":236,"meta":{}},"./strapi-admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/es6.string.raw.js":{"id":237,"meta":{}},"./strapi-admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/es6.string.trim.js":{"id":238,"meta":{}},"./strapi-admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/es6.string.iterator.js":{"id":239,"meta":{}},"./strapi-admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/es6.string.code-point-at.js":{"id":240,"meta":{}},"./strapi-admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/es6.string.ends-with.js":{"id":241,"meta":{}},"./strapi-admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/es6.string.includes.js":{"id":242,"meta":{}},"./strapi-admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/es6.string.repeat.js":{"id":243,"meta":{}},"./strapi-admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/es6.string.starts-with.js":{"id":244,"meta":{}},"./strapi-admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/es6.string.anchor.js":{"id":245,"meta":{}},"./strapi-admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/es6.string.big.js":{"id":246,"meta":{}},"./strapi-admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/es6.string.blink.js":{"id":247,"meta":{}},"./strapi-admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/es6.string.bold.js":{"id":248,"meta":{}},"./strapi-admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/es6.string.fixed.js":{"id":249,"meta":{}},"./strapi-admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/es6.string.fontcolor.js":{"id":250,"meta":{}},"./strapi-admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/es6.string.fontsize.js":{"id":251,"meta":{}},"./strapi-admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/es6.string.italics.js":{"id":252,"meta":{}},"./strapi-admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/es6.string.link.js":{"id":253,"meta":{}},"./strapi-admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/es6.string.small.js":{"id":254,"meta":{}},"./strapi-admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/es6.string.strike.js":{"id":255,"meta":{}},"./strapi-admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/es6.string.sub.js":{"id":256,"meta":{}},"./strapi-admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/es6.string.sup.js":{"id":257,"meta":{}},"./strapi-admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/es6.date.now.js":{"id":258,"meta":{}},"./strapi-admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/es6.date.to-json.js":{"id":259,"meta":{}},"./strapi-admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/es6.date.to-iso-string.js":{"id":260,"meta":{}},"./strapi-admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/_date-to-iso-string.js":{"id":261,"meta":{}},"./strapi-admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/es6.date.to-string.js":{"id":262,"meta":{}},"./strapi-admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/es6.date.to-primitive.js":{"id":263,"meta":{}},"./strapi-admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/_date-to-primitive.js":{"id":264,"meta":{}},"./strapi-admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/es6.array.is-array.js":{"id":265,"meta":{}},"./strapi-admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/es6.array.from.js":{"id":266,"meta":{}},"./strapi-admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/es6.array.of.js":{"id":267,"meta":{}},"./strapi-admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/es6.array.join.js":{"id":268,"meta":{}},"./strapi-admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/es6.array.slice.js":{"id":269,"meta":{}},"./strapi-admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/es6.array.sort.js":{"id":270,"meta":{}},"./strapi-admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/es6.array.for-each.js":{"id":271,"meta":{}},"./strapi-admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/_array-species-constructor.js":{"id":272,"meta":{}},"./strapi-admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/es6.array.map.js":{"id":273,"meta":{}},"./strapi-admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/es6.array.filter.js":{"id":274,"meta":{}},"./strapi-admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/es6.array.some.js":{"id":275,"meta":{}},"./strapi-admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/es6.array.every.js":{"id":276,"meta":{}},"./strapi-admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/es6.array.reduce.js":{"id":277,"meta":{}},"./strapi-admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/es6.array.reduce-right.js":{"id":278,"meta":{}},"./strapi-admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/es6.array.index-of.js":{"id":279,"meta":{}},"./strapi-admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/es6.array.last-index-of.js":{"id":280,"meta":{}},"./strapi-admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/es6.array.copy-within.js":{"id":281,"meta":{}},"./strapi-admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/es6.array.fill.js":{"id":282,"meta":{}},"./strapi-admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/es6.array.find.js":{"id":283,"meta":{}},"./strapi-admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/es6.array.find-index.js":{"id":284,"meta":{}},"./strapi-admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/es6.array.species.js":{"id":285,"meta":{}},"./strapi-admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/es6.regexp.constructor.js":{"id":286,"meta":{}},"./strapi-admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/es6.regexp.to-string.js":{"id":287,"meta":{}},"./strapi-admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/es6.regexp.match.js":{"id":288,"meta":{}},"./strapi-admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/es6.regexp.replace.js":{"id":289,"meta":{}},"./strapi-admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/es6.regexp.search.js":{"id":290,"meta":{}},"./strapi-admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/es6.regexp.split.js":{"id":291,"meta":{}},"./strapi-admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/es6.promise.js":{"id":292,"meta":{}},"./strapi-admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/es6.weak-set.js":{"id":293,"meta":{}},"./strapi-admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/es6.typed.array-buffer.js":{"id":294,"meta":{}},"./strapi-admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/es6.typed.data-view.js":{"id":295,"meta":{}},"./strapi-admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/es6.typed.int8-array.js":{"id":296,"meta":{}},"./strapi-admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/es6.typed.uint8-array.js":{"id":297,"meta":{}},"./strapi-admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/es6.typed.uint8-clamped-array.js":{"id":298,"meta":{}},"./strapi-admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/es6.typed.int16-array.js":{"id":299,"meta":{}},"./strapi-admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/es6.typed.uint16-array.js":{"id":300,"meta":{}},"./strapi-admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/es6.typed.int32-array.js":{"id":301,"meta":{}},"./strapi-admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/es6.typed.uint32-array.js":{"id":302,"meta":{}},"./strapi-admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/es6.typed.float32-array.js":{"id":303,"meta":{}},"./strapi-admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/es6.typed.float64-array.js":{"id":304,"meta":{}},"./strapi-admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/es6.reflect.apply.js":{"id":305,"meta":{}},"./strapi-admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/es6.reflect.construct.js":{"id":306,"meta":{}},"./strapi-admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/es6.reflect.define-property.js":{"id":307,"meta":{}},"./strapi-admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/es6.reflect.delete-property.js":{"id":308,"meta":{}},"./strapi-admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/es6.reflect.enumerate.js":{"id":309,"meta":{}},"./strapi-admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/es6.reflect.get.js":{"id":310,"meta":{}},"./strapi-admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/es6.reflect.get-own-property-descriptor.js":{"id":311,"meta":{}},"./strapi-admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/es6.reflect.get-prototype-of.js":{"id":312,"meta":{}},"./strapi-admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/es6.reflect.has.js":{"id":313,"meta":{}},"./strapi-admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/es6.reflect.is-extensible.js":{"id":314,"meta":{}},"./strapi-admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/es6.reflect.own-keys.js":{"id":315,"meta":{}},"./strapi-admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/es6.reflect.prevent-extensions.js":{"id":316,"meta":{}},"./strapi-admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/es6.reflect.set.js":{"id":317,"meta":{}},"./strapi-admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/es6.reflect.set-prototype-of.js":{"id":318,"meta":{}},"./strapi-admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/es7.array.includes.js":{"id":319,"meta":{}},"./strapi-admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/es7.array.flat-map.js":{"id":320,"meta":{}},"./strapi-admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/es7.array.flatten.js":{"id":321,"meta":{}},"./strapi-admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/es7.string.at.js":{"id":322,"meta":{}},"./strapi-admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/es7.string.pad-start.js":{"id":323,"meta":{}},"./strapi-admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/es7.string.pad-end.js":{"id":324,"meta":{}},"./strapi-admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/es7.string.trim-left.js":{"id":325,"meta":{}},"./strapi-admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/es7.string.trim-right.js":{"id":326,"meta":{}},"./strapi-admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/es7.string.match-all.js":{"id":327,"meta":{}},"./strapi-admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/es7.symbol.async-iterator.js":{"id":328,"meta":{}},"./strapi-admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/es7.symbol.observable.js":{"id":329,"meta":{}},"./strapi-admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/es7.object.get-own-property-descriptors.js":{"id":330,"meta":{}},"./strapi-admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/es7.object.values.js":{"id":331,"meta":{}},"./strapi-admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/es7.object.entries.js":{"id":332,"meta":{}},"./strapi-admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/es7.object.define-getter.js":{"id":333,"meta":{}},"./strapi-admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/es7.object.define-setter.js":{"id":334,"meta":{}},"./strapi-admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/es7.object.lookup-getter.js":{"id":335,"meta":{}},"./strapi-admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/es7.object.lookup-setter.js":{"id":336,"meta":{}},"./strapi-admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/es7.map.to-json.js":{"id":337,"meta":{}},"./strapi-admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/es7.set.to-json.js":{"id":338,"meta":{}},"./strapi-admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/es7.map.of.js":{"id":339,"meta":{}},"./strapi-admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/es7.set.of.js":{"id":340,"meta":{}},"./strapi-admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/es7.weak-map.of.js":{"id":341,"meta":{}},"./strapi-admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/es7.weak-set.of.js":{"id":342,"meta":{}},"./strapi-admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/es7.map.from.js":{"id":343,"meta":{}},"./strapi-admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/es7.set.from.js":{"id":344,"meta":{}},"./strapi-admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/es7.weak-map.from.js":{"id":345,"meta":{}},"./strapi-admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/es7.weak-set.from.js":{"id":346,"meta":{}},"./strapi-admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/es7.global.js":{"id":347,"meta":{}},"./strapi-admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/es7.system.global.js":{"id":348,"meta":{}},"./strapi-admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/es7.error.is-error.js":{"id":349,"meta":{}},"./strapi-admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/es7.math.clamp.js":{"id":350,"meta":{}},"./strapi-admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/es7.math.deg-per-rad.js":{"id":351,"meta":{}},"./strapi-admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/es7.math.degrees.js":{"id":352,"meta":{}},"./strapi-admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/es7.math.fscale.js":{"id":353,"meta":{}},"./strapi-admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/es7.math.iaddh.js":{"id":354,"meta":{}},"./strapi-admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/es7.math.isubh.js":{"id":355,"meta":{}},"./strapi-admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/es7.math.imulh.js":{"id":356,"meta":{}},"./strapi-admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/es7.math.rad-per-deg.js":{"id":357,"meta":{}},"./strapi-admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/es7.math.radians.js":{"id":358,"meta":{}},"./strapi-admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/es7.math.scale.js":{"id":359,"meta":{}},"./strapi-admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/es7.math.umulh.js":{"id":360,"meta":{}},"./strapi-admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/es7.math.signbit.js":{"id":361,"meta":{}},"./strapi-admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/es7.promise.finally.js":{"id":362,"meta":{}},"./strapi-admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/es7.promise.try.js":{"id":363,"meta":{}},"./strapi-admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/es7.reflect.define-metadata.js":{"id":364,"meta":{}},"./strapi-admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/es7.reflect.delete-metadata.js":{"id":365,"meta":{}},"./strapi-admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/es7.reflect.get-metadata.js":{"id":366,"meta":{}},"./strapi-admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/es7.reflect.get-metadata-keys.js":{"id":367,"meta":{}},"./strapi-admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/es7.reflect.get-own-metadata.js":{"id":368,"meta":{}},"./strapi-admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/es7.reflect.get-own-metadata-keys.js":{"id":369,"meta":{}},"./strapi-admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/es7.reflect.has-metadata.js":{"id":370,"meta":{}},"./strapi-admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/es7.reflect.has-own-metadata.js":{"id":371,"meta":{}},"./strapi-admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/es7.reflect.metadata.js":{"id":372,"meta":{}},"./strapi-admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/es7.asap.js":{"id":373,"meta":{}},"./strapi-admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/es7.observable.js":{"id":374,"meta":{}},"./strapi-admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/web.timers.js":{"id":375,"meta":{}},"./strapi-admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/web.immediate.js":{"id":376,"meta":{}},"./strapi-admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/web.dom.iterable.js":{"id":377,"meta":{}},"./strapi-admin/node_modules/strapi-helper-plugin/node_modules/babel-polyfill/node_modules/regenerator-runtime/runtime.js":{"id":378,"meta":{}},"./strapi-admin/node_modules/strapi-helper-plugin/node_modules/core-js/fn/regexp/escape.js":{"id":379,"meta":{}},"./strapi-admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/core.regexp.escape.js":{"id":380,"meta":{}},"./strapi-admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/_replacer.js":{"id":381,"meta":{}}}} \ No newline at end of file +{"name":"vendor_lib","content":{"./admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/_export.js":{"id":0,"meta":{}},"./admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/_an-object.js":{"id":1,"meta":{}},"./admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/_global.js":{"id":2,"meta":{}},"./admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/_fails.js":{"id":3,"meta":{}},"./admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/_is-object.js":{"id":4,"meta":{}},"./admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/_wks.js":{"id":5,"meta":{}},"./admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/_descriptors.js":{"id":6,"meta":{}},"./admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/_object-dp.js":{"id":7,"meta":{}},"./admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/_to-length.js":{"id":8,"meta":{}},"./admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/_to-object.js":{"id":9,"meta":{}},"./admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/_a-function.js":{"id":10,"meta":{}},"./admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/_hide.js":{"id":11,"meta":{}},"./admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/_redefine.js":{"id":12,"meta":{}},"./admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/_string-html.js":{"id":13,"meta":{}},"./admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/_has.js":{"id":14,"meta":{}},"./admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/_to-iobject.js":{"id":15,"meta":{}},"./admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/_object-gopd.js":{"id":16,"meta":{}},"./admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/_object-gpo.js":{"id":17,"meta":{}},"./admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/_core.js":{"id":18,"meta":{}},"./admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/_ctx.js":{"id":19,"meta":{}},"./admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/_cof.js":{"id":20,"meta":{}},"./admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/_strict-method.js":{"id":21,"meta":{}},"./admin/node_modules/strapi-helper-plugin/node_modules/react/index.js":{"id":22,"meta":{}},"./admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/_to-primitive.js":{"id":23,"meta":{}},"./admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/_defined.js":{"id":24,"meta":{}},"./admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/_to-integer.js":{"id":25,"meta":{}},"./admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/_object-sap.js":{"id":26,"meta":{}},"./admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/_array-methods.js":{"id":27,"meta":{}},"./admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/_typed-array.js":{"id":28,"meta":{}},"./admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/_metadata.js":{"id":29,"meta":{}},"./admin/node_modules/strapi-helper-plugin/node_modules/prop-types/index.js":{"id":30,"meta":{}},"./admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/_meta.js":{"id":31,"meta":{}},"./admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/_library.js":{"id":32,"meta":{}},"./admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/_add-to-unscopables.js":{"id":33,"meta":{}},"./admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/_property-desc.js":{"id":34,"meta":{}},"./admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/_uid.js":{"id":35,"meta":{}},"./admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/_object-keys.js":{"id":36,"meta":{}},"./admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/_to-absolute-index.js":{"id":37,"meta":{}},"./admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/_object-create.js":{"id":38,"meta":{}},"./admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/_object-gopn.js":{"id":39,"meta":{}},"./admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/_set-species.js":{"id":40,"meta":{}},"./admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/_an-instance.js":{"id":41,"meta":{}},"./admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/_for-of.js":{"id":42,"meta":{}},"./admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/_redefine-all.js":{"id":43,"meta":{}},"./admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/_set-to-string-tag.js":{"id":44,"meta":{}},"./admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/_string-trim.js":{"id":45,"meta":{}},"./admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/_iterators.js":{"id":46,"meta":{}},"./admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/_validate-collection.js":{"id":47,"meta":{}},"./admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/_iobject.js":{"id":48,"meta":{}},"./admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/_object-pie.js":{"id":49,"meta":{}},"./admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/_classof.js":{"id":50,"meta":{}},"./admin/node_modules/strapi-helper-plugin/node_modules/react-dom/index.js":{"id":51,"meta":{}},"../../repository/strapi/packages/strapi-helper-plugin/node_modules/webpack/buildin/global.js":{"id":52,"meta":{}},"./admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/_shared.js":{"id":53,"meta":{}},"./admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/_array-includes.js":{"id":54,"meta":{}},"./admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/_object-gops.js":{"id":55,"meta":{}},"./admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/_is-array.js":{"id":56,"meta":{}},"./admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/_is-regexp.js":{"id":57,"meta":{}},"./admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/_iter-detect.js":{"id":58,"meta":{}},"./admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/_flags.js":{"id":59,"meta":{}},"./admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/_fix-re-wks.js":{"id":60,"meta":{}},"./admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/_species-constructor.js":{"id":61,"meta":{}},"./admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/_user-agent.js":{"id":62,"meta":{}},"./admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/_collection.js":{"id":63,"meta":{}},"./admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/_typed.js":{"id":64,"meta":{}},"./admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/_object-forced-pam.js":{"id":65,"meta":{}},"./admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/_set-collection-of.js":{"id":66,"meta":{}},"./admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/_set-collection-from.js":{"id":67,"meta":{}},"./admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/_dom-create.js":{"id":68,"meta":{}},"./admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/_wks-define.js":{"id":69,"meta":{}},"./admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/_shared-key.js":{"id":70,"meta":{}},"./admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/_enum-bug-keys.js":{"id":71,"meta":{}},"./admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/_html.js":{"id":72,"meta":{}},"./admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/_set-proto.js":{"id":73,"meta":{}},"./admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/_string-ws.js":{"id":74,"meta":{}},"./admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/_inherit-if-required.js":{"id":75,"meta":{}},"./admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/_string-repeat.js":{"id":76,"meta":{}},"./admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/_math-sign.js":{"id":77,"meta":{}},"./admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/_math-expm1.js":{"id":78,"meta":{}},"./admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/_string-at.js":{"id":79,"meta":{}},"./admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/_iter-define.js":{"id":80,"meta":{}},"./admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/_iter-create.js":{"id":81,"meta":{}},"./admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/_string-context.js":{"id":82,"meta":{}},"./admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/_fails-is-regexp.js":{"id":83,"meta":{}},"./admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/_is-array-iter.js":{"id":84,"meta":{}},"./admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/_create-property.js":{"id":85,"meta":{}},"./admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/core.get-iterator-method.js":{"id":86,"meta":{}},"./admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/_array-species-create.js":{"id":87,"meta":{}},"./admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/_array-fill.js":{"id":88,"meta":{}},"./admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/es6.array.iterator.js":{"id":89,"meta":{}},"./admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/_task.js":{"id":90,"meta":{}},"./admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/_microtask.js":{"id":91,"meta":{}},"./admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/_new-promise-capability.js":{"id":92,"meta":{}},"./admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/_typed-buffer.js":{"id":93,"meta":{}},"./admin/node_modules/strapi-helper-plugin/node_modules/object-assign/index.js":{"id":94,"meta":{}},"./admin/node_modules/strapi-helper-plugin/node_modules/intl-messageformat/src/main.js":{"id":95,"meta":{"harmonyModule":true},"exports":["default"]},"./admin/node_modules/strapi-helper-plugin/node_modules/intl-messageformat/src/utils.js":{"id":96,"meta":{"harmonyModule":true},"exports":["hop","extend"]},"./admin/node_modules/strapi-helper-plugin/node_modules/react-transition-group/Transition.js":{"id":97,"meta":{}},"./admin/node_modules/strapi-helper-plugin/node_modules/react-lifecycles-compat/react-lifecycles-compat.cjs.js":{"id":98,"meta":{}},"./admin/node_modules/strapi-helper-plugin/node_modules/react-transition-group/utils/PropTypes.js":{"id":99,"meta":{}},"./admin/node_modules/strapi-helper-plugin/node_modules/react-transition-group/TransitionGroup.js":{"id":100,"meta":{}},"./admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/_ie8-dom-define.js":{"id":101,"meta":{}},"./admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/_wks-ext.js":{"id":102,"meta":{}},"./admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/_object-keys-internal.js":{"id":103,"meta":{}},"./admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/_object-dps.js":{"id":104,"meta":{}},"./admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/_object-gopn-ext.js":{"id":105,"meta":{}},"./admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/_object-assign.js":{"id":106,"meta":{}},"./admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/_bind.js":{"id":107,"meta":{}},"./admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/_invoke.js":{"id":108,"meta":{}},"./admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/_parse-int.js":{"id":109,"meta":{}},"./admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/_parse-float.js":{"id":110,"meta":{}},"./admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/_a-number-value.js":{"id":111,"meta":{}},"./admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/_is-integer.js":{"id":112,"meta":{}},"./admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/_math-log1p.js":{"id":113,"meta":{}},"./admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/_math-fround.js":{"id":114,"meta":{}},"./admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/_iter-call.js":{"id":115,"meta":{}},"./admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/_array-reduce.js":{"id":116,"meta":{}},"./admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/_array-copy-within.js":{"id":117,"meta":{}},"./admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/_iter-step.js":{"id":118,"meta":{}},"./admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/es6.regexp.flags.js":{"id":119,"meta":{}},"./admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/_perform.js":{"id":120,"meta":{}},"./admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/_promise-resolve.js":{"id":121,"meta":{}},"./admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/es6.map.js":{"id":122,"meta":{}},"./admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/_collection-strong.js":{"id":123,"meta":{}},"./admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/es6.set.js":{"id":124,"meta":{}},"./admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/es6.weak-map.js":{"id":125,"meta":{}},"./admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/_collection-weak.js":{"id":126,"meta":{}},"./admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/_to-index.js":{"id":127,"meta":{}},"./admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/_own-keys.js":{"id":128,"meta":{}},"./admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/_flatten-into-array.js":{"id":129,"meta":{}},"./admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/_string-pad.js":{"id":130,"meta":{}},"./admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/_object-to-array.js":{"id":131,"meta":{}},"./admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/_collection-to-json.js":{"id":132,"meta":{}},"./admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/_array-from-iterable.js":{"id":133,"meta":{}},"./admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/_math-scale.js":{"id":134,"meta":{}},"./admin/node_modules/strapi-helper-plugin/node_modules/react/cjs/react.production.min.js":{"id":136,"meta":{}},"./admin/node_modules/strapi-helper-plugin/node_modules/react-dom/cjs/react-dom.production.min.js":{"id":137,"meta":{}},"./admin/node_modules/strapi-helper-plugin/node_modules/schedule/index.js":{"id":138,"meta":{}},"./admin/node_modules/strapi-helper-plugin/node_modules/schedule/cjs/schedule.production.min.js":{"id":139,"meta":{}},"./admin/node_modules/strapi-helper-plugin/node_modules/react-intl/lib/index.es.js":{"id":140,"meta":{"harmonyModule":true},"exports":["addLocaleData","intlShape","injectIntl","defineMessages","IntlProvider","FormattedDate","FormattedTime","FormattedRelative","FormattedNumber","FormattedPlural","FormattedMessage","FormattedHTMLMessage"]},"./admin/node_modules/strapi-helper-plugin/node_modules/intl-messageformat/src/core.js":{"id":142,"meta":{"harmonyModule":true},"exports":["default"]},"./admin/node_modules/strapi-helper-plugin/node_modules/intl-messageformat/src/es5.js":{"id":143,"meta":{"harmonyModule":true},"exports":["defineProperty","objCreate"]},"./admin/node_modules/strapi-helper-plugin/node_modules/intl-messageformat/src/compiler.js":{"id":144,"meta":{"harmonyModule":true},"exports":["default"]},"./admin/node_modules/strapi-helper-plugin/node_modules/intl-messageformat-parser/src/parser.js":{"id":145,"meta":{"harmonyModule":true},"exports":["default"]},"./admin/node_modules/strapi-helper-plugin/node_modules/intl-messageformat/src/en.js":{"id":146,"meta":{"harmonyModule":true},"exports":["default"]},"./admin/node_modules/strapi-helper-plugin/node_modules/intl-relativeformat/src/main.js":{"id":147,"meta":{"harmonyModule":true},"exports":["default"]},"./admin/node_modules/strapi-helper-plugin/node_modules/intl-relativeformat/src/core.js":{"id":148,"meta":{"harmonyModule":true},"exports":["default"]},"./admin/node_modules/strapi-helper-plugin/node_modules/intl-relativeformat/src/diff.js":{"id":149,"meta":{"harmonyModule":true},"exports":["default"]},"./admin/node_modules/strapi-helper-plugin/node_modules/intl-relativeformat/src/es5.js":{"id":150,"meta":{"harmonyModule":true},"exports":["defineProperty","objCreate","arrIndexOf","isArray","dateNow"]},"./admin/node_modules/strapi-helper-plugin/node_modules/intl-relativeformat/src/en.js":{"id":151,"meta":{"harmonyModule":true},"exports":["default"]},"./admin/node_modules/strapi-helper-plugin/node_modules/prop-types/factoryWithThrowingShims.js":{"id":152,"meta":{}},"./admin/node_modules/strapi-helper-plugin/node_modules/prop-types/lib/ReactPropTypesSecret.js":{"id":153,"meta":{}},"./admin/node_modules/strapi-helper-plugin/node_modules/invariant/browser.js":{"id":154,"meta":{}},"./admin/node_modules/strapi-helper-plugin/node_modules/intl-format-cache/src/memoizer.js":{"id":155,"meta":{"harmonyModule":true},"exports":["default"]},"./admin/node_modules/strapi-helper-plugin/node_modules/intl-format-cache/src/es5.js":{"id":156,"meta":{"harmonyModule":true},"exports":["bind","defineProperty","objCreate"]},"./admin/node_modules/strapi-helper-plugin/node_modules/reactstrap/dist/reactstrap.es.js":{"id":157,"meta":{"harmonyModule":true},"exports":["Alert","Container","Row","Col","Navbar","NavbarBrand","NavbarToggler","Nav","NavItem","NavDropdown","NavLink","Breadcrumb","BreadcrumbItem","Button","ButtonDropdown","ButtonGroup","ButtonToolbar","Dropdown","DropdownItem","DropdownMenu","DropdownToggle","Fade","Badge","Card","CardLink","CardGroup","CardDeck","CardColumns","CardBody","CardBlock","CardFooter","CardHeader","CardImg","CardImgOverlay","Carousel","UncontrolledCarousel","CarouselControl","CarouselItem","CarouselIndicators","CarouselCaption","CardSubtitle","CardText","CardTitle","Popover","PopoverContent","PopoverBody","PopoverTitle","PopoverHeader","Progress","Modal","ModalHeader","ModalBody","ModalFooter","PopperContent","PopperTargetHelper","Tooltip","Table","ListGroup","Form","FormFeedback","FormGroup","FormText","Input","InputGroup","InputGroupAddon","InputGroupButton","InputGroupButtonDropdown","InputGroupText","Label","Media","Pagination","PaginationItem","PaginationLink","TabContent","TabPane","Jumbotron","Collapse","ListGroupItem","ListGroupItemText","ListGroupItemHeading","UncontrolledAlert","UncontrolledButtonDropdown","UncontrolledDropdown","UncontrolledNavDropdown","UncontrolledTooltip","Util"]},"./admin/node_modules/strapi-helper-plugin/node_modules/classnames/index.js":{"id":158,"meta":{}},"./admin/node_modules/strapi-helper-plugin/node_modules/lodash.isfunction/index.js":{"id":159,"meta":{}},"./admin/node_modules/strapi-helper-plugin/node_modules/lodash.isobject/index.js":{"id":160,"meta":{}},"./admin/node_modules/strapi-helper-plugin/node_modules/react-popper/dist/react-popper.js":{"id":161,"meta":{}},"./admin/node_modules/strapi-helper-plugin/node_modules/lodash.tonumber/index.js":{"id":162,"meta":{}},"./admin/node_modules/strapi-helper-plugin/node_modules/react-transition-group/index.js":{"id":163,"meta":{}},"./admin/node_modules/strapi-helper-plugin/node_modules/react-transition-group/CSSTransition.js":{"id":164,"meta":{}},"./admin/node_modules/strapi-helper-plugin/node_modules/dom-helpers/class/addClass.js":{"id":165,"meta":{}},"./admin/node_modules/strapi-helper-plugin/node_modules/dom-helpers/class/hasClass.js":{"id":166,"meta":{}},"./admin/node_modules/strapi-helper-plugin/node_modules/dom-helpers/class/removeClass.js":{"id":167,"meta":{}},"./admin/node_modules/strapi-helper-plugin/node_modules/react-transition-group/ReplaceTransition.js":{"id":168,"meta":{}},"./admin/node_modules/strapi-helper-plugin/node_modules/react-transition-group/utils/ChildMapping.js":{"id":169,"meta":{}},"./admin/node_modules/strapi-helper-plugin/node_modules/immutable/dist/immutable.js":{"id":170,"meta":{}},"./admin/node_modules/strapi-helper-plugin/node_modules/lodash/lodash.js":{"id":171,"meta":{}},"../../repository/strapi/packages/strapi-helper-plugin/node_modules/webpack/buildin/module.js":{"id":172,"meta":{}},"./admin/node_modules/strapi-helper-plugin/node_modules/babel-polyfill/lib/index.js":{"id":173,"meta":{}},"./admin/node_modules/strapi-helper-plugin/node_modules/core-js/shim.js":{"id":174,"meta":{}},"./admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/es6.symbol.js":{"id":175,"meta":{}},"./admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/_enum-keys.js":{"id":176,"meta":{}},"./admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/es6.object.create.js":{"id":177,"meta":{}},"./admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/es6.object.define-property.js":{"id":178,"meta":{}},"./admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/es6.object.define-properties.js":{"id":179,"meta":{}},"./admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/es6.object.get-own-property-descriptor.js":{"id":180,"meta":{}},"./admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/es6.object.get-prototype-of.js":{"id":181,"meta":{}},"./admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/es6.object.keys.js":{"id":182,"meta":{}},"./admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/es6.object.get-own-property-names.js":{"id":183,"meta":{}},"./admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/es6.object.freeze.js":{"id":184,"meta":{}},"./admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/es6.object.seal.js":{"id":185,"meta":{}},"./admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/es6.object.prevent-extensions.js":{"id":186,"meta":{}},"./admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/es6.object.is-frozen.js":{"id":187,"meta":{}},"./admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/es6.object.is-sealed.js":{"id":188,"meta":{}},"./admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/es6.object.is-extensible.js":{"id":189,"meta":{}},"./admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/es6.object.assign.js":{"id":190,"meta":{}},"./admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/es6.object.is.js":{"id":191,"meta":{}},"./admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/_same-value.js":{"id":192,"meta":{}},"./admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/es6.object.set-prototype-of.js":{"id":193,"meta":{}},"./admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/es6.object.to-string.js":{"id":194,"meta":{}},"./admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/es6.function.bind.js":{"id":195,"meta":{}},"./admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/es6.function.name.js":{"id":196,"meta":{}},"./admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/es6.function.has-instance.js":{"id":197,"meta":{}},"./admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/es6.parse-int.js":{"id":198,"meta":{}},"./admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/es6.parse-float.js":{"id":199,"meta":{}},"./admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/es6.number.constructor.js":{"id":200,"meta":{}},"./admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/es6.number.to-fixed.js":{"id":201,"meta":{}},"./admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/es6.number.to-precision.js":{"id":202,"meta":{}},"./admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/es6.number.epsilon.js":{"id":203,"meta":{}},"./admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/es6.number.is-finite.js":{"id":204,"meta":{}},"./admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/es6.number.is-integer.js":{"id":205,"meta":{}},"./admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/es6.number.is-nan.js":{"id":206,"meta":{}},"./admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/es6.number.is-safe-integer.js":{"id":207,"meta":{}},"./admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/es6.number.max-safe-integer.js":{"id":208,"meta":{}},"./admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/es6.number.min-safe-integer.js":{"id":209,"meta":{}},"./admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/es6.number.parse-float.js":{"id":210,"meta":{}},"./admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/es6.number.parse-int.js":{"id":211,"meta":{}},"./admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/es6.math.acosh.js":{"id":212,"meta":{}},"./admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/es6.math.asinh.js":{"id":213,"meta":{}},"./admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/es6.math.atanh.js":{"id":214,"meta":{}},"./admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/es6.math.cbrt.js":{"id":215,"meta":{}},"./admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/es6.math.clz32.js":{"id":216,"meta":{}},"./admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/es6.math.cosh.js":{"id":217,"meta":{}},"./admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/es6.math.expm1.js":{"id":218,"meta":{}},"./admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/es6.math.fround.js":{"id":219,"meta":{}},"./admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/es6.math.hypot.js":{"id":220,"meta":{}},"./admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/es6.math.imul.js":{"id":221,"meta":{}},"./admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/es6.math.log10.js":{"id":222,"meta":{}},"./admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/es6.math.log1p.js":{"id":223,"meta":{}},"./admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/es6.math.log2.js":{"id":224,"meta":{}},"./admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/es6.math.sign.js":{"id":225,"meta":{}},"./admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/es6.math.sinh.js":{"id":226,"meta":{}},"./admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/es6.math.tanh.js":{"id":227,"meta":{}},"./admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/es6.math.trunc.js":{"id":228,"meta":{}},"./admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/es6.string.from-code-point.js":{"id":229,"meta":{}},"./admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/es6.string.raw.js":{"id":230,"meta":{}},"./admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/es6.string.trim.js":{"id":231,"meta":{}},"./admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/es6.string.iterator.js":{"id":232,"meta":{}},"./admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/es6.string.code-point-at.js":{"id":233,"meta":{}},"./admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/es6.string.ends-with.js":{"id":234,"meta":{}},"./admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/es6.string.includes.js":{"id":235,"meta":{}},"./admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/es6.string.repeat.js":{"id":236,"meta":{}},"./admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/es6.string.starts-with.js":{"id":237,"meta":{}},"./admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/es6.string.anchor.js":{"id":238,"meta":{}},"./admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/es6.string.big.js":{"id":239,"meta":{}},"./admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/es6.string.blink.js":{"id":240,"meta":{}},"./admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/es6.string.bold.js":{"id":241,"meta":{}},"./admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/es6.string.fixed.js":{"id":242,"meta":{}},"./admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/es6.string.fontcolor.js":{"id":243,"meta":{}},"./admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/es6.string.fontsize.js":{"id":244,"meta":{}},"./admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/es6.string.italics.js":{"id":245,"meta":{}},"./admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/es6.string.link.js":{"id":246,"meta":{}},"./admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/es6.string.small.js":{"id":247,"meta":{}},"./admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/es6.string.strike.js":{"id":248,"meta":{}},"./admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/es6.string.sub.js":{"id":249,"meta":{}},"./admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/es6.string.sup.js":{"id":250,"meta":{}},"./admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/es6.date.now.js":{"id":251,"meta":{}},"./admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/es6.date.to-json.js":{"id":252,"meta":{}},"./admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/es6.date.to-iso-string.js":{"id":253,"meta":{}},"./admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/_date-to-iso-string.js":{"id":254,"meta":{}},"./admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/es6.date.to-string.js":{"id":255,"meta":{}},"./admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/es6.date.to-primitive.js":{"id":256,"meta":{}},"./admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/_date-to-primitive.js":{"id":257,"meta":{}},"./admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/es6.array.is-array.js":{"id":258,"meta":{}},"./admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/es6.array.from.js":{"id":259,"meta":{}},"./admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/es6.array.of.js":{"id":260,"meta":{}},"./admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/es6.array.join.js":{"id":261,"meta":{}},"./admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/es6.array.slice.js":{"id":262,"meta":{}},"./admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/es6.array.sort.js":{"id":263,"meta":{}},"./admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/es6.array.for-each.js":{"id":264,"meta":{}},"./admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/_array-species-constructor.js":{"id":265,"meta":{}},"./admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/es6.array.map.js":{"id":266,"meta":{}},"./admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/es6.array.filter.js":{"id":267,"meta":{}},"./admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/es6.array.some.js":{"id":268,"meta":{}},"./admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/es6.array.every.js":{"id":269,"meta":{}},"./admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/es6.array.reduce.js":{"id":270,"meta":{}},"./admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/es6.array.reduce-right.js":{"id":271,"meta":{}},"./admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/es6.array.index-of.js":{"id":272,"meta":{}},"./admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/es6.array.last-index-of.js":{"id":273,"meta":{}},"./admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/es6.array.copy-within.js":{"id":274,"meta":{}},"./admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/es6.array.fill.js":{"id":275,"meta":{}},"./admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/es6.array.find.js":{"id":276,"meta":{}},"./admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/es6.array.find-index.js":{"id":277,"meta":{}},"./admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/es6.array.species.js":{"id":278,"meta":{}},"./admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/es6.regexp.constructor.js":{"id":279,"meta":{}},"./admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/es6.regexp.to-string.js":{"id":280,"meta":{}},"./admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/es6.regexp.match.js":{"id":281,"meta":{}},"./admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/es6.regexp.replace.js":{"id":282,"meta":{}},"./admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/es6.regexp.search.js":{"id":283,"meta":{}},"./admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/es6.regexp.split.js":{"id":284,"meta":{}},"./admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/es6.promise.js":{"id":285,"meta":{}},"./admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/es6.weak-set.js":{"id":286,"meta":{}},"./admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/es6.typed.array-buffer.js":{"id":287,"meta":{}},"./admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/es6.typed.data-view.js":{"id":288,"meta":{}},"./admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/es6.typed.int8-array.js":{"id":289,"meta":{}},"./admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/es6.typed.uint8-array.js":{"id":290,"meta":{}},"./admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/es6.typed.uint8-clamped-array.js":{"id":291,"meta":{}},"./admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/es6.typed.int16-array.js":{"id":292,"meta":{}},"./admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/es6.typed.uint16-array.js":{"id":293,"meta":{}},"./admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/es6.typed.int32-array.js":{"id":294,"meta":{}},"./admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/es6.typed.uint32-array.js":{"id":295,"meta":{}},"./admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/es6.typed.float32-array.js":{"id":296,"meta":{}},"./admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/es6.typed.float64-array.js":{"id":297,"meta":{}},"./admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/es6.reflect.apply.js":{"id":298,"meta":{}},"./admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/es6.reflect.construct.js":{"id":299,"meta":{}},"./admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/es6.reflect.define-property.js":{"id":300,"meta":{}},"./admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/es6.reflect.delete-property.js":{"id":301,"meta":{}},"./admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/es6.reflect.enumerate.js":{"id":302,"meta":{}},"./admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/es6.reflect.get.js":{"id":303,"meta":{}},"./admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/es6.reflect.get-own-property-descriptor.js":{"id":304,"meta":{}},"./admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/es6.reflect.get-prototype-of.js":{"id":305,"meta":{}},"./admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/es6.reflect.has.js":{"id":306,"meta":{}},"./admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/es6.reflect.is-extensible.js":{"id":307,"meta":{}},"./admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/es6.reflect.own-keys.js":{"id":308,"meta":{}},"./admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/es6.reflect.prevent-extensions.js":{"id":309,"meta":{}},"./admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/es6.reflect.set.js":{"id":310,"meta":{}},"./admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/es6.reflect.set-prototype-of.js":{"id":311,"meta":{}},"./admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/es7.array.includes.js":{"id":312,"meta":{}},"./admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/es7.array.flat-map.js":{"id":313,"meta":{}},"./admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/es7.array.flatten.js":{"id":314,"meta":{}},"./admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/es7.string.at.js":{"id":315,"meta":{}},"./admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/es7.string.pad-start.js":{"id":316,"meta":{}},"./admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/es7.string.pad-end.js":{"id":317,"meta":{}},"./admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/es7.string.trim-left.js":{"id":318,"meta":{}},"./admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/es7.string.trim-right.js":{"id":319,"meta":{}},"./admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/es7.string.match-all.js":{"id":320,"meta":{}},"./admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/es7.symbol.async-iterator.js":{"id":321,"meta":{}},"./admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/es7.symbol.observable.js":{"id":322,"meta":{}},"./admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/es7.object.get-own-property-descriptors.js":{"id":323,"meta":{}},"./admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/es7.object.values.js":{"id":324,"meta":{}},"./admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/es7.object.entries.js":{"id":325,"meta":{}},"./admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/es7.object.define-getter.js":{"id":326,"meta":{}},"./admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/es7.object.define-setter.js":{"id":327,"meta":{}},"./admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/es7.object.lookup-getter.js":{"id":328,"meta":{}},"./admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/es7.object.lookup-setter.js":{"id":329,"meta":{}},"./admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/es7.map.to-json.js":{"id":330,"meta":{}},"./admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/es7.set.to-json.js":{"id":331,"meta":{}},"./admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/es7.map.of.js":{"id":332,"meta":{}},"./admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/es7.set.of.js":{"id":333,"meta":{}},"./admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/es7.weak-map.of.js":{"id":334,"meta":{}},"./admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/es7.weak-set.of.js":{"id":335,"meta":{}},"./admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/es7.map.from.js":{"id":336,"meta":{}},"./admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/es7.set.from.js":{"id":337,"meta":{}},"./admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/es7.weak-map.from.js":{"id":338,"meta":{}},"./admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/es7.weak-set.from.js":{"id":339,"meta":{}},"./admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/es7.global.js":{"id":340,"meta":{}},"./admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/es7.system.global.js":{"id":341,"meta":{}},"./admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/es7.error.is-error.js":{"id":342,"meta":{}},"./admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/es7.math.clamp.js":{"id":343,"meta":{}},"./admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/es7.math.deg-per-rad.js":{"id":344,"meta":{}},"./admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/es7.math.degrees.js":{"id":345,"meta":{}},"./admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/es7.math.fscale.js":{"id":346,"meta":{}},"./admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/es7.math.iaddh.js":{"id":347,"meta":{}},"./admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/es7.math.isubh.js":{"id":348,"meta":{}},"./admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/es7.math.imulh.js":{"id":349,"meta":{}},"./admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/es7.math.rad-per-deg.js":{"id":350,"meta":{}},"./admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/es7.math.radians.js":{"id":351,"meta":{}},"./admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/es7.math.scale.js":{"id":352,"meta":{}},"./admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/es7.math.umulh.js":{"id":353,"meta":{}},"./admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/es7.math.signbit.js":{"id":354,"meta":{}},"./admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/es7.promise.finally.js":{"id":355,"meta":{}},"./admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/es7.promise.try.js":{"id":356,"meta":{}},"./admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/es7.reflect.define-metadata.js":{"id":357,"meta":{}},"./admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/es7.reflect.delete-metadata.js":{"id":358,"meta":{}},"./admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/es7.reflect.get-metadata.js":{"id":359,"meta":{}},"./admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/es7.reflect.get-metadata-keys.js":{"id":360,"meta":{}},"./admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/es7.reflect.get-own-metadata.js":{"id":361,"meta":{}},"./admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/es7.reflect.get-own-metadata-keys.js":{"id":362,"meta":{}},"./admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/es7.reflect.has-metadata.js":{"id":363,"meta":{}},"./admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/es7.reflect.has-own-metadata.js":{"id":364,"meta":{}},"./admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/es7.reflect.metadata.js":{"id":365,"meta":{}},"./admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/es7.asap.js":{"id":366,"meta":{}},"./admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/es7.observable.js":{"id":367,"meta":{}},"./admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/web.timers.js":{"id":368,"meta":{}},"./admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/web.immediate.js":{"id":369,"meta":{}},"./admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/web.dom.iterable.js":{"id":370,"meta":{}},"./admin/node_modules/strapi-helper-plugin/node_modules/babel-polyfill/node_modules/regenerator-runtime/runtime.js":{"id":371,"meta":{}},"./admin/node_modules/strapi-helper-plugin/node_modules/core-js/fn/regexp/escape.js":{"id":372,"meta":{}},"./admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/core.regexp.escape.js":{"id":373,"meta":{}},"./admin/node_modules/strapi-helper-plugin/node_modules/core-js/modules/_replacer.js":{"id":374,"meta":{}}}} \ No newline at end of file diff --git a/packages/strapi-admin/admin/src/containers/AdminPage/index.js b/packages/strapi-admin/admin/src/containers/AdminPage/index.js index a4ffe76d70..5824914ba8 100644 --- a/packages/strapi-admin/admin/src/containers/AdminPage/index.js +++ b/packages/strapi-admin/admin/src/containers/AdminPage/index.js @@ -44,7 +44,7 @@ import ListPluginsPage from 'containers/ListPluginsPage/Loadable'; import Logout from 'components/Logout'; import NotFoundPage from 'containers/NotFoundPage/Loadable'; import OverlayBlocker from 'components/OverlayBlocker'; -import PluginPage from 'containers/PluginPage/Loadable'; +import PluginPage from 'containers/PluginPage'; // Utils import auth from 'utils/auth'; diff --git a/packages/strapi-admin/admin/src/containers/AdminPage/saga.js b/packages/strapi-admin/admin/src/containers/AdminPage/saga.js index 6f2437af0e..6bb488aeb9 100644 --- a/packages/strapi-admin/admin/src/containers/AdminPage/saga.js +++ b/packages/strapi-admin/admin/src/containers/AdminPage/saga.js @@ -11,15 +11,15 @@ import { GET_GA_STATUS, GET_LAYOUT } from './constants'; function* getGaStatus() { try { - const [allowGa, strapiVersion, currentEnvironment] = yield [ + const [{ allowGa }, { strapiVersion }, { currentEnvironment }] = yield [ call(request, '/admin/gaConfig', { method: 'GET' }), call(request, '/admin/strapiVersion', { method: 'GET' }), call(request, '/admin/currentEnvironment', { method: 'GET' }), ]; - yield put(getCurrEnvSucceeded(currentEnvironment.currentEnvironment)); + yield put(getCurrEnvSucceeded(currentEnvironment)); yield put(getGaStatusSucceeded(allowGa)); - yield put(getStrapiVersionSucceeded(strapiVersion.strapiVersion)); + yield put(getStrapiVersionSucceeded(strapiVersion)); } catch(err) { strapi.notification.error('notification.error'); } diff --git a/packages/strapi-admin/admin/src/containers/HomePage/index.js b/packages/strapi-admin/admin/src/containers/HomePage/index.js index 39a12cc6aa..a307417f03 100644 --- a/packages/strapi-admin/admin/src/containers/HomePage/index.js +++ b/packages/strapi-admin/admin/src/containers/HomePage/index.js @@ -137,7 +137,7 @@ export class HomePage extends React.PureComponent { const data = this.showFirstBlock() ? { className: styles.homePageTutorialButton, - href: 'https://strapi.io/documentation/getting-started/quick-start.html', + href: 'https://strapi.io/documentation/getting-started/quick-start.html#create-your-first-api', id: 'app.components.HomePage.button.quickStart', primary: true, } diff --git a/packages/strapi-admin/admin/src/containers/HomePage/styles.scss b/packages/strapi-admin/admin/src/containers/HomePage/styles.scss index ffab3626ff..6c8c72d2fd 100644 --- a/packages/strapi-admin/admin/src/containers/HomePage/styles.scss +++ b/packages/strapi-admin/admin/src/containers/HomePage/styles.scss @@ -1,9 +1,9 @@ .blockLink { position: relative; width: calc(50% - 6px); - height: 99px; + height: auto; margin-top: 41px; - padding: 22px 25px 0 96px; + padding: 22px 25px 19px 96px; background: #F7F8F8; border-radius: 3px; line-height: 18px; diff --git a/packages/strapi-admin/admin/src/containers/InstallPluginPage/actions.js b/packages/strapi-admin/admin/src/containers/InstallPluginPage/actions.js index 4de39e11f8..6f58869926 100644 --- a/packages/strapi-admin/admin/src/containers/InstallPluginPage/actions.js +++ b/packages/strapi-admin/admin/src/containers/InstallPluginPage/actions.js @@ -8,8 +8,10 @@ import { DOWNLOAD_PLUGIN, DOWNLOAD_PLUGIN_ERROR, DOWNLOAD_PLUGIN_SUCCEEDED, - GET_PLUGINS, - GET_PLUGINS_SUCCEEDED, + GET_AVAILABLE_PLUGINS, + GET_AVAILABLE_PLUGINS_SUCCEEDED, + GET_INSTALLED_PLUGINS, + GET_INSTALLED_PLUGINS_SUCCEEDED, ON_CHANGE, } from './constants'; @@ -32,19 +34,32 @@ export function downloadPluginSucceeded() { }; } -export function getPlugins() { +export function getAvailablePlugins() { return { - type: GET_PLUGINS, + type: GET_AVAILABLE_PLUGINS, }; } -export function getPluginsSucceeded(availablePlugins) { +export function getAvailablePluginsSucceeded(availablePlugins) { return { - type: GET_PLUGINS_SUCCEEDED, + type: GET_AVAILABLE_PLUGINS_SUCCEEDED, availablePlugins, }; } +export function getInstalledPlugins() { + return { + type: GET_INSTALLED_PLUGINS, + }; +} + +export function getInstalledPluginsSucceeded(installedPlugins) { + return { + type: GET_INSTALLED_PLUGINS_SUCCEEDED, + installedPlugins, + }; +} + export function onChange({ target }) { return { type: ON_CHANGE, diff --git a/packages/strapi-admin/admin/src/containers/InstallPluginPage/constants.js b/packages/strapi-admin/admin/src/containers/InstallPluginPage/constants.js index 2141383d83..06650ea0e6 100644 --- a/packages/strapi-admin/admin/src/containers/InstallPluginPage/constants.js +++ b/packages/strapi-admin/admin/src/containers/InstallPluginPage/constants.js @@ -7,6 +7,8 @@ export const DOWNLOAD_PLUGIN = 'StrapiAdmin/InstallPluginPage/DOWNLOAD_PLUGIN'; export const DOWNLOAD_PLUGIN_ERROR = 'StrapiAdmin/InstallPluginPage/DOWNLOAD_PLUGIN_ERROR'; export const DOWNLOAD_PLUGIN_SUCCEEDED = 'StrapiAdmin/InstallPluginPage/DOWNLOAD_PLUGIN_SUCCEEDED'; -export const GET_PLUGINS = 'StrapiAdmin/InstallPluginPage/GET_PLUGINS'; -export const GET_PLUGINS_SUCCEEDED = 'StrapiAdmin/InstallPluginPage/GET_PLUGINS_SUCCEEDED'; +export const GET_AVAILABLE_PLUGINS = 'StrapiAdmin/InstallPluginPage/GET_AVAILABLE_PLUGINS'; +export const GET_AVAILABLE_PLUGINS_SUCCEEDED = 'StrapiAdmin/InstallPluginPage/GET_AVAILABLE_PLUGINS_SUCCEEDED'; +export const GET_INSTALLED_PLUGINS = 'StrapiAdmin/InstallPluginPage/GET_INSTALLED_PLUGINS'; +export const GET_INSTALLED_PLUGINS_SUCCEEDED = 'StrapiAdmin/InstallPluginPage/GET_INSTALLED_PLUGINS_SUCCEEDED'; export const ON_CHANGE = 'StrapiAdmin/InstallPluginPage/ON_CHANGE'; diff --git a/packages/strapi-admin/admin/src/containers/InstallPluginPage/index.js b/packages/strapi-admin/admin/src/containers/InstallPluginPage/index.js index 49634bb882..c1c4016891 100644 --- a/packages/strapi-admin/admin/src/containers/InstallPluginPage/index.js +++ b/packages/strapi-admin/admin/src/containers/InstallPluginPage/index.js @@ -11,7 +11,7 @@ import { Helmet } from 'react-helmet'; import { FormattedMessage } from 'react-intl'; import { bindActionCreators, compose } from 'redux'; import cn from 'classnames'; -import { get, isUndefined, map } from 'lodash'; +import { map } from 'lodash'; import { disableGlobalOverlayBlocker, @@ -32,7 +32,8 @@ import injectReducer from 'utils/injectReducer'; import { downloadPlugin, - getPlugins, + getAvailablePlugins, + getInstalledPlugins, onChange, } from './actions'; @@ -55,8 +56,11 @@ export class InstallPluginPage extends React.Component { // eslint-disable-line // Don't fetch the available plugins if it has already been done if (!this.props.didFetchPlugins) { - this.props.getPlugins(); + this.props.getAvailablePlugins(); } + + // Get installed plugins + this.props.getInstalledPlugins(); } componentWillUnmount() { @@ -65,10 +69,10 @@ export class InstallPluginPage extends React.Component { // eslint-disable-line } render() { - if (!this.props.didFetchPlugins) { + if (!this.props.didFetchPlugins || !this.props.didFetchInstalledPlugins) { return ; } - + return (
@@ -112,7 +116,7 @@ export class InstallPluginPage extends React.Component { // eslint-disable-line key={plugin.id} plugin={plugin} showSupportUsButton={plugin.id === 'support-us'} - isAlreadyInstalled={!isUndefined(get(this.context.plugins.toJS(), plugin.id))} + isAlreadyInstalled={this.props.installedPlugins.includes(plugin.id)} downloadPlugin={(e) => { e.preventDefault(); e.stopPropagation(); @@ -134,19 +138,18 @@ InstallPluginPage.childContextTypes = { downloadPlugin: PropTypes.func.isRequired, }; -InstallPluginPage.contextTypes = { - plugins: PropTypes.object.isRequired, -}; - InstallPluginPage.propTypes = { availablePlugins: PropTypes.array.isRequired, blockApp: PropTypes.bool.isRequired, + didFetchInstalledPlugins: PropTypes.bool.isRequired, didFetchPlugins: PropTypes.bool.isRequired, disableGlobalOverlayBlocker: PropTypes.func.isRequired, downloadPlugin: PropTypes.func.isRequired, enableGlobalOverlayBlocker: PropTypes.func.isRequired, - getPlugins: PropTypes.func.isRequired, + getAvailablePlugins: PropTypes.func.isRequired, + getInstalledPlugins: PropTypes.func.isRequired, history: PropTypes.object.isRequired, + installedPlugins: PropTypes.array.isRequired, // onChange: PropTypes.func.isRequired, // search: PropTypes.string.isRequired, }; @@ -159,7 +162,8 @@ function mapDispatchToProps(dispatch) { disableGlobalOverlayBlocker, downloadPlugin, enableGlobalOverlayBlocker, - getPlugins, + getAvailablePlugins, + getInstalledPlugins, onChange, }, dispatch, diff --git a/packages/strapi-admin/admin/src/containers/InstallPluginPage/reducer.js b/packages/strapi-admin/admin/src/containers/InstallPluginPage/reducer.js index cc70f6f36c..2f7f007b46 100644 --- a/packages/strapi-admin/admin/src/containers/InstallPluginPage/reducer.js +++ b/packages/strapi-admin/admin/src/containers/InstallPluginPage/reducer.js @@ -9,14 +9,17 @@ import { DOWNLOAD_PLUGIN, DOWNLOAD_PLUGIN_ERROR, DOWNLOAD_PLUGIN_SUCCEEDED, - GET_PLUGINS_SUCCEEDED, + GET_AVAILABLE_PLUGINS_SUCCEEDED, + GET_INSTALLED_PLUGINS_SUCCEEDED, ON_CHANGE, } from './constants'; const initialState = fromJS({ availablePlugins: List([]), + installedPlugins: List([]), blockApp: false, didFetchPlugins: false, + didFetchInstalledPlugins: false, pluginToDownload: '', search: '', }); @@ -35,10 +38,14 @@ function installPluginPageReducer(state = initialState, action) { return state .set('blockApp', false) .set('pluginToDownload', ''); - case GET_PLUGINS_SUCCEEDED: + case GET_AVAILABLE_PLUGINS_SUCCEEDED: return state .set('didFetchPlugins', true) .set('availablePlugins', List(action.availablePlugins)); + case GET_INSTALLED_PLUGINS_SUCCEEDED: + return state + .set('didFetchInstalledPlugins', true) + .set('installedPlugins', List(action.installedPlugins)); case ON_CHANGE: return state.updateIn(action.keys, () => action.value); default: diff --git a/packages/strapi-admin/admin/src/containers/InstallPluginPage/saga.js b/packages/strapi-admin/admin/src/containers/InstallPluginPage/saga.js index a338200eb6..0a2384a6b0 100644 --- a/packages/strapi-admin/admin/src/containers/InstallPluginPage/saga.js +++ b/packages/strapi-admin/admin/src/containers/InstallPluginPage/saga.js @@ -15,9 +15,10 @@ import { selectLocale } from '../LanguageProvider/selectors'; import { downloadPluginError, downloadPluginSucceeded, - getPluginsSucceeded, + getAvailablePluginsSucceeded, + getInstalledPluginsSucceeded, } from './actions'; -import { DOWNLOAD_PLUGIN, GET_PLUGINS } from './constants'; +import { DOWNLOAD_PLUGIN, GET_AVAILABLE_PLUGINS, GET_INSTALLED_PLUGINS } from './constants'; import { makeSelectPluginToDownload } from './selectors'; @@ -49,7 +50,7 @@ export function* pluginDownload() { } } -export function* pluginsGet() { +export function* getAvailablePlugins() { try { // Get current locale. const locale = yield select(selectLocale()); @@ -64,23 +65,53 @@ export function* pluginsGet() { }, }; - // Retrieve plugins list. - const availablePlugins = yield call(request, 'https://marketplace.strapi.io/plugins', opts); + let availablePlugins; - yield put(getPluginsSucceeded(availablePlugins)); + try { + // Retrieve plugins list. + availablePlugins = yield call(request, 'https://marketplace.strapi.io/plugins', opts); + } catch (e) { + availablePlugins = []; + } + + yield put(getAvailablePluginsSucceeded(availablePlugins)); } catch(err) { strapi.notification.error('notification.error'); } } +export function* getInstalledPlugins() { + try { + const opts = { + method: 'GET', + headers: { + 'Content-Type': 'application/json', + }, + }; + let installedPlugins; + + try { + // Retrieve plugins list. + installedPlugins = yield call(request, '/admin/plugins', opts); + } catch (e) { + installedPlugins = []; + } + + yield put(getInstalledPluginsSucceeded(Object.keys(installedPlugins.plugins))); + } catch(err) { + strapi.notification.error('notification.error'); + } +} // Individual exports for testing export default function* defaultSaga() { - const loadPluginsWatcher = yield fork(takeLatest, GET_PLUGINS, pluginsGet); + const loadAvailablePluginsWatcher = yield fork(takeLatest, GET_AVAILABLE_PLUGINS, getAvailablePlugins); + const loadInstalledPluginsWatcher = yield fork(takeLatest, GET_INSTALLED_PLUGINS, getInstalledPlugins); yield fork(takeLatest, DOWNLOAD_PLUGIN, pluginDownload); yield take(LOCATION_CHANGE); - yield cancel(loadPluginsWatcher); + yield cancel(loadAvailablePluginsWatcher); + yield cancel(loadInstalledPluginsWatcher); } diff --git a/packages/strapi-admin/admin/src/containers/LeftMenu/index.js b/packages/strapi-admin/admin/src/containers/LeftMenu/index.js index ba940f7f3d..3303b61d6a 100755 --- a/packages/strapi-admin/admin/src/containers/LeftMenu/index.js +++ b/packages/strapi-admin/admin/src/containers/LeftMenu/index.js @@ -14,16 +14,14 @@ import LeftMenuFooter from 'components/LeftMenuFooter'; import styles from './styles.scss'; -export class LeftMenu extends React.Component { // eslint-disable-line react/prefer-stateless-function - render() { - return ( -
- - - -
- ); - } +function LeftMenu(props) { + return ( +
+ + + +
+ ); } LeftMenu.defaultProps = { diff --git a/packages/strapi-admin/admin/src/containers/ListPluginsPage/saga.js b/packages/strapi-admin/admin/src/containers/ListPluginsPage/saga.js index efb3f3dff0..a6257fbb1c 100644 --- a/packages/strapi-admin/admin/src/containers/ListPluginsPage/saga.js +++ b/packages/strapi-admin/admin/src/containers/ListPluginsPage/saga.js @@ -52,8 +52,14 @@ export function* pluginsGet() { }, }; - // Fetch plugins informations. - const availablePlugins = yield call(request, 'https://marketplace.strapi.io/plugins', opts); + let availablePlugins; + + try { + // Fetch plugins informations. + availablePlugins = yield call(request, 'https://marketplace.strapi.io/plugins', opts); + } catch (e) { + availablePlugins = []; + } // Add logo URL to object. Object.keys(response[0].plugins).map(name => { diff --git a/packages/strapi-admin/admin/src/styles/base/animations.scss b/packages/strapi-admin/admin/src/styles/base/animations.scss index 2d309e6d27..3b542d9384 100755 --- a/packages/strapi-admin/admin/src/styles/base/animations.scss +++ b/packages/strapi-admin/admin/src/styles/base/animations.scss @@ -4,22 +4,20 @@ .notification-enter { opacity: 0.01; - right: -45rem; + top: -60px; } .notification-enter.notification-enter-active { opacity: 1; transition: all 400ms ease-in; - right: 0; + top: 0; } .notification-exit { opacity: 1; - right: 0; } .notification-exit.notification-exit-active { opacity: 0.01; transition: all 400ms ease-in; - right: -45rem; } diff --git a/packages/strapi-admin/admin/src/translations/de.json b/packages/strapi-admin/admin/src/translations/de.json index 42893def0c..e302899aba 100644 --- a/packages/strapi-admin/admin/src/translations/de.json +++ b/packages/strapi-admin/admin/src/translations/de.json @@ -1,4 +1,12 @@ { + "app.components.Button.save": "Speichern", + "app.components.Button.cancel": "Abbrechen", + + "app.components.BlockLink.documentation": "Lese die Dokumentation", + "app.components.BlockLink.documentation.content": "Entdecke die Konzepte, Referenzanleitungen und Tutorials.", + "app.components.BlockLink.code": "Code Beispiele", + "app.components.BlockLink.code.content": "Lerne durch das Testen realer Projekte, die die Community entwickelt haben.", + "app.components.ComingSoonPage.comingSoon": "Bald verfügbar", "app.components.ComingSoonPage.featuresNotAvailable": "Dieses Feature ist derzeit noch in aktiver Entwicklung.", @@ -6,13 +14,24 @@ "app.components.DownloadInfo.text": "Dies könnte kurz dauern. Danke für deine Geduld.", "app.components.HomePage.welcome": "Willkommen an Bord!", - "app.components.HomePage.create": "Erstelle deinen ersten Content-Typ", - "app.components.HomePage.welcomeBlock.content": "Wir freuen uns, dich als Mitglied der Community zu haben. Wir sind offen für Feedback, senden uns einfach eine direkt Nachricht an\u0020", - "app.components.HomePage.welcomeBlock.content.issues": "Fehler", - "app.components.HomePage.welcomeBlock.content.raise": "\u0020oder erhöhen\u0020", + "app.components.HomePage.welcome.again": "Willkommen", + "app.components.HomePage.create": "Erstelle deinen ersten Inhaltstyp", + "app.components.HomePage.welcomeBlock.content": "Wir freuen uns, dich als Mitglied der Community zu haben. Wir sind offen für Feedback, senden uns einfach eine direkt Nachricht in\u0020", + "app.components.HomePage.welcomeBlock.content.issues": "Ticket.", + "app.components.HomePage.welcomeBlock.content.raise": "\u0020oder eröffne\u0020", "app.components.HomePage.createBlock.content.first": "Das\u0020", - "app.components.HomePage.createBlock.content.second": "\u0020Plugin wird dir helfen, die Datenstruktur deiner Modelle zu definieren. Wenn du neu hier bist, empfehlen wir dir unsere\u0020", - "app.components.HomePage.createBlock.content.tutorial": "\u0020Anleitung.", + "app.components.HomePage.createBlock.content.second": "\u0020Plugin wird dir helfen, die Datenstruktur deiner Modelle zu definieren. Wenn du neu hier bist, empfehlen wir dir unser\u0020", + "app.components.HomePage.createBlock.content.tutorial": "\u0020Tutorial.", + "app.components.HomePage.welcomeBlock.content.again": "Wir hoffen, dass du Fortschritte bei deinem Projekt machst.... Lese das Neueste über Strapi. Wir geben unser Bestes, um das Produkt auf der Grundlage deines Feedbacks zu verbessern.", + "app.components.HomePage.cta": "BESTÄTIGEN", + "app.components.HomePage.community": "Finde die Community im Web", + "app.components.HomePage.community.content": "Diskutiere mit Teammitgliedern, Mitwirkenden und Entwicklern auf verschiedenen Kanälen.", + "app.components.HomePage.newsLetter": "Abonniere den Newsletter, um sich über Strapi zu informieren.", + "app.components.HomePage.button.quickStart" : "STARTE DAS QUICK-START-TUTORIAL", + "app.components.HomePage.button.blog": "MEHR DAZU IM BLOG", + "app.components.HomePage.support": "UNTERSTÜTZE UNS", + "app.components.HomePage.support.content": "Durch den Kauf des T-Shirts können wir unsere Arbeit am Projekt fortsetzen, um Ihnen das bestmögliche Erlebnis zu bieten!", + "app.components.HomePage.support.link": "HOLE DIR JETZT DEIN T-SHIRT", "app.components.InputFile.newFile": "Neue Datei hinzufügen", "app.components.InputFileDetails.open": "In einem neuen Tab öffnen", @@ -25,7 +44,7 @@ "app.components.InstallPluginPage.helmet": "Marktplatz - Plugins", "app.components.InstallPluginPage.title": "Marktplatz - Plugins", - "app.components.InstallPluginPage.description": "Erweitere problemlos deine App", + "app.components.InstallPluginPage.description": "Erweitere problemlos deine App.", "app.components.InstallPluginPage.plugin.support-us.description": "Unterstütze uns durch den Kauf eines Strapi T-Shirts. Das erlaubt uns, weiter an dem Projekt arbeiten zu können und es so gut wie nur möglich zu gestalten!", "app.components.InstallPluginPage.InputSearch.label": " ", "app.components.InstallPluginPage.InputSearch.placeholder": "Suche nach einem Plugin... (z.B.: Authentifizierung)", @@ -69,6 +88,8 @@ "app.utils.placeholder.defaultMessage": "\u0020", "app.utils.SelectOption.defaultMessage": "\u0020", + "app.utils.defaultMessage": "", + "components.AutoReloadBlocker.header": "Dieses Plugin benötigt das Neuladen-Feature.", "components.AutoReloadBlocker.description": "Öffne die folgende Datei und aktiviere das Feature.", @@ -78,6 +99,8 @@ "components.OverlayBlocker.title": "Auf Neustart warten...", "components.OverlayBlocker.description": "Du verwendest ein Feature, das einen Neustart des Servers erfordert. Bitte warte, bis der Server wieder gestartet wurde.", + "components.PageFooter.select": "Einträge pro Seite", + "components.ProductionBlocker.header": "Dieses Plugin ist nur in der Entwicklungsumgebung verfügbar.", "components.ProductionBlocker.description": "Aus Sicherheitsgründen müssen wir dieses Plugin in anderen Umgebungen deaktivieren.", @@ -93,6 +116,7 @@ "components.Input.error.validation.min": "Dieser Wert ist zu niedrig.", "components.Input.error.validation.maxLength": "Dieser Wert ist zu lang.", "components.Input.error.validation.minLength": "Dieser Wert ist zu kurz.", + "components.Input.error.validation.json": "Dies entspricht nicht dem JSON-Format.", "components.Input.error.contentTypeName.taken": "Dieser Name existiert bereits", "components.Input.error.attribute.taken": "Dieser Feldname ist bereits vergeben", "components.Input.error.attribute.key.taken": "Dieser Wert existiert bereits", @@ -102,33 +126,41 @@ "components.ListRow.empty": "Es gibt keine Daten.", - "components.Wysiwyg.collapse": "Collapse", - "components.Wysiwyg.selectOptions.title": "Add a title", - "components.Wysiwyg.selectOptions.H1": "Title H1", - "components.Wysiwyg.selectOptions.H2": "Title H2", - "components.Wysiwyg.selectOptions.H3": "Title H3", - "components.Wysiwyg.selectOptions.H4": "Title H4", - "components.Wysiwyg.selectOptions.H5": "Title H5", - "components.Wysiwyg.selectOptions.H6": "Title H6", - "components.Wysiwyg.ToggleMode.markdown": "Switch to markdown", - "components.Wysiwyg.ToggleMode.preview": "Switch to preview", - "components.WysiwygBottomControls.charactersIndicators": "characters", - "components.WysiwygBottomControls.uploadFiles": "Attach files by dragging & dropping, {browse}, or pasting from the clipboard.", - "components.WysiwygBottomControls.uploadFiles.browse": "selecting them", - "components.WysiwygBottomControls.fullscreen": "Expand", + "components.Wysiwyg.collapse": "Verkleinern", + "components.Wysiwyg.selectOptions.title": "Füge einen Überschrift hinzu", + "components.Wysiwyg.selectOptions.H1": "Überschrift H1", + "components.Wysiwyg.selectOptions.H2": "Überschrift H2", + "components.Wysiwyg.selectOptions.H3": "Überschrift H3", + "components.Wysiwyg.selectOptions.H4": "Überschrift H4", + "components.Wysiwyg.selectOptions.H5": "Überschrift H5", + "components.Wysiwyg.selectOptions.H6": "Überschrift H6", + "components.Wysiwyg.ToggleMode.markdown": "Wechsel zu Markdown", + "components.Wysiwyg.ToggleMode.preview": "Wechsel zur Vorschau", + "components.WysiwygBottomControls.charactersIndicators": "Zeichen", + "components.WysiwygBottomControls.uploadFiles": "Ziehe eine Datei hierher, {browse} eine Datei zum hochladen aus oder füge sie aus der Zwischenablage ein.", + "components.WysiwygBottomControls.uploadFiles.browse": "wähle", + "components.WysiwygBottomControls.fullscreen": "Vergrößern", + + "HomePage.notification.newsLetter.success": "Newsletter erfolgreich abonniert", "notification.error": "Ein Fehler ist aufgetreten", + "notification.error.layout": "Das Layout konnte nicht abgerufen werden.", + "Analytics": "Analytics", "Auth & Permissions": "Authentifizierung & Berechtigungen", - "Content Manager": "Content-Manager", - "Content Type Builder": "Content-Typ-Manager", + "Content Manager": "Inhalts-Manager", + "Content Type Builder": "Inhaltstyp-Manager", + "Files Upload": "Dateien hochladen", "Settings Manager": "Einstellungs-Manager", "Email": "E-Mail", "Password": "Passwort", + "Users": "Benutzer", "Username": "Benutzername", + "Users & Permissions": "Benutzer & Berechtigungen", "Provider": "Methode", "ResetPasswordToken": "Passwort-Token zurücksetzen", "Role": "Rolle", + "Roles & Permissions" : "Rollen & Berechtigungen", "New entry": "Neuer Eintrag", "request.error.model.unknown": "Dieses Schema existiert nicht" } diff --git a/packages/strapi-admin/admin/src/translations/en.json b/packages/strapi-admin/admin/src/translations/en.json index a5e7f718f7..5e5e54c33e 100755 --- a/packages/strapi-admin/admin/src/translations/en.json +++ b/packages/strapi-admin/admin/src/translations/en.json @@ -8,6 +8,8 @@ "app.components.DownloadInfo.download": "Download in progress...", "app.components.DownloadInfo.text": "This could take a minute. Thanks for your patience.", + "app.components.EmptyAttributes.title": "There are no fields yet", + "app.components.HomePage.welcome": "Welcome on board!", "app.components.HomePage.welcome.again": "Welcome ", "app.components.HomePage.cta": "CONFIRM", diff --git a/packages/strapi-admin/admin/src/translations/es.json b/packages/strapi-admin/admin/src/translations/es.json new file mode 100644 index 0000000000..6da3497017 --- /dev/null +++ b/packages/strapi-admin/admin/src/translations/es.json @@ -0,0 +1,165 @@ +{ + "app.components.Button.save": "Guardar", + "app.components.Button.cancel": "Cancelar", + + "app.components.ComingSoonPage.comingSoon": "Próximamente", + "app.components.ComingSoonPage.featuresNotAvailable": "Esta característica está aún en desarrollo.", + + "app.components.DownloadInfo.download": "Descarga en curso...", + "app.components.DownloadInfo.text": "Esto puede tardar un minuto. Gracias por su paciencia.", + + "app.components.HomePage.welcome": "¡Bienvenido a bordo!", + "app.components.HomePage.welcome.again": "Bienvenido ", + "app.components.HomePage.cta": "CONFIRMAR", + "app.components.HomePage.community": "Encuentra la comunidad en la web", + "app.components.HomePage.newsLetter": "Suscríbase al boletín de noticias para ponerse en contacto con Strapi", + "app.components.HomePage.community.content": "Discutir con los miembros del equipo, colaboradores y desarrolladores en diferentes canales.", + "app.components.HomePage.create": "Crea tu primer Tipo de Contenido", + "app.components.HomePage.welcomeBlock.content": "Estamos felices de tenerlo como miembro de la comunidad. Estamos constantemente en busca de comentarios así que no dude en enviarnos un DM en\u0020", + "app.components.HomePage.welcomeBlock.content.again": "Esperamos que estés progresando en tu proyecto.... Siéntase libre de leer las últimas novedades sobre Strapi. Estamos dando lo mejor de nosotros mismos para mejorar el producto basándonos en sus comentarios.", + "app.components.HomePage.welcomeBlock.content.issues": "problema.", + "app.components.HomePage.welcomeBlock.content.raise": "\u0020o reportar cualquier\u0020", + "app.components.HomePage.createBlock.content.first": "El\u0020", + "app.components.HomePage.createBlock.content.second": "\u0020le ayudará a definir la estructura de datos de sus modelos. Si eres nuevo aquí, te recomendamos encarecidamente que sigas nuestro\u0020", + "app.components.HomePage.createBlock.content.tutorial": "\u0020", + "app.components.HomePage.button.quickStart": "INICIAR EL TUTORIAL DE INICIO RÁPIDO", + "app.components.HomePage.button.blog": "VER MÁS EN EL BLOG", + "app.components.HomePage.support": "APÓYANOS", + "app.components.HomePage.support.content": "¡Al comprar la camiseta, nos permitirá continuar nuestro trabajo en el proyecto para darle la mejor experiencia posible!", + "app.components.HomePage.support.link": "CONSIGUE TU CAMISETA AHORA", + + "app.components.BlockLink.documentation": "Lea la documentación", + "app.components.BlockLink.documentation.content": "Descubra los conceptos, guías de referencia y tutoriales.", + "app.components.BlockLink.code": "Ejemplos de código", + "app.components.BlockLink.code.content": "Aprenda probando proyectos reales desarrollados por la comunidad.", + + + "app.components.InputFile.newFile": "Añadir nuevo archivo", + "app.components.InputFileDetails.open": "Abrir en una nueva pestaña", + "app.components.InputFileDetails.remove": "Eliminar este archivo", + "app.components.InputFileDetails.originalName": "Nombre original:", + "app.components.InputFileDetails.size": "Tamaño:", + + "app.components.ImgPreview.hint": "Arrastre y suelte el archivo en esta área o {browse} para cargar un archivo.", + "app.components.ImgPreview.hint.browse": "buscar", + + "app.components.InstallPluginPage.helmet": "Tienda - Plugins", + "app.components.InstallPluginPage.title": "Tienda - Plugins", + "app.components.InstallPluginPage.description": "Extienda su aplicación sin esfuerzo.", + "app.components.InstallPluginPage.plugin.support-us.description": "¡Apóyanos comprando la camiseta Strapi. Eso nos permitirá seguir trabajando en el proyecto y tratar de darle la mejor experiencia posible!", + "app.components.InstallPluginPage.InputSearch.label": " ", + "app.components.InstallPluginPage.InputSearch.placeholder": "Buscar un plugin... (ej: autenticación)", + "app.components.InstallPluginPopup.downloads": "descargar", + "app.components.InstallPluginPopup.navLink.description": "Descripción", + "app.components.InstallPluginPopup.navLink.screenshots": "Capturas de pantalla", + "app.components.InstallPluginPopup.navLink.avis": "opinión", + "app.components.InstallPluginPopup.navLink.faq": "preguntas frecuentes", + "app.components.InstallPluginPopup.navLink.changelog": "changelog", + "app.components.InstallPluginPopup.noDescription": "No hay descripción disponible", + + "app.components.LeftMenuFooter.poweredBy": "Potenciado por ", + "app.components.LeftMenuLinkContainer.configuration": "Configuraciones", + "app.components.LeftMenuLinkContainer.general": "General", + "app.components.LeftMenuLinkContainer.installNewPlugin": "Tienda", + "app.components.LeftMenuLinkContainer.listPlugins": "Plugins", + "app.components.LeftMenuLinkContainer.noPluginsInstalled": "No hay plugins instalados todavía", + "app.components.LeftMenuLinkContainer.plugins": "Plugins", + + "app.components.ListPluginsPage.helmet.title": "Lista de plugins", + "app.components.ListPluginsPage.title": "Plugins", + "app.components.ListPluginsPage.description": "Lista de los plugins instalados en el proyecto.", + "app.components.listPluginsPage.deletePlugin.error": "Se ha producido un error al desinstalar el plugin", + "app.components.listPlugins.title.singular": "{number} plugin está instalado", + "app.components.listPlugins.title.plural": "{number} plugins están instalados", + "app.components.listPlugins.title.none": "No hay plugins instalados", + "app.components.listPlugins.button": "Añadir nuevo plugin", + + "app.components.NotFoundPage.description": "No encontrado", + "app.components.NotFoundPage.back": "Volver a la página de inicio", + + "app.components.Official": "Oficial", + + "app.components.PluginCard.compatible": "Compatible con su aplicación", + "app.components.PluginCard.compatibleCommunity": "Compatible con la comunidad", + "app.components.PluginCard.Button.label.download": "Descargar", + "app.components.PluginCard.Button.label.install": "Ya instalado", + "app.components.PluginCard.Button.label.support": "Apóyenos", + "app.components.PluginCard.price.free": "Gratuito", + "app.components.PluginCard.more-details": "Más detalles", + + "app.utils.placeholder.defaultMessage": "\u0020", + "app.utils.SelectOption.defaultMessage": "\u0020", + "app.utils.defaultMessage": "\u0020", + + "components.AutoReloadBlocker.header": "Es necesario recargar para este plugin.", + "components.AutoReloadBlocker.description": "Abra el siguiente archivo y habilite la función.", + + "components.ErrorBoundary.title": "Algo salió mal...", + + "components.OverlayBlocker.title": "Esperando el reinicio...", + "components.OverlayBlocker.description": "Está utilizando una función que necesita que el servidor se reinicie. Por favor, espere hasta que el servidor esté listo..", + + "components.PageFooter.select": "entradas por página", + + "components.ProductionBlocker.header": "Este plugin sólo está disponible en entornos de desarrollo.", + "components.ProductionBlocker.description": "Por razones de seguridad tenemos que desactivar este plugin en otros entornos.", + + "components.popUpWarning.button.cancel": "Cancelar", + "components.popUpWarning.button.confirm": "Confirmar", + "components.popUpWarning.title": "Por favor, confirme", + "components.popUpWarning.message": "¿Estás seguro de que quieres borrar esto?", + + "components.Input.error.validation.email": "Esto no es un email", + "components.Input.error.validation.required": "Este valor es obligatorio.", + "components.Input.error.validation.regex": "El valor no coincide con el de regex.", + "components.Input.error.validation.max": "El valor es demasiado alto.", + "components.Input.error.validation.min": "El valor es demasiado bajo.", + "components.Input.error.validation.maxLength": "El valor es demasiado largo.", + "components.Input.error.validation.minLength": "El valor es demasiado corto.", + "components.Input.error.contentTypeName.taken": "Este nombre ya existe", + "components.Input.error.attribute.taken": "Este nombre de campo ya existe", + "components.Input.error.attribute.key.taken": "Este valor ya existe", + "components.Input.error.attribute.sameKeyAndName": "No puede ser igual", + "components.Input.error.validation.minSupMax": "No puede ser superior", + "components.Input.error.custom-error": "{errorMessage} ", + "components.Input.error.validation.json": "Esto no coincide con el formato JSON", + + "components.ListRow.empty": "No hay datos que mostrar.", + + "components.Wysiwyg.collapse": "Contraer menú", + "components.Wysiwyg.selectOptions.title": "Añadir un título", + "components.Wysiwyg.selectOptions.H1": "Título H1", + "components.Wysiwyg.selectOptions.H2": "Título H2", + "components.Wysiwyg.selectOptions.H3": "Título H3", + "components.Wysiwyg.selectOptions.H4": "Título H4", + "components.Wysiwyg.selectOptions.H5": "Título H5", + "components.Wysiwyg.selectOptions.H6": "Título H6", + "components.Wysiwyg.ToggleMode.markdown": "Cambiar a markdown", + "components.Wysiwyg.ToggleMode.preview": "Cambiar a vista previa", + "components.WysiwygBottomControls.charactersIndicators": "caracteres", + "components.WysiwygBottomControls.uploadFiles": "Arrastrar y soltar archivos, pegar desde el portapapeles o {browse}.", + "components.WysiwygBottomControls.uploadFiles.browse": "seleccionarlos", + "components.WysiwygBottomControls.fullscreen": "Expandir", + + "HomePage.notification.newsLetter.success": "Suscribirse con éxito al boletín de noticias", + + "notification.error": "Se ha producido un error", + "notification.error.layout": "No se pudo recuperar el esquema", + + "Users & Permissions": "Usuarios y permisos", + "Content Manager": "Gestor de Contenidos", + "Content Type Builder": "Constructor de Tipos de Contenido", + "Files Upload": "Carga de archivos", + "Roles & Permissions": "Roles y Permisos", + "Settings Manager": "Gestor de ajustes", + "Email": "Email", + "Password": "Contraseña", + "Username": "Nombre de usuario", + "Provider": "Proveedor", + "ResetPasswordToken": "Restablecer Token de Contraseña", + "Role": "Rol", + "New entry": "Entrada nueva", + "request.error.model.unknown": "Este modelo no existe", + "Users": "Usuarios", + "Analytics": "Analytics" +} diff --git a/packages/strapi-admin/admin/src/translations/fr.json b/packages/strapi-admin/admin/src/translations/fr.json index 93cd53f358..021f620fa7 100755 --- a/packages/strapi-admin/admin/src/translations/fr.json +++ b/packages/strapi-admin/admin/src/translations/fr.json @@ -7,6 +7,7 @@ "app.components.DownloadInfo.download": "Téléchargement en cours...", "app.components.DownloadInfo.text": "Cela peut prendre une minute. Merci de patienter.", + "app.components.EmptyAttributes.title": "Il n'y a pas encore de champ", "app.components.HomePage.welcome": "Bienvenue à bord!", "app.components.HomePage.welcome.again": "Bienvenue ", @@ -26,6 +27,7 @@ "app.components.HomePage.support": "SUPPORT US", "app.components.HomePage.support.content": "En achetant notre T-shirt, vous nous aidez à poursuivre à maintenir le projet pour que nous puissions vous donner la meilleure expérience possible!", "app.components.HomePage.support.link": "OBTENEZ VOTRE T-SHIRT!", + "app.components.HomePage.newsLetter": "Inscrivez-vous à la newsletter pour être contacté à propos de Strapi", "app.components.BlockLink.documentation": "Voir la documentation", "app.components.BlockLink.documentation.content": "Découvrez les concepts, guides et tutoriaux.", @@ -144,16 +146,21 @@ "notification.error": "Une erreur est survenue", "notification.error.layout": "Impossible de récupérer le layout de l'admin", + "Analytics": "Statistiques", "Auth & Permissions": "Auth & Permissions", "Content Manager": "Content Manager", "Content Type Builder": "Content Type Builder", + "Files Upload": "Téléversement de fichiers", "Settings Manager": "Settings Manager", "Email": "Email", "Password": "Mot de passe", "Username": "Nom d'utilisateur", + "Users": "Utilisateurs", + "Users & Permissions": "Utilisateurs et autorisations", "Provider": "Provider", "ResetPasswordToken": "ResetPasswordToken", "Role": "Rôle", + "Roles & Permissions": "Rôles et autorisations", "New entry": "Nouvelle entrée", "request.error.model.unknown": "Le model n'existe pas" } diff --git a/packages/strapi-admin/admin/src/translations/it.json b/packages/strapi-admin/admin/src/translations/it.json index 0b5d31579e..b2fbabb75f 100644 --- a/packages/strapi-admin/admin/src/translations/it.json +++ b/packages/strapi-admin/admin/src/translations/it.json @@ -89,6 +89,7 @@ "components.popUpWarning.title": "Per favore conferma", "components.popUpWarning.message": "Sei sicuro di volerlo cancellare?", "components.Input.error.validation.email": "Non è un'email", + "components.Input.error.validation.json" : "Formato JSON non corrispondente", "components.Input.error.validation.required": "Valore obbligatorio.", "components.Input.error.validation.regex": "Questo valore non coincide con il regex.", "components.Input.error.validation.max": "Valore troppo alto.", @@ -119,18 +120,21 @@ "HomePage.notification.newsLetter.success": "Iscritto con successo alla newsletter", "notification.error": "Si è verificato un errore", "notification.error.layout": "Non è stato possibile recuperare il layout", + "request.error.model.unknown" : "Questo modello non esiste", "Users & Permissions": "Utenti & Permessi", "Content Manager": "Gestione Contenuti", "Content Type Builder": "Generatore di Tipo Contenuti", "Settings Manager": "Gestione Impostazioni", "Email": "Email", + "Files Upload" : "Caricamento Files", "Password": "Password", "Username": "Username", "Provider": "Provider", "ResetPasswordToken": "Reimposta Token Password", "Role": "Ruolo", + "Roles & Permissions" : "Ruoli e Permessi", "New entry": "Nuovo elemento", "request.error.model.unknow": "Questo modello non esiste", "Users": "Utenti", "Analytics": "Analytics" -} \ No newline at end of file +} diff --git a/packages/strapi-admin/admin/src/translations/nl.json b/packages/strapi-admin/admin/src/translations/nl.json new file mode 100644 index 0000000000..005d34dc4a --- /dev/null +++ b/packages/strapi-admin/admin/src/translations/nl.json @@ -0,0 +1,165 @@ +{ + "app.components.Button.save": "Opslaan", + "app.components.Button.cancel": "Annuleren", + + "app.components.ComingSoonPage.comingSoon": "Binnenkort beschikbaar", + "app.components.ComingSoonPage.featuresNotAvailable": "Deze feature is nog in ontwikkeling", + + "app.components.DownloadInfo.download": "Aan het downloaden...", + "app.components.DownloadInfo.text": "Dit kan even duren. Bedankt voor je geduld.", + + "app.components.HomePage.welcome": "Welkom aan boord!", + "app.components.HomePage.welcome.again": "Welkom ", + "app.components.HomePage.cta": "AKKOORD", + "app.components.HomePage.community": "Vind de community op het web", + "app.components.HomePage.newsLetter": "Abonneer op de nieuwsbrief om contact te houden met Strapi", + "app.components.HomePage.community.content": "Bespreek met teamleden, bijdragers en ontwikkelaars op verschillende kanalen.", + "app.components.HomePage.create": "Creëer je eerste Content Type", + "app.components.HomePage.welcomeBlock.content": "We zijn blij met jou als community lid. Wij zijn constant op zoek naar feedback dus stuur gerust een DM op\u0020", + "app.components.HomePage.welcomeBlock.content.again": "We hopen dat je voortgang boekt met je project... Je kant altijd het laatste nieuws van Strapi lezen. We doen ons best om het product te verbeteren met jouw feedback.", + "app.components.HomePage.welcomeBlock.content.issues": "problemen.", + "app.components.HomePage.welcomeBlock.content.raise": "\u0020of verhogen\u0020", + "app.components.HomePage.createBlock.content.first": "De\u0020", + "app.components.HomePage.createBlock.content.second": "\u0020plugin kan je helpen om de data structuur van je models aan te geven. Als je nieuw bent hier raden we je aan om onze\u0020 te volgen", + "app.components.HomePage.createBlock.content.tutorial": "\u0020handleiding.", + "app.components.HomePage.button.quickStart": "START DE SNELLE START HANDLEIDING", + "app.components.HomePage.button.blog": "LEES MEER OP DE BLOG", + "app.components.HomePage.support": "STEUN ONS", + "app.components.HomePage.support.content": "Door een T-shirt te kopen kunnen we doorgaan met ons werk aan het project om jou de beste ervaring te geven!", + "app.components.HomePage.support.link": "KOOP NU EEN T-SHIRT", + + "app.components.BlockLink.documentation": "Lees de documentatie", + "app.components.BlockLink.documentation.content": "Ontdek de concepten, referentie handleidingen en tutorials.", + "app.components.BlockLink.code": "Code voorbeelden", + "app.components.BlockLink.code.content": "Leer door echte projecten gemaakt door de community te testen", + + + "app.components.InputFile.newFile": "Nieuw bestand toevoegen", + "app.components.InputFileDetails.open": "Openen in nieuw tabblad", + "app.components.InputFileDetails.remove": "Verwijder dit bestand", + "app.components.InputFileDetails.originalName": "Orginele naam:", + "app.components.InputFileDetails.size": "Grootte:", + + "app.components.ImgPreview.hint": "Sleep je bestand in dit gedeelte of {browse} voor een bestand om te uploaden", + "app.components.ImgPreview.hint.browse": "blader", + + "app.components.InstallPluginPage.helmet": "Marktplaats - Extensies", + "app.components.InstallPluginPage.title": "Marktplaats - Extensies", + "app.components.InstallPluginPage.description": "Breid je app zonder moeite uit.", + "app.components.InstallPluginPage.plugin.support-us.description": "Steun ons door een Strapi T-shirt te kopen. Hierdoor kunnen wij doorwerken aan het project en jou de best mogelijke ervaring bieden!", + "app.components.InstallPluginPage.InputSearch.label": " ", + "app.components.InstallPluginPage.InputSearch.placeholder": "Zoek naar een extensie... (bijv. authenticatie)", + "app.components.InstallPluginPopup.downloads": "download", + "app.components.InstallPluginPopup.navLink.description": "Beschrijving", + "app.components.InstallPluginPopup.navLink.screenshots": "Schermopnames", + "app.components.InstallPluginPopup.navLink.avis": "avis", + "app.components.InstallPluginPopup.navLink.faq": "faq", + "app.components.InstallPluginPopup.navLink.changelog": "logboek", + "app.components.InstallPluginPopup.noDescription": "Geen beschrijving beschikbaar", + + "app.components.LeftMenuFooter.poweredBy": "Mogelijk gemaakt door ", + "app.components.LeftMenuLinkContainer.configuration": "Configuraties", + "app.components.LeftMenuLinkContainer.general": "Algemeen", + "app.components.LeftMenuLinkContainer.installNewPlugin": "Marktplaats", + "app.components.LeftMenuLinkContainer.listPlugins": "Extensies", + "app.components.LeftMenuLinkContainer.noPluginsInstalled": "Nog geen extensies geïnstalleerd", + "app.components.LeftMenuLinkContainer.plugins": "Extensies", + + "app.components.ListPluginsPage.helmet.title": "Alle extensies", + "app.components.ListPluginsPage.title": "Extensies", + "app.components.ListPluginsPage.description": "Lijst van alle plugins voor dit project", + "app.components.listPluginsPage.deletePlugin.error": "Er is een fout opgetreden tijdens het verwijderen van de plugin", + "app.components.listPlugins.title.singular": "{number} extensie is geïnstalleerd", + "app.components.listPlugins.title.plural": "{number} extensies zijn geïnstalleerd", + "app.components.listPlugins.title.none": "Geen extensies geïnstalleerd", + "app.components.listPlugins.button": "Nieuwe extensie toevoegen", + + "app.components.NotFoundPage.description": "Niets gevonden", + "app.components.NotFoundPage.back": "Terug naar home pagina", + + "app.components.Official": "Officieel", + + "app.components.PluginCard.compatible": "Geschikt voor jouw app", + "app.components.PluginCard.compatibleCommunity": "Geschikt voor de community", + "app.components.PluginCard.Button.label.download": "Download", + "app.components.PluginCard.Button.label.install": "Al geïnstalleerd", + "app.components.PluginCard.Button.label.support": "Steun ons", + "app.components.PluginCard.price.free": "Gratis", + "app.components.PluginCard.more-details": "Meer details", + + "app.utils.placeholder.defaultMessage": "\u0020", + "app.utils.SelectOption.defaultMessage": "\u0020", + "app.utils.defaultMessage": "\u0020", + + "components.AutoReloadBlocker.header": "De herlaad feature is nodig voor deze extensie", + "components.AutoReloadBlocker.description": "Open het volgende bestand en ze deze feature aan.", + + "components.ErrorBoundary.title": "Er is iets fout gegaan...", + + "components.OverlayBlocker.title": "Wachten op herstart...", + "components.OverlayBlocker.description": "Je gebruikt een feature waardoor de server opnieuw op moet starten. Een moment geduld terwijl de server opnieuw opstart.", + + "components.PageFooter.select": "inzendingen per pagina", + + "components.ProductionBlocker.header": "Deze extensie is alleen beschikbaar in ontwikkel modus", + "components.ProductionBlocker.description": "Om veiligheids redenen hebben we deze extensie uitgeschakeld in andere omgevingen.", + + "components.popUpWarning.button.cancel": "Annuleren", + "components.popUpWarning.button.confirm": "Akkoord", + "components.popUpWarning.title": "Ga a.u.b. akkoord", + "components.popUpWarning.message": "Weet je zeker dat je dit wilt verwijderen?", + + "components.Input.error.validation.email": "Dit is geen email", + "components.Input.error.validation.required": "Deze waarde is verplicht.", + "components.Input.error.validation.regex": "De ingevoerde waarde komt niet overeen met de regex.", + "components.Input.error.validation.max": "De waarde is te hoog.", + "components.Input.error.validation.min": "De waarde is te laag.", + "components.Input.error.validation.maxLength": "De waarde is te lang.", + "components.Input.error.validation.minLength": "De waarde is te kort.", + "components.Input.error.contentTypeName.taken": "Deze naam bestaat al", + "components.Input.error.attribute.taken": "Deze veld naam bestaat al", + "components.Input.error.attribute.key.taken": "Deze waarde bestaat al.", + "components.Input.error.attribute.sameKeyAndName": "Mag niet gelijk zijn.", + "components.Input.error.validation.minSupMax": "Mag niet superieur zijn.", + "components.Input.error.custom-error": "{errorMessage} ", + "components.Input.error.validation.json": "Dit komt niet overeen met het JSON formaat", + + "components.ListRow.empty": "Er is geen data beschikbaar.", + + "components.Wysiwyg.collapse": "Inklappen", + "components.Wysiwyg.selectOptions.title": "Voeg een titel toe", + "components.Wysiwyg.selectOptions.H1": "H1 titel", + "components.Wysiwyg.selectOptions.H2": "H2 titel", + "components.Wysiwyg.selectOptions.H3": "H3 titel", + "components.Wysiwyg.selectOptions.H4": "H4 titel", + "components.Wysiwyg.selectOptions.H5": "H5 titel", + "components.Wysiwyg.selectOptions.H6": "H6 titel", + "components.Wysiwyg.ToggleMode.markdown": "Overschakelen naar markdown", + "components.Wysiwyg.ToggleMode.preview": "Overschakelen naar voorbeeld", + "components.WysiwygBottomControls.charactersIndicators": "karakters", + "components.WysiwygBottomControls.uploadFiles": "Sleep bestanden, plak ze of {browse}.", + "components.WysiwygBottomControls.uploadFiles.browse": "selecteer ze", + "components.WysiwygBottomControls.fullscreen": "Uitklappen", + + "HomePage.notification.newsLetter.success": "Succesvol geabonneerd op de nieuwsbrief", + + "notification.error": "Er is een fout opgetreden", + "notification.error.layout": "Kon de opzet niet laden", + + "Users & Permissions": "Gebruikers & Permissies", + "Content Manager": "Inhoud Manager", + "Content Type Builder": "Content Type Bouwer", + "Files Upload": "Bestand Upload", + "Roles & Permissions": "Rollen & Permissies", + "Settings Manager": "Instellingen Manager", + "Email": "E-mail", + "Password": "Wachtwoord", + "Username": "Gebruikersnaam", + "Provider": "Leverancier", + "ResetPasswordToken": "Wachtwoord Reset Token", + "Role": "Rol", + "New entry": "Nieuwe inzending", + "request.error.model.unknown": "Dit model bestaat niet", + "Users": "Gebruikers", + "Analytics": "Analytics" +} diff --git a/packages/strapi-admin/admin/src/translations/pt-BR.json b/packages/strapi-admin/admin/src/translations/pt-BR.json index e441d3b2e8..641d549341 100644 --- a/packages/strapi-admin/admin/src/translations/pt-BR.json +++ b/packages/strapi-admin/admin/src/translations/pt-BR.json @@ -149,6 +149,8 @@ "Users & Permissions": "Usuários & Permissões", "Content Manager": "Gestão de conteúdo", "Content Type Builder": "Construtor de Conteúdo", + "Files Upload": "Enviar arquivos", + "Roles & Permissions": "Papéis e permissões", "Settings Manager": "Gerenciador de configurações", "Email": "E-mail", "Password": "Senha", diff --git a/packages/strapi-admin/admin/src/translations/ru.json b/packages/strapi-admin/admin/src/translations/ru.json index b479e2f0d1..6178832419 100644 --- a/packages/strapi-admin/admin/src/translations/ru.json +++ b/packages/strapi-admin/admin/src/translations/ru.json @@ -17,10 +17,13 @@ "app.components.HomePage.welcomeBlock.content": "Мы рады, что вы вступили в сообщество. Нам необходима обратная связь для развития проекта, поэтому не стесняйтесь писать нам\u0020", "app.components.HomePage.welcomeBlock.content.issues": "проблема.", "app.components.HomePage.welcomeBlock.content.raise": "\u0020или поднять\u0020", + "app.components.HomePage.welcome.again": "Добро пожаловать", + "app.components.HomePage.welcomeBlock.content.again": "Надеемся у вы делаете успехи в вашем проекте... Следите с последними новостями о Strapi. Мы стараемся изо всех сил, чтобы улучшить продукт основываясь на ваших пожеланиях.", "app.components.HomePage.createBlock.content.first": "\u0020", "app.components.HomePage.createBlock.content.second": "\u0020плагин поможет вам создать структуру ваших данных. Если вы новичок, мы настоятельно рекомендуем вам следить за нашими\u0020", "app.components.HomePage.createBlock.content.tutorial": "\u0020руководство.", "app.components.HomePage.button.quickStart": "ОЗНАКОМТЕСЬ С РУКОВОДСТВОМ ПО БЫСТРОМУ СТАРТУ", + "app.components.HomePage.button.blog": "СМОТРИТЕ БОЛЬШЕ В БЛОГЕ", "app.components.HomePage.support": "ПОДДЕРЖИТЕ НАС", "app.components.HomePage.support.content": "Купите футболку, это поможет нам продолжать работу над проектом, чтобы предоставить вам наилучшее из возможных решений!", "app.components.HomePage.support.link": "ЗАКАЗАТЬ НАШУ ФУТБОЛКУ СЕЙЧАС", @@ -113,6 +116,7 @@ "components.Input.error.validation.min": "Слишком маленькое.", "components.Input.error.validation.maxLength": "Слишком длинное.", "components.Input.error.validation.minLength": "Слишком короткое.", + "components.Input.error.validation.json": "Не соответствует JSON формату", "components.Input.error.contentTypeName.taken": "Это название уже существует", "components.Input.error.attribute.taken": "Поле с таким названием уже существует", "components.Input.error.attribute.key.taken": "Это значение уже существует", @@ -136,6 +140,8 @@ "components.WysiwygBottomControls.uploadFiles": "Перетащите файлы в эту область, добавляйте из буфер обмена или {browse}.", "components.WysiwygBottomControls.uploadFiles.browse": "выделите их", "components.WysiwygBottomControls.fullscreen": "Развернуть", + "Files Upload": "Загрузка файлов", + "HomePage.notification.newsLetter.success": "Успешная подписка на рассылку новостей", @@ -143,6 +149,7 @@ "notification.error.layout": "Не удалось получить макет", "Users & Permissions": "Пользователи & Доступы", + "Roles & Permissions": "Роли и доступы", "Content Manager": "Редактор контента", "Content Type Builder": "Конструктор Типов Контента", "Settings Manager": "Менеджер Настроек", diff --git a/packages/strapi-admin/admin/src/translations/tr.json b/packages/strapi-admin/admin/src/translations/tr.json index 6d54e48144..0bc2bd7af5 100755 --- a/packages/strapi-admin/admin/src/translations/tr.json +++ b/packages/strapi-admin/admin/src/translations/tr.json @@ -122,10 +122,11 @@ "components.Input.error.attribute.sameKeyAndName": "Eşit olamaz", "components.Input.error.validation.minSupMax": "Üstü olamaz", "components.Input.error.custom-error": "{errorMessage} ", + "components.Input.error.validation.json": "Bu JSON biçimi ile eşleşmiyor", "components.ListRow.empty": "Gösterilecek veri bulunmamaktadır.", - "components.Wysiwyg.collapse": "Collapse", + "components.Wysiwyg.collapse": "Daralt", "components.Wysiwyg.selectOptions.title": "Başlık ekle", "components.Wysiwyg.selectOptions.H1": "H1 başlık", "components.Wysiwyg.selectOptions.H2": "H2 başlık", @@ -143,11 +144,13 @@ "HomePage.notification.newsLetter.success": "Bültene başarıyla abone olundu", "notification.error": "Bir hata oluştu", - "notification.error.layout": "Couldn't retrieve the layout", + "notification.error.layout": "Düzen alınamadı", "Users & Permissions": "Kullanıcılar & İzinler", "Content Manager": "İçerik Yönetimi", "Content Type Builder": "İçerik Türü Oluşturucusu", + "Files Upload": "Dosya yükleme", + "Roles & Permissions": "Roller & İzinler", "Settings Manager": "Yönetici Ayarları", "Email": "E-posta", "Password": "Şifre", diff --git a/packages/strapi-admin/controllers/Admin.js b/packages/strapi-admin/controllers/Admin.js index e4a4f9d14b..a239825e62 100755 --- a/packages/strapi-admin/controllers/Admin.js +++ b/packages/strapi-admin/controllers/Admin.js @@ -1,7 +1,7 @@ 'use strict'; const path = require('path'); -const exec = require('child_process').execSync; +const exec = require('child_process').spawnSync; const _ = require('lodash'); /** @@ -53,8 +53,7 @@ module.exports = { strapi.reload.isWatching = false; strapi.log.info(`Installing ${plugin}...`); - - exec(`node "${strapiBin}" install ${plugin} ${port === '4000' ? '--dev' : ''}`); + exec('node', [strapiBin, 'install', plugin, (port === '4000') ? '--dev' : '']); ctx.send({ ok: true }); @@ -87,7 +86,7 @@ module.exports = { strapi.reload.isWatching = false; strapi.log.info(`Uninstalling ${plugin}...`); - exec(`node "${strapiBin}" uninstall ${plugin}`); + exec('node', [strapiBin, 'uninstall', plugin]); ctx.send({ ok: true }); diff --git a/packages/strapi-admin/package.json b/packages/strapi-admin/package.json index 0bee1a6cc3..f2a44f60a2 100755 --- a/packages/strapi-admin/package.json +++ b/packages/strapi-admin/package.json @@ -1,6 +1,6 @@ { "name": "strapi-admin", - "version": "3.0.0-alpha.12.7.1", + "version": "3.0.0-alpha.14", "description": "Strapi Admin", "repository": { "type": "git", @@ -31,8 +31,8 @@ }, "devDependencies": { "sanitize.css": "^4.1.0", - "strapi-helper-plugin": "3.0.0-alpha.12.7.1", - "strapi-utils": "3.0.0-alpha.12.7.1" + "strapi-helper-plugin": "3.0.0-alpha.14", + "strapi-utils": "3.0.0-alpha.14" }, "author": { "name": "Strapi", @@ -51,4 +51,4 @@ "npm": ">= 5.0.0" }, "license": "MIT" -} \ No newline at end of file +} diff --git a/packages/strapi-ejs/.editorconfig b/packages/strapi-email-amazon-ses/.editorconfig old mode 100755 new mode 100644 similarity index 100% rename from packages/strapi-ejs/.editorconfig rename to packages/strapi-email-amazon-ses/.editorconfig diff --git a/packages/strapi-bookshelf/.gitignore b/packages/strapi-email-amazon-ses/.gitignore old mode 100755 new mode 100644 similarity index 98% rename from packages/strapi-bookshelf/.gitignore rename to packages/strapi-email-amazon-ses/.gitignore index 218a5700e9..ab74240ce1 --- a/packages/strapi-bookshelf/.gitignore +++ b/packages/strapi-email-amazon-ses/.gitignore @@ -93,3 +93,4 @@ build node_modules .node_history package-lock.json +yarn.lock diff --git a/packages/strapi-email-amazon-ses/.npmignore b/packages/strapi-email-amazon-ses/.npmignore new file mode 100644 index 0000000000..af4e0054dd --- /dev/null +++ b/packages/strapi-email-amazon-ses/.npmignore @@ -0,0 +1,104 @@ +############################ +# OS X +############################ + +.DS_Store +.AppleDouble +.LSOverride +Icon +.Spotlight-V100 +.Trashes +._* + + +############################ +# Linux +############################ + +*~ + + +############################ +# Windows +############################ + +Thumbs.db +ehthumbs.db +Desktop.ini +$RECYCLE.BIN/ +*.cab +*.msi +*.msm +*.msp + + +############################ +# Packages +############################ + +*.7z +*.csv +*.dat +*.dmg +*.gz +*.iso +*.jar +*.rar +*.tar +*.zip +*.com +*.class +*.dll +*.exe +*.o +*.seed +*.so +*.swo +*.swp +*.swn +*.swm +*.out +*.pid + + +############################ +# Logs and databases +############################ + +.tmp +*.log +*.sql +*.sqlite + + +############################ +# Misc. +############################ + +*# +ssl +.editorconfig +.gitattributes +.idea +nbproject + + +############################ +# Node.js +############################ + +lib-cov +lcov.info +pids +logs +results +build +node_modules +.node_history + + +############################ +# Tests +############################ + +test diff --git a/packages/strapi-email-amazon-ses/LICENSE.md b/packages/strapi-email-amazon-ses/LICENSE.md new file mode 100644 index 0000000000..6865b7e33f --- /dev/null +++ b/packages/strapi-email-amazon-ses/LICENSE.md @@ -0,0 +1,7 @@ +Copyright (c) 2018 Nikolay Tsenkov (nikolay@tsenkov.net). + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/packages/strapi-email-amazon-ses/README.md b/packages/strapi-email-amazon-ses/README.md new file mode 100644 index 0000000000..b6c41e6f2e --- /dev/null +++ b/packages/strapi-email-amazon-ses/README.md @@ -0,0 +1,11 @@ +# strapi-email-amazon-ses + +## Resources + +- [MIT License](LICENSE.md) + +## Links + +- [Strapi website](http://strapi.io/) +- [Strapi community on Slack](http://slack.strapi.io) +- [Strapi news on Twitter](https://twitter.com/strapijs) diff --git a/packages/strapi-email-amazon-ses/lib/index.js b/packages/strapi-email-amazon-ses/lib/index.js new file mode 100644 index 0000000000..5b0532820a --- /dev/null +++ b/packages/strapi-email-amazon-ses/lib/index.js @@ -0,0 +1,73 @@ +'use strict'; + +/** + * Module dependencies + */ + +/* eslint-disable import/no-unresolved */ +/* eslint-disable prefer-template */ +// Public node modules. +const _ = require('lodash'); +const nodeSES = require('node-ses'); + +/* eslint-disable no-unused-vars */ +module.exports = { + provider: 'amazon-ses', + name: 'Amazon SES', + auth: { + amazon_ses_default_from: { + label: 'Default From', + type: 'text' + }, + amazon_ses_default_replyto: { + label: 'Default Reply-To', + type: 'text' + }, + amazon_ses_api_key: { + label: 'Amazon Access key ID', + type: 'text' + }, + amazon_ses_secret: { + label: 'Amazon Secret access key', + type: 'text' + } + }, + + init: (config) => { + + var client = nodeSES.createClient({ + key: config.amazon_ses_api_key, + secret: config.amazon_ses_secret + }); + + return { + send: (options, cb) => { + return new Promise((resolve, reject) => { + // Default values. + options = _.isObject(options) ? options : {}; + options.from = options.from || config.amazon_ses_default_from; + options.replyTo = options.replyTo || config.amazon_ses_default_replyto; + options.text = options.text || options.html; + options.html = options.html || options.text; + + let msg = { + from: options.from, + to: options.to, + replyTo: options.replyTo, + subject: options.subject, + altText: options.text, + message: options.html + }; + + client.sendEmail(msg, function (err) { + if (err) { + reject([{ messages: [{ id: 'Auth.form.error.email.invalid' }] }]); + } else { + resolve(); + } + }); + }); + } + }; + } +}; diff --git a/packages/strapi-email-amazon-ses/package.json b/packages/strapi-email-amazon-ses/package.json new file mode 100644 index 0000000000..0665aebc18 --- /dev/null +++ b/packages/strapi-email-amazon-ses/package.json @@ -0,0 +1,45 @@ +{ + "name": "strapi-email-amazon-ses", + "version": "3.0.0-alpha.14", + "description": "Amazon SES provider for strapi email", + "homepage": "http://strapi.io", + "keywords": [ + "email", + "strapi", + "amazon", + "ses" + ], + "directories": { + "lib": "./lib" + }, + "main": "./lib", + "dependencies": { + "node-ses": "^2.1.0" + }, + "strapi": { + "isProvider": true + }, + "author": { + "email": "nikolay@tsenkov.net", + "name": "Nikolay tsenkov" + }, + "maintainers": [ + { + "name": "Strapi team", + "email": "hi@strapi.io", + "url": "http://strapi.io" + } + ], + "repository": { + "type": "git", + "url": "git://github.com/strapi/strapi.git" + }, + "bugs": { + "url": "https://github.com/strapi/strapi/issues" + }, + "engines": { + "node": ">= 9.0.0", + "npm": ">= 5.3.0" + }, + "license": "MIT" +} \ No newline at end of file diff --git a/packages/strapi-email-mailgun/.gitignore b/packages/strapi-email-mailgun/.gitignore index 218a5700e9..ab74240ce1 100644 --- a/packages/strapi-email-mailgun/.gitignore +++ b/packages/strapi-email-mailgun/.gitignore @@ -93,3 +93,4 @@ build node_modules .node_history package-lock.json +yarn.lock diff --git a/packages/strapi-email-mailgun/package.json b/packages/strapi-email-mailgun/package.json index 6ba14565a8..9df7eabd53 100644 --- a/packages/strapi-email-mailgun/package.json +++ b/packages/strapi-email-mailgun/package.json @@ -1,6 +1,6 @@ { "name": "strapi-email-mailgun", - "version": "3.0.0-alpha.12.7.1", + "version": "3.0.0-alpha.14", "description": "Mailgun provider for strapi email plugin", "homepage": "http://strapi.io", "keywords": [ diff --git a/packages/strapi-email-sendgrid/.gitignore b/packages/strapi-email-sendgrid/.gitignore index 218a5700e9..ab74240ce1 100644 --- a/packages/strapi-email-sendgrid/.gitignore +++ b/packages/strapi-email-sendgrid/.gitignore @@ -93,3 +93,4 @@ build node_modules .node_history package-lock.json +yarn.lock diff --git a/packages/strapi-email-sendgrid/package.json b/packages/strapi-email-sendgrid/package.json index 70881c48f9..5303150007 100644 --- a/packages/strapi-email-sendgrid/package.json +++ b/packages/strapi-email-sendgrid/package.json @@ -1,6 +1,6 @@ { "name": "strapi-email-sendgrid", - "version": "3.0.0-alpha.12.7.1", + "version": "3.0.0-alpha.14", "description": "Sendgrid provider for strapi email", "homepage": "http://strapi.io", "keywords": [ diff --git a/packages/strapi-email-sendmail/.gitignore b/packages/strapi-email-sendmail/.gitignore index 218a5700e9..ab74240ce1 100644 --- a/packages/strapi-email-sendmail/.gitignore +++ b/packages/strapi-email-sendmail/.gitignore @@ -93,3 +93,4 @@ build node_modules .node_history package-lock.json +yarn.lock diff --git a/packages/strapi-email-sendmail/package.json b/packages/strapi-email-sendmail/package.json index 9a5fffc717..9e9edaff20 100644 --- a/packages/strapi-email-sendmail/package.json +++ b/packages/strapi-email-sendmail/package.json @@ -1,6 +1,6 @@ { "name": "strapi-email-sendmail", - "version": "3.0.0-alpha.12.7.1", + "version": "3.0.0-alpha.14", "description": "Sendmail provider for strapi email", "homepage": "http://strapi.io", "keywords": [ diff --git a/packages/strapi-generate-admin/.gitignore b/packages/strapi-generate-admin/.gitignore index f25de1b578..0fcc1a7b21 100755 --- a/packages/strapi-generate-admin/.gitignore +++ b/packages/strapi-generate-admin/.gitignore @@ -92,6 +92,7 @@ build node_modules .node_history package-lock.json +yarn.lock ############################ diff --git a/packages/strapi-generate-admin/files/.gitignore b/packages/strapi-generate-admin/files/.gitignore index afe256bf30..2e39eb54ca 100644 --- a/packages/strapi-generate-admin/files/.gitignore +++ b/packages/strapi-generate-admin/files/.gitignore @@ -3,6 +3,7 @@ coverage node_modules stats.json package-lock.json +yarn.lock # Cruft .DS_Store diff --git a/packages/strapi-generate-admin/package.json b/packages/strapi-generate-admin/package.json index 1b32ebedc8..252edc5b31 100755 --- a/packages/strapi-generate-admin/package.json +++ b/packages/strapi-generate-admin/package.json @@ -1,6 +1,6 @@ { "name": "strapi-generate-admin", - "version": "3.0.0-alpha.12.7.1", + "version": "3.0.0-alpha.14", "description": "Generate the default admin panel for a Strapi application.", "homepage": "http://strapi.io", "keywords": [ @@ -14,9 +14,9 @@ }, "dependencies": { "fs-extra": "^4.0.1", - "lodash": "^4.17.4", - "strapi-admin": "3.0.0-alpha.12.7.1", - "strapi-utils": "3.0.0-alpha.12.7.1" + "lodash": "^4.17.5", + "strapi-admin": "3.0.0-alpha.14", + "strapi-utils": "3.0.0-alpha.14" }, "author": { "email": "hi@strapi.io", diff --git a/packages/strapi-generate-api/.gitignore b/packages/strapi-generate-api/.gitignore index 2511d26edc..cb4cb3ca05 100755 --- a/packages/strapi-generate-api/.gitignore +++ b/packages/strapi-generate-api/.gitignore @@ -93,6 +93,7 @@ build node_modules .node_history package-lock.json +yarn.lock ############################ diff --git a/packages/strapi-generate-api/jest.config.js b/packages/strapi-generate-api/jest.config.js new file mode 100644 index 0000000000..e8e1096d92 --- /dev/null +++ b/packages/strapi-generate-api/jest.config.js @@ -0,0 +1,5 @@ +module.exports = { + name: 'generate-api', + displayName: 'Generated API', + testMatch: ['**/test/?(*.)+(spec|test).js'] +}; diff --git a/packages/strapi-generate-api/lib/before.js b/packages/strapi-generate-api/lib/before.js index 6f0900b41b..dd1d8892bb 100755 --- a/packages/strapi-generate-api/lib/before.js +++ b/packages/strapi-generate-api/lib/before.js @@ -39,7 +39,7 @@ module.exports = (scope, cb) => { idPluralized: pluralize.plural(_.trim(_.camelCase(scope.id))), parentId: _.isEmpty(parent) ? undefined : _.trim(_.deburr(parent)), parentIdPluralized: _.isEmpty(scope.parentId) ? undefined : pluralize.plural(_.trim(_.camelCase(scope.parentId))), - environment: process.NODE_ENV || 'development' + environment: process.env.NODE_ENV || 'development' }); // Determine default values based on the available scope. diff --git a/packages/strapi-generate-api/package.json b/packages/strapi-generate-api/package.json index 525227d876..42a6d06cd3 100755 --- a/packages/strapi-generate-api/package.json +++ b/packages/strapi-generate-api/package.json @@ -1,6 +1,6 @@ { "name": "strapi-generate-api", - "version": "3.0.0-alpha.12.7.1", + "version": "3.0.0-alpha.14", "description": "Generate an API for a Strapi application.", "homepage": "http://strapi.io", "keywords": [ @@ -13,7 +13,7 @@ "lib": "./lib" }, "dependencies": { - "lodash": "^4.17.4", + "lodash": "^4.17.5", "pluralize": "^6.0.0" }, "scripts": { diff --git a/packages/strapi-generate-api/templates/bookshelf/service.template b/packages/strapi-generate-api/templates/bookshelf/service.template index 0685e6362e..948a990494 100755 --- a/packages/strapi-generate-api/templates/bookshelf/service.template +++ b/packages/strapi-generate-api/templates/bookshelf/service.template @@ -10,7 +10,7 @@ const _ = require('lodash'); // Strapi utilities. -const utils = require('strapi-bookshelf/lib/utils/'); +const utils = require('strapi-hook-bookshelf/lib/utils/'); module.exports = { diff --git a/packages/strapi-generate-api/templates/mongoose/service.template b/packages/strapi-generate-api/templates/mongoose/service.template index 324fdedd6c..e94e72554e 100755 --- a/packages/strapi-generate-api/templates/mongoose/service.template +++ b/packages/strapi-generate-api/templates/mongoose/service.template @@ -83,7 +83,7 @@ module.exports = { const entry = await <%= globalID %>.create(data); // Create relational data and return the entry. - return <%= globalID %>.updateRelations({ id: entry.id, values: relations }); + return <%= globalID %>.updateRelations({ _id: entry.id, values: relations }); }, /** diff --git a/packages/strapi-generate-api/test/endpoint.test.js b/packages/strapi-generate-api/test/endpoint.test.js new file mode 100644 index 0000000000..9def326869 --- /dev/null +++ b/packages/strapi-generate-api/test/endpoint.test.js @@ -0,0 +1,843 @@ +// Helpers. +const {login} = require('../../../test/helpers/auth'); +const form = require('../../../test/helpers/generators'); +const restart = require('../../../test/helpers/restart'); +const rq = require('../../../test/helpers/request'); + +const cleanDate = (entry) => { + delete entry.updatedAt; + delete entry.createdAt; + delete entry.created_at; + delete entry.updated_at; +}; + +let data; + +describe('App setup auth', () => { + test( + 'Login admin user', + async () => { + await restart(rq); + + const body = await login(); + + rq.defaults({ + headers: { + 'Authorization': `Bearer ${body.jwt}` + } + }); + } + ); +}); + +describe('Generate test APIs', () => { + beforeEach(async () => { + await restart(rq); + }, 60000); + + test( + 'Create new article API', + async () => { + await rq({ + url: `/content-type-builder/models`, + method: 'POST', + body: form.article, + json: true + }); + } + ); + test( + 'Create new tag API', + async () => { + await rq({ + url: `/content-type-builder/models`, + method: 'POST', + body: form.tag, + json: true + }); + } + ); + test( + 'Create new category API', + async () => { + await rq({ + url: `/content-type-builder/models`, + method: 'POST', + body: form.category, + json: true + }); + } + ); + test( + 'Create new reference API', + async () => { + await rq({ + url: `/content-type-builder/models`, + method: 'POST', + body: form.reference, + json: true + }); + } + ); +}); + +describe('Test manyToMany relation (article - tag) with Content Manager', () => { + beforeAll(() => { + data = { + articles: [], + tags: [] + }; + }); + + beforeEach(async () => { + await restart(rq); + }, 60000); + + test( + 'Create tag1', + async () => { + let body = await rq({ + url: `/tag`, + method: 'POST', + body: { + name: 'tag1' + }, + json: true + }); + + data.tags.push(body); + + expect(body.id); + expect(Array.isArray(body.articles)).toBeTruthy(); + expect(body.name).toBe('tag1'); + } + ); + test( + 'Create tag2', + async () => { + let body = await rq({ + url: `/tag`, + method: 'POST', + body: { + name: 'tag2' + }, + json: true + }); + + data.tags.push(body); + + expect(body.id); + expect(Array.isArray(body.articles)).toBeTruthy(); + expect(body.name).toBe('tag2'); + } + ); + test( + 'Create tag3', + async () => { + let body = await rq({ + url: `/tag`, + method: 'POST', + body: { + name: 'tag3' + }, + json: true + }); + + data.tags.push(body); + + expect(body.id); + expect(Array.isArray(body.articles)).toBeTruthy(); + expect(body.name).toBe('tag3'); + } + ); + test( + 'Create article1 without relation', + async () => { + const entry = { + title: 'Article 1', + content: 'My super content 1' + }; + + let body = await rq({ + url: `/article`, + method: 'POST', + body: entry, + json: true + }); + + data.articles.push(body); + + expect(body.id); + expect(body.title).toBe(entry.title); + expect(body.content).toBe(entry.content); + expect(Array.isArray(body.tags)).toBeTruthy(); + expect(body.tags.length).toBe(0); + } + ); + test( + 'Create article2 with tag1', + async () => { + const entry = { + title: 'Article 2', + content: 'Content 2', + tags: [data.tags[0]] + }; + + let body = await rq({ + url: `/article`, + method: 'POST', + body: entry, + json: true + }); + + data.articles.push(body); + + expect(body.id); + expect(body.title).toBe(entry.title); + expect(body.content).toBe(entry.content); + expect(Array.isArray(body.tags)).toBeTruthy(); + expect(body.tags.length).toBe(1); + expect(body.tags[0].id).toBe(data.tags[0].id); + } + ); + test( + 'Update article1 add tag2', + async () => { + const entry = Object.assign({}, data.articles[0], { + tags: [data.tags[1]] + }); + + cleanDate(entry); + + let body = await rq({ + url: `/article/${entry.id}`, + method: 'PUT', + body: entry, + json: true + }); + + data.articles[0] = body; + + expect(body.id); + expect(body.title).toBe(entry.title); + expect(body.content).toBe(entry.content); + expect(Array.isArray(body.tags)).toBeTruthy(); + expect(body.tags.length).toBe(1); + expect(body.tags[0].id).toBe(data.tags[1].id); + } + ); + test( + 'Update article1 add tag1 and tag3', + async () => { + const entry = Object.assign({}, data.articles[0]); + entry.tags.push(data.tags[0]); + entry.tags.push(data.tags[2]); + + cleanDate(entry); + + let body = await rq({ + url: `/article/${entry.id}`, + method: 'PUT', + body: entry, + json: true + }); + + data.articles[0] = body; + + expect(body.id); + expect(body.title).toBe(entry.title); + expect(body.content).toBe(entry.content); + expect(Array.isArray(body.tags)).toBeTruthy(); + expect(body.tags.length).toBe(3); + } + ); + test( + 'Update article1 remove one tag', + async () => { + const entry = Object.assign({}, data.articles[0]); + entry.tags = entry.tags.slice(1); + + cleanDate(entry); + + let body = await rq({ + url: `/article/${entry.id}`, + method: 'PUT', + body: entry, + json: true + }); + + data.articles[0] = body; + + expect(body.id); + expect(body.title).toBe(entry.title); + expect(body.content).toBe(entry.content); + expect(Array.isArray(body.tags)).toBeTruthy(); + expect(body.tags.length).toBe(2); + } + ); + test( + 'Update article1 remove all tag', + async () => { + const entry = Object.assign({}, data.articles[0], { + tags: [] + }); + + cleanDate(entry); + + let body = await rq({ + url: `/article/${entry.id}`, + method: 'PUT', + body: entry, + json: true + }); + + data.articles[0] = body; + + expect(body.id); + expect(body.title).toBe(entry.title); + expect(body.content).toBe(entry.content); + expect(Array.isArray(body.tags)).toBeTruthy(); + expect(body.tags.length).toBe(0); + } + ); +}); + +describe('Test oneToMany - manyToOne relation (article - category) with Content Manager', () => { + beforeAll(() => { + data = { + articles: [], + categories: [] + }; + }); + + beforeEach(async () => { + await restart(rq); + }, 60000); + + test( + 'Create cat1', + async () => { + let body = await rq({ + url: `/category`, + method: 'POST', + body: { + name: 'cat1' + }, + json: true + }); + + data.categories.push(body); + + expect(body.id); + expect(Array.isArray(body.articles)).toBeTruthy(); + expect(body.name).toBe('cat1'); + } + ); + test( + 'Create cat2', + async () => { + let body = await rq({ + url: `/category`, + method: 'POST', + body: { + name: 'cat2' + }, + json: true + }); + + data.categories.push(body); + + expect(body.id); + expect(Array.isArray(body.articles)).toBeTruthy(); + expect(body.name).toBe('cat2'); + } + ); + test( + 'Create article1 with cat1', + async () => { + const entry = { + title: 'Article 1', + content: 'Content 1', + category: data.categories[0] + }; + + let body = await rq({ + url: `/article`, + method: 'POST', + body: entry, + json: true + }); + + data.articles.push(body); + + expect(body.id); + expect(body.title).toBe(entry.title); + expect(body.content).toBe(entry.content); + expect(body.category.name).toBe(entry.category.name); + expect(Array.isArray(body.tags)).toBeTruthy(); + } + ); + test( + 'Update article1 with cat2', + async () => { + const entry = Object.assign({}, data.articles[0], { + category: data.categories[1] + }); + + cleanDate(entry); + + let body = await rq({ + url: `/article/${entry.id}`, + method: 'PUT', + body: entry, + json: true + }); + + data.articles[0] = body; + + expect(body.id); + expect(body.title).toBe(entry.title); + expect(body.content).toBe(entry.content); + expect(body.category.name).toBe(entry.category.name); + expect(Array.isArray(body.tags)).toBeTruthy(); + } + ); + test( + 'Create article2', + async () => { + const entry = { + title: 'Article 2', + content: 'Content 2' + }; + + let body = await rq({ + url: `/article`, + method: 'POST', + body: entry, + json: true + }); + + data.articles.push(body); + + expect(body.id); + expect(body.title).toBe(entry.title); + expect(body.content).toBe(entry.content); + expect(Array.isArray(body.tags)).toBeTruthy(); + } + ); + test( + 'Update article2 with cat2', + async () => { + const entry = Object.assign({}, data.articles[1], { + category: data.categories[1] + }); + + cleanDate(entry); + + let body = await rq({ + url: `/article/${entry.id}`, + method: 'PUT', + body: entry, + json: true + }); + + data.articles[1] = body; + + expect(body.id); + expect(body.title).toBe(entry.title); + expect(body.content).toBe(entry.content); + expect(body.category.name).toBe(entry.category.name); + expect(Array.isArray(body.tags)).toBeTruthy(); + } + ); + test( + 'Update cat1 with article1', + async () => { + const entry = Object.assign({}, data.categories[0]); + entry.articles.push(data.articles[0]); + + cleanDate(entry); + + let body = await rq({ + url: `/category/${entry.id}`, + method: 'PUT', + body: entry, + json: true + }); + + data.categories[0] = body; + + expect(body.id); + expect(Array.isArray(body.articles)).toBeTruthy(); + expect(body.articles.length).toBe(1); + expect(body.name).toBe(entry.name); + } + ); + test( + 'Create cat3 with article1', + async () => { + const entry = { + name: 'cat3', + articles: [data.articles[0]] + }; + + let body = await rq({ + url: `/category`, + method: 'POST', + body: entry, + json: true + }); + + data.categories.push(body); + + expect(body.id); + expect(Array.isArray(body.articles)).toBeTruthy(); + expect(body.articles.length).toBe(1); + expect(body.name).toBe(entry.name); + } + ); + test( + 'Get article1 with cat3', + async () => { + let body = await rq({ + url: `/article/${data.articles[0].id}`, + method: 'GET', + json: true + }); + + expect(body.id); + expect(body.category.id).toBe(data.categories[2].id) + } + ); + test( + 'Get article2 with cat2', + async () => { + let body = await rq({ + url: `/article/${data.articles[1].id}`, + method: 'GET', + json: true + }); + + expect(body.id); + expect(body.category.id).toBe(data.categories[1].id) + } + ); + test( + 'Get cat1 without relations', + async () => { + let body = await rq({ + url: `/category/${data.categories[0].id}`, + method: 'GET', + json: true + }); + + expect(body.id); + expect(body.articles.length).toBe(0); + } + ); + test( + 'Get cat2 with article2', + async () => { + let body = await rq({ + url: `/category/${data.categories[1].id}`, + method: 'GET', + json: true + }); + + expect(body.id); + expect(body.articles.length).toBe(1); + expect(body.articles[0].id).toBe(data.articles[1].id); + } + ); + test( + 'Get cat3 with article1', + async () => { + let body = await rq({ + url: `/category/${data.categories[2].id}`, + method: 'GET', + json: true + }); + + expect(body.id); + expect(body.articles.length).toBe(1); + expect(body.articles[0].id).toBe(data.articles[0].id); + } + ); +}); + +describe('Test oneToOne relation (article - reference) with Content Manager', () => { + beforeAll(() => { + data = { + articles: [], + references: [] + }; + }); + + beforeEach(async () => { + await restart(rq); + }, 60000); + + test( + 'Create ref1', + async () => { + let body = await rq({ + url: `/reference`, + method: 'POST', + body: { + name: 'ref1' + }, + json: true + }); + + data.references.push(body); + + expect(body.id); + expect(body.name).toBe('ref1'); + } + ); + test( + 'Create article1', + async () => { + const entry = { + title: 'Article 1', + content: 'Content 1' + }; + + let body = await rq({ + url: `/article`, + method: 'POST', + body: entry, + json: true + }); + + data.articles.push(body); + + expect(body.id); + expect(body.title).toBe(entry.title); + expect(body.content).toBe(entry.content); + } + ); + test( + 'Update article1 with ref1', + async () => { + const entry = Object.assign({}, data.articles[0], { + reference: data.references[0].id + }); + + cleanDate(entry); + + let body = await rq({ + url: `/article/${entry.id}`, + method: 'PUT', + body: entry, + json: true + }); + + data.articles[0] = body; + + expect(body.id); + expect(body.title).toBe(entry.title); + expect(body.content).toBe(entry.content); + expect(body.reference.id).toBe(entry.reference); + } + ); + test( + 'Create article2 with ref1', + async () => { + const entry = { + title: 'Article 2', + content: 'Content 2', + reference: data.references[0].id + }; + + let body = await rq({ + url: `/article`, + method: 'POST', + body: entry, + json: true + }); + + data.articles.push(body); + + expect(body.id); + expect(body.title).toBe(entry.title); + expect(body.content).toBe(entry.content); + expect(body.reference.id).toBe(entry.reference); + } + ); + test( + 'Get article1 without relations', + async () => { + let body = await rq({ + url: `/article/${data.articles[0].id}`, + method: 'GET', + json: true + }); + + expect(body.id); + expect(body.reference).toBe(null); + } + ); +}); + +describe('Test oneWay relation (reference - tag) with Content Manager', () => { + beforeEach(async () => { + await restart(rq); + }, 60000); + + test( + 'Attach Tag to a Reference', + async () => { + const tagToCreate = await rq({ + url: `/tag`, + method: 'POST', + json: true, + body: { + name: 'tag111' + } + }); + + const referenceToCreate = await rq({ + url: `/reference`, + method: 'POST', + json: true, + body: { + name: 'cat111', + tag: tagToCreate + } + }); + + expect(referenceToCreate.tag.id).toBe(tagToCreate.id); + } + ); + + test( + 'Detach Tag to a Reference', + async () => { + const tagToCreate = await rq({ + url: `/tag`, + method: 'POST', + json: true, + body: { + name: 'tag111' + } + }); + + const referenceToCreate = await rq({ + url: `/reference`, + method: 'POST', + json: true, + body: { + name: 'cat111', + tag: tagToCreate + } + }); + + expect(referenceToCreate.tag.id).toBe(tagToCreate.id); + + const referenceToUpdate = await rq({ + url: `/reference/${referenceToCreate.id}`, + method: 'PUT', + json: true, + body: { + tag: null + } + }); + + expect(referenceToUpdate.tag).toBe(null); + } + ); + + test( + 'Delete Tag so the relation in the Reference side should be removed', + async () => { + const tagToCreate = await rq({ + url: `/tag`, + method: 'POST', + json: true, + body: { + name: 'tag111' + } + }); + + const referenceToCreate = await rq({ + url: `/reference`, + method: 'POST', + json: true, + body: { + name: 'cat111', + tag: tagToCreate + } + }); + + const tagToDelete = await rq({ + url: `/tag/${tagToCreate.id}`, + method: 'DELETE', + json: true + }); + + const referenceToGet = await rq({ + url: `/reference/${referenceToCreate.id}`, + method: 'GET', + json: true + }); + + try { + if (Object.keys(referenceToGet.tag).length == 0) { + referenceToGet.tag = null; + } + } catch(err) { + // Silent + } + + expect(referenceToGet.tag).toBe(null); + } + ); +}); + +describe('Delete test APIs', () => { + beforeEach(async () => { + await restart(rq); + }, 60000); + + test( + 'Delete article API', + async () => { + await rq({ + url: `/content-type-builder/models/article`, + method: 'DELETE', + json: true + }); + } + ); + test( + 'Delete tag API', + async () => { + await rq({ + url: `/content-type-builder/models/tag`, + method: 'DELETE', + json: true + }); + } + ); + test( + 'Delete category API', + async () => { + await rq({ + url: `/content-type-builder/models/category`, + method: 'DELETE', + json: true + }); + } + ); + test( + 'Delete reference API', + async () => { + await rq({ + url: `/content-type-builder/models/reference`, + method: 'DELETE', + json: true + }); + } + ); +}); diff --git a/packages/strapi-generate-controller/.gitignore b/packages/strapi-generate-controller/.gitignore index 2511d26edc..cb4cb3ca05 100755 --- a/packages/strapi-generate-controller/.gitignore +++ b/packages/strapi-generate-controller/.gitignore @@ -93,6 +93,7 @@ build node_modules .node_history package-lock.json +yarn.lock ############################ diff --git a/packages/strapi-generate-controller/package.json b/packages/strapi-generate-controller/package.json index 3ccc09b29b..a639d420d6 100755 --- a/packages/strapi-generate-controller/package.json +++ b/packages/strapi-generate-controller/package.json @@ -1,6 +1,6 @@ { "name": "strapi-generate-controller", - "version": "3.0.0-alpha.12.7.1", + "version": "3.0.0-alpha.14", "description": "Generate a controller for a Strapi API.", "homepage": "http://strapi.io", "keywords": [ @@ -14,7 +14,7 @@ "lib": "./lib" }, "dependencies": { - "lodash": "^4.17.4" + "lodash": "^4.17.5" }, "scripts": { "prepublish": "npm prune" diff --git a/packages/strapi-generate-model/.gitignore b/packages/strapi-generate-model/.gitignore index 2511d26edc..cb4cb3ca05 100755 --- a/packages/strapi-generate-model/.gitignore +++ b/packages/strapi-generate-model/.gitignore @@ -93,6 +93,7 @@ build node_modules .node_history package-lock.json +yarn.lock ############################ diff --git a/packages/strapi-generate-model/lib/before.js b/packages/strapi-generate-model/lib/before.js index 4715422d01..13e4525ba5 100755 --- a/packages/strapi-generate-model/lib/before.js +++ b/packages/strapi-generate-model/lib/before.js @@ -33,7 +33,7 @@ module.exports = (scope, cb) => { _.defaults(scope, { id: _.trim(_.deburr(scope.id)), idPluralized: pluralize.plural(_.trim(_.deburr(scope.id))), - environment: process.NODE_ENV || 'development' + environment: process.env.NODE_ENV || 'development' }); // Determine default values based on the available scope. diff --git a/packages/strapi-generate-model/package.json b/packages/strapi-generate-model/package.json index e438755b1d..92350e840a 100755 --- a/packages/strapi-generate-model/package.json +++ b/packages/strapi-generate-model/package.json @@ -1,6 +1,6 @@ { "name": "strapi-generate-model", - "version": "3.0.0-alpha.12.7.1", + "version": "3.0.0-alpha.14", "description": "Generate a model for a Strapi API.", "homepage": "http://strapi.io", "keywords": [ @@ -14,7 +14,7 @@ "lib": "./lib" }, "dependencies": { - "lodash": "^4.17.4" + "lodash": "^4.17.5" }, "scripts": { "prepublish": "npm prune" diff --git a/packages/strapi-generate-new/.gitignore b/packages/strapi-generate-new/.gitignore index f25de1b578..0fcc1a7b21 100755 --- a/packages/strapi-generate-new/.gitignore +++ b/packages/strapi-generate-new/.gitignore @@ -92,6 +92,7 @@ build node_modules .node_history package-lock.json +yarn.lock ############################ diff --git a/packages/strapi-generate-new/files/config/environments/development/server.json b/packages/strapi-generate-new/files/config/environments/development/server.json index 2c9235833f..46db2086bb 100755 --- a/packages/strapi-generate-new/files/config/environments/development/server.json +++ b/packages/strapi-generate-new/files/config/environments/development/server.json @@ -1,6 +1,9 @@ { "host": "localhost", "port": 1337, + "proxy": { + "enabled": false + }, "autoReload": { "enabled": true }, diff --git a/packages/strapi-generate-new/files/config/environments/production/database.json b/packages/strapi-generate-new/files/config/environments/production/database.json index 287a8121e0..3075fabf91 100755 --- a/packages/strapi-generate-new/files/config/environments/production/database.json +++ b/packages/strapi-generate-new/files/config/environments/production/database.json @@ -2,7 +2,7 @@ "defaultConnection": "default", "connections": { "default": { - "connector": "strapi-mongoose", + "connector": "strapi-hook-mongoose", "settings": { "client": "mongo", "uri": "${process.env.DATABASE_URI || ''}", diff --git a/packages/strapi-generate-new/files/config/environments/production/server.json b/packages/strapi-generate-new/files/config/environments/production/server.json index 0a35d003aa..bc0294e31f 100755 --- a/packages/strapi-generate-new/files/config/environments/production/server.json +++ b/packages/strapi-generate-new/files/config/environments/production/server.json @@ -1,6 +1,9 @@ { "host": "localhost", "port": "${process.env.PORT || 1337}", + "proxy": { + "enabled": false + }, "autoReload": { "enabled": false }, diff --git a/packages/strapi-generate-new/files/config/environments/staging/database.json b/packages/strapi-generate-new/files/config/environments/staging/database.json index ec7c26da4c..43edf2421d 100755 --- a/packages/strapi-generate-new/files/config/environments/staging/database.json +++ b/packages/strapi-generate-new/files/config/environments/staging/database.json @@ -2,7 +2,7 @@ "defaultConnection": "default", "connections": { "default": { - "connector": "strapi-mongoose", + "connector": "strapi-hook-mongoose", "settings": { "client": "mongo", "uri": "${process.env.DATABASE_URI || ''}", diff --git a/packages/strapi-generate-new/files/config/environments/staging/server.json b/packages/strapi-generate-new/files/config/environments/staging/server.json index 0a35d003aa..bc0294e31f 100755 --- a/packages/strapi-generate-new/files/config/environments/staging/server.json +++ b/packages/strapi-generate-new/files/config/environments/staging/server.json @@ -1,6 +1,9 @@ { "host": "localhost", "port": "${process.env.PORT || 1337}", + "proxy": { + "enabled": false + }, "autoReload": { "enabled": false }, diff --git a/packages/strapi-generate-new/lib/after.js b/packages/strapi-generate-new/lib/after.js index f704dc6d5e..17a8e38092 100755 --- a/packages/strapi-generate-new/lib/after.js +++ b/packages/strapi-generate-new/lib/after.js @@ -187,10 +187,10 @@ module.exports = (scope, cb) => { console.log(); console.log(`👌 Your new application ${green(scope.name)} is ready at ${cyan(scope.rootPath)}.`); console.log(); - console.log('⚡️ change directory:'); + console.log('⚡️ Change directory:'); console.log(`$ ${green(`cd ${scope.name}`)}`); console.log(); - console.log('⚡️ start application:'); + console.log('⚡️ Start application:'); console.log(`$ ${green('strapi start')}`); cb(); diff --git a/packages/strapi-generate-new/lib/before.js b/packages/strapi-generate-new/lib/before.js index b22f2edca6..1f6381aac8 100755 --- a/packages/strapi-generate-new/lib/before.js +++ b/packages/strapi-generate-new/lib/before.js @@ -69,14 +69,14 @@ module.exports = (scope, cb) => { name: 'MongoDB (recommended)', value: { database: 'mongo', - connector: 'strapi-mongoose' + connector: 'strapi-hook-mongoose' } }, { name: 'Postgres', value: { database: 'postgres', - connector: 'strapi-bookshelf', + connector: 'strapi-hook-bookshelf', module: 'pg' } }, @@ -84,7 +84,7 @@ module.exports = (scope, cb) => { name: 'MySQL', value: { database: 'mysql', - connector: 'strapi-bookshelf', + connector: 'strapi-hook-bookshelf', module: 'mysql' } } @@ -132,7 +132,7 @@ module.exports = (scope, cb) => { type: 'input', name: 'database', message: 'Database name:', - default: _.get(scope.database, 'database', 'strapi') + default: _.get(scope.database, 'database', scope.name) }, { when: !hasDatabaseConfig, @@ -141,11 +141,18 @@ module.exports = (scope, cb) => { message: 'Host:', default: _.get(scope.database, 'host', '127.0.0.1') }, + { + when: !hasDatabaseConfig && scope.client.database === 'mongo', + type: 'boolean', + name: 'srv', + message: '+srv connection:', + default: _.get(scope.database, 'srv', false) + }, { when: !hasDatabaseConfig, type: 'input', name: 'port', - message: 'Port:', + message: `Port${scope.client.database === 'mongo' ? ' (It will be ignored if you enable +srv)' : ''}:`, default: (answers) => { // eslint-disable-line no-unused-vars if (_.get(scope.database, 'port')) { return scope.database.port; @@ -179,7 +186,7 @@ module.exports = (scope, cb) => { when: !hasDatabaseConfig && scope.client.database === 'mongo', type: 'input', name: 'authenticationDatabase', - message: 'Authentication database:', + message: 'Authentication database (Maybe "admin" or blank):', default: _.get(scope.database, 'authenticationDatabase', undefined) }, { @@ -192,10 +199,11 @@ module.exports = (scope, cb) => { ]) .then(answers => { if (hasDatabaseConfig) { - answers = _.omit(scope.database.settings, ['client']); + answers = _.merge((_.omit(scope.database.settings, ['client'])), scope.database.options); } scope.database.settings.host = answers.host; + scope.database.settings.srv = _.toString(answers.srv) === 'true'; scope.database.settings.port = answers.port; scope.database.settings.database = answers.database; scope.database.settings.username = answers.username; @@ -223,10 +231,10 @@ module.exports = (scope, cb) => { cmd += ` ${scope.client.module}`; } - if (scope.client.connector === 'strapi-bookshelf') { - cmd += ' strapi-knex@alpha'; + if (scope.client.connector === 'strapi-hook-bookshelf') { + cmd += ` strapi-hook-knex@alpha`; - scope.additionalsDependencies = ['strapi-knex', 'knex']; + scope.additionalsDependencies = ['strapi-hook-knex', 'knex']; } exec(cmd, () => { @@ -234,8 +242,8 @@ module.exports = (scope, cb) => { const lock = require(path.join(`${scope.tmpPath}`, '/node_modules/', `${scope.client.module}/package.json`)); scope.client.version = lock.version; - if (scope.developerMode === true && scope.client.connector === 'strapi-bookshelf') { - const knexVersion = require(path.join(`${scope.tmpPath}`, '/node_modules/', 'knex/package.json')); + if (scope.developerMode === true && scope.client.connector === 'strapi-hook-bookshelf') { + const knexVersion = require(path.join(`${scope.tmpPath}`,`/node_modules/`,`knex/package.json`)); scope.additionalsDependencies[1] = `knex@${knexVersion.version || 'latest'}`; } } @@ -251,6 +259,7 @@ module.exports = (scope, cb) => { require(path.join(`${scope.tmpPath}`, '/node_modules/', `${scope.client.connector}/lib/utils/connectivity.js`))(scope, cb.success, connectionValidation); } catch(err) { shell.rm('-r', scope.tmpPath); + console.log(err); cb.success(); } }); diff --git a/packages/strapi-generate-new/package.json b/packages/strapi-generate-new/package.json index a57f5d9971..ecdd8e7b9d 100755 --- a/packages/strapi-generate-new/package.json +++ b/packages/strapi-generate-new/package.json @@ -1,6 +1,6 @@ { "name": "strapi-generate-new", - "version": "3.0.0-alpha.12.7.1", + "version": "3.0.0-alpha.14", "description": "Generate a new Strapi application.", "homepage": "http://strapi.io", "keywords": [ @@ -17,9 +17,9 @@ "fs-extra": "^4.0.0", "inquirer": "^4.0.2", "listr": "^0.14.1", - "lodash": "^4.17.4", + "lodash": "^4.17.5", "ora": "^2.1.0", - "strapi-utils": "3.0.0-alpha.12.7.1", + "strapi-utils": "3.0.0-alpha.14", "uuid": "^3.1.0" }, "scripts": { diff --git a/packages/strapi-generate-plugin/.gitignore b/packages/strapi-generate-plugin/.gitignore index 2511d26edc..cb4cb3ca05 100755 --- a/packages/strapi-generate-plugin/.gitignore +++ b/packages/strapi-generate-plugin/.gitignore @@ -93,6 +93,7 @@ build node_modules .node_history package-lock.json +yarn.lock ############################ diff --git a/packages/strapi-generate-plugin/files/admin/src/translations/ar.json b/packages/strapi-generate-plugin/files/admin/src/translations/ar.json new file mode 100644 index 0000000000..9e26dfeeb6 --- /dev/null +++ b/packages/strapi-generate-plugin/files/admin/src/translations/ar.json @@ -0,0 +1 @@ +{} \ No newline at end of file diff --git a/packages/strapi-generate-plugin/files/admin/src/translations/es.json b/packages/strapi-generate-plugin/files/admin/src/translations/es.json new file mode 100755 index 0000000000..9e26dfeeb6 --- /dev/null +++ b/packages/strapi-generate-plugin/files/admin/src/translations/es.json @@ -0,0 +1 @@ +{} \ No newline at end of file diff --git a/packages/strapi-generate-plugin/files/admin/src/translations/it.json b/packages/strapi-generate-plugin/files/admin/src/translations/it.json new file mode 100644 index 0000000000..9e26dfeeb6 --- /dev/null +++ b/packages/strapi-generate-plugin/files/admin/src/translations/it.json @@ -0,0 +1 @@ +{} \ No newline at end of file diff --git a/packages/strapi-generate-plugin/files/admin/src/translations/ko.json b/packages/strapi-generate-plugin/files/admin/src/translations/ko.json new file mode 100644 index 0000000000..9e26dfeeb6 --- /dev/null +++ b/packages/strapi-generate-plugin/files/admin/src/translations/ko.json @@ -0,0 +1 @@ +{} \ No newline at end of file diff --git a/packages/strapi-generate-plugin/files/admin/src/translations/nl.json b/packages/strapi-generate-plugin/files/admin/src/translations/nl.json new file mode 100644 index 0000000000..9e26dfeeb6 --- /dev/null +++ b/packages/strapi-generate-plugin/files/admin/src/translations/nl.json @@ -0,0 +1 @@ +{} \ No newline at end of file diff --git a/packages/strapi-generate-plugin/files/admin/src/translations/pt-BR.json b/packages/strapi-generate-plugin/files/admin/src/translations/pt-BR.json new file mode 100644 index 0000000000..9e26dfeeb6 --- /dev/null +++ b/packages/strapi-generate-plugin/files/admin/src/translations/pt-BR.json @@ -0,0 +1 @@ +{} \ No newline at end of file diff --git a/packages/strapi-generate-plugin/files/admin/src/translations/pt.json b/packages/strapi-generate-plugin/files/admin/src/translations/pt.json new file mode 100644 index 0000000000..9e26dfeeb6 --- /dev/null +++ b/packages/strapi-generate-plugin/files/admin/src/translations/pt.json @@ -0,0 +1 @@ +{} \ No newline at end of file diff --git a/packages/strapi-generate-plugin/package.json b/packages/strapi-generate-plugin/package.json index 9249e29b27..6a03ae15a1 100755 --- a/packages/strapi-generate-plugin/package.json +++ b/packages/strapi-generate-plugin/package.json @@ -1,6 +1,6 @@ { "name": "strapi-generate-plugin", - "version": "3.0.0-alpha.12.7.1", + "version": "3.0.0-alpha.14", "description": "Generate an plugin for a Strapi application.", "homepage": "http://strapi.io", "keywords": [ @@ -14,7 +14,7 @@ }, "dependencies": { "fs-extra": "^4.0.0", - "lodash": "^4.17.4", + "lodash": "^4.17.5", "pluralize": "^6.0.0" }, "scripts": { diff --git a/packages/strapi-generate-policy/.gitignore b/packages/strapi-generate-policy/.gitignore index 2511d26edc..cb4cb3ca05 100755 --- a/packages/strapi-generate-policy/.gitignore +++ b/packages/strapi-generate-policy/.gitignore @@ -93,6 +93,7 @@ build node_modules .node_history package-lock.json +yarn.lock ############################ diff --git a/packages/strapi-generate-policy/package.json b/packages/strapi-generate-policy/package.json index 34aa032a44..587105a2e0 100755 --- a/packages/strapi-generate-policy/package.json +++ b/packages/strapi-generate-policy/package.json @@ -1,6 +1,6 @@ { "name": "strapi-generate-policy", - "version": "3.0.0-alpha.12.7.1", + "version": "3.0.0-alpha.14", "description": "Generate a policy for a Strapi API.", "homepage": "http://strapi.io", "keywords": [ @@ -14,7 +14,7 @@ "lib": "./lib" }, "dependencies": { - "lodash": "^4.17.4" + "lodash": "^4.17.5" }, "scripts": { "prepublishOnly": "npm prune" diff --git a/packages/strapi-generate-service/.gitignore b/packages/strapi-generate-service/.gitignore index 2511d26edc..cb4cb3ca05 100755 --- a/packages/strapi-generate-service/.gitignore +++ b/packages/strapi-generate-service/.gitignore @@ -93,6 +93,7 @@ build node_modules .node_history package-lock.json +yarn.lock ############################ diff --git a/packages/strapi-generate-service/package.json b/packages/strapi-generate-service/package.json index 1c31534206..ae84e994d1 100755 --- a/packages/strapi-generate-service/package.json +++ b/packages/strapi-generate-service/package.json @@ -1,6 +1,6 @@ { "name": "strapi-generate-service", - "version": "3.0.0-alpha.12.7.1", + "version": "3.0.0-alpha.14", "description": "Generate a service for a Strapi API.", "homepage": "http://strapi.io", "keywords": [ @@ -14,7 +14,7 @@ "lib": "./lib" }, "dependencies": { - "lodash": "^4.17.4" + "lodash": "^4.17.5" }, "scripts": { "prepublishOnly": "npm prune" diff --git a/packages/strapi-generate/.gitignore b/packages/strapi-generate/.gitignore index 2511d26edc..cb4cb3ca05 100755 --- a/packages/strapi-generate/.gitignore +++ b/packages/strapi-generate/.gitignore @@ -93,6 +93,7 @@ build node_modules .node_history package-lock.json +yarn.lock ############################ diff --git a/packages/strapi-generate/package.json b/packages/strapi-generate/package.json index 86b993c988..d6c97ec741 100755 --- a/packages/strapi-generate/package.json +++ b/packages/strapi-generate/package.json @@ -1,6 +1,6 @@ { "name": "strapi-generate", - "version": "3.0.0-alpha.12.7.1", + "version": "3.0.0-alpha.14", "description": "Master of ceremonies for the Strapi generators.", "homepage": "http://strapi.io", "keywords": [ @@ -15,9 +15,9 @@ "dependencies": { "async": "^2.5.0", "fs-extra": "^4.0.0", - "lodash": "^4.17.4", + "lodash": "^4.17.5", "reportback": "^2.0.1", - "strapi-utils": "3.0.0-alpha.12.7.1" + "strapi-utils": "3.0.0-alpha.14" }, "author": { "name": "Strapi team", diff --git a/packages/strapi-helper-plugin/.gitignore b/packages/strapi-helper-plugin/.gitignore index 7043ceb8bd..6b9ea7540c 100755 --- a/packages/strapi-helper-plugin/.gitignore +++ b/packages/strapi-helper-plugin/.gitignore @@ -5,6 +5,7 @@ build node_modules stats.json package-lock.json +yarn.lock # Cruft .DS_Store diff --git a/packages/strapi-helper-plugin/lib/internals/webpack/webpack.base.babel.js b/packages/strapi-helper-plugin/lib/internals/webpack/webpack.base.babel.js index 708d4bb4e5..3e446c207e 100755 --- a/packages/strapi-helper-plugin/lib/internals/webpack/webpack.base.babel.js +++ b/packages/strapi-helper-plugin/lib/internals/webpack/webpack.base.babel.js @@ -51,7 +51,10 @@ if (isAdmin && !isSetup) { ); try { - const server = require(serverConfig); + const { templateConfiguration } = require(path.join(adminPath, 'node_modules', 'strapi-utils')); + + let server = require(serverConfig); + server = templateConfiguration(server); if (process.env.PWD.indexOf('/admin') !== -1) { if (_.get(server, 'admin.build.host')) { @@ -61,7 +64,7 @@ if (isAdmin && !isSetup) { } URLs.publicPath = URLs.host; - URLs.backend = _.get(server, 'admin.build.backend', `/`); + URLs.backend = _.get(server, 'admin.build.backend', '/'); if (_.get(server, 'admin.build.plugins.source') === 'backend') { URLs.mode = 'backend'; diff --git a/packages/strapi-helper-plugin/lib/internals/webpack/webpack.dev.babel.js b/packages/strapi-helper-plugin/lib/internals/webpack/webpack.dev.babel.js index 0c60f380b1..c309f9b0cc 100755 --- a/packages/strapi-helper-plugin/lib/internals/webpack/webpack.dev.babel.js +++ b/packages/strapi-helper-plugin/lib/internals/webpack/webpack.dev.babel.js @@ -203,6 +203,20 @@ module.exports = require('./webpack.base.babel')({ 'node_modules', 'styled-components', ), + 'react-dnd': path.resolve( + rootAdminpath, + 'node_modules', + 'strapi-helper-plugin', + 'node_modules', + 'react-dnd', + ), + 'react-dnd-html5-backend': path.resolve( + rootAdminpath, + 'node_modules', + 'strapi-helper-plugin', + 'node_modules', + 'react-dnd-html5-backend', + ), }, // Emit a source map for easier debugging diff --git a/packages/strapi-helper-plugin/lib/internals/webpack/webpack.dll.babel.js b/packages/strapi-helper-plugin/lib/internals/webpack/webpack.dll.babel.js index 3f2bfcd30d..b6d0a487c4 100755 --- a/packages/strapi-helper-plugin/lib/internals/webpack/webpack.dll.babel.js +++ b/packages/strapi-helper-plugin/lib/internals/webpack/webpack.dll.babel.js @@ -67,6 +67,9 @@ module.exports = { 'react-dom': path.resolve(rootAdminpath, 'node_modules', 'strapi-helper-plugin', 'node_modules', 'react-dom'), 'react-transition-group': path.resolve(rootAdminpath, 'node_modules', 'strapi-helper-plugin', 'node_modules', 'react-transition-group'), 'reactstrap': path.resolve(rootAdminpath, 'node_modules', 'strapi-helper-plugin', 'node_modules', 'reactstrap'), + 'react-dnd': path.resolve(rootAdminpath, 'node_modules', 'strapi-helper-plugin', 'node_modules', 'react-dnd'), + 'react-dnd-hmtl5-backend': path.resolve(rootAdminpath, 'node_modules', 'strapi-helper-plugin', 'node_modules', 'react-dnd-html5-backend'), + 'styled-components': path.resolve(rootAdminpath, 'node_modules', 'strapi-helper-plugin', 'node_modules', 'styled-components'), }, symlinks: false, extensions: [ diff --git a/packages/strapi-helper-plugin/lib/internals/webpack/webpack.prod.babel.js b/packages/strapi-helper-plugin/lib/internals/webpack/webpack.prod.babel.js index 7a4f600b3d..975b48f54b 100755 --- a/packages/strapi-helper-plugin/lib/internals/webpack/webpack.prod.babel.js +++ b/packages/strapi-helper-plugin/lib/internals/webpack/webpack.prod.babel.js @@ -238,11 +238,5 @@ module.exports = base({ devtool: 'cheap-module-source-map', disableExtractTextPlugin: false, - externals: { - 'styled-components': { - commonjs: 'styled-components', - commonjs2: 'styled-components', - amd: 'styled-components', - }, - }, + externals: {}, }); diff --git a/packages/strapi-helper-plugin/lib/src/components/BackHeader/styles.scss b/packages/strapi-helper-plugin/lib/src/components/BackHeader/styles.scss index c3d3e8de52..82756ef663 100644 --- a/packages/strapi-helper-plugin/lib/src/components/BackHeader/styles.scss +++ b/packages/strapi-helper-plugin/lib/src/components/BackHeader/styles.scss @@ -4,7 +4,7 @@ height: 6rem; width: 6.5rem; line-height: 6rem; - z-index: 999; + z-index: 1050; text-align: center; background-color: #FFFFFF; color: #81848A; diff --git a/packages/strapi-helper-plugin/lib/src/components/BlockerComponent/index.js b/packages/strapi-helper-plugin/lib/src/components/BlockerComponent/index.js index e0e450d54a..1826abc4c5 100644 --- a/packages/strapi-helper-plugin/lib/src/components/BlockerComponent/index.js +++ b/packages/strapi-helper-plugin/lib/src/components/BlockerComponent/index.js @@ -59,7 +59,7 @@ const renderIde = () => (  "port": 1337,
-  "autoReload": true, +  "autoReload": { enabled: true }
 "proxi": { diff --git a/packages/strapi-helper-plugin/lib/src/components/Button/styles.scss b/packages/strapi-helper-plugin/lib/src/components/Button/styles.scss index 495b33d2d6..1d52efe957 100755 --- a/packages/strapi-helper-plugin/lib/src/components/Button/styles.scss +++ b/packages/strapi-helper-plugin/lib/src/components/Button/styles.scss @@ -26,6 +26,7 @@ height: 3rem; position: relative; border-radius: 0.3rem; + white-space: nowrap; margin-right: 1.8rem; cursor: pointer; font-family: Lato; diff --git a/packages/strapi-helper-plugin/lib/src/components/EmptyAttributesBlock/index.js b/packages/strapi-helper-plugin/lib/src/components/EmptyAttributesBlock/index.js new file mode 100644 index 0000000000..3272310def --- /dev/null +++ b/packages/strapi-helper-plugin/lib/src/components/EmptyAttributesBlock/index.js @@ -0,0 +1,49 @@ +/** +* +* EmptyAttributesBlock +* +*/ + +import React from 'react'; +import PropTypes from 'prop-types'; +import { FormattedMessage } from 'react-intl'; +import Button from 'components/Button'; +import styles from './styles.scss'; + +function EmptyAttributesBlock({ description, label, onClick, title }) { + return ( +
+
+ + {(msg) =>
{msg}
} +
+ + {(msg) =>
{msg}
} +
+
+
+
+
+ ); +} + +EmptyAttributesBlock.defaultProps = { + description: 'app.utils.defaultMessage', + label: 'app.utils.defaultMessage', + onClick: () => {}, + title: 'app.components.EmptyAttributes.title', +}; + +EmptyAttributesBlock.propTypes = { + description: PropTypes.string, + label: PropTypes.string, + onClick: PropTypes.func, + title: PropTypes.string, +}; + +export default EmptyAttributesBlock; \ No newline at end of file diff --git a/packages/strapi-plugin-content-type-builder/admin/src/components/EmptyAttributesView/styles.scss b/packages/strapi-helper-plugin/lib/src/components/EmptyAttributesBlock/styles.scss similarity index 80% rename from packages/strapi-plugin-content-type-builder/admin/src/components/EmptyAttributesView/styles.scss rename to packages/strapi-helper-plugin/lib/src/components/EmptyAttributesBlock/styles.scss index f7fd669948..bfcc71f833 100644 --- a/packages/strapi-plugin-content-type-builder/admin/src/components/EmptyAttributesView/styles.scss +++ b/packages/strapi-helper-plugin/lib/src/components/EmptyAttributesBlock/styles.scss @@ -1,8 +1,8 @@ -.emptyAttributesView { /* stylelint-disable */ +.emptyAttributesBlock { /* stylelint-disable */ height: 22.1rem; margin-top: -.1rem; padding-top: 5.8rem; - background-image: url('../../assets/images/background_empty.svg'); + background-image: url('../../assets/images/background-empty-attributes.svg'); background-color: #FFFFFF; background-repeat: repeat; text-align: center; diff --git a/packages/strapi-helper-plugin/lib/src/components/ImgPreview/index.js b/packages/strapi-helper-plugin/lib/src/components/ImgPreview/index.js index 197af53f44..ea8972e693 100644 --- a/packages/strapi-helper-plugin/lib/src/components/ImgPreview/index.js +++ b/packages/strapi-helper-plugin/lib/src/components/ImgPreview/index.js @@ -56,6 +56,10 @@ class ImgPreview extends React.Component { this.setState({ isInitValue: true }); } } + + if (isEmpty(nextProps.files)) { + this.setState({ isImg: false, imgURL: null }); + } } componentDidCatch(error, info) { diff --git a/packages/strapi-helper-plugin/lib/src/components/ImgPreview/styles.scss b/packages/strapi-helper-plugin/lib/src/components/ImgPreview/styles.scss index 4073f1c0c9..ff676fed82 100644 --- a/packages/strapi-helper-plugin/lib/src/components/ImgPreview/styles.scss +++ b/packages/strapi-helper-plugin/lib/src/components/ImgPreview/styles.scss @@ -20,7 +20,7 @@ background-repeat: no-repeat !important; white-space: nowrap; - z-index: 1; + z-index: 1 !important; > img { position: absolute; top: 0; @@ -49,7 +49,7 @@ display: block; position: absolute; top: 37px; - z-index: 9999; + z-index: 999; padding: 12px 40px 0 40px; line-height: 18px; color: #fff !important; diff --git a/packages/strapi-helper-plugin/lib/src/components/ImgPreviewRemoveIcon/styles.scss b/packages/strapi-helper-plugin/lib/src/components/ImgPreviewRemoveIcon/styles.scss index 0a52aadf99..f035392e83 100644 --- a/packages/strapi-helper-plugin/lib/src/components/ImgPreviewRemoveIcon/styles.scss +++ b/packages/strapi-helper-plugin/lib/src/components/ImgPreviewRemoveIcon/styles.scss @@ -6,6 +6,6 @@ height: 20px; color: #fff; font-size: 11px; - z-index: 9999; + z-index: 999; cursor: pointer; } diff --git a/packages/strapi-helper-plugin/lib/src/components/InputAddon/index.js b/packages/strapi-helper-plugin/lib/src/components/InputAddon/index.js index 7d528a7354..bbbb37db00 100644 --- a/packages/strapi-helper-plugin/lib/src/components/InputAddon/index.js +++ b/packages/strapi-helper-plugin/lib/src/components/InputAddon/index.js @@ -43,6 +43,7 @@ class InputAddon extends React.Component { tabIndex, value, } = this.props; + const formattedPlaceholder = placeholder === '' ? 'app.utils.placeholder.defaultMessage' : placeholder; return (
@@ -60,7 +61,7 @@ class InputAddon extends React.Component { )} - + {(message) => ( + {(placeholder) => ( @@ -50,7 +51,7 @@ class InputEmail extends React.Component { !deactivateErrorHighlight && error && styles.errorAddon, )} /> - + {(message) => ( this.addFilesToProps(target.files); addFilesToProps = (files) => { + if (files.length === 0) { + return; + } + const initAcc = this.props.multiple ? cloneDeep(this.props.value) : {}; const value = Object.keys(files).reduce((acc, current) => { @@ -55,7 +60,8 @@ class InputFile extends React.Component { type: 'file', value, }; - + + this.inputFile.value = ''; this.setState({ isUploading: !this.state.isUploading }); this.props.onChange({ target }); } @@ -71,11 +77,12 @@ class InputFile extends React.Component { if (this.props.multiple) { value.splice(this.state.position, 1); } + // Update the parent's props const target = { name: this.props.name, type: 'file', - value, + value: Object.keys(value).length === 0 ? '' : value, }; this.props.onChange({ target }); @@ -94,6 +101,19 @@ class InputFile extends React.Component { this.setState({ position: newPosition }); } + isVisibleDetails = () => { + const {value} = this.props; + + if (!value || + (isArray(value) && value.length === 0) || + (isObject(value) && Object.keys(value).length === 0) + ) { + return false; + } + + return true; + } + render() { const { multiple, @@ -104,39 +124,43 @@ class InputFile extends React.Component { return (
- - +
+ {this.isVisibleDetails() && ( + + )}
); } @@ -146,9 +170,12 @@ InputFile.defaultProps = { multiple: false, setLabel: () => {}, value: [], + error: false, + }; InputFile.propTypes = { + error: PropTypes.bool, multiple: PropTypes.bool, name: PropTypes.string.isRequired, onChange: PropTypes.func.isRequired, diff --git a/packages/strapi-helper-plugin/lib/src/components/InputFile/styles.scss b/packages/strapi-helper-plugin/lib/src/components/InputFile/styles.scss index c6c81e3692..26799e7784 100644 --- a/packages/strapi-helper-plugin/lib/src/components/InputFile/styles.scss +++ b/packages/strapi-helper-plugin/lib/src/components/InputFile/styles.scss @@ -32,3 +32,7 @@ .copy { cursor: copy !important; } + +.inputFileControlForm { + padding: 0; +} \ No newline at end of file diff --git a/packages/strapi-helper-plugin/lib/src/components/InputFileWithErrors/index.js b/packages/strapi-helper-plugin/lib/src/components/InputFileWithErrors/index.js index 6ca7ac1ff8..14a04f2336 100644 --- a/packages/strapi-helper-plugin/lib/src/components/InputFileWithErrors/index.js +++ b/packages/strapi-helper-plugin/lib/src/components/InputFileWithErrors/index.js @@ -14,32 +14,59 @@ import Label from 'components/Label'; import InputDescription from 'components/InputDescription'; import InputFile from 'components/InputFile'; import InputSpacer from 'components/InputSpacer'; +import InputErrors from 'components/InputErrors'; +// Styles import styles from './styles.scss'; -class InputFileWithErrors extends React.Component { - state = { label: false, hasValue: false }; - +class InputFileWithErrors extends React.PureComponent { + state = { errors: [], label: null, hasValue: false }; + componentDidMount() { + const { errors } = this.props; + let newState = Object.assign({}, this.state); + if (this.props.multiple && !isEmpty(this.props.value)) { - this.setState({ label: 1, hasValue: true }); + newState = Object.assign({}, newState, { label: 1, hasValue: true }); } + + if (!isEmpty(errors)) { + newState = Object.assign({}, newState, { errors }); + } + + this.setState(newState); } - componentWillReceiveProps(nextProps) { - if (!this.state.hasValue && !isEmpty(nextProps.value) && nextProps.multiple && differenceBy(nextProps.value, this.props.value, 'name').length > 0) { - this.setState({ label: 1, hasValue: true }); + componentDidUpdate(prevProps) { + if (!this.state.hasValue && !isEmpty(this.props.value) && this.props.multiple && differenceBy(this.props.value, prevProps.value, 'name').length > 0) { + this.updateState({ label: 1, hasValue: true }); + } else if(isEmpty(this.props.value)) { + this.updateState({ label: null }); + } + // Check if errors have been updated during validations + if (prevProps.didCheckErrors !== this.props.didCheckErrors) { + // Remove from the state the errors that have already been set + const errors = isEmpty(this.props.errors) ? [] : this.props.errors; + this.updateState({ errors }); } } setLabel = (label) => { this.setState({ label }); } + + updateState = state => { + this.setState(state); + } + // TODO handle errors lifecycle render() { const { className, customBootstrapClass, + errorsClassName, + errorsStyle, + noErrorsDescription, inputDescription, inputDescriptionClassName, inputDescriptionStyle, @@ -76,6 +103,7 @@ class InputFileWithErrors extends React.Component { )} + {spacer}
); @@ -93,8 +126,12 @@ class InputFileWithErrors extends React.Component { } InputFileWithErrors.defaultProps = { + errors: [], + errorsClassName: '', + errorsStyle: {}, className: '', customBootstrapClass: 'col-md-6', + didCheckErrors: false, inputDescription: '', inputDescriptionClassName: '', inputDescriptionStyle: {}, @@ -102,6 +139,7 @@ InputFileWithErrors.defaultProps = { labelClassName: '', labelStyle: {}, multiple: false, + noErrorsDescription: false, style: {}, value: [], }; @@ -109,6 +147,10 @@ InputFileWithErrors.defaultProps = { InputFileWithErrors.propTypes = { className: PropTypes.string, customBootstrapClass: PropTypes.string, + didCheckErrors: PropTypes.bool, + errors: PropTypes.array, + errorsClassName: PropTypes.string, + errorsStyle: PropTypes.object, inputDescription: PropTypes.oneOfType([ PropTypes.string, PropTypes.func, @@ -131,6 +173,7 @@ InputFileWithErrors.propTypes = { labelStyle: PropTypes.object, multiple: PropTypes.bool, name: PropTypes.string.isRequired, + noErrorsDescription: PropTypes.bool, onChange: PropTypes.func.isRequired, style: PropTypes.object, value: PropTypes.oneOfType([ diff --git a/packages/strapi-helper-plugin/lib/src/components/InputNumber/index.js b/packages/strapi-helper-plugin/lib/src/components/InputNumber/index.js index 249b611264..c5fa4dce0e 100644 --- a/packages/strapi-helper-plugin/lib/src/components/InputNumber/index.js +++ b/packages/strapi-helper-plugin/lib/src/components/InputNumber/index.js @@ -8,8 +8,10 @@ import styles from './styles.scss'; /* eslint-disable jsx-a11y/no-autofocus */ function InputNumber(props) { + const formattedPlaceholder = props.placeholder === '' ? 'app.utils.placeholder.defaultMessage' : props.placeholder; + return ( - + {(message) => ( - + {(message) => ( @@ -51,7 +52,7 @@ class InputSearch extends React.Component { !deactivateErrorHighlight && error && styles.errorAddon, )} /> - + {(message) => ( diff --git a/packages/strapi-helper-plugin/lib/src/components/InputToggle/styles.scss b/packages/strapi-helper-plugin/lib/src/components/InputToggle/styles.scss index 15586f9ca7..8c257908a4 100644 --- a/packages/strapi-helper-plugin/lib/src/components/InputToggle/styles.scss +++ b/packages/strapi-helper-plugin/lib/src/components/InputToggle/styles.scss @@ -1,13 +1,25 @@ .gradientOff { background-image: linear-gradient( to bottom right, #F65A1D, #F68E0E ); color: white !important; - box-shadow: inset -1px 1px 3px rgba(0,0,0,0.1); + z-index: 0!important; + + &:active, :hover { + box-shadow: inset -1px 1px 3px rgba(0,0,0,0.1); + background-image: linear-gradient( to bottom right, #F65A1D, #F68E0E ); + color: white !important; + z-index: 0!important; + } } .gradientOn { background-image: linear-gradient( to bottom right, #005EEA, #0097F6); color: white !important; box-shadow: inset 1px 1px 3px rgba(0,0,0,0.1); + &:active, :hover { + background-image: linear-gradient( to bottom right, #005EEA, #0097F6); + color: white !important; + z-index: 0!important; + } } .inputToggleContainer { diff --git a/packages/strapi-helper-plugin/lib/src/components/InputsIndex/index.js b/packages/strapi-helper-plugin/lib/src/components/InputsIndex/index.js index ef8e995a99..8e7cfdf2c6 100644 --- a/packages/strapi-helper-plugin/lib/src/components/InputsIndex/index.js +++ b/packages/strapi-helper-plugin/lib/src/components/InputsIndex/index.js @@ -21,8 +21,6 @@ import InputPasswordWithErrors from 'components/InputPasswordWithErrors'; import InputTextAreaWithErrors from 'components/InputTextAreaWithErrors'; import InputTextWithErrors from 'components/InputTextWithErrors'; import InputToggleWithErrors from 'components/InputToggleWithErrors'; -import WysiwygWithErrors from 'components/WysiwygWithErrors'; -import InputJSONWithErrors from 'components/InputJSONWithErrors'; const DefaultInputError = ({ type }) =>
Your input type: {type} does not exist
; @@ -32,7 +30,6 @@ const inputs = { date: InputDateWithErrors, email: InputEmailWithErrors, file: InputFileWithErrors, - json: InputJSONWithErrors, number: InputNumberWithErrors, password: InputPasswordWithErrors, search: InputSearchWithErrors, @@ -41,7 +38,6 @@ const inputs = { text: InputTextWithErrors, textarea: InputTextAreaWithErrors, toggle: InputToggleWithErrors, - wysiwyg: WysiwygWithErrors, }; function InputsIndex(props) { diff --git a/packages/strapi-helper-plugin/lib/src/components/LoadingIndicatorPage/index.js b/packages/strapi-helper-plugin/lib/src/components/LoadingIndicatorPage/index.js index bd5e0960a8..ad867a92b6 100644 --- a/packages/strapi-helper-plugin/lib/src/components/LoadingIndicatorPage/index.js +++ b/packages/strapi-helper-plugin/lib/src/components/LoadingIndicatorPage/index.js @@ -5,13 +5,28 @@ */ import React from 'react'; +import PropTypes from 'prop-types'; import styles from './styles.scss'; -const LoadingIndicatorPage = () => { +const LoadingIndicatorPage = (props) => { + + if (props.error) { + console.log(props.error); + return
An error occurred
; + } + return (
); }; +LoadingIndicatorPage.defaultProps = { + error: null, +}; + +LoadingIndicatorPage.propTypes = { + error: PropTypes.object, +}; + export default LoadingIndicatorPage; diff --git a/packages/strapi-helper-plugin/lib/src/components/PopUpWarning/styles.scss b/packages/strapi-helper-plugin/lib/src/components/PopUpWarning/styles.scss index c573bda639..4616bcf0b8 100644 --- a/packages/strapi-helper-plugin/lib/src/components/PopUpWarning/styles.scss +++ b/packages/strapi-helper-plugin/lib/src/components/PopUpWarning/styles.scss @@ -48,6 +48,7 @@ } .modalPosition { + max-width: 37.5rem !important; > div { width: 37.5rem; padding: 0 !important; diff --git a/packages/strapi-helper-plugin/lib/src/utils/Manager.js b/packages/strapi-helper-plugin/lib/src/utils/Manager.js new file mode 100644 index 0000000000..8dfb050258 --- /dev/null +++ b/packages/strapi-helper-plugin/lib/src/utils/Manager.js @@ -0,0 +1,252 @@ +const { findIndex, lowerCase, pullAt, range } = require('lodash'); +const { List } = require('immutable'); + +class Manager { + constructor(state, list, keys, index, layout) { + this.state = state; + this.keys = keys.split('.'); + this.layout = layout; + this.list = list; + this.index = index; + this.arrayOfEndLineElements = this.getLinesBound(); + this.attrToRemoveInfos = this.attrToRemoveInfos(); + } + + /** + * Retrieve the bootstrap col index, name and type of a field + * @param {Number} index + * @returns {Object} + */ + getAttrInfos(index) { + const name = this.getAttrName(index); + const appearance = this.layout.getIn([name, 'appearance']); + const type = appearance !== '' && appearance !== undefined ? appearance : this.getType(name); + const bootstrapCol = this.getBootStrapCol(type); + + const infos = { + bootstrapCol, + index, + name, + type, + }; + + return infos; + } + + getColsToAdd(number) { + let ret; + + switch(number) { + case 12: + ret = []; + break; + case 9: + ret = ['__col-md-3__', '__col-md-6__']; + break; + case 8: + ret = ['__col-md-4__', '__col-md-4__']; + break; + case 4: + ret = ['__col-md-4__']; + break; + case 6: + ret = ['__col-md-6__']; + break; + default: + ret = ['__col-md-3__']; + } + + const random = Math.random().toString(36).substring(7); + const random1 = Math.random().toString(36).substring(8); + + return ret.map((v, i) => { + + if (i === 0) { + return `${v}${random}`; + } + + return `${v}${random1}`; + }); + } + + /** + * Retrieve a field default bootstrap col + * NOTE: will change if we add the customisation of an input's width + * @param {String} type + * @returns {Number} + */ + getBootStrapCol(type) { + switch(lowerCase(type)) { + case 'checkbox': + case 'boolean': + case 'date': + case 'bigint': + case 'decimal': + case 'float': + case 'integer': + case 'number': + return 4; + case 'json': + case 'wysiwyg': + return 12; + default: + return 6; + } + } + + getElementsOnALine(itemsToPull, arr = this.list) { + const array = List.isList(arr) ? arr.toJS() : arr; + + return pullAt(array, itemsToPull); + } + + /** + * Retrieve the field to remove infos + * @returns {Object} + */ + attrToRemoveInfos() { + return this.getAttrInfos(this.index); + } + + /** + * + * Retrieve the last element of each line of a bootstrap grid and push it into an array + * @returns {Array} + */ + getLinesBound() { + const array = []; + let sum = 0; // sum of each element associated bootstrap col (max sum === 12) + + this.list.forEach((item, i) => { + let { bootstrapCol, index, name, type } = this.getAttrInfos(i); + + if (!type && name.includes('__col')) { // Only for the added elements + bootstrapCol = parseInt(name.split('__')[1].split('-')[2], 10); + } + + sum += bootstrapCol; + + if (sum === 12 || bootstrapCol === 12) { // Push into the array the element so we know each right bound of a grid line + const isFullSize = bootstrapCol === 12; + array.push({ name, index, isFullSize }); + sum = 0; + } + + if (sum > 12) { // Reset the sum + sum = 0; + } + + if (i < this.list.size - 1) { + let { bootstrapCol: nextBootstrapCol, name: nextName, type: nextType } = this.getAttrInfos(i + 1); + + if (!nextType && nextName.includes('__col')) { + nextBootstrapCol = parseInt(nextName.split('__')[1].split('-')[2], 10); + } + + if (sum + nextBootstrapCol > 12) { + const isFullSize = bootstrapCol === 12; + array.push({ name, index, isFullSize }); + sum = 0; + } + } + }); + + return array; + } + + /** + * + * Retrieve the field's type depending on its name + * @param {String} itemName + * @returns {String} + */ + getType(itemName) { + return this.state + .getIn(['modifiedSchema', 'models', ...this.keys, 'availableFields', itemName, 'type']); + } + + /** + * Retrieve a field name depending on its index + * @param {Number} itemIndex + * @returns {String} + */ + getAttrName(itemIndex){ + return this.state + .getIn(['modifiedSchema', 'models', ...this.keys, 'fields', itemIndex]); + } + + /** + * Retrieve the line bootstrap col sum + * @param {Number} leftBound + * @param {Number} rightBound + * @returns {Number} + */ + + getLineSize(elements) { + return elements.reduce((acc, current) => { + const appearance = this.layout.getIn([current, 'appearance']); + const type = appearance !== '' && appearance !== undefined ? appearance : this.getType(current); + const col = current.includes('__col') ? parseInt(current.split('__')[1].split('-')[2], 10) : this.getBootStrapCol(type); + + return acc += col; + }, 0); + } + + /** + * Given an attribute index from the list retrieve the elements' bound (loeft or right) + * @param {Bool} dir sup or min + * @param {Number} pivot the center + * @returns {Object} the first sup or last sup + */ + getBound(dir, pivot = this.index) { + let result = {}; + let didFindResult = false; + + this.arrayOfEndLineElements.forEach(item => { + + const rightBondCondition = findIndex(this.arrayOfEndLineElements, ['index', pivot]) !== -1 ? + item.index < pivot + : item.index <= pivot; + const condition = dir === true ? item.index >= pivot && !didFindResult : rightBondCondition; // Left or right bound of an item in the bootstrap grid. + + + if (condition) { + didFindResult = true; + result = dir === true ? item : { name: this.list.get(item.index + 1), index: item.index + 1, isFullSize: false }; + } + }); + + return result; + } + + /** + * Make sure to complete each line with __col-md-the-missing-number to complete a line on the bootstrap grid + * @returns {List} + */ + getLayout() { + let newList = this.list; + let sum = 0; + + this.arrayOfEndLineElements.forEach((item, i) => { + const firstLineItem = i === 0 ? 0 : this.arrayOfEndLineElements[i - 1].index +1; + const lastLineItem = item.index + 1; + const lineRange = firstLineItem === lastLineItem ? [firstLineItem] : range(firstLineItem, lastLineItem); + const lineItems = this.getElementsOnALine(lineRange); + const lineSize = this.getLineSize(lineItems); + + if (lineSize < 10 && i < this.arrayOfEndLineElements.length - 1) { + const colsToAdd = this.getColsToAdd(12 - lineSize); + newList = newList.insert(lastLineItem + sum, colsToAdd[0]); + + if (colsToAdd.length > 1) { + newList = newList.insert(lastLineItem + sum, colsToAdd[1]); + } + sum += 1; + } + }); + + return newList; + } +} + +module.exports = Manager; \ No newline at end of file diff --git a/packages/strapi-helper-plugin/lib/src/utils/inputsValidations.js b/packages/strapi-helper-plugin/lib/src/utils/inputsValidations.js index c16cbc3f1f..cf24cf825c 100644 --- a/packages/strapi-helper-plugin/lib/src/utils/inputsValidations.js +++ b/packages/strapi-helper-plugin/lib/src/utils/inputsValidations.js @@ -23,7 +23,7 @@ const validateInput = (value, inputValidations = {}, type = 'text') => { } break; case 'maxLength': - if (value.length > validationValue) { + if (value && value.length > validationValue) { errors.push({ id: 'components.Input.error.validation.maxLength' }); } break; @@ -33,12 +33,12 @@ const validateInput = (value, inputValidations = {}, type = 'text') => { } break; case 'minLength': - if (value.length < validationValue) { + if (!value || value.length < validationValue) { errors.push({ id: 'components.Input.error.validation.minLength' }); } break; case 'required': - if (value.length === 0) { + if (value == null || value.length === 0) { errors.push({ id: 'components.Input.error.validation.required' }); } break; diff --git a/packages/strapi-helper-plugin/lib/src/utils/storeData.js b/packages/strapi-helper-plugin/lib/src/utils/storeData.js new file mode 100644 index 0000000000..463f91e36d --- /dev/null +++ b/packages/strapi-helper-plugin/lib/src/utils/storeData.js @@ -0,0 +1,25 @@ +const storeData = { + clear(key) { + if (localStorage) { + return localStorage.removeItem(key); + } + + return null; + }, + + get(key) { + if (localStorage && localStorage.getItem(key)) { + return JSON.parse(localStorage.getItem(key)); + } + + return null; + }, + + set(key, value) { + if (localStorage) { + return localStorage.setItem(key, JSON.stringify(value, null, 2)); + } + }, +}; + +export default storeData; \ No newline at end of file diff --git a/packages/strapi-helper-plugin/package.json b/packages/strapi-helper-plugin/package.json index cebaccf82e..53a1f5e71b 100755 --- a/packages/strapi-helper-plugin/package.json +++ b/packages/strapi-helper-plugin/package.json @@ -1,6 +1,6 @@ { "name": "strapi-helper-plugin", - "version": "3.0.0-alpha.12.7.1", + "version": "3.0.0-alpha.14", "description": "Helper for Strapi plugins development", "engines": { "node": ">= 9.0.0", @@ -48,11 +48,9 @@ "bootstrap": "^4.0.0-alpha.6", "chalk": "^2.1.0", "classnames": "^2.2.5", - "codemirror": "^5.38.0", "copy-webpack-plugin": "^4.3.1", "cross-env": "^5.0.5", "css-loader": "^0.28.5", - "draft-js": "^0.10.5", "eslint": "4.4.1", "eslint-config-airbnb": "15.1.0", "eslint-config-airbnb-base": "11.3.1", @@ -75,7 +73,7 @@ "intl": "^1.2.5", "invariant": "2.2.1", "json-loader": "^0.5.7", - "lodash": "^4.17.4", + "lodash": "^4.17.5", "lodash-webpack-plugin": "^0.11.4", "mocha": "3.1.2", "moment": "^2.16.0", @@ -92,6 +90,8 @@ "prop-types": "^15.5.10", "react": "^16.0.0", "react-datetime": "^2.8.6", + "react-dnd": "^5.0.0", + "react-dnd-html5-backend": "^5.0.1", "react-dom": "^16.0.0", "react-helmet": "^5.1.3", "react-intl": "^2.3.0", @@ -108,9 +108,8 @@ "rimraf": "^2.6.1", "sass-loader": "^6.0.6", "shelljs": "^0.7.8", - "showdown": "^1.8.6", "style-loader": "^0.18.2", - "styled-components": "^3.2.6", + "styled-components": "3.2.6", "url-loader": "^0.5.9", "webpack": "^3.5.5", "webpack-bundle-analyzer": "^2.9.0", diff --git a/packages/strapi-knex/.gitignore b/packages/strapi-hook-bookshelf/.gitignore similarity index 98% rename from packages/strapi-knex/.gitignore rename to packages/strapi-hook-bookshelf/.gitignore index 218a5700e9..ab74240ce1 100755 --- a/packages/strapi-knex/.gitignore +++ b/packages/strapi-hook-bookshelf/.gitignore @@ -93,3 +93,4 @@ build node_modules .node_history package-lock.json +yarn.lock diff --git a/packages/strapi-bookshelf/.npmignore b/packages/strapi-hook-bookshelf/.npmignore similarity index 100% rename from packages/strapi-bookshelf/.npmignore rename to packages/strapi-hook-bookshelf/.npmignore diff --git a/packages/strapi-bookshelf/LICENSE.md b/packages/strapi-hook-bookshelf/LICENSE.md similarity index 100% rename from packages/strapi-bookshelf/LICENSE.md rename to packages/strapi-hook-bookshelf/LICENSE.md diff --git a/packages/strapi-bookshelf/README.md b/packages/strapi-hook-bookshelf/README.md similarity index 62% rename from packages/strapi-bookshelf/README.md rename to packages/strapi-hook-bookshelf/README.md index 5d1ce7ecb7..96ba285413 100755 --- a/packages/strapi-bookshelf/README.md +++ b/packages/strapi-hook-bookshelf/README.md @@ -1,12 +1,12 @@ -# strapi-bookshelf +# strapi-hook-bookshelf -[![npm version](https://img.shields.io/npm/v/strapi-bookshelf.svg)](https://www.npmjs.org/package/strapi-bookshelf) -[![npm downloads](https://img.shields.io/npm/dm/strapi-bookshelf.svg)](https://www.npmjs.org/package/strapi-bookshelf) -[![npm dependencies](https://david-dm.org/strapi/strapi-bookshelf.svg)](https://david-dm.org/strapi/strapi-bookshelf) -[![Build status](https://travis-ci.org/strapi/strapi-bookshelf.svg?branch=master)](https://travis-ci.org/strapi/strapi-bookshelf) +[![npm version](https://img.shields.io/npm/v/strapi-hook-bookshelf.svg)](https://www.npmjs.org/package/strapi-hook-bookshelf) +[![npm downloads](https://img.shields.io/npm/dm/strapi-hook-bookshelf.svg)](https://www.npmjs.org/package/strapi-hook-bookshelf) +[![npm dependencies](https://david-dm.org/strapi/strapi-hook-bookshelf.svg)](https://david-dm.org/strapi/strapi-hook-bookshelf) +[![Build status](https://travis-ci.org/strapi/strapi-hook-bookshelf.svg?branch=master)](https://travis-ci.org/strapi/strapi-hook-bookshelf) [![Slack status](http://strapi-slack.herokuapp.com/badge.svg)](http://slack.strapi.io) -This built-in hook allows you to use the [Bookshelf ORM](http://bookshelfjs.org/) using the `strapi-knex` hook. +This built-in hook allows you to use the [Bookshelf ORM](http://bookshelfjs.org/) using the `strapi-hook-knex` hook. [Bookshelf ORM](http://bookshelfjs.org/) is a JavaScript ORM for Node.js, built on the [Knex node module](http://knexjs.org/) SQL query builder. Featuring both promise based and traditional callback interfaces, providing transaction support, eager/nested-eager relation loading, polymorphic associations, and support for one-to-one, one-to-many, and many-to-many relations. diff --git a/packages/strapi-bookshelf/lib/index.js b/packages/strapi-hook-bookshelf/lib/index.js similarity index 94% rename from packages/strapi-bookshelf/lib/index.js rename to packages/strapi-hook-bookshelf/lib/index.js index d73418eba4..5d4ddbc11c 100755 --- a/packages/strapi-bookshelf/lib/index.js +++ b/packages/strapi-hook-bookshelf/lib/index.js @@ -26,9 +26,6 @@ const GLOBALS = {}; * Bookshelf hook */ -/* eslint-disable no-unused-vars */ -/* eslint-disable prefer-template */ -/* eslint-disable no-case-declarations */ module.exports = function(strapi) { const hook = _.merge({ /** @@ -45,7 +42,7 @@ module.exports = function(strapi) { */ initialize: async cb => { - const connections = _.pickBy(strapi.config.connections, { connector: 'strapi-bookshelf' }); + const connections = _.pickBy(strapi.config.connections, { connector: 'strapi-hook-bookshelf' }); const databaseUpdate = []; @@ -79,17 +76,16 @@ module.exports = function(strapi) { _.forEach(models, (definition, model) => { definition.globalName = _.upperFirst(_.camelCase(definition.globalId)); - _.defaults(definition, { - primaryKey: 'id' - }); - // Define local GLOBALS to expose every models in this file. GLOBALS[definition.globalId] = {}; // Add some informations about ORM & client connection & tableName definition.orm = 'bookshelf'; definition.client = _.get(connection.settings, 'client'); - + _.defaults(definition, { + primaryKey: 'id', + primaryKeyType: _.get(definition, 'options.idAttributeType', 'integer') + }); // Register the final model for Bookshelf. const loadedModel = _.assign({ tableName: definition.collectionName, @@ -287,7 +283,7 @@ module.exports = function(strapi) { : Promise.resolve(); }); - this.on('saving', (instance, attrs) => { + this.on('saving', (instance, attrs, options) => { //eslint-disable-line instance.attributes = mapper(instance.attributes); attrs = mapper(attrs); @@ -394,15 +390,18 @@ module.exports = function(strapi) { case 'oneToOne': case 'manyToOne': case 'oneWay': - type = definition.client === 'pg' ? 'integer' : 'int'; + type = definition.primaryKeyType; break; default: return null; } } else { switch (attribute.type) { + case 'uuid': + type = definition.client === 'pg' ? 'uuid' : 'varchar(36)'; + break; case 'text': - type = 'text'; + type = definition.client === 'pg' ? type = 'text' : 'longtext'; break; case 'json': type = definition.client === 'pg' ? 'jsonb' : 'longtext'; @@ -457,15 +456,15 @@ module.exports = function(strapi) { }, start); }; - const generateIndexes = async (table, attrs) => { + const generateIndexes = async (table) => { try { const connection = strapi.config.connections[definition.connection]; let columns = Object.keys(attributes).filter(attribute => ['string', 'text'].includes(attributes[attribute].type)); switch (connection.settings.client) { - case 'pg': + case 'pg': { // Enable extension to allow GIN indexes. - await ORM.knex.raw(`CREATE EXTENSION IF NOT EXISTS pg_trgm`); + await ORM.knex.raw('CREATE EXTENSION IF NOT EXISTS pg_trgm'); // Create GIN indexes for every column. const indexes = columns @@ -480,6 +479,8 @@ module.exports = function(strapi) { await Promise.all(indexes); break; + } + default: columns = columns .map(attribute => `\`${attribute}\``) @@ -521,7 +522,13 @@ module.exports = function(strapi) { if (!tableExist) { - const columns = generateColumns(attributes, [`id ${definition.client === 'pg' ? 'SERIAL' : 'INT AUTO_INCREMENT'} NOT NULL PRIMARY KEY`]).join(',\n\r'); + let idAttributeBuilder = [`id ${definition.client === 'pg' ? 'SERIAL' : 'INT AUTO_INCREMENT'} NOT NULL PRIMARY KEY`]; + if (definition.primaryKeyType === 'uuid' && definition.client === 'pg') { + idAttributeBuilder = ['id uuid NOT NULL DEFAULT uuid_generate_v4() NOT NULL PRIMARY KEY']; + } else if (definition.primaryKeyType !== 'integer') { + idAttributeBuilder = [`id ${getType({type: definition.primaryKeyType})} NOT NULL PRIMARY KEY`]; + } + const columns = generateColumns(attributes, idAttributeBuilder).join(',\n\r'); // Create table await ORM.knex.raw(` @@ -593,7 +600,6 @@ module.exports = function(strapi) { const changeRequired = definition.client === 'pg' ? `ALTER COLUMN ${quote}${attribute}${quote} ${attributes[attribute].required ? 'SET' : 'DROP'} NOT NULL` : `CHANGE ${quote}${attribute}${quote} ${quote}${attribute}${quote} ${type} ${attributes[attribute].required ? 'NOT' : ''} NULL`; - await ORM.knex.raw(`ALTER TABLE ${quote}${table}${quote} ${changeType}`); await ORM.knex.raw(`ALTER TABLE ${quote}${table}${quote} ${changeRequired}`); } @@ -631,10 +637,10 @@ module.exports = function(strapi) { if (morphRelations) { const attributes = { [`${loadedModel.tableName}_id`]: { - type: 'integer' + type: definition.primaryKeyType }, [`${morphRelations.alias}_id`]: { - type: 'integer' + type: definition.primaryKeyType }, [`${morphRelations.alias}_type`]: { type: 'text' @@ -661,10 +667,10 @@ module.exports = function(strapi) { const attributes = { [`${pluralize.singular(manyRelations.collection)}_id`]: { - type: 'integer' + type: definition.primaryKeyType }, [`${pluralize.singular(definition.globalId.toLowerCase())}_id`]: { - type: 'integer' + type: definition.primaryKeyType } }; @@ -681,9 +687,8 @@ module.exports = function(strapi) { ), table => { return _.snakeCase( - pluralize.plural(table.collection) + - ' ' + - pluralize.plural(table.via) + // eslint-disable-next-line prefer-template + pluralize.plural(table.collection) + ' ' + pluralize.plural(table.via) ); } ).join('__'); @@ -700,7 +705,7 @@ module.exports = function(strapi) { resolve(); })); } catch (err) { - strapi.log.error('Impossible to register the `' + model + '` model.'); + strapi.log.error(`Impossible to register the '${model}' model.`); strapi.log.error(err); strapi.stop(); } @@ -818,9 +823,8 @@ module.exports = function(strapi) { ), table => { return _.snakeCase( - pluralize.plural(table.collection) + - ' ' + - pluralize.plural(table.via) + // eslint-disable-next-line prefer-template + pluralize.plural(table.collection) + ' ' + pluralize.plural(table.via) ); } ).join('__'); @@ -844,10 +848,7 @@ module.exports = function(strapi) { // Sometimes the many-to-many relationships // is on the same keys on the same models (ex: `friends` key in model `User`) - if ( - details.attribute + '_' + details.column === - relationship.attribute + '_' + relationship.column - ) { + if (`${details.attribute}_${details.column}` === `${relationship.attribute}_${relationship.column}`) { relationship.attribute = pluralize.singular(details.via); } @@ -862,16 +863,16 @@ module.exports = function(strapi) { return this.belongsToMany( GLOBALS[globalId], collectionName, - relationship.attribute + '_' + relationship.column, - details.attribute + '_' + details.column + `${relationship.attribute}_${relationship.column}`, + `${details.attribute}_${details.column}` ).withPivot(details.withPivot); } return this.belongsToMany( GLOBALS[globalId], collectionName, - relationship.attribute + '_' + relationship.column, - details.attribute + '_' + details.column + `${relationship.attribute}_${relationship.column}`, + `${details.attribute}_${details.column}` ); }; break; @@ -929,7 +930,7 @@ module.exports = function(strapi) { } if (models.length === 0) { - strapi.log.error('Impossible to register the `' + model + '` model.'); + strapi.log.error(`Impossible to register the '${model}' model.`); strapi.log.error('The collection name cannot be found for the morphTo method.'); strapi.stop(); } @@ -999,7 +1000,7 @@ module.exports = function(strapi) { cb(); }, - getQueryParams: (value, type, key) => { + getQueryParams: (value, type, key) =>{ const result = {}; switch (type) { @@ -1046,18 +1047,18 @@ module.exports = function(strapi) { }; break; case '_sort': - result.key = `sort`; + result.key = 'sort'; result.value = { key, order: value.toUpperCase() }; break; case '_start': - result.key = `start`; + result.key = 'start'; result.value = parseFloat(value); break; case '_limit': - result.key = `limit`; + result.key = 'limit'; result.value = parseFloat(value); break; case '_contains': diff --git a/packages/strapi-bookshelf/lib/relations.js b/packages/strapi-hook-bookshelf/lib/relations.js similarity index 96% rename from packages/strapi-bookshelf/lib/relations.js rename to packages/strapi-hook-bookshelf/lib/relations.js index 57ab4bf757..e22b8418d1 100644 --- a/packages/strapi-bookshelf/lib/relations.js +++ b/packages/strapi-hook-bookshelf/lib/relations.js @@ -10,15 +10,17 @@ const _ = require('lodash'); // Utils const { models: { getValuePrimaryKey } } = require('strapi-utils'); -const transformToArrayID = (array) => { +const transformToArrayID = (array, association) => { if(_.isArray(array)) { - return array.map(value => { + array = array.map(value => { if (_.isPlainObject(value)) { - return value._id || value.id; + return value._id || value.id || false; } return value; }); + + return array.filter(n => n); } if (association.type === 'model' || (association.type === 'collection' && _.isObject(array))) { @@ -95,7 +97,7 @@ module.exports = { module.exports.findOne .call(model, { [model.primaryKey]: recordId }, [details.via]) .then(record => { - if (record && _.isObject(record[details.via])) { + if (record && _.isObject(record[details.via]) && record.id !== record[details.via][current]) { return module.exports.update.call(this, { id: getValuePrimaryKey(record[details.via], model.primaryKey), values: { @@ -140,8 +142,8 @@ module.exports = { case 'manyToMany': if (response[current] && _.isArray(response[current]) && current !== 'id') { // Compare array of ID to find deleted files. - const currentValue = transformToArrayID(response[current]).map(id => id.toString()); - const storedValue = transformToArrayID(params.values[current]).map(id => id.toString()); + const currentValue = transformToArrayID(response[current], association).map(id => id.toString()); + const storedValue = transformToArrayID(params.values[current], association).map(id => id.toString()); const toAdd = _.difference(storedValue, currentValue); const toRemove = _.difference(currentValue, storedValue); @@ -229,8 +231,8 @@ module.exports = { case 'oneToManyMorph': case 'manyToManyMorph': { // Compare array of ID to find deleted files. - const currentValue = transformToArrayID(response[current]).map(id => id.toString()); - const storedValue = transformToArrayID(params.values[current]).map(id => id.toString()); + const currentValue = transformToArrayID(response[current], association).map(id => id.toString()); + const storedValue = transformToArrayID(params.values[current], association).map(id => id.toString()); const toAdd = _.difference(storedValue, currentValue); const toRemove = _.difference(currentValue, storedValue); @@ -379,7 +381,7 @@ module.exports = { [`${params.alias}_id`]: params.refId, [`${params.alias}_type`]: params.ref, field: params.field - }, _.isEmpty)) + }, _.isUndefined)) .destroy(); } }; diff --git a/packages/strapi-bookshelf/lib/utils/connectivity.js b/packages/strapi-hook-bookshelf/lib/utils/connectivity.js similarity index 100% rename from packages/strapi-bookshelf/lib/utils/connectivity.js rename to packages/strapi-hook-bookshelf/lib/utils/connectivity.js diff --git a/packages/strapi-bookshelf/lib/utils/graphql.js b/packages/strapi-hook-bookshelf/lib/utils/graphql.js similarity index 100% rename from packages/strapi-bookshelf/lib/utils/graphql.js rename to packages/strapi-hook-bookshelf/lib/utils/graphql.js diff --git a/packages/strapi-bookshelf/lib/utils/index.js b/packages/strapi-hook-bookshelf/lib/utils/index.js similarity index 100% rename from packages/strapi-bookshelf/lib/utils/index.js rename to packages/strapi-hook-bookshelf/lib/utils/index.js diff --git a/packages/strapi-bookshelf/package.json b/packages/strapi-hook-bookshelf/package.json similarity index 81% rename from packages/strapi-bookshelf/package.json rename to packages/strapi-hook-bookshelf/package.json index 7b3117939d..06d97acdff 100755 --- a/packages/strapi-bookshelf/package.json +++ b/packages/strapi-hook-bookshelf/package.json @@ -1,6 +1,6 @@ { - "name": "strapi-bookshelf", - "version": "3.0.0-alpha.12.7.1", + "name": "strapi-hook-bookshelf", + "version": "3.0.0-alpha.14", "description": "Bookshelf hook for the Strapi framework", "homepage": "http://strapi.io", "keywords": [ @@ -18,15 +18,14 @@ "dependencies": { "bookshelf": "^0.12.1", "inquirer": "^5.2.0", - "lodash": "^4.17.4", + "lodash": "^4.17.5", "pluralize": "^6.0.0", - "strapi-knex": "3.0.0-alpha.12.7.1", - "strapi-utils": "3.0.0-alpha.12.7.1" + "strapi-hook-knex": "3.0.0-alpha.14", + "strapi-utils": "3.0.0-alpha.14" }, "strapi": { - "isHook": true, "dependencies": [ - "strapi-knex" + "knex" ] }, "scripts": { diff --git a/packages/strapi-knex/.editorconfig b/packages/strapi-hook-ejs/.editorconfig similarity index 100% rename from packages/strapi-knex/.editorconfig rename to packages/strapi-hook-ejs/.editorconfig diff --git a/packages/strapi-ejs/.gitignore b/packages/strapi-hook-ejs/.gitignore similarity index 98% rename from packages/strapi-ejs/.gitignore rename to packages/strapi-hook-ejs/.gitignore index 218a5700e9..ab74240ce1 100755 --- a/packages/strapi-ejs/.gitignore +++ b/packages/strapi-hook-ejs/.gitignore @@ -93,3 +93,4 @@ build node_modules .node_history package-lock.json +yarn.lock diff --git a/packages/strapi-ejs/.npmignore b/packages/strapi-hook-ejs/.npmignore similarity index 100% rename from packages/strapi-ejs/.npmignore rename to packages/strapi-hook-ejs/.npmignore diff --git a/packages/strapi-ejs/LICENSE.md b/packages/strapi-hook-ejs/LICENSE.md similarity index 100% rename from packages/strapi-ejs/LICENSE.md rename to packages/strapi-hook-ejs/LICENSE.md diff --git a/packages/strapi-ejs/README.md b/packages/strapi-hook-ejs/README.md similarity index 62% rename from packages/strapi-ejs/README.md rename to packages/strapi-hook-ejs/README.md index 1f351d978f..e30761b2b7 100755 --- a/packages/strapi-ejs/README.md +++ b/packages/strapi-hook-ejs/README.md @@ -1,4 +1,4 @@ -# strapi-ejs +# strapi-hook-ejs [![npm version](https://img.shields.io/npm/v/strapi-ejs.svg)](https://www.npmjs.org/package/strapi-ejs) [![npm downloads](https://img.shields.io/npm/dm/strapi-ejs.svg)](https://www.npmjs.org/package/strapi-ejs) @@ -8,24 +8,40 @@ This built-in hook allows you to use the EJS template engine with custom options. -# How To use +## Configuration To configure your hook with custom options, you need to edit your `./config/hooks.json` file in your Strapi app. ```javascript { - hooks: { - ... - websockets: true, - ejs: { - layout: layout, // Global layout file (default: layout)(set false to disable layout) - viewExt: ejs, // View file extension (default: ejs) - cache: true, // Cache compiled templates (default: true). - debug: true // Debug flag (default: false) - } - ... + ... + "ejs": { + "enabled": true, + "layout": "layout", + "viewExt": "ejs", + "partial": true, + "cache": false, + "debug": true } } ``` +More information in the Koa ejs module https://github.com/koajs/ejs#settings + +## Usage + +Insert code in your controller to render a view. + +```javascript +module.exports = { + home: async (ctx) => { + return ctx.render('home', { + title: 'My app title' + }); + } +}; +``` + +This will render the `views/home.ejs` file and you will have access to `<%= title %>` data in your ejs file. + ## Resources diff --git a/packages/strapi-ejs/lib/index.js b/packages/strapi-hook-ejs/lib/index.js similarity index 100% rename from packages/strapi-ejs/lib/index.js rename to packages/strapi-hook-ejs/lib/index.js diff --git a/packages/strapi-ejs/package.json b/packages/strapi-hook-ejs/package.json similarity index 89% rename from packages/strapi-ejs/package.json rename to packages/strapi-hook-ejs/package.json index 110ec9b2c3..14188a53ab 100755 --- a/packages/strapi-ejs/package.json +++ b/packages/strapi-hook-ejs/package.json @@ -1,6 +1,6 @@ { - "name": "strapi-ejs", - "version": "3.0.0-alpha.12.7.1", + "name": "strapi-hook-ejs", + "version": "3.0.0-alpha.14", "description": "EJS hook for the Strapi framework", "homepage": "http://strapi.io", "keywords": [ @@ -16,9 +16,6 @@ "co": "^4.6.0", "koa-ejs": "^4.1.0" }, - "strapi": { - "isHook": true - }, "scripts": { "prepublishOnly": "npm prune" }, diff --git a/packages/strapi-mongoose/.editorconfig b/packages/strapi-hook-knex/.editorconfig similarity index 100% rename from packages/strapi-mongoose/.editorconfig rename to packages/strapi-hook-knex/.editorconfig diff --git a/packages/strapi-mongoose/.gitignore b/packages/strapi-hook-knex/.gitignore similarity index 98% rename from packages/strapi-mongoose/.gitignore rename to packages/strapi-hook-knex/.gitignore index 218a5700e9..ab74240ce1 100755 --- a/packages/strapi-mongoose/.gitignore +++ b/packages/strapi-hook-knex/.gitignore @@ -93,3 +93,4 @@ build node_modules .node_history package-lock.json +yarn.lock diff --git a/packages/strapi-knex/.npmignore b/packages/strapi-hook-knex/.npmignore similarity index 100% rename from packages/strapi-knex/.npmignore rename to packages/strapi-hook-knex/.npmignore diff --git a/packages/strapi-knex/LICENSE.md b/packages/strapi-hook-knex/LICENSE.md similarity index 100% rename from packages/strapi-knex/LICENSE.md rename to packages/strapi-hook-knex/LICENSE.md diff --git a/packages/strapi-knex/README.md b/packages/strapi-hook-knex/README.md similarity index 65% rename from packages/strapi-knex/README.md rename to packages/strapi-hook-knex/README.md index 694ced22bb..eb8fbb5470 100755 --- a/packages/strapi-knex/README.md +++ b/packages/strapi-hook-knex/README.md @@ -1,9 +1,9 @@ -# strapi-knex +# strapi-hook-knex -[![npm version](https://img.shields.io/npm/v/strapi-knex.svg)](https://www.npmjs.org/package/strapi-knex) -[![npm downloads](https://img.shields.io/npm/dm/strapi-knex.svg)](https://www.npmjs.org/package/strapi-knex) -[![npm dependencies](https://david-dm.org/strapi/strapi-knex.svg)](https://david-dm.org/strapi/strapi-knex) -[![Build status](https://travis-ci.org/strapi/strapi-knex.svg?branch=master)](https://travis-ci.org/strapi/strapi-knex) +[![npm version](https://img.shields.io/npm/v/strapi-hook-knex.svg)](https://www.npmjs.org/package/strapi-hook-knex) +[![npm downloads](https://img.shields.io/npm/dm/strapi-hook-knex.svg)](https://www.npmjs.org/package/strapi-hook-knex) +[![npm dependencies](https://david-dm.org/strapi/strapi-hook-knex.svg)](https://david-dm.org/strapi/strapi-hook-knex) +[![Build status](https://travis-ci.org/strapi/strapi-hook-knex.svg?branch=master)](https://travis-ci.org/strapi/strapi-hook-knex) [![Slack status](http://strapi-slack.herokuapp.com/badge.svg)](http://slack.strapi.io) This built-in hook allows you to directly make SQL queries from your Strapi connections to your databases thanks to the [Knex node module](http://knexjs.org/). diff --git a/packages/strapi-knex/lib/index.js b/packages/strapi-hook-knex/lib/index.js similarity index 98% rename from packages/strapi-knex/lib/index.js rename to packages/strapi-hook-knex/lib/index.js index f853163ecf..ceacd92131 100755 --- a/packages/strapi-knex/lib/index.js +++ b/packages/strapi-hook-knex/lib/index.js @@ -46,7 +46,7 @@ module.exports = strapi => { initialize: cb => { // For each connection in the config register a new Knex connection. - _.forEach(_.pickBy(strapi.config.connections, {connector: 'strapi-bookshelf'}), (connection, name) => { + _.forEach(_.pickBy(strapi.config.connections, {connector: 'strapi-hook-bookshelf'}), (connection, name) => { // Make sure we use the client even if the typo is not the exact one. switch (connection.settings.client) { diff --git a/packages/strapi-knex/package.json b/packages/strapi-hook-knex/package.json similarity index 86% rename from packages/strapi-knex/package.json rename to packages/strapi-hook-knex/package.json index 4fa1428fa6..d511a692d6 100755 --- a/packages/strapi-knex/package.json +++ b/packages/strapi-hook-knex/package.json @@ -1,6 +1,6 @@ { - "name": "strapi-knex", - "version": "3.0.0-alpha.12.7.1", + "name": "strapi-hook-knex", + "version": "3.0.0-alpha.14", "description": "Knex hook for the Strapi framework", "homepage": "http://strapi.io", "keywords": [ @@ -17,10 +17,7 @@ "main": "./lib", "dependencies": { "knex": "^0.13.0", - "lodash": "^4.17.4" - }, - "strapi": { - "isHook": true + "lodash": "^4.17.5" }, "author": { "email": "hi@strapi.io", diff --git a/packages/strapi-redis/.editorconfig b/packages/strapi-hook-mongoose/.editorconfig similarity index 100% rename from packages/strapi-redis/.editorconfig rename to packages/strapi-hook-mongoose/.editorconfig diff --git a/packages/strapi-hook-mongoose/.gitignore b/packages/strapi-hook-mongoose/.gitignore new file mode 100755 index 0000000000..ab74240ce1 --- /dev/null +++ b/packages/strapi-hook-mongoose/.gitignore @@ -0,0 +1,96 @@ +############################ +# OS X +############################ + +.DS_Store +.AppleDouble +.LSOverride +Icon +.Spotlight-V100 +.Trashes +._* + + +############################ +# Linux +############################ + +*~ + + +############################ +# Windows +############################ + +Thumbs.db +ehthumbs.db +Desktop.ini +$RECYCLE.BIN/ +*.cab +*.msi +*.msm +*.msp + + +############################ +# Packages +############################ + +*.7z +*.csv +*.dat +*.dmg +*.gz +*.iso +*.jar +*.rar +*.tar +*.zip +*.com +*.class +*.dll +*.exe +*.o +*.seed +*.so +*.swo +*.swp +*.swn +*.swm +*.out +*.pid + + +############################ +# Logs and databases +############################ + +.tmp +*.log +*.sql +*.sqlite + + +############################ +# Misc. +############################ + +*# +.idea +nbproject + + +############################ +# Node.js +############################ + +lib-cov +lcov.info +pids +logs +results +build +node_modules +.node_history +package-lock.json +yarn.lock diff --git a/packages/strapi-mongoose/.npmignore b/packages/strapi-hook-mongoose/.npmignore similarity index 100% rename from packages/strapi-mongoose/.npmignore rename to packages/strapi-hook-mongoose/.npmignore diff --git a/packages/strapi-mongoose/LICENSE.md b/packages/strapi-hook-mongoose/LICENSE.md similarity index 100% rename from packages/strapi-mongoose/LICENSE.md rename to packages/strapi-hook-mongoose/LICENSE.md diff --git a/packages/strapi-mongoose/README.md b/packages/strapi-hook-mongoose/README.md similarity index 52% rename from packages/strapi-mongoose/README.md rename to packages/strapi-hook-mongoose/README.md index 49c59d8f23..5f45c405c7 100755 --- a/packages/strapi-mongoose/README.md +++ b/packages/strapi-hook-mongoose/README.md @@ -1,9 +1,9 @@ -# strapi-mongoose +# strapi-hook-mongoose -[![npm version](https://img.shields.io/npm/v/strapi-mongoose.svg)](https://www.npmjs.org/package/strapi-mongoose) -[![npm downloads](https://img.shields.io/npm/dm/strapi-mongoose.svg)](https://www.npmjs.org/package/strapi-mongoose) -[![npm dependencies](https://david-dm.org/strapi/strapi-mongoose.svg)](https://david-dm.org/strapi/strapi-mongoose) -[![Build status](https://travis-ci.org/strapi/strapi-mongoose.svg?branch=master)](https://travis-ci.org/strapi/strapi-bookshelf) +[![npm version](https://img.shields.io/npm/v/strapi-hook-mongoose.svg)](https://www.npmjs.org/package/strapi-hook-mongoose) +[![npm downloads](https://img.shields.io/npm/dm/strapi-hook-mongoose.svg)](https://www.npmjs.org/package/strapi-hook-mongoose) +[![npm dependencies](https://david-dm.org/strapi/strapi-hook-mongoose.svg)](https://david-dm.org/strapi/strapi-hook-mongoose) +[![Build status](https://travis-ci.org/strapi/strapi-hook-mongoose.svg?branch=master)](https://travis-ci.org/strapi/strapi-hook-mongoose) [![Slack status](http://strapi-slack.herokuapp.com/badge.svg)](http://slack.strapi.io) This built-in hook allows you to use the [Mongoose ORM](http://mongoosejs.com/). diff --git a/packages/strapi-mongoose/lib/index.js b/packages/strapi-hook-mongoose/lib/index.js similarity index 96% rename from packages/strapi-mongoose/lib/index.js rename to packages/strapi-hook-mongoose/lib/index.js index 3b688a5608..2ad6a41574 100755 --- a/packages/strapi-mongoose/lib/index.js +++ b/packages/strapi-hook-mongoose/lib/index.js @@ -49,11 +49,12 @@ module.exports = function (strapi) { */ initialize: cb => { - _.forEach(_.pickBy(strapi.config.connections, {connector: 'strapi-mongoose'}), (connection, connectionName) => { + _.forEach(_.pickBy(strapi.config.connections, {connector: 'strapi-hook-mongoose'}), (connection, connectionName) => { const instance = new Mongoose(); - const { uri, host, port, username, password, database } = _.defaults(connection.settings, strapi.config.hook.settings.mongoose); + const { uri, host, port, username, password, database, srv } = _.defaults(connection.settings, strapi.config.hook.settings.mongoose); const uriOptions = uri ? url.parse(uri, true).query : {}; const { authenticationDatabase, ssl, debug } = _.defaults(connection.options, uriOptions, strapi.config.hook.settings.mongoose); + const isSrv = srv === true || srv === 'true'; // Connect to mongo database const connectOptions = {}; @@ -73,10 +74,16 @@ module.exports = function (strapi) { connectOptions.ssl = ssl === true || ssl === 'true'; connectOptions.useNewUrlParser = true; + connectOptions.dbName = database; options.debug = debug === true || debug === 'true'; - instance.connect(uri || `mongodb://${host}:${port}/${database}`, connectOptions); + /* FIXME: for now, mongoose doesn't support srv auth except the way including user/pass in URI. + * https://github.com/Automattic/mongoose/issues/6881 */ + instance.connect(uri || + `mongodb${isSrv ? '+srv' : ''}://${username}:${password}@${host}${ !isSrv ? ':' + port : '' }/`, + connectOptions + ); for (let key in options) { instance.set(key, options[key]); diff --git a/packages/strapi-mongoose/lib/relations.js b/packages/strapi-hook-mongoose/lib/relations.js similarity index 99% rename from packages/strapi-mongoose/lib/relations.js rename to packages/strapi-hook-mongoose/lib/relations.js index 1b1de2c7b5..0dcf8d35b7 100644 --- a/packages/strapi-mongoose/lib/relations.js +++ b/packages/strapi-hook-mongoose/lib/relations.js @@ -50,7 +50,7 @@ module.exports = { .findOne({ [model.primaryKey]: value[current] }) .populate(details.via) .then(record => { - if (record && _.isObject(record[details.via])) { + if (record && _.isObject(record[details.via]) && record._id.toString() !== record[details.via][current].toString()) { return module.exports.update.call(this, { id: getValuePrimaryKey(record[details.via], model.primaryKey), values: { @@ -174,7 +174,7 @@ module.exports = { strapi.models[_.toLower(obj.ref)].globalId; // Define the object stored in database. - // The shape is this object is defined by the strapi-mongoose connector. + // The shape is this object is defined by the strapi-hook-mongoose connector. return { ref: obj.refId, kind: globalId, diff --git a/packages/strapi-mongoose/lib/utils/connectivity.js b/packages/strapi-hook-mongoose/lib/utils/connectivity.js similarity index 75% rename from packages/strapi-mongoose/lib/utils/connectivity.js rename to packages/strapi-hook-mongoose/lib/utils/connectivity.js index c53950005c..cf1164750b 100644 --- a/packages/strapi-mongoose/lib/utils/connectivity.js +++ b/packages/strapi-hook-mongoose/lib/utils/connectivity.js @@ -7,7 +7,7 @@ const path = require('path'); module.exports = (scope, success, error) => { const Mongoose = require(path.resolve(`${scope.tmpPath}/node_modules/mongoose`)); - const { username, password } = scope.database.settings; + const { username, password, srv } = scope.database.settings; const { authenticationDatabase, ssl } = scope.database.options; const connectOptions = {}; @@ -26,8 +26,9 @@ module.exports = (scope, success, error) => { connectOptions.ssl = ssl ? true : false; connectOptions.useNewUrlParser = true; + connectOptions.dbName = scope.database.settings.database; - Mongoose.connect(`mongodb://${scope.database.settings.host}:${scope.database.settings.port}/${scope.database.settings.database}`, connectOptions, function (err) { + Mongoose.connect(`mongodb${srv ? '+srv' : ''}://${scope.database.settings.host}${!srv ? `:${scope.database.settings.port}` : ''}/`, connectOptions, function (err) { if (err) { console.log('⚠️ Database connection has failed! Make sure your database is running.'); return error(); diff --git a/packages/strapi-mongoose/lib/utils/index.js b/packages/strapi-hook-mongoose/lib/utils/index.js similarity index 100% rename from packages/strapi-mongoose/lib/utils/index.js rename to packages/strapi-hook-mongoose/lib/utils/index.js diff --git a/packages/strapi-mongoose/package.json b/packages/strapi-hook-mongoose/package.json similarity index 83% rename from packages/strapi-mongoose/package.json rename to packages/strapi-hook-mongoose/package.json index 0becf7761b..eb468ed910 100755 --- a/packages/strapi-mongoose/package.json +++ b/packages/strapi-hook-mongoose/package.json @@ -1,6 +1,6 @@ { - "name": "strapi-mongoose", - "version": "3.0.0-alpha.12.7.1", + "name": "strapi-hook-mongoose", + "version": "3.0.0-alpha.14", "description": "Mongoose hook for the Strapi framework", "homepage": "http://strapi.io", "keywords": [ @@ -15,14 +15,11 @@ }, "main": "./lib", "dependencies": { - "lodash": "^4.17.4", + "lodash": "^4.17.5", "mongoose": "^5.0.16", "mongoose-float": "^1.0.2", "pluralize": "^6.0.0", - "strapi-utils": "3.0.0-alpha.12.7.1" - }, - "strapi": { - "isHook": true + "strapi-utils": "3.0.0-alpha.14" }, "author": { "email": "hi@strapi.io", diff --git a/packages/strapi-hook-redis/.editorconfig b/packages/strapi-hook-redis/.editorconfig new file mode 100755 index 0000000000..473e45184b --- /dev/null +++ b/packages/strapi-hook-redis/.editorconfig @@ -0,0 +1,16 @@ +root = true + +[*] +indent_style = space +indent_size = 2 +end_of_line = lf +charset = utf-8 +trim_trailing_whitespace = true +insert_final_newline = true + +[{package.json,*.yml}] +indent_style = space +indent_size = 2 + +[*.md] +trim_trailing_whitespace = false diff --git a/packages/strapi-hook-redis/.gitignore b/packages/strapi-hook-redis/.gitignore new file mode 100755 index 0000000000..ab74240ce1 --- /dev/null +++ b/packages/strapi-hook-redis/.gitignore @@ -0,0 +1,96 @@ +############################ +# OS X +############################ + +.DS_Store +.AppleDouble +.LSOverride +Icon +.Spotlight-V100 +.Trashes +._* + + +############################ +# Linux +############################ + +*~ + + +############################ +# Windows +############################ + +Thumbs.db +ehthumbs.db +Desktop.ini +$RECYCLE.BIN/ +*.cab +*.msi +*.msm +*.msp + + +############################ +# Packages +############################ + +*.7z +*.csv +*.dat +*.dmg +*.gz +*.iso +*.jar +*.rar +*.tar +*.zip +*.com +*.class +*.dll +*.exe +*.o +*.seed +*.so +*.swo +*.swp +*.swn +*.swm +*.out +*.pid + + +############################ +# Logs and databases +############################ + +.tmp +*.log +*.sql +*.sqlite + + +############################ +# Misc. +############################ + +*# +.idea +nbproject + + +############################ +# Node.js +############################ + +lib-cov +lcov.info +pids +logs +results +build +node_modules +.node_history +package-lock.json +yarn.lock diff --git a/packages/strapi-redis/.npmignore b/packages/strapi-hook-redis/.npmignore similarity index 100% rename from packages/strapi-redis/.npmignore rename to packages/strapi-hook-redis/.npmignore diff --git a/packages/strapi-redis/LICENSE.md b/packages/strapi-hook-redis/LICENSE.md similarity index 100% rename from packages/strapi-redis/LICENSE.md rename to packages/strapi-hook-redis/LICENSE.md diff --git a/packages/strapi-redis/README.md b/packages/strapi-hook-redis/README.md similarity index 100% rename from packages/strapi-redis/README.md rename to packages/strapi-hook-redis/README.md diff --git a/packages/strapi-redis/lib/index.js b/packages/strapi-hook-redis/lib/index.js similarity index 98% rename from packages/strapi-redis/lib/index.js rename to packages/strapi-hook-redis/lib/index.js index 6b26ad51e8..c224615397 100755 --- a/packages/strapi-redis/lib/index.js +++ b/packages/strapi-hook-redis/lib/index.js @@ -38,13 +38,13 @@ module.exports = function(strapi) { initialize: cb => { if (_.isEmpty(strapi.models) || !_.pickBy(strapi.config.connections, { - connector: 'strapi-redis' + connector: 'strapi-hook-redis' })) { return cb(); } const connections = _.pickBy(strapi.config.connections, { - connector: 'strapi-redis' + connector: 'strapi-hook-redis' }); if(_.size(connections) === 0) { diff --git a/packages/strapi-redis/lib/utils/connectivity.js b/packages/strapi-hook-redis/lib/utils/connectivity.js similarity index 100% rename from packages/strapi-redis/lib/utils/connectivity.js rename to packages/strapi-hook-redis/lib/utils/connectivity.js diff --git a/packages/strapi-redis/package.json b/packages/strapi-hook-redis/package.json similarity index 82% rename from packages/strapi-redis/package.json rename to packages/strapi-hook-redis/package.json index d4058c94e3..cd3f81efcd 100755 --- a/packages/strapi-redis/package.json +++ b/packages/strapi-hook-redis/package.json @@ -1,6 +1,6 @@ { - "name": "strapi-redis", - "version": "3.0.0-alpha.12.7.1", + "name": "strapi-hook-redis", + "version": "3.0.0-alpha.14", "description": "Redis hook for the Strapi framework", "homepage": "http://strapi.io", "keywords": [ @@ -16,12 +16,9 @@ "main": "./lib", "dependencies": { "ioredis": "^3.1.2", - "lodash": "^4.17.4", + "lodash": "^4.17.5", "stack-trace": "0.0.10", - "strapi-utils": "3.0.0-alpha.12.7.1" - }, - "strapi": { - "isHook": true + "strapi-utils": "3.0.0-alpha.14" }, "author": { "email": "hi@strapi.io", diff --git a/packages/strapi-lint/package.json b/packages/strapi-lint/package.json index 63cd9e66f6..dd594a1622 100644 --- a/packages/strapi-lint/package.json +++ b/packages/strapi-lint/package.json @@ -1,6 +1,6 @@ { "name": "strapi-lint", - "version": "3.0.0-alpha.12.7.1", + "version": "3.0.0-alpha.14", "description": "Strapi eslint and prettier configurations", "directories": { "lib": "lib" diff --git a/packages/strapi-middleware-views/.gitignore b/packages/strapi-middleware-views/.gitignore index 218a5700e9..ab74240ce1 100755 --- a/packages/strapi-middleware-views/.gitignore +++ b/packages/strapi-middleware-views/.gitignore @@ -93,3 +93,4 @@ build node_modules .node_history package-lock.json +yarn.lock diff --git a/packages/strapi-middleware-views/package.json b/packages/strapi-middleware-views/package.json index d11be2e120..b1a24d36a1 100755 --- a/packages/strapi-middleware-views/package.json +++ b/packages/strapi-middleware-views/package.json @@ -1,7 +1,7 @@ { "name": "strapi-middleware-views", - "version": "3.0.0-alpha.12.7.1", - "description": "Views hook to enable server-side rendering for the Strapi framework", + "version": "3.0.0-alpha.14", + "description": "Views middleware to enable server-side rendering for the Strapi framework", "homepage": "http://strapi.io", "keywords": [ "redis", @@ -17,10 +17,7 @@ "dependencies": { "consolidate": "^0.14.5", "koa-views": "^6.1.1", - "lodash": "^4.17.4" - }, - "strapi": { - "isHook": true + "lodash": "^4.17.5" }, "author": { "email": "hi@strapi.io", diff --git a/packages/strapi-plugin-content-manager/.gitignore b/packages/strapi-plugin-content-manager/.gitignore index 04f75820e8..79f18e9f40 100755 --- a/packages/strapi-plugin-content-manager/.gitignore +++ b/packages/strapi-plugin-content-manager/.gitignore @@ -4,6 +4,7 @@ node_modules stats.json config/layout.json package-lock.json +yarn.lock # Cruft .DS_Store diff --git a/packages/strapi-plugin-content-manager/admin/src/assets/icons/icon_barred.svg b/packages/strapi-plugin-content-manager/admin/src/assets/icons/icon_barred.svg new file mode 100755 index 0000000000..2f3c7b9111 --- /dev/null +++ b/packages/strapi-plugin-content-manager/admin/src/assets/icons/icon_barred.svg @@ -0,0 +1 @@ +abc \ No newline at end of file diff --git a/packages/strapi-plugin-content-manager/admin/src/assets/icons/icon_bold.svg b/packages/strapi-plugin-content-manager/admin/src/assets/icons/icon_bold.svg new file mode 100755 index 0000000000..cee3cf4011 --- /dev/null +++ b/packages/strapi-plugin-content-manager/admin/src/assets/icons/icon_bold.svg @@ -0,0 +1 @@ +B \ No newline at end of file diff --git a/packages/strapi-plugin-content-manager/admin/src/assets/icons/icon_bullet-list.svg b/packages/strapi-plugin-content-manager/admin/src/assets/icons/icon_bullet-list.svg new file mode 100755 index 0000000000..6e060c39f5 --- /dev/null +++ b/packages/strapi-plugin-content-manager/admin/src/assets/icons/icon_bullet-list.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/strapi-plugin-content-manager/admin/src/assets/icons/icon_code-block.svg b/packages/strapi-plugin-content-manager/admin/src/assets/icons/icon_code-block.svg new file mode 100755 index 0000000000..a47ca10058 --- /dev/null +++ b/packages/strapi-plugin-content-manager/admin/src/assets/icons/icon_code-block.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/strapi-plugin-content-manager/admin/src/assets/icons/icon_italic.svg b/packages/strapi-plugin-content-manager/admin/src/assets/icons/icon_italic.svg new file mode 100755 index 0000000000..6181979e31 --- /dev/null +++ b/packages/strapi-plugin-content-manager/admin/src/assets/icons/icon_italic.svg @@ -0,0 +1 @@ +I \ No newline at end of file diff --git a/packages/strapi-plugin-content-manager/admin/src/assets/icons/icon_link.svg b/packages/strapi-plugin-content-manager/admin/src/assets/icons/icon_link.svg new file mode 100755 index 0000000000..53b6125367 --- /dev/null +++ b/packages/strapi-plugin-content-manager/admin/src/assets/icons/icon_link.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/strapi-plugin-content-manager/admin/src/assets/icons/icon_media.svg b/packages/strapi-plugin-content-manager/admin/src/assets/icons/icon_media.svg new file mode 100755 index 0000000000..d7401730c7 --- /dev/null +++ b/packages/strapi-plugin-content-manager/admin/src/assets/icons/icon_media.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/strapi-plugin-content-manager/admin/src/assets/icons/icon_numbered-list.svg b/packages/strapi-plugin-content-manager/admin/src/assets/icons/icon_numbered-list.svg new file mode 100755 index 0000000000..79c8091f86 --- /dev/null +++ b/packages/strapi-plugin-content-manager/admin/src/assets/icons/icon_numbered-list.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/strapi-plugin-content-manager/admin/src/assets/icons/icon_quote-block.svg b/packages/strapi-plugin-content-manager/admin/src/assets/icons/icon_quote-block.svg new file mode 100755 index 0000000000..62a7990e92 --- /dev/null +++ b/packages/strapi-plugin-content-manager/admin/src/assets/icons/icon_quote-block.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/strapi-plugin-content-manager/admin/src/assets/icons/icon_underline.svg b/packages/strapi-plugin-content-manager/admin/src/assets/icons/icon_underline.svg new file mode 100755 index 0000000000..7a9b8da4e1 --- /dev/null +++ b/packages/strapi-plugin-content-manager/admin/src/assets/icons/icon_underline.svg @@ -0,0 +1 @@ +U \ No newline at end of file diff --git a/packages/strapi-plugin-content-manager/admin/src/assets/images/icon-cross-blue.svg b/packages/strapi-plugin-content-manager/admin/src/assets/images/icon-cross-blue.svg index 58f0a5d8c7..2c77b183ae 100644 --- a/packages/strapi-plugin-content-manager/admin/src/assets/images/icon-cross-blue.svg +++ b/packages/strapi-plugin-content-manager/admin/src/assets/images/icon-cross-blue.svg @@ -1,18 +1 @@ - - - - Shape - Created with Sketch. - - - - - - - - - - - - - \ No newline at end of file + \ No newline at end of file diff --git a/packages/strapi-plugin-content-manager/admin/src/assets/images/icon-cross.svg b/packages/strapi-plugin-content-manager/admin/src/assets/images/icon-cross.svg index aa113a980d..d627d9bdac 100644 --- a/packages/strapi-plugin-content-manager/admin/src/assets/images/icon-cross.svg +++ b/packages/strapi-plugin-content-manager/admin/src/assets/images/icon-cross.svg @@ -1,14 +1 @@ - - - - Shape - Created with Sketch. - - - - - - - - - \ No newline at end of file + \ No newline at end of file diff --git a/packages/strapi-plugin-content-manager/admin/src/assets/images/icon-edit-blue.svg b/packages/strapi-plugin-content-manager/admin/src/assets/images/icon-edit-blue.svg new file mode 100644 index 0000000000..0da67b8b3b --- /dev/null +++ b/packages/strapi-plugin-content-manager/admin/src/assets/images/icon-edit-blue.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/strapi-plugin-content-manager/admin/src/assets/images/icon_grab.svg b/packages/strapi-plugin-content-manager/admin/src/assets/images/icon_grab.svg new file mode 100644 index 0000000000..b0d911b651 --- /dev/null +++ b/packages/strapi-plugin-content-manager/admin/src/assets/images/icon_grab.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/strapi-plugin-content-manager/admin/src/assets/images/icon_grab_blue.svg b/packages/strapi-plugin-content-manager/admin/src/assets/images/icon_grab_blue.svg new file mode 100644 index 0000000000..5abdac71c3 --- /dev/null +++ b/packages/strapi-plugin-content-manager/admin/src/assets/images/icon_grab_blue.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/strapi-plugin-content-manager/admin/src/assets/images/icon_remove.svg b/packages/strapi-plugin-content-manager/admin/src/assets/images/icon_remove.svg new file mode 100644 index 0000000000..594f147935 --- /dev/null +++ b/packages/strapi-plugin-content-manager/admin/src/assets/images/icon_remove.svg @@ -0,0 +1,19 @@ + + + + Icon remove + Created with Sketch. + + + + + + + + + + + + + + \ No newline at end of file diff --git a/packages/strapi-plugin-content-manager/admin/src/bootstrap.js b/packages/strapi-plugin-content-manager/admin/src/bootstrap.js index dd8585d266..524be693df 100644 --- a/packages/strapi-plugin-content-manager/admin/src/bootstrap.js +++ b/packages/strapi-plugin-content-manager/admin/src/bootstrap.js @@ -1,13 +1,24 @@ -import { generateMenu } from 'containers/App/sagas'; +import { map, omit } from 'lodash'; +import request from 'utils/request'; // This method is executed before the load of the plugin const bootstrap = (plugin) => new Promise((resolve, reject) => { - generateMenu() - .then(menu => { + request('/content-manager/models', { method: 'GET' }) + .then(models => { + const menu = [{ + name: 'Content Types', + links: map(omit(models.models.models, 'plugins'), (model, key) => ({ + label: model.labelPlural || model.label || key, + destination: key, + })), + }]; plugin.leftMenuSections = menu; resolve(plugin); }) - .catch(e => reject(e)); + .catch(e => { + strapi.notification.error('content-manager.error.model.fetch'); + reject(e); + }); }); export default bootstrap; diff --git a/packages/strapi-plugin-content-manager/admin/src/components/Block/index.js b/packages/strapi-plugin-content-manager/admin/src/components/Block/index.js new file mode 100644 index 0000000000..d9ab20fdf7 --- /dev/null +++ b/packages/strapi-plugin-content-manager/admin/src/components/Block/index.js @@ -0,0 +1,43 @@ +/** + * + * Block + */ + +import React from 'react'; +import PropTypes from 'prop-types'; +import { FormattedMessage } from 'react-intl'; + +import styles from './styles.scss'; + +const renderMsg = msg =>

{msg}

; + +const Block = ({ children, description, style, title }) => ( +
+
+
+ + + {renderMsg} + +
+ {children} +
+
+); + + +Block.defaultProps = { + children: null, + description: 'app.utils.defaultMessage', + style: {}, + title: 'app.utils.defaultMessage', +}; + +Block.propTypes = { + children: PropTypes.any, + description: PropTypes.string, + style: PropTypes.object, + title: PropTypes.string, +}; + +export default Block; \ No newline at end of file diff --git a/packages/strapi-plugin-content-manager/admin/src/components/Block/styles.scss b/packages/strapi-plugin-content-manager/admin/src/components/Block/styles.scss new file mode 100644 index 0000000000..a37d677fea --- /dev/null +++ b/packages/strapi-plugin-content-manager/admin/src/components/Block/styles.scss @@ -0,0 +1,22 @@ +.ctmBlock{ + margin-bottom: 35px; + background: #ffffff; + padding: 22px 28px 18px; + border-radius: 2px; + box-shadow: 0 2px 4px #E3E9F3; + -webkit-font-smoothing: antialiased; +} + +.ctmBlockTitle { + padding-top: 0px; + line-height: 18px; + > span { + font-weight: 600; + color: #333740; + font-size: 18px; + } + > p { + color: #787E8F; + font-size: 13px; + } +} \ No newline at end of file diff --git a/packages/strapi-plugin-content-manager/admin/src/components/ClickOverHint/index.js b/packages/strapi-plugin-content-manager/admin/src/components/ClickOverHint/index.js new file mode 100644 index 0000000000..6e76225a1d --- /dev/null +++ b/packages/strapi-plugin-content-manager/admin/src/components/ClickOverHint/index.js @@ -0,0 +1,27 @@ +/** + * + * ClickOverHint + */ + +import React from 'react'; +import { FormattedMessage } from 'react-intl'; +import PropTypes from 'prop-types'; +import styles from './styles.scss'; + +function ClickOverHint({ show }) { + if (show) { + return ( +
+ +
+ ); + } + + return null; +} + +ClickOverHint.propTypes = { + show: PropTypes.bool.isRequired, +}; + +export default ClickOverHint; \ No newline at end of file diff --git a/packages/strapi-plugin-content-manager/admin/src/components/ClickOverHint/styles.scss b/packages/strapi-plugin-content-manager/admin/src/components/ClickOverHint/styles.scss new file mode 100644 index 0000000000..819edd743c --- /dev/null +++ b/packages/strapi-plugin-content-manager/admin/src/components/ClickOverHint/styles.scss @@ -0,0 +1,7 @@ +.clickOverHint { + position: absolute; + top: 0; + right: 40px; + color: #B4B6BA; + font-style: italic; +} \ No newline at end of file diff --git a/packages/strapi-plugin-content-manager/admin/src/components/CustomDragLayer/index.js b/packages/strapi-plugin-content-manager/admin/src/components/CustomDragLayer/index.js new file mode 100644 index 0000000000..b3955a9168 --- /dev/null +++ b/packages/strapi-plugin-content-manager/admin/src/components/CustomDragLayer/index.js @@ -0,0 +1,82 @@ +/** + * + * CustomDragLayer + */ + +import React from 'react'; +import PropTypes from 'prop-types'; +import { DragLayer } from 'react-dnd'; +import { flow } from 'lodash'; +import DragBox from 'components/DragBox'; +import SelectManyDraggedItem from 'components/SelectManyDraggedItem'; +import ItemTypes from 'utils/ItemTypes'; +import styles from './styles.scss'; + +function getItemStyles(props) { + const { initialOffset, currentOffset, mouseOffset } = props; + + if (!initialOffset || !currentOffset) { + return { display: 'none' }; + } + + const { x, y } = mouseOffset; + const transform = `translate(${x -50}px, ${y-5}px)`; + + return { + transform, + WebkitTransform: transform, + }; +} + +class CustomDragLayer extends React.Component { + renderItem(type, item) { + switch (type) { + case ItemTypes.VARIABLE: + case ItemTypes.NORMAL: + return ; + case ItemTypes.SORTABLEITEM: + return ; + default: + return null; + } + } + + render() { + const { item, itemType, isDragging } = this.props; + + if (!isDragging) { + return null; + } + + return ( +
+
+ {this.renderItem(itemType, item)} +
+
+ ); + } +} + +const withDragLayer = DragLayer(monitor => ({ + item: monitor.getItem(), + itemType: monitor.getItemType(), + initialOffset: monitor.getInitialSourceClientOffset(), + currentOffset: monitor.getSourceClientOffset(), + isDragging: monitor.isDragging(), + mouseOffset: monitor.getClientOffset(), +})); + +CustomDragLayer.defaultProps = { + isDragging: false, + item: null, + itemType: '', +}; + +CustomDragLayer.propTypes = { + isDragging: PropTypes.bool, + item: PropTypes.object, + itemType: PropTypes.string, +}; + +export default flow([withDragLayer])(CustomDragLayer); \ No newline at end of file diff --git a/packages/strapi-plugin-content-manager/admin/src/components/CustomDragLayer/styles.scss b/packages/strapi-plugin-content-manager/admin/src/components/CustomDragLayer/styles.scss new file mode 100644 index 0000000000..92b166c527 --- /dev/null +++ b/packages/strapi-plugin-content-manager/admin/src/components/CustomDragLayer/styles.scss @@ -0,0 +1,34 @@ +.layer { + position: fixed; + pointer-events: none; + z-index: 100; + left: 0; + top: 0; + width: 100%; + height: 100%; +} + +.customDragLayer { + height: 30px !important; + width: 100%; + margin-bottom: 6px; + box-shadow: 0!important; + padding-left: 10px; + justify-content: space-between; + background: #E6F0FB !important; + line-height: 30px; + color: #333740; + border: 1px solid darken(#AED4FB, 20%)!important; + border-radius: 2px; + // cursor: move !important; + + > i { + margin-right: 10px; + font-size: 10px; + color: #B3B5B9; + } + + > span:last-child { + background:#AED4FB; + } +} \ No newline at end of file diff --git a/packages/strapi-plugin-content-manager/admin/src/components/DragBox/index.js b/packages/strapi-plugin-content-manager/admin/src/components/DragBox/index.js new file mode 100644 index 0000000000..4f4c340ce7 --- /dev/null +++ b/packages/strapi-plugin-content-manager/admin/src/components/DragBox/index.js @@ -0,0 +1,33 @@ +/** + * + * DragBox + */ + +import React from 'react'; +import PropTypes from 'prop-types'; +import DraggedRemovedIcon from 'components/DraggedRemovedIcon'; + +import GrabIcon from 'assets/images/icon_grab_blue.svg'; + +import styles from './styles.scss'; + + +function DragBox({ name }) { + return ( +
+ Grab Icon Active + {name} + +
+ ); +} + +DragBox.defaultProps = { + name: '', +}; + +DragBox.propTypes = { + name: PropTypes.string, +}; + +export default DragBox; \ No newline at end of file diff --git a/packages/strapi-plugin-content-manager/admin/src/components/DragBox/styles.scss b/packages/strapi-plugin-content-manager/admin/src/components/DragBox/styles.scss new file mode 100644 index 0000000000..b8c639e8ed --- /dev/null +++ b/packages/strapi-plugin-content-manager/admin/src/components/DragBox/styles.scss @@ -0,0 +1,25 @@ +.dragBox { + height: 30px !important; + width: 100%; + margin-bottom: 6px; + box-shadow: 0!important; + padding-left: 10px; + justify-content: space-between; + background: #E6F0FB !important; + line-height: 30px; + // box-sizing: border-box; + border: 1px solid #AED4FB!important; + border-radius: 2px; + color: #007EFF; + font-weight: 500; + + > img { + max-width: 8px; + margin-right: 10px; + margin-top: -1px; + } + + > span:last-child { + background:#AED4FB; + } +} \ No newline at end of file diff --git a/packages/strapi-plugin-content-manager/admin/src/components/DraggableAttr/index.js b/packages/strapi-plugin-content-manager/admin/src/components/DraggableAttr/index.js new file mode 100644 index 0000000000..8116d5b9bc --- /dev/null +++ b/packages/strapi-plugin-content-manager/admin/src/components/DraggableAttr/index.js @@ -0,0 +1,210 @@ +/** + * + * DraggableAttr + */ + +/* eslint-disable react/no-find-dom-node */ +import React from 'react'; +import { findDOMNode } from 'react-dom'; +import { + DragSource, + DropTarget, +} from 'react-dnd'; +import { getEmptyImage } from 'react-dnd-html5-backend'; +import { flow } from 'lodash'; +import PropTypes from 'prop-types'; +import cn from 'classnames'; +import ClickOverHint from 'components/ClickOverHint'; +import DraggedRemovedIcon from 'components/DraggedRemovedIcon'; +import VariableEditIcon from 'components/VariableEditIcon'; +import ItemTypes from 'utils/ItemTypes'; + +import GrabIconBlue from 'assets/images/icon_grab_blue.svg'; +import GrabIcon from 'assets/images/icon_grab.svg'; + +import styles from './styles.scss'; + +const draggableAttrSource = { + beginDrag: (props) => { + props.updateSiblingHoverState(); + + return { + id: props.name, // This returns undefined + index: props.index, + }; + }, + endDrag: (props) => { + props.updateSiblingHoverState(); + + return {}; + }, +}; + +const draggableAttrTarget = { + hover: (props, monitor, component) => { + const dragIndex = monitor.getItem().index; + const hoverIndex = props.index; + + // Don't replace items with themselves + if (dragIndex === hoverIndex) { + return; + } + + // Determine rectangle on screen + const hoverBoundingRect = findDOMNode(component).getBoundingClientRect(); + + // Get vertical middle + const hoverMiddleY = (hoverBoundingRect.bottom - hoverBoundingRect.top) / 2; + + // Determine mouse position + const clientOffset = monitor.getClientOffset(); + + // Get pixels to the top + const hoverClientY = clientOffset.y - hoverBoundingRect.top; + + // Only perform the move when the mouse has crossed half of the items height + // When dragging downwards, only move when the cursor is below 50% + // When dragging upwards, only move when the cursor is above 50% + + // Dragging downwards + if (dragIndex < hoverIndex && hoverClientY < hoverMiddleY) { + return; + } + + // Dragging upwards + if (dragIndex > hoverIndex && hoverClientY > hoverMiddleY) { + return; + } + + // Time to actually perform the action + props.moveAttr(dragIndex, hoverIndex, props.keys); + + // Note: we're mutating the monitor item here! + // Generally it's better to avoid mutations, + // but it's good here for the sake of performance + // to avoid expensive index searches. + monitor.getItem().index = hoverIndex; + }, +}; + +class DraggableAttr extends React.Component { + state = { isOver: false, dragStart: false }; + + componentDidMount() { + // Use empty image as a drag preview so browsers don't draw it + // and we can draw whatever we want on the custom drag layer instead. + this.props.connectDragPreview(getEmptyImage(), { + // IE fallback: specify that we'd rather screenshot the node + // when it already knows it's being dragged so we can hide it with CSS. + // Removginv the fallabck makes it handle variable height elements + // captureDraggingState: true, + }); + } + + componentDidUpdate(prevProps) { + const { isDraggingSibling } = this.props; + + if (isDraggingSibling !== prevProps.isDraggingSibling && isDraggingSibling) { + this.handleMouseLeave(); + } + + if (prevProps.isDragging !== this.props.isDragging && this.props.isDragging) { + this.props.onClickEdit(this.props.index); + } + } + + handleClickEdit = (e) => { + e.preventDefault(); + e.stopPropagation(); + this.props.onClickEdit(this.props.index); + } + + handleDragEnd = () => this.setState({ dragStart: false }); + + handleDragStart = () => this.setState({ dragStart: true }); + + handleMouseEnter = () => { + if (!this.props.isDraggingSibling) { + this.setState({ isOver: true }); + } + }; + + handleMouseLeave = () => this.setState({ isOver: false }); + + handleRemove = (e) => { + e.preventDefault(); + e.stopPropagation(); + this.props.onRemove(this.props.index, this.props.keys); + } + + render() { + const { label, name, isDragging, isEditing, connectDragSource, connectDropTarget } = this.props; + const { isOver, dragStart } = this.state; + const opacity = isDragging ? 0.2 : 1; + const overClass = isOver ? styles.draggableAttrOvered : ''; + const className = dragStart ? styles.dragged : styles.draggableAttr; + + return ( + connectDragSource( + connectDropTarget( +
+ Grab Icon + {name} + + { (!isOver || isEditing) && name.toLowerCase() !== label.toLowerCase() && ( +
+ {label.toLowerCase() === 'id' ? 'ID' : label} +
+ )} + {isEditing && !isOver ? ( + + ) : ( + + + )} +
+ ), + ) + ); + } +} + +DraggableAttr.defaultProps = { + isEditing: false, + onRemove: () => {}, +}; + +DraggableAttr.propTypes = { + connectDragPreview: PropTypes.func.isRequired, + connectDragSource: PropTypes.func.isRequired, + connectDropTarget: PropTypes.func.isRequired, + index: PropTypes.number.isRequired, + isDragging: PropTypes.bool.isRequired, + isDraggingSibling: PropTypes.bool.isRequired, + isEditing: PropTypes.bool, + keys: PropTypes.string.isRequired, + label: PropTypes.string.isRequired, + name: PropTypes.string.isRequired, + onClickEdit: PropTypes.func.isRequired, + onRemove: PropTypes.func, +}; + +const withDropTarget = DropTarget(ItemTypes.NORMAL, draggableAttrTarget, connect => ({ + connectDropTarget: connect.dropTarget(), +})); + +const withDragSource = DragSource(ItemTypes.NORMAL, draggableAttrSource, (connect, monitor) => ({ + connectDragPreview: connect.dragPreview(), + connectDragSource: connect.dragSource(), + isDragging: monitor.isDragging(), +})); + +export default flow([withDropTarget, withDragSource])(DraggableAttr); \ No newline at end of file diff --git a/packages/strapi-plugin-content-manager/admin/src/components/DraggableAttr/styles.scss b/packages/strapi-plugin-content-manager/admin/src/components/DraggableAttr/styles.scss new file mode 100644 index 0000000000..86b36d128d --- /dev/null +++ b/packages/strapi-plugin-content-manager/admin/src/components/DraggableAttr/styles.scss @@ -0,0 +1,79 @@ +.draggableAttr { + position: relative; + height: 30px; + width: 100%; + margin-bottom: 6px; + padding-left: 10px; + justify-content: space-between; + background: #FAFAFB; + line-height: 28px; + color: #333740; + font-size: 13px; + border: 1px solid #E3E9F3; + border-radius: 2px; + &:hover{ + cursor: move; + } + > img { + max-width: 8px; + margin-right: 10px; + margin-top: -1px; + } +} + +.draggableAttrOvered { + border: 1px solid #AED4FB!important; +} + +.editingAttr { + background: #E6F0FB!important; + border: 1px solid #AED4FB!important; + color: #007EFF; + font-weight: 500; +} + +.info { + position: absolute; + top: 0; + right: 40px; + color: #B4B6BA; + font-style: italic; +} + +.infoLabel { + position: absolute; + top: 0; + right: 40px; + color: #858B9A; + font-weight: 400; +} + +.infoLabelHover{ + color: #007EFF; +} + +.dragged { + position: relative; + height: 30px !important; + width: 100%; + margin-bottom: 6px; + box-shadow: 0!important; + padding-left: 10px; + justify-content: space-between; + background: #E6F0FB !important; + line-height: 30px; + color: #333740; + border: 1px solid darken(#AED4FB, 20%)!important; + border-radius: 2px; + cursor: move !important; + + > i { + margin-right: 10px; + font-size: 10px; + color: #B3B5B9; + } + + > span:last-child { + background:#AED4FB; + } +} diff --git a/packages/strapi-plugin-content-manager/admin/src/components/DraggedRemovedIcon/index.js b/packages/strapi-plugin-content-manager/admin/src/components/DraggedRemovedIcon/index.js new file mode 100644 index 0000000000..30e6b1c287 --- /dev/null +++ b/packages/strapi-plugin-content-manager/admin/src/components/DraggedRemovedIcon/index.js @@ -0,0 +1,45 @@ +/** + * + * DraggedRemovedIcon + * + */ + +import React from 'react'; +import PropTypes from 'prop-types'; +import styles from './styles.scss'; + +function DraggedRemovedIcon({ isDragging, onRemove, withLongerHeight, ...rest }) { + let className; + + if (isDragging && withLongerHeight) { + className = styles.removeIconLongerDragged; + } else if (withLongerHeight) { + className = styles.removeIconLonger; + } else if (isDragging) { + className = styles.removeIconDragged; + } else { + className = styles.removeIcon; + } + + return ( + + ); +} + +DraggedRemovedIcon.defaultProps = { + isDragging: false, + onRemove: () => {}, + withLongerHeight: false, +}; + +DraggedRemovedIcon.propTypes = { + isDragging: PropTypes.bool, + onRemove: PropTypes.func, + withLongerHeight: PropTypes.bool, +}; + +export default DraggedRemovedIcon; \ No newline at end of file diff --git a/packages/strapi-plugin-content-manager/admin/src/components/DraggedRemovedIcon/styles.scss b/packages/strapi-plugin-content-manager/admin/src/components/DraggedRemovedIcon/styles.scss new file mode 100644 index 0000000000..4ea9e55616 --- /dev/null +++ b/packages/strapi-plugin-content-manager/admin/src/components/DraggedRemovedIcon/styles.scss @@ -0,0 +1,75 @@ +.removeIcon { + width: 30px; + background: #F3F4F4; + height: 28px; + cursor: pointer; + text-align: center; + float: right; + line-height: 28px !important; + + &:after { + display: inline-block; + content: ''; + width: 8px; + height: 8px; + margin: auto; + background-image: url('../../assets/images/icon-cross.svg'); + } +} + +.removeIconDragged { + width: 30px; + background: #AED4FB; + height: 28px; + cursor: pointer; + text-align: center; + float: right; + + &:after { + display: inline-block; + content: ''; + width: 8px; + height: 8px; + margin: auto; + margin-top: -3px; + background-image: url('../../assets/images/icon-cross-blue.svg'); + } +} + +.removeIconLonger { + width: 30px; + background: #F3F4F4; + height: 82px; + line-height: 82px; + cursor: pointer; + text-align: center; + float: right; + + &:after { + display: inline-block; + content: ''; + width: 8px; + height: 8px; + margin: auto; + background-image: url('../../assets/images/icon-cross.svg'); + } +} + +.removeIconLongerDragged { + width: 30px; + background: #AED4FB; + height: 82px; + line-height: 82px; + cursor: pointer; + text-align: center; + float: right; + + &:after { + display: inline-block; + content: ''; + width: 8px; + height: 8px; + margin: auto; + background-image: url('../../assets/images/icon-cross-blue.svg'); + } +} \ No newline at end of file diff --git a/packages/strapi-plugin-content-manager/admin/src/components/Edit/index.js b/packages/strapi-plugin-content-manager/admin/src/components/Edit/index.js index cbc76c9427..74d882ced4 100644 --- a/packages/strapi-plugin-content-manager/admin/src/components/Edit/index.js +++ b/packages/strapi-plugin-content-manager/admin/src/components/Edit/index.js @@ -12,22 +12,20 @@ import { has, isEmpty, isFunction, - merge, - omit, upperFirst, } from 'lodash'; - // You can find these components in either // ./node_modules/strapi-helper-plugin/lib/src // or strapi/packages/strapi-helper-plugin/lib/src import Input from 'components/InputsIndex'; - +import InputJSONWithErrors from 'components/InputJSONWithErrors'; +import WysiwygWithErrors from 'components/WysiwygWithErrors'; import styles from './styles.scss'; const getInputType = (type = '') => { switch (type.toLowerCase()) { case 'boolean': - return 'checkbox'; + return 'toggle'; case 'bigint': case 'decimal': case 'float': @@ -57,25 +55,6 @@ const getInputType = (type = '') => { }; class Edit extends React.PureComponent { - state = { currentLayout: {}, displayedFields: {} }; - - componentDidMount() { - this.setLayout(this.props); - } - - componentWillReceiveProps(nextProps) { - if (isEmpty(this.props.layout) && !isEmpty(nextProps.layout)) { - this.setLayout(nextProps); - } - } - - setLayout = (props) => { - const currentLayout = get(props.layout, [props.modelName, 'attributes']); - const displayedFields = merge(this.getUploadRelations(props), get(currentLayout), omit(props.schema.fields, 'id')); - - this.setState({ currentLayout, displayedFields }); - } - getInputErrors = (attr) => { const index = findIndex(this.props.formErrors, ['name', attr]); return index !== -1 ? this.props.formErrors[index].errors : []; @@ -86,14 +65,17 @@ class Edit extends React.PureComponent { * @param {String} attr [description] * @return {Object} Object containing the Input's label customBootstrapClass, ... */ - getInputLayout = (attr) => ( - Object.keys(get(this.state.currentLayout, attr, {})).reduce((acc, current) => { - acc[current] = isFunction(this.state.currentLayout[attr][current]) ? - this.state.currentLayout[attr][current](this) : - this.state.currentLayout[attr][current]; + getInputLayout = (attr) => { + const { layout } = this.props; + + return Object.keys(get(layout, ['attributes', attr], {})).reduce((acc, current) => { + acc[current] = isFunction(layout.attributes[attr][current]) ? + layout.attributes[attr][current](this) : + layout.attributes[attr][current]; + return acc; - }, {}) - ) + }, {}); + }; /** * Retrieve the input's validations @@ -128,41 +110,59 @@ class Edit extends React.PureComponent { fileRelationAllowMultipleUpload = (relationName) => has(this.props.schema, ['relations', relationName, 'collection']); - // orderAttributes = (displayedFields) => Object.keys(displayedFields).sort(name => Object.keys(this.getUploadRelations(this.props)).includes(name)); - orderAttributes = (displayedFields) => Object.keys(displayedFields); + orderAttributes = () => get(this.props.schema, ['editDisplay', 'fields'], []); + + renderAttr = (attr, key) => { + if (attr.includes('__col-md')) { + const className = attr.split('__')[1]; + + return
; + } + + const details = get(this.props.schema, ['editDisplay', 'availableFields', attr]); + // Retrieve the input's bootstrapClass from the layout + const layout = this.getInputLayout(attr); + const appearance = get(layout, 'appearance'); + const type = !isEmpty(appearance) ? appearance.toLowerCase() : get(layout, 'type', getInputType(details.type)); + const inputDescription = get(details, 'description', null); + const inputStyle = type === 'textarea' ? { height: '196px' } : {}; + let className = get(layout, 'className'); + + if (type === 'toggle' && !className) { + className = 'col-md-4'; + } + + return ( + + ); + } render(){ return (
- {this.orderAttributes(this.state.displayedFields).map((attr, key) => { - const details = this.state.displayedFields[attr]; - // Retrieve the input's bootstrapClass from the layout - const layout = this.getInputLayout(attr); - const appearance = get(layout, 'appearance'); - const type = !isEmpty(appearance) ? appearance.toLowerCase() : get(layout, 'type', getInputType(details.type)); - - return ( - - ); - })} + {this.orderAttributes().map(this.renderAttr)}
); diff --git a/packages/strapi-plugin-content-manager/admin/src/components/EditRelations/index.js b/packages/strapi-plugin-content-manager/admin/src/components/EditRelations/index.js index 96bf90c636..814c04f4d0 100644 --- a/packages/strapi-plugin-content-manager/admin/src/components/EditRelations/index.js +++ b/packages/strapi-plugin-content-manager/admin/src/components/EditRelations/index.js @@ -5,9 +5,8 @@ */ import React from 'react'; -import { FormattedMessage } from 'react-intl'; import PropTypes from 'prop-types'; -import { get, map } from 'lodash'; +import { get } from 'lodash'; // Components. import SelectOne from 'components/SelectOne'; @@ -15,34 +14,41 @@ import SelectMany from 'components/SelectMany'; import styles from './styles.scss'; -const filterRelationsUpload = (data) => Object.keys(data).reduce((acc, current) => { - if (get(data, [current, 'plugin']) !== 'upload') { - acc[current] = data[current]; - } - - return acc; -}, {}); - function EditRelations(props) { return (
- - {(message) =>

{message}

} -
- {map(filterRelationsUpload(props.schema.relations), (relation, key) => { - if (relation.nature.toLowerCase().includes('morph') && relation[key]) return ''; - - const Select = ['oneWay', 'oneToOne', 'manyToOne', 'oneToManyMorph', 'oneToOneMorph'].includes(relation.nature) ? SelectOne : SelectMany; + {props.displayedRelations.map(relationName => { + const relation = get(props.schema, ['relations', relationName], {}); + if(['oneWay', 'oneToOne', 'manyToOne', 'oneToManyMorph', 'oneToOneMorph'].includes(relation.nature)) { + return ( + + ); + } + return ( - + {name} + +
+
+ ); +} + +InputCheckbox.defaultProps = { + onChange: () => {}, + value: false, +}; + +InputCheckbox.propTypes = { + name: PropTypes.string.isRequired, + onChange: PropTypes.func, + value: PropTypes.bool, +}; + +export default InputCheckbox; \ No newline at end of file diff --git a/packages/strapi-plugin-content-manager/admin/src/components/InputCheckbox/styles.scss b/packages/strapi-plugin-content-manager/admin/src/components/InputCheckbox/styles.scss new file mode 100644 index 0000000000..96657b2968 --- /dev/null +++ b/packages/strapi-plugin-content-manager/admin/src/components/InputCheckbox/styles.scss @@ -0,0 +1,51 @@ +.checked { + font-weight: 500; + &:after { + content: '\f00c'; + position: absolute; + top: 1px; left: 17px; + font-size: 10px; + font-family: 'FontAwesome'; + font-weight: 100; + color: #1C5DE7; + transition: all .2s; + } +} + +.inputCheckboxCTM { + padding-left: 0; + font-size: 13px; + &:active, :focus { + outline: 0 !important; + } + > div { + &:active, :focus { + outline: 0 !important; + } + height: 27px; + margin: 0!important; + padding-left: 15px; + line-height: 27px; + } +} + +.inputCheckbockCTMLabel { + margin: 0; + margin-left: 9px; + color: #333740 !important; + cursor: pointer; + > input { + display: none; + margin-right: 9px; + } + + &:before { + content: ''; + position: absolute; + left:15px; top: 7px; + width: 14px; height: 14px; + border: 1px solid rgba(16,22,34,0.15); + background-color: #FDFDFD; + border-radius: 3px; + } +} \ No newline at end of file diff --git a/packages/strapi-helper-plugin/lib/src/components/InputJSON/index.js b/packages/strapi-plugin-content-manager/admin/src/components/InputJSON/index.js similarity index 100% rename from packages/strapi-helper-plugin/lib/src/components/InputJSON/index.js rename to packages/strapi-plugin-content-manager/admin/src/components/InputJSON/index.js diff --git a/packages/strapi-helper-plugin/lib/src/components/InputJSON/jsonlint.js b/packages/strapi-plugin-content-manager/admin/src/components/InputJSON/jsonlint.js similarity index 100% rename from packages/strapi-helper-plugin/lib/src/components/InputJSON/jsonlint.js rename to packages/strapi-plugin-content-manager/admin/src/components/InputJSON/jsonlint.js diff --git a/packages/strapi-helper-plugin/lib/src/components/InputJSON/styles.scss b/packages/strapi-plugin-content-manager/admin/src/components/InputJSON/styles.scss similarity index 100% rename from packages/strapi-helper-plugin/lib/src/components/InputJSON/styles.scss rename to packages/strapi-plugin-content-manager/admin/src/components/InputJSON/styles.scss diff --git a/packages/strapi-helper-plugin/lib/src/components/InputJSONWithErrors/index.js b/packages/strapi-plugin-content-manager/admin/src/components/InputJSONWithErrors/index.js similarity index 100% rename from packages/strapi-helper-plugin/lib/src/components/InputJSONWithErrors/index.js rename to packages/strapi-plugin-content-manager/admin/src/components/InputJSONWithErrors/index.js diff --git a/packages/strapi-helper-plugin/lib/src/components/InputJSONWithErrors/styles.scss b/packages/strapi-plugin-content-manager/admin/src/components/InputJSONWithErrors/styles.scss similarity index 100% rename from packages/strapi-helper-plugin/lib/src/components/InputJSONWithErrors/styles.scss rename to packages/strapi-plugin-content-manager/admin/src/components/InputJSONWithErrors/styles.scss diff --git a/packages/strapi-plugin-content-manager/admin/src/components/Search/styles.scss b/packages/strapi-plugin-content-manager/admin/src/components/Search/styles.scss index 5453b08aa8..f2729871d9 100644 --- a/packages/strapi-plugin-content-manager/admin/src/components/Search/styles.scss +++ b/packages/strapi-plugin-content-manager/admin/src/components/Search/styles.scss @@ -3,13 +3,13 @@ top: 0; display: flex; align-items: center; - overflow: auto; + overflow: hidden; min-width: 44rem; height: 6rem; padding-right: 20px; background-color: #FFFFFF; border-right: 1px solid #F3F4F4; - z-index: 999; + z-index: 1060; color: #9EA7B8; line-height: 6rem; letter-spacing: 0; diff --git a/packages/strapi-plugin-content-manager/admin/src/components/SelectMany/SortableItem.js b/packages/strapi-plugin-content-manager/admin/src/components/SelectMany/SortableItem.js new file mode 100644 index 0000000000..24700fac26 --- /dev/null +++ b/packages/strapi-plugin-content-manager/admin/src/components/SelectMany/SortableItem.js @@ -0,0 +1,148 @@ +/** + * + * SortableItem + * + */ + +/* eslint-disable react/no-find-dom-node */ +import React from 'react'; +import { findDOMNode } from 'react-dom'; +import { DragSource, DropTarget } from 'react-dnd'; +import { getEmptyImage } from 'react-dnd-html5-backend'; +import PropTypes from 'prop-types'; +import { flow, get } from 'lodash'; +import cn from 'classnames'; +import SelectManyDraggedItem from 'components/SelectManyDraggedItem'; +import ItemTypes from 'utils/ItemTypes'; +import styles from './styles.scss'; + +const sortableItemSource = { + beginDrag: props => { + return { + id: get(props, ['item', 'value', 'id' ]) || get(props, ['item', 'value', '_id'], ''), + index: props.index, + data: props.item, + }; + }, + endDrag: props => { + props.moveAttrEnd(); + return {}; + }, +}; + +const sortableItemTarget = { + hover: (props, monitor, component) => { + const dragIndex = monitor.getItem().index; + const hoverIndex = props.index; + + // Don't replace items with themselves + if (dragIndex === hoverIndex) { + return; + } + + // Determine rectangle on screen + const hoverBoundingRect = findDOMNode(component).getBoundingClientRect(); + + // Get vertical middle + const hoverMiddleY = (hoverBoundingRect.bottom - hoverBoundingRect.top) / 2; + + // Determine mouse position + const clientOffset = monitor.getClientOffset(); + + // Get pixels to the top + const hoverClientY = clientOffset.y - hoverBoundingRect.top; + + // Only perform the move when the mouse has crossed half of the items height + // When dragging downwards, only move when the cursor is below 50% + // When dragging upwards, only move when the cursor is above 50% + + // Dragging downwards + if (dragIndex < hoverIndex && hoverClientY < hoverMiddleY) { + return; + } + + // Dragging upwards + if (dragIndex > hoverIndex && hoverClientY > hoverMiddleY) { + return; + } + + // Time to actually perform the action + + props.moveAttr(dragIndex, hoverIndex, props.keys); + + // Note: we're mutating the monitor item here! + // Generally it's better to avoid mutations, + // but it's good here for the sake of performance + // to avoid expensive index searches. + monitor.getItem().index = hoverIndex; + + }, +}; + +class SortableItem extends React.Component { + componentDidMount() { + // Use empty image as a drag preview so browsers don't draw it + // and we can draw whatever we want on the custom drag layer instead. + this.props.connectDragPreview(getEmptyImage(), { + // IE fallback: specify that we'd rather screenshot the node + // when it already knows it's being dragged so we can hide it with CSS. + // Removginv the fallabck makes it handle variable height elements + // captureDraggingState: true, + }); + } + + render() { + const { + connectDragSource, + connectDropTarget, + index, + item, + isDragging, + isDraggingSibling, + onClick, + onRemove, + } = this.props; + const opacity = isDragging ? 0.2 : 1; + + return ( + connectDragSource( + connectDropTarget( +
  • + +
  • + ), + ) + ); + } +} + +const withDropTarget = DropTarget(ItemTypes.SORTABLEITEM, sortableItemTarget, connect => ({ + connectDropTarget: connect.dropTarget(), +})); + +const withDragSource = DragSource(ItemTypes.SORTABLEITEM, sortableItemSource, (connect, monitor) => ({ + connectDragPreview: connect.dragPreview(), + connectDragSource: connect.dragSource(), + isDragging: monitor.isDragging(), +})); + +SortableItem.defaultProps = { + isDraggingSibling: false, +}; + +SortableItem.propTypes = { + connectDragPreview: PropTypes.func.isRequired, + connectDragSource: PropTypes.func.isRequired, + connectDropTarget: PropTypes.func.isRequired, + index: PropTypes.number.isRequired, + isDragging: PropTypes.bool.isRequired, + isDraggingSibling: PropTypes.bool, + item: PropTypes.object.isRequired, + onClick: PropTypes.func.isRequired, + onRemove: PropTypes.func.isRequired, +}; + +export default flow([withDropTarget, withDragSource])(SortableItem); \ No newline at end of file diff --git a/packages/strapi-plugin-content-manager/admin/src/components/SelectMany/SortableList.js b/packages/strapi-plugin-content-manager/admin/src/components/SelectMany/SortableList.js new file mode 100644 index 0000000000..5723cf59d6 --- /dev/null +++ b/packages/strapi-plugin-content-manager/admin/src/components/SelectMany/SortableList.js @@ -0,0 +1,48 @@ +/** + * + * SortableList + * + */ + +import React from 'react'; +import PropTypes from 'prop-types'; +import cn from 'classnames'; +// import { SortableContainer } from 'react-sortable-hoc'; +import SortableItem from './SortableItem'; +// CSS. +import styles from './styles.scss'; + +const SortableList = ({ items, isDraggingSibling, keys, moveAttr, moveAttrEnd, onClick, onRemove }) => { + return ( +
    +
      + {items.map((item, index) => ( + + ))} +
    + {items.length > 4 &&
    } +
    + ); +}; + +SortableList.propTypes = { + isDraggingSibling: PropTypes.bool.isRequired, + items: PropTypes.array.isRequired, + keys: PropTypes.string.isRequired, + moveAttr: PropTypes.func.isRequired, + moveAttrEnd: PropTypes.func.isRequired, + onClick: PropTypes.func.isRequired, + onRemove: PropTypes.func.isRequired, +}; + +export default SortableList; \ No newline at end of file diff --git a/packages/strapi-plugin-content-manager/admin/src/components/SelectMany/index.js b/packages/strapi-plugin-content-manager/admin/src/components/SelectMany/index.js index 22d95a29fd..0093f87a92 100644 --- a/packages/strapi-plugin-content-manager/admin/src/components/SelectMany/index.js +++ b/packages/strapi-plugin-content-manager/admin/src/components/SelectMany/index.js @@ -6,32 +6,44 @@ import React from 'react'; import Select from 'react-select'; +import { FormattedMessage } from 'react-intl'; import PropTypes from 'prop-types'; -import 'react-select/dist/react-select.css'; -import { cloneDeep, isArray, isNull, isUndefined, get, findIndex, includes } from 'lodash'; +import { cloneDeep, isArray, isNull, isUndefined, get, findIndex, isEmpty } from 'lodash'; +// Utils. import request from 'utils/request'; import templateObject from 'utils/templateObject'; +// CSS. +import 'react-select/dist/react-select.css'; +// Component. +import SortableList from './SortableList'; +// CSS. import styles from './styles.scss'; -class SelectMany extends React.Component { - // eslint-disable-line react/prefer-stateless-function - constructor(props) { - super(props); - - this.state = { - isLoading: true, - options: [], - toSkip: 0, - }; - } +class SelectMany extends React.PureComponent { + state = { + isLoading: true, + options: [], + toSkip: 0, + }; componentDidMount() { this.getOptions(''); } componentDidUpdate(prevProps, prevState) { + if (isEmpty(prevProps.record) && !isEmpty(this.props.record)) { + const values = (get(this.props.record, this.props.relation.alias) || []) + .map(el => (el.id || el._id)); + + const options = this.state.options.filter(el => { + return !values.includes(el.value.id || el.value._id); + }); + + this.state.options = options; + } + if (prevState.toSkip !== this.state.toSkip) { this.getOptions(''); } @@ -92,16 +104,15 @@ class SelectMany extends React.Component { }; handleChange = value => { - const filteredValue = value.filter( - (data, index) => findIndex(value, o => o.value.id === data.value.id) === index + // Remove new added value from available option; + this.state.options = this.state.options.filter(el => + !((el.value._id || el.value.id) === (value.value.id || value.value._id)) ); - const target = { - name: `record.${this.props.relation.alias}`, - type: 'select', - value: filteredValue, - }; - this.props.setRecordAttribute({ target }); + this.props.onAddRelationalItem({ + key: this.props.relation.alias, + value: value.value, + }); }; handleBottomScroll = () => { @@ -112,13 +123,32 @@ class SelectMany extends React.Component { }); } - handleInputChange = (value) => { - const clonedOptions = this.state.options; - const filteredValues = clonedOptions.filter(data => includes(data.label, value)); + handleRemove = (index) => { + const values = get(this.props.record, this.props.relation.alias); - if (filteredValues.length === 0) { - return this.getOptions(value); - } + // Add removed value from available option; + const toAdd = { + value: values[index], + label: templateObject({ mainField: this.props.relation.displayedAttribute }, values[index]).mainField, + }; + + this.setState(prevState => ({ + options: prevState.options.concat([toAdd]), + })); + + this.props.onRemoveRelationItem({ + key: this.props.relation.alias, + index, + }); + } + + // Redirect to the edit page + handleClick = (item = {}) => { + this.props.onRedirect({ + model: this.props.relation.collection || this.props.relation.model, + id: item.value.id || item.value._id, + source: this.props.relation.plugin, + }); } render() { @@ -127,26 +157,28 @@ class SelectMany extends React.Component { ) : ( '' ); + const value = get(this.props.record, this.props.relation.alias) || []; - const value = get(this.props.record, this.props.relation.alias); /* eslint-disable jsx-a11y/label-has-for */ return ( -
    - +
    4 && styles.selectManyUpdate}`}> + {description} onClick(destination)}> +
    +
    +
    + +
    + {upperFirst(name)} +
    + onClick(destination) }]} /> +
    +
    + ); +} + +SettingsRow.propTypes = { + destination: PropTypes.string.isRequired, + name: PropTypes.string.isRequired, + onClick: PropTypes.func.isRequired, +}; + +export default SettingsRow; diff --git a/packages/strapi-plugin-content-manager/admin/src/components/SettingsRow/styles.scss b/packages/strapi-plugin-content-manager/admin/src/components/SettingsRow/styles.scss new file mode 100644 index 0000000000..dcaee4c528 --- /dev/null +++ b/packages/strapi-plugin-content-manager/admin/src/components/SettingsRow/styles.scss @@ -0,0 +1,32 @@ +.settingsRow { + height: 54px; + + &:hover { + background-color: #F7F8F8; + } + > div { + display: flex; + line-height: 53px; + margin: 0 28px 0 36px; + justify-content: space-between; + border-bottom: 1px solid rgba(14,22,34,0.04); + font-size: 13px; + color: #333740; + } + cursor: pointer; +} + +.icon { + position: absolute; + left: 0; + height: 53px; + display: flex; + flex-direction: column; + justify-content: center; +} + +.frame { + position: relative; + padding-left: 55px; + font-weight: 500; +} \ No newline at end of file diff --git a/packages/strapi-plugin-content-manager/admin/src/components/Table/index.js b/packages/strapi-plugin-content-manager/admin/src/components/Table/index.js index d9cd09701e..c596fc0580 100755 --- a/packages/strapi-plugin-content-manager/admin/src/components/Table/index.js +++ b/packages/strapi-plugin-content-manager/admin/src/components/Table/index.js @@ -22,13 +22,14 @@ class Table extends React.Component { ( ) : this.props.records.map((record, key) => ( )); const entriesToDeleteNumber = this.props.entriesToDelete.length; - + return ( {}, search: '', @@ -82,6 +85,7 @@ Table.defaultProps = { Table.propTypes = { deleteAllValue: PropTypes.bool.isRequired, + enableBulkActions: PropTypes.bool, entriesToDelete: PropTypes.array, filters: PropTypes.array.isRequired, handleDelete: PropTypes.func, diff --git a/packages/strapi-plugin-content-manager/admin/src/components/TableHeader/index.js b/packages/strapi-plugin-content-manager/admin/src/components/TableHeader/index.js index f7fe1923c2..b8d95174e4 100755 --- a/packages/strapi-plugin-content-manager/admin/src/components/TableHeader/index.js +++ b/packages/strapi-plugin-content-manager/admin/src/components/TableHeader/index.js @@ -6,6 +6,7 @@ import React from 'react'; import PropTypes from 'prop-types'; +import cn from 'classnames'; import CustomInputCheckbox from 'components/CustomInputCheckbox'; @@ -22,17 +23,23 @@ class TableHeader extends React.Component { } } - renderBulk = () => ( - - ); + renderBulk = () => { + if (this.props.enableBulkActions) { + return ( + + ); + } + + return null; + } render() { // Generate headers list @@ -40,7 +47,7 @@ class TableHeader extends React.Component { // Define sort icon let icon; - if (this.props.sort === header.name) { + if (this.props.sort === header.name || this.props.sort === 'id' && header.name === '_id') { icon = ; } else if (this.props.sort === `-${header.name}`) { icon = ; @@ -49,7 +56,11 @@ class TableHeader extends React.Component { return ( ); return ( - + {[this.renderBulk()].concat(headers)} @@ -74,10 +85,12 @@ class TableHeader extends React.Component { } TableHeader.defaultProps = { + enableBulkActions: true, value: false, }; TableHeader.propTypes = { + enableBulkActions: PropTypes.bool, entriesToDelete: PropTypes.array.isRequired, headers: PropTypes.array.isRequired, onChangeSort: PropTypes.func.isRequired, diff --git a/packages/strapi-plugin-content-manager/admin/src/components/TableHeader/styles.scss b/packages/strapi-plugin-content-manager/admin/src/components/TableHeader/styles.scss index 9b5d95c27b..172afc27bd 100755 --- a/packages/strapi-plugin-content-manager/admin/src/components/TableHeader/styles.scss +++ b/packages/strapi-plugin-content-manager/admin/src/components/TableHeader/styles.scss @@ -14,7 +14,9 @@ cursor: pointer; } } +} +.withBulk { > tr { th:first-child { width: 50px; diff --git a/packages/strapi-plugin-content-manager/admin/src/components/TableRow/index.js b/packages/strapi-plugin-content-manager/admin/src/components/TableRow/index.js index c724b57d8a..50bb74aedf 100755 --- a/packages/strapi-plugin-content-manager/admin/src/components/TableRow/index.js +++ b/packages/strapi-plugin-content-manager/admin/src/components/TableRow/index.js @@ -8,6 +8,7 @@ import React from 'react'; import PropTypes from 'prop-types'; import moment from 'moment'; import { isEmpty, isObject, toString } from 'lodash'; +import cn from 'classnames'; import CustomInputCheckbox from 'components/CustomInputCheckbox'; import IcoContainer from 'components/IcoContainer'; @@ -100,19 +101,25 @@ class TableRow extends React.Component { .concat([this.renderAction()]); } - renderDelete = () => ( - - ); + renderDelete = () => { + if (this.props.enableBulkActions) { + return ( + + ); + } + + return null; + } render() { return ( - this.handleClick(this.props.destination)}> + this.handleClick(this.props.destination)}> {this.renderCells()} ); @@ -124,11 +131,13 @@ TableRow.contextTypes = { }; TableRow.defaultProps = { + enableBulkActions: true, value: false, }; TableRow.propTypes = { destination: PropTypes.string.isRequired, + enableBulkActions: PropTypes.bool, headers: PropTypes.array.isRequired, onChange: PropTypes.func.isRequired, onDelete: PropTypes.func, diff --git a/packages/strapi-plugin-content-manager/admin/src/components/TableRow/styles.scss b/packages/strapi-plugin-content-manager/admin/src/components/TableRow/styles.scss index d7433c1466..f01f5dc497 100755 --- a/packages/strapi-plugin-content-manager/admin/src/components/TableRow/styles.scss +++ b/packages/strapi-plugin-content-manager/admin/src/components/TableRow/styles.scss @@ -16,9 +16,6 @@ border-collapse: collapse; border-top: 1px solid #F1F1F2 !important; } - > td:first-child { - width: 50px; - } } .truncate { diff --git a/packages/strapi-plugin-content-manager/admin/src/components/VariableDraggableAttr/Carret.js b/packages/strapi-plugin-content-manager/admin/src/components/VariableDraggableAttr/Carret.js new file mode 100644 index 0000000000..07bf75fc09 --- /dev/null +++ b/packages/strapi-plugin-content-manager/admin/src/components/VariableDraggableAttr/Carret.js @@ -0,0 +1,22 @@ +/** + * + * Carret + */ + +import React from 'react'; +import PropTypes from 'prop-types'; +import styles from './styles.scss'; + +const Carret = ({ style }) => { + return
    ; +}; + +Carret.defaultProps = { + style: {}, +}; + +Carret.propTypes = { + style: PropTypes.object, +}; + +export default Carret; \ No newline at end of file diff --git a/packages/strapi-plugin-content-manager/admin/src/components/VariableDraggableAttr/index.js b/packages/strapi-plugin-content-manager/admin/src/components/VariableDraggableAttr/index.js new file mode 100644 index 0000000000..65e0b6e419 --- /dev/null +++ b/packages/strapi-plugin-content-manager/admin/src/components/VariableDraggableAttr/index.js @@ -0,0 +1,338 @@ +/** + * + * VariableDraggableAttr + */ + +/* eslint-disable react/no-find-dom-node */ +import React from 'react'; +import PropTypes from 'prop-types'; +import { findDOMNode } from 'react-dom'; +import { + DragSource, + DropTarget, +} from 'react-dnd'; +import { getEmptyImage } from 'react-dnd-html5-backend'; +import { get, flow } from 'lodash'; +import cn from 'classnames'; +import ClickOverHint from 'components/ClickOverHint'; +import DraggedRemovedIcon from 'components/DraggedRemovedIcon'; +import VariableEditIcon from 'components/VariableEditIcon'; +import ItemTypes from 'utils/ItemTypes'; + +import GrabIconBlue from 'assets/images/icon_grab_blue.svg'; +import GrabIcon from 'assets/images/icon_grab.svg'; + +import Carret from './Carret'; +import styles from './styles.scss'; + +const getBootstrapClass = attrType => { + switch(attrType) { + case 'checkbox': + case 'boolean': + case 'toggle': + case 'date': + case 'bigint': + case 'decimal': + case 'float': + case 'integer': + case 'number': + return { + bootstrap: 'col-md-4', + wrapper: cn(styles.attrWrapper), + withLongerHeight: false, + }; + case 'json': + case 'wysiwyg': + case 'WYSIWYG': + return { + bootstrap: 'col-md-12', + wrapper: cn(styles.attrWrapper, styles.customHeight), + withLongerHeight: true, + }; + case 'file': + case 'text': + return { + bootstrap: 'col-md-6', + wrapper: cn(styles.attrWrapper, styles.customHeight), + withLongerHeight: true, + }; + default: + return { + bootstrap: 'col-md-6', + wrapper: cn(styles.attrWrapper), + withLongerHeight: false, + }; + } +}; +const variableDraggableAttrSource = { + beginDrag: (props, monitor, component) => { + props.beginMove(props.name, props.index, props.keys); + + return { + component, + id: props.id, + index: props.index, + }; + }, + endDrag: props => { + props.endMove(props.keys); + return {}; + }, +}; +const variableDraggableAttrTarget = { + hover: (props, monitor, component) => { + const dragIndex = monitor.getItem().index; + const hoverIndex = props.index; + + // Don't replace items with themselves + if (dragIndex === hoverIndex) { + return; + } + + // Determine rectangle on screen + const hoverBoundingRect = findDOMNode(component).getBoundingClientRect(); + + // Get vertical middle + const hoverMiddleY = (hoverBoundingRect.bottom - hoverBoundingRect.top) / 2; + + // Determine mouse position + const clientOffset = monitor.getClientOffset(); + + // Get pixels to the top + const hoverClientY = clientOffset.y - hoverBoundingRect.top; + + // Only perform the move when the mouse has crossed half of the items height + // When dragging downwards, only move when the cursor is below 50% + // When dragging upwards, only move when the cursor is above 50% + + // Dragging downwards + if (dragIndex < hoverIndex && hoverClientY < hoverMiddleY) { + return; + } + + // Dragging upwards + if (dragIndex > hoverIndex && hoverClientY > hoverMiddleY) { + return; + } + + // Time to actually perform the action + props.moveAttr(dragIndex, hoverIndex, props.keys); + + // Note: we're mutating the monitor item here! + // Generally it's better to avoid mutations, + // but it's good here for the sake of performance + // to avoid expensive index searches. + monitor.getItem().index = hoverIndex; + }, +}; + +class VariableDraggableAttr extends React.Component { + constructor(props) { + super(props); + const { data, layout, name } = this.props; + const appearance = get(layout, [name, 'appearance'], ''); + const type = appearance !== '' ? appearance : data.type; + let classNames = getBootstrapClass(type); + let style = {}; + + if (!type) { + style = { display: 'none' }; + classNames = { + bootstrap: name.split('__')[1], + wrapper: cn(styles.attrWrapper), + withLongerHeight: false, + }; + } + this.state = { + classNames, + dragStart: false, + isOver: false, + style, + }; + } + + componentDidMount() { + // Use empty image as a drag preview so browsers don't draw it + // and we can draw whatever we want on the custom drag layer instead. + this.props.connectDragPreview(getEmptyImage(), {}); + } + + componentDidUpdate(prevProps) { + if (prevProps.isDragging !== this.props.isDragging) { + this.handleDragEffect(); + } + + if (prevProps.isDragging !== this.props.isDragging && this.props.isDragging) { + this.handleClickEdit(); + } + } + + handleClickEdit = () => { + this.props.onClickEdit(this.props.index); + } + + handleDragEffect = () => this.setState(prevState => ({ dragStart: !prevState.dragStart })); + + handleMouseEnter= () => { + this.setState({ isOver: true }); + } + + handleMouseLeave = () => this.setState({ isOver: false }); + + handleRemove = (e) => { + e.preventDefault(); + e.stopPropagation(); + const { index, keys, onRemove } = this.props; + onRemove(index, keys); + } + + renderContent = () => { + let { classNames, isOver, style, dragStart } = this.state; + const { data, draggedItemName, grid, hoverIndex, index, initDragLine, isEditing, name } = this.props; + const isFullSize = classNames.bootstrap === 'col-md-12'; + let itemLine = -1; + let itemLineEls = []; + // Retrieve from the grid the attr's y coordinate + grid.forEach((line, index) => { + if (line.indexOf(name) !== -1) { + itemLine = index; + itemLineEls = line; + } + }); + // Retrieve from the grid the attr's x coordinate + const itemPosition = get(grid, itemLine, []).indexOf(name); + // Retrieve the draggedItem's y coordinate in order to display a custom dropTarget (the blue caret). + const draggedItemLineIndex = get(grid, itemLine, []).indexOf(draggedItemName); + // The source target can either located on the left or right of an attr + let showLeftCarret = hoverIndex === index && initDragLine !== itemLine; + let showRightCarret = hoverIndex === index && initDragLine === itemLine; + + if (hoverIndex === index && initDragLine === itemLine && (itemPosition === 0 || itemPosition === 1 && itemLineEls.length > 2)) { + if (itemLineEls.length < 3 || itemPosition === 0 || draggedItemLineIndex > itemPosition) { + showLeftCarret = true; + showRightCarret = false; + } + } + + /** + * Retrieve the blue Caret custom style depending on its position and attr's height + */ + const carretStyle = (() => { + let style = { height: '30px', marginRight: '3px' }; + + if (classNames.withLongerHeight) { + style = { height: '84px', marginRight: '3px' }; + } + + if (isFullSize) { + style = { width: '100%', height: '2px', marginBottom: '6px' }; + } + + if (showRightCarret) { + style = { height: '30px', marginLeft: '3px' }; + } + + return style; + })(); + + // If the draggedItem is full size, for instance the WYSIWYG or the json field return a full size blue caret + if (dragStart && isFullSize) { + return ; + } + + return ( +
    + { showLeftCarret && } +
    + Grab Icon + + {name} + + + {(!isOver || isEditing) && get(data, 'name', '').toLowerCase() !== get(data, 'label', '').toLowerCase() && ( +
    + {data.label} +
    + )} + {isEditing && !isOver ? ( + + ) : ( + + )} +
    + { showRightCarret && } +
    + ); + } + + render() { + const { classNames } = this.state; + const { + connectDragSource, + connectDropTarget, + } = this.props; + + return ( + connectDragSource( + connectDropTarget( +
    + {this.renderContent()} +
    + ), + ) + ); + } +} + +VariableDraggableAttr.defaultProps = { + data: { + type: 'text', + }, + draggedItemName: null, + grid: [], + hoverIndex: -1, + index: 0, + initDragLine: -1, + isDragging: false, + isEditing: false, + keys: '', + layout: {}, + name: '', + onClickEdit: () => {}, + onRemove: () => {}, +}; + +VariableDraggableAttr.propTypes = { + connectDragPreview: PropTypes.func.isRequired, + connectDragSource: PropTypes.func.isRequired, + connectDropTarget: PropTypes.func.isRequired, + data: PropTypes.object, + draggedItemName: PropTypes.string, + grid: PropTypes.array, + hoverIndex: PropTypes.number, + index: PropTypes.number, + initDragLine: PropTypes.number, + isDragging: PropTypes.bool, + isEditing: PropTypes.bool, + keys: PropTypes.string, + layout: PropTypes.object, + name: PropTypes.string, + onClickEdit: PropTypes.func, + onRemove: PropTypes.func, +}; + +const withDropTarget = DropTarget(ItemTypes.VARIABLE, variableDraggableAttrTarget, (connect) => ({ + connectDropTarget: connect.dropTarget(), +})); +const withDragSource = DragSource(ItemTypes.VARIABLE, variableDraggableAttrSource, (connect, monitor) => ({ + connectDragSource: connect.dragSource(), + connectDragPreview: connect.dragPreview(), + isDragging: monitor.isDragging(), +})); + +export default flow([withDropTarget, withDragSource])(VariableDraggableAttr); \ No newline at end of file diff --git a/packages/strapi-plugin-content-manager/admin/src/components/VariableDraggableAttr/styles.scss b/packages/strapi-plugin-content-manager/admin/src/components/VariableDraggableAttr/styles.scss new file mode 100644 index 0000000000..03a64ff697 --- /dev/null +++ b/packages/strapi-plugin-content-manager/admin/src/components/VariableDraggableAttr/styles.scss @@ -0,0 +1,95 @@ +.attrWrapper { + position: relative; + height: 30px; + width: 100%; + margin-bottom: 6px; + display: flex; + padding-left: 10px; + justify-content: space-between; + background: #FAFAFB; + line-height: 28px; + color: #333740; + border: 1px solid #E3E9F3; + border-radius: 2px; + &:hover{ + cursor: move; + } + > img { + align-self: flex-start; + vertical-align: top; + max-width: 8px; + margin-right: 10px; + margin-top: 11px; + } +} + +.customHeight { + height: 84px !important; +} + +.editIcon { + width: 30px; + background: #E6F0FB; + height: 84px; + cursor: pointer; + text-align: center; + float: right; + + &:after { + display: inline-block; + content: ''; + width: 10px; + height: 10px; + margin: auto; + margin-top: -3px; + background-image: url('../../assets/images/icon-edit-blue.svg'); + } +} + +.editingVariableAttr { + background: #E6F0FB!important; + border: 1px solid #AED4FB!important; +} + +.info { + position: absolute; + top: 0; + right: 40px; + color: #B4B6BA; + font-style: italic; +} + +.infoLabel { + position: absolute; + top: 0; + right: 40px; + color: #858B9A; + font-weight: 400; +} + +.infoLabelHover{ + color: #007EFF; +} + +.truncated { + overflow: hidden; + white-space:nowrap; + text-overflow:ellipsis; + width: calc(100% - 50px); + display: block; +} + +.shrink { + transform: scale(0.5); +} + +.carret { + width: 2px; + border-radius: 2px; + background: #007EFF; +} + +.editing { + color: #007EFF; + font-weight: 500; +} \ No newline at end of file diff --git a/packages/strapi-plugin-content-manager/admin/src/components/VariableEditIcon/index.js b/packages/strapi-plugin-content-manager/admin/src/components/VariableEditIcon/index.js new file mode 100644 index 0000000000..e309818312 --- /dev/null +++ b/packages/strapi-plugin-content-manager/admin/src/components/VariableEditIcon/index.js @@ -0,0 +1,31 @@ +/** + * + * VariableEditIcon + */ + +import React from 'react'; +import PropTypes from 'prop-types'; +import cn from 'classnames'; +import styles from './styles.scss'; + +function VariableEditIcon({ onClick, withLongerHeight, ...rest }) { + return ( + + ); +} + +VariableEditIcon.defaultProps = { + onClick: () => {}, + withLongerHeight: false, +}; + +VariableEditIcon.propTypes = { + onClick: PropTypes.func, + withLongerHeight: PropTypes.bool, +}; + +export default VariableEditIcon; \ No newline at end of file diff --git a/packages/strapi-plugin-content-manager/admin/src/components/VariableEditIcon/styles.scss b/packages/strapi-plugin-content-manager/admin/src/components/VariableEditIcon/styles.scss new file mode 100644 index 0000000000..04fc0b97b2 --- /dev/null +++ b/packages/strapi-plugin-content-manager/admin/src/components/VariableEditIcon/styles.scss @@ -0,0 +1,37 @@ +.editIcon { + width: 30px; + background: #AED4FB; + height: 28px; + cursor: pointer; + text-align: center; + float: right; + + &:after { + display: inline-block; + content: ''; + width: 10px; + height: 10px; + margin: auto; + margin-top: -3px; + background-image: url('../../assets/images/icon-edit-blue.svg'); + } +} + +.editIconLonger { + width: 30px; + background:#AED4FB; + height: 82px; + line-height: 82px; + cursor: pointer; + text-align: center; + float: right; + + &:after { + display: inline-block; + content: ''; + width: 10px; + height: 10px; + margin: auto; + background-image: url('../../assets/images/icon-edit-blue.svg'); + } +} \ No newline at end of file diff --git a/packages/strapi-helper-plugin/lib/src/components/Wysiwyg/componentsStyles.scss b/packages/strapi-plugin-content-manager/admin/src/components/Wysiwyg/componentsStyles.scss similarity index 100% rename from packages/strapi-helper-plugin/lib/src/components/Wysiwyg/componentsStyles.scss rename to packages/strapi-plugin-content-manager/admin/src/components/Wysiwyg/componentsStyles.scss diff --git a/packages/strapi-helper-plugin/lib/src/components/Wysiwyg/constants.js b/packages/strapi-plugin-content-manager/admin/src/components/Wysiwyg/constants.js similarity index 100% rename from packages/strapi-helper-plugin/lib/src/components/Wysiwyg/constants.js rename to packages/strapi-plugin-content-manager/admin/src/components/Wysiwyg/constants.js diff --git a/packages/strapi-helper-plugin/lib/src/components/Wysiwyg/converter.js b/packages/strapi-plugin-content-manager/admin/src/components/Wysiwyg/converter.js similarity index 100% rename from packages/strapi-helper-plugin/lib/src/components/Wysiwyg/converter.js rename to packages/strapi-plugin-content-manager/admin/src/components/Wysiwyg/converter.js diff --git a/packages/strapi-helper-plugin/lib/src/components/Wysiwyg/customSelect.js b/packages/strapi-plugin-content-manager/admin/src/components/Wysiwyg/customSelect.js similarity index 100% rename from packages/strapi-helper-plugin/lib/src/components/Wysiwyg/customSelect.js rename to packages/strapi-plugin-content-manager/admin/src/components/Wysiwyg/customSelect.js diff --git a/packages/strapi-helper-plugin/lib/src/components/Wysiwyg/helpers.js b/packages/strapi-plugin-content-manager/admin/src/components/Wysiwyg/helpers.js similarity index 100% rename from packages/strapi-helper-plugin/lib/src/components/Wysiwyg/helpers.js rename to packages/strapi-plugin-content-manager/admin/src/components/Wysiwyg/helpers.js diff --git a/packages/strapi-helper-plugin/lib/src/components/Wysiwyg/image.js b/packages/strapi-plugin-content-manager/admin/src/components/Wysiwyg/image.js similarity index 100% rename from packages/strapi-helper-plugin/lib/src/components/Wysiwyg/image.js rename to packages/strapi-plugin-content-manager/admin/src/components/Wysiwyg/image.js diff --git a/packages/strapi-helper-plugin/lib/src/components/Wysiwyg/index.js b/packages/strapi-plugin-content-manager/admin/src/components/Wysiwyg/index.js similarity index 100% rename from packages/strapi-helper-plugin/lib/src/components/Wysiwyg/index.js rename to packages/strapi-plugin-content-manager/admin/src/components/Wysiwyg/index.js diff --git a/packages/strapi-helper-plugin/lib/src/components/Wysiwyg/link.js b/packages/strapi-plugin-content-manager/admin/src/components/Wysiwyg/link.js similarity index 100% rename from packages/strapi-helper-plugin/lib/src/components/Wysiwyg/link.js rename to packages/strapi-plugin-content-manager/admin/src/components/Wysiwyg/link.js diff --git a/packages/strapi-helper-plugin/lib/src/components/Wysiwyg/previewControl.js b/packages/strapi-plugin-content-manager/admin/src/components/Wysiwyg/previewControl.js similarity index 100% rename from packages/strapi-helper-plugin/lib/src/components/Wysiwyg/previewControl.js rename to packages/strapi-plugin-content-manager/admin/src/components/Wysiwyg/previewControl.js diff --git a/packages/strapi-helper-plugin/lib/src/components/Wysiwyg/previewWysiwyg.js b/packages/strapi-plugin-content-manager/admin/src/components/Wysiwyg/previewWysiwyg.js similarity index 100% rename from packages/strapi-helper-plugin/lib/src/components/Wysiwyg/previewWysiwyg.js rename to packages/strapi-plugin-content-manager/admin/src/components/Wysiwyg/previewWysiwyg.js diff --git a/packages/strapi-helper-plugin/lib/src/components/Wysiwyg/strategies.js b/packages/strapi-plugin-content-manager/admin/src/components/Wysiwyg/strategies.js similarity index 100% rename from packages/strapi-helper-plugin/lib/src/components/Wysiwyg/strategies.js rename to packages/strapi-plugin-content-manager/admin/src/components/Wysiwyg/strategies.js diff --git a/packages/strapi-helper-plugin/lib/src/components/Wysiwyg/styles.scss b/packages/strapi-plugin-content-manager/admin/src/components/Wysiwyg/styles.scss similarity index 100% rename from packages/strapi-helper-plugin/lib/src/components/Wysiwyg/styles.scss rename to packages/strapi-plugin-content-manager/admin/src/components/Wysiwyg/styles.scss diff --git a/packages/strapi-helper-plugin/lib/src/components/Wysiwyg/toggleMode.js b/packages/strapi-plugin-content-manager/admin/src/components/Wysiwyg/toggleMode.js similarity index 100% rename from packages/strapi-helper-plugin/lib/src/components/Wysiwyg/toggleMode.js rename to packages/strapi-plugin-content-manager/admin/src/components/Wysiwyg/toggleMode.js diff --git a/packages/strapi-helper-plugin/lib/src/components/Wysiwyg/utils.js b/packages/strapi-plugin-content-manager/admin/src/components/Wysiwyg/utils.js similarity index 100% rename from packages/strapi-helper-plugin/lib/src/components/Wysiwyg/utils.js rename to packages/strapi-plugin-content-manager/admin/src/components/Wysiwyg/utils.js diff --git a/packages/strapi-helper-plugin/lib/src/components/Wysiwyg/video.js b/packages/strapi-plugin-content-manager/admin/src/components/Wysiwyg/video.js similarity index 100% rename from packages/strapi-helper-plugin/lib/src/components/Wysiwyg/video.js rename to packages/strapi-plugin-content-manager/admin/src/components/Wysiwyg/video.js diff --git a/packages/strapi-helper-plugin/lib/src/components/WysiwygBottomControls/index.js b/packages/strapi-plugin-content-manager/admin/src/components/WysiwygBottomControls/index.js similarity index 100% rename from packages/strapi-helper-plugin/lib/src/components/WysiwygBottomControls/index.js rename to packages/strapi-plugin-content-manager/admin/src/components/WysiwygBottomControls/index.js diff --git a/packages/strapi-helper-plugin/lib/src/components/WysiwygBottomControls/styles.scss b/packages/strapi-plugin-content-manager/admin/src/components/WysiwygBottomControls/styles.scss similarity index 100% rename from packages/strapi-helper-plugin/lib/src/components/WysiwygBottomControls/styles.scss rename to packages/strapi-plugin-content-manager/admin/src/components/WysiwygBottomControls/styles.scss diff --git a/packages/strapi-helper-plugin/lib/src/components/WysiwygDropUpload/index.js b/packages/strapi-plugin-content-manager/admin/src/components/WysiwygDropUpload/index.js similarity index 100% rename from packages/strapi-helper-plugin/lib/src/components/WysiwygDropUpload/index.js rename to packages/strapi-plugin-content-manager/admin/src/components/WysiwygDropUpload/index.js diff --git a/packages/strapi-helper-plugin/lib/src/components/WysiwygDropUpload/styles.scss b/packages/strapi-plugin-content-manager/admin/src/components/WysiwygDropUpload/styles.scss similarity index 100% rename from packages/strapi-helper-plugin/lib/src/components/WysiwygDropUpload/styles.scss rename to packages/strapi-plugin-content-manager/admin/src/components/WysiwygDropUpload/styles.scss diff --git a/packages/strapi-helper-plugin/lib/src/components/WysiwygEditor/index.js b/packages/strapi-plugin-content-manager/admin/src/components/WysiwygEditor/index.js similarity index 100% rename from packages/strapi-helper-plugin/lib/src/components/WysiwygEditor/index.js rename to packages/strapi-plugin-content-manager/admin/src/components/WysiwygEditor/index.js diff --git a/packages/strapi-helper-plugin/lib/src/components/WysiwygInlineControls/index.js b/packages/strapi-plugin-content-manager/admin/src/components/WysiwygInlineControls/index.js similarity index 100% rename from packages/strapi-helper-plugin/lib/src/components/WysiwygInlineControls/index.js rename to packages/strapi-plugin-content-manager/admin/src/components/WysiwygInlineControls/index.js diff --git a/packages/strapi-helper-plugin/lib/src/components/WysiwygInlineControls/styles.scss b/packages/strapi-plugin-content-manager/admin/src/components/WysiwygInlineControls/styles.scss similarity index 100% rename from packages/strapi-helper-plugin/lib/src/components/WysiwygInlineControls/styles.scss rename to packages/strapi-plugin-content-manager/admin/src/components/WysiwygInlineControls/styles.scss diff --git a/packages/strapi-helper-plugin/lib/src/components/WysiwygWithErrors/Loadable.js b/packages/strapi-plugin-content-manager/admin/src/components/WysiwygWithErrors/Loadable.js similarity index 100% rename from packages/strapi-helper-plugin/lib/src/components/WysiwygWithErrors/Loadable.js rename to packages/strapi-plugin-content-manager/admin/src/components/WysiwygWithErrors/Loadable.js diff --git a/packages/strapi-helper-plugin/lib/src/components/WysiwygWithErrors/Loader.js b/packages/strapi-plugin-content-manager/admin/src/components/WysiwygWithErrors/Loader.js similarity index 100% rename from packages/strapi-helper-plugin/lib/src/components/WysiwygWithErrors/Loader.js rename to packages/strapi-plugin-content-manager/admin/src/components/WysiwygWithErrors/Loader.js diff --git a/packages/strapi-helper-plugin/lib/src/components/WysiwygWithErrors/index.js b/packages/strapi-plugin-content-manager/admin/src/components/WysiwygWithErrors/index.js similarity index 100% rename from packages/strapi-helper-plugin/lib/src/components/WysiwygWithErrors/index.js rename to packages/strapi-plugin-content-manager/admin/src/components/WysiwygWithErrors/index.js diff --git a/packages/strapi-helper-plugin/lib/src/components/WysiwygWithErrors/styles.scss b/packages/strapi-plugin-content-manager/admin/src/components/WysiwygWithErrors/styles.scss similarity index 100% rename from packages/strapi-helper-plugin/lib/src/components/WysiwygWithErrors/styles.scss rename to packages/strapi-plugin-content-manager/admin/src/components/WysiwygWithErrors/styles.scss diff --git a/packages/strapi-plugin-content-manager/admin/src/containers/App/actions.js b/packages/strapi-plugin-content-manager/admin/src/containers/App/actions.js index 5a4bbb306c..1c04520f82 100755 --- a/packages/strapi-plugin-content-manager/admin/src/containers/App/actions.js +++ b/packages/strapi-plugin-content-manager/admin/src/containers/App/actions.js @@ -4,21 +4,53 @@ * */ +import { includes } from 'lodash'; import { + BEGIN_MOVE, EMPTY_STORE, + END_MOVE, GET_MODEL_ENTRIES, GET_MODEL_ENTRIES_SUCCEEDED, LOAD_MODELS, LOADED_MODELS, - UPDATE_SCHEMA, + MOVE_ATTR, + MOVE_ATTR_EDIT_VIEW, + MOVE_VARIABLE_ATTR_EDIT_VIEW, + ON_CHANGE, + ON_CHANGE_SETTINGS, + ON_CLICK_ADD_ATTR, + ON_CLICK_ADD_ATTR_FIELD, + ON_REMOVE, + ON_REMOVE_EDIT_VIEW_FIELD_ATTR, + ON_REMOVE_EDIT_VIEW_RELATION_ATTR, + ON_RESET, + ON_SUBMIT, + SET_LAYOUT, + SUBMIT_SUCCEEDED, } from './constants'; +export function beginMove(name, index, keys) { + return { + type: BEGIN_MOVE, + name, + index, + keys, + }; +} + export function emptyStore() { return { type: EMPTY_STORE, }; } +export function endMove(keys) { + return { + type: END_MOVE, + keys, + }; +} + export function getModelEntries(modelName, source) { return { type: GET_MODEL_ENTRIES, @@ -47,9 +79,114 @@ export function loadedModels(models) { }; } -export function updateSchema(schema) { +export function moveAttr(dragIndex, hoverIndex, keys) { return { - type: UPDATE_SCHEMA, - schema, + type: MOVE_ATTR, + dragIndex, + hoverIndex, + keys, }; } + +export function moveAttrEditView(dragIndex, hoverIndex, keys) { + return { + type: MOVE_ATTR_EDIT_VIEW, + dragIndex, + hoverIndex, + keys, + }; +} + +export function moveVariableAttrEditView(dragIndex, hoverIndex, keys) { + return { + type: MOVE_VARIABLE_ATTR_EDIT_VIEW, + dragIndex, + hoverIndex, + keys, + }; +} + +export function onChange({ target }) { + const value = includes(target.name, 'pageEntries') ? parseInt(target.value, 10) : target.value; + + return { + type: ON_CHANGE, + keys: target.name.split('.'), + value, + }; +} + +export function onChangeSettings({ target }) { + const value = includes(target.name, 'pageEntries') ? parseInt(target.value, 10) : target.value; + + return { + type: ON_CHANGE_SETTINGS, + keys: target.name.split('.'), + value, + }; +} + +export function onClickAddAttr(data, keys) { + return { + type: ON_CLICK_ADD_ATTR, + data, + keys, + }; +} + +export function onClickAddAttrField(data, keys) { + return { + type: ON_CLICK_ADD_ATTR_FIELD, + data, + keys, + }; +} + +export function onRemove(index, keys) { + return { + type: ON_REMOVE, + index, + keys, + }; +} + +export function onRemoveEditViewFieldAttr(index, keys) { + return { + type: ON_REMOVE_EDIT_VIEW_FIELD_ATTR, + index, + keys, + }; +} + +export function onRemoveEditViewRelationAttr(index, keys) { + return { + type: ON_REMOVE_EDIT_VIEW_RELATION_ATTR, + index, + keys, + }; +} + +export function onReset() { + return { + type: ON_RESET, + }; +} + +export function onSubmit() { + return { + type: ON_SUBMIT, + }; +} + +export function setLayout(keys) { + return { + type: SET_LAYOUT, + keys, + }; +} + +export function submitSucceeded() { + return { + type: SUBMIT_SUCCEEDED, + }; +} \ No newline at end of file diff --git a/packages/strapi-plugin-content-manager/admin/src/containers/App/constants.js b/packages/strapi-plugin-content-manager/admin/src/containers/App/constants.js index fd3bc00938..195ff2ea2c 100755 --- a/packages/strapi-plugin-content-manager/admin/src/containers/App/constants.js +++ b/packages/strapi-plugin-content-manager/admin/src/containers/App/constants.js @@ -4,9 +4,24 @@ * */ +export const BEGIN_MOVE = 'contentManager/App/BEGIN_MOVE'; export const EMPTY_STORE = 'contentManager/App/EMPTY_STORE'; +export const END_MOVE = 'contentManager/App/END_MOVE'; export const GET_MODEL_ENTRIES = 'contentManager/App/GET_MODEL_ENTRIES'; export const GET_MODEL_ENTRIES_SUCCEEDED = 'contentManager/App/GET_MODEL_ENTRIES_SUCCEEDED'; export const LOAD_MODELS = 'contentManager/App/LOAD_MODELS'; export const LOADED_MODELS = 'contentManager/App/LOADED_MODELS'; -export const UPDATE_SCHEMA = 'contentManager/App/UPDATE_SCHEMA'; +export const MOVE_ATTR = 'contentManager/App/MOVE_ATTR'; +export const MOVE_ATTR_EDIT_VIEW = 'contentManager/App/MOVE_ATTR_EDIT_VIEW'; +export const MOVE_VARIABLE_ATTR_EDIT_VIEW = 'contentManager/App/MOVE_VARIABLE_ATTR_EDIT_VIEW'; +export const ON_CHANGE = 'contentManager/App/ON_CHANGE'; +export const ON_CHANGE_SETTINGS = 'contentManager/App/ON_CHANGE_SETTINGS'; +export const ON_CLICK_ADD_ATTR = 'contentManager/App/ON_CLICK_ADD_ATTR'; +export const ON_CLICK_ADD_ATTR_FIELD = 'contentManager/App/ON_CLICK_ADD_ATTR_FIELD'; +export const ON_REMOVE = 'contentManager/App/ON_REMOVE'; +export const ON_REMOVE_EDIT_VIEW_FIELD_ATTR = 'contentManager/App/ON_REMOVE_EDIT_VIEW_FIELD_ATTR'; +export const ON_REMOVE_EDIT_VIEW_RELATION_ATTR = 'contentManager/App/ON_REMOVE_EDIT_VIEW_RELATION_ATTR'; +export const ON_RESET = 'contentManager/App/ON_RESET'; +export const ON_SUBMIT = 'contentManager/App/ON_SUBMIT'; +export const SET_LAYOUT = 'contentManager/App/SET_LAYOUT'; +export const SUBMIT_SUCCEEDED = 'contentManager/App/SUBMIT_SUCCEEDED'; diff --git a/packages/strapi-plugin-content-manager/admin/src/containers/App/helpers.js b/packages/strapi-plugin-content-manager/admin/src/containers/App/helpers.js new file mode 100644 index 0000000000..1b1df7ae34 --- /dev/null +++ b/packages/strapi-plugin-content-manager/admin/src/containers/App/helpers.js @@ -0,0 +1,145 @@ +import { List } from 'immutable'; +import { flattenDeep, get, range } from 'lodash'; +import Manager from 'utils/Manager'; + +/** + * Update an object with new data + * @param {fromJS} obj + * @param {List} array + * @param {String} keys + */ +const stateUpdater = (obj, array, keys) => obj.updateIn(['modifiedSchema', 'models', ...keys.split('.'), 'fields'], () => array); + +/** + * Create a Manager class + * @param {fromJS} obj + * @param {List} array + * @param {String} keys + * @param {Number} dropIndex + * @param {Map || Object} layout + */ +const createManager = (obj, array, keys, dropIndex, layout) => new Manager(stateUpdater(obj, array, keys), array, keys, dropIndex, layout); + +/** + * Retrieve the elements of a line from the bootstrap grid + * @param {Class} manager + * @param {Number} line + * @param {List} list + */ +const getElementsOnALine = (manager, line, list) => { + const firstElIndex = line === 0 ? 0 : get(manager.arrayOfEndLineElements[line - 1], 'index', list.size -1) + 1; + const lastElIndex = get(manager.arrayOfEndLineElements[line], 'index', list.size -1) + 1; + const elements = manager.getElementsOnALine(range(firstElIndex, lastElIndex)); + + return { elements, lastElIndex }; +}; + +/** + * Retrieve the last elements of each line of a bootstrap grid + * @param {Class} manager + * @param {List} list + */ +const createArrayOfLastEls = (manager, list) => { + const { name, index, bootstrapCol } = manager.getAttrInfos(list.size - 1); + const isFullSize = bootstrapCol === 12; + + return manager.arrayOfEndLineElements.concat({ name, index, isFullSize }); +}; + +/** + * Remove each line composed of added elements that keeps the layout organised + * A line may look like this [__col-md-4, __col-md-4, __col-md_4] + * @param {Class} manager + * @param {List} list + * @returns {List} + */ +const removeColsLine = (manager, list) => { + let addedElsToRemove = []; + const arrayOfEndLineElements = createArrayOfLastEls(manager, list); + + arrayOfEndLineElements.forEach((item, i) => { + if (i < arrayOfEndLineElements.length) { + const firstElementOnLine = i === 0 ? 0 : arrayOfEndLineElements[i - 1].index + 1; + const lastElementOnLine = arrayOfEndLineElements[i].index; + const rangeIndex = range(firstElementOnLine, lastElementOnLine + 1); + const elementsOnLine = manager.getElementsOnALine(rangeIndex) + .filter(name => !name.includes('__col')); + + if (elementsOnLine.length === 0) { + addedElsToRemove = addedElsToRemove.concat(rangeIndex); + } + } + }); + + return list.filter((item, index) => { + const indexToKeep = addedElsToRemove.indexOf(index) === -1; + + return indexToKeep; + }); +}; + +/** + * Make sure each line of the bootstrap ends with the added elements (__col-md-something) so we can't have blank space at the begining of a line + * These method also ensure we have unique added element name + * @param {Class} manager + * @param {List} list + */ +const reorderList = (manager, list) => { + const lines = getLines(manager, list); + const reordered = lines + .reduce((acc, curr) => { + const line = curr.reduce((acc, current, index) => { + if (current && current.includes('__col-md')) { + acc.splice(index, 1); + acc.splice(curr.length -1, 0, current); + } + + return acc; + }, [...curr]); + + return acc.concat(line); + }, []) + .filter(a => a !== undefined); + + // Make sure each added element is unique by name since the name of an element is used as key in the rdnd container + const uniqueIdList = reordered.reduce((acc, current, index) => { + if (reordered.indexOf(current) === index) { + acc.push(current); + } else { + const bootstrapCol = parseInt(current.split('__')[1].split('-')[2], 10); + const random = Math.random().toString(36).substring(7); + acc.push(`__col-md-${bootstrapCol}__${random}`); + } + + return acc; + }, []); + + return List(flattenDeep(uniqueIdList)); +}; + +/** + * Retrieve the elements displayed on each line of the bootstrap grid + * @param {Class} manager + * @param {List} list + * @returns {Array} + */ +const getLines = (manager, list) => { + const array = createArrayOfLastEls(manager, list); + const lines = []; + + array.forEach((item, i) => { + const { elements } = getElementsOnALine(manager, i, list); + lines.push(elements); + }); + + return lines; +}; + +export { + createArrayOfLastEls, + createManager, + getElementsOnALine, + getLines, + removeColsLine, + reorderList, +}; \ No newline at end of file diff --git a/packages/strapi-plugin-content-manager/admin/src/containers/App/index.js b/packages/strapi-plugin-content-manager/admin/src/containers/App/index.js index bdce95e523..c331419502 100755 --- a/packages/strapi-plugin-content-manager/admin/src/containers/App/index.js +++ b/packages/strapi-plugin-content-manager/admin/src/containers/App/index.js @@ -16,16 +16,17 @@ import { Switch, Route } from 'react-router-dom'; import injectSaga from 'utils/injectSaga'; import getQueryParameters from 'utils/getQueryParameters'; -import Home from 'containers/Home'; import EditPage from 'containers/EditPage'; import ListPage from 'containers/ListPage'; +import SettingsPage from 'containers/SettingsPage'; +import SettingPage from 'containers/SettingPage'; import LoadingIndicatorPage from 'components/LoadingIndicatorPage'; import EmptyAttributesView from 'components/EmptyAttributesView'; import { loadModels, } from './actions'; -import { makeSelectLoading, makeSelectModels, makeSelectModelEntries } from './selectors'; +import { makeSelectLoading, makeSelectModelEntries, makeSelectSchema } from './selectors'; import saga from './sagas'; @@ -41,19 +42,19 @@ class App extends React.Component { const currentModelName = this.props.location.pathname.split('/')[3]; const source = getQueryParameters(this.props.location.search, 'source'); + const attrPath = source === 'content-manager' ? ['models', currentModelName, 'fields'] : ['models', 'plugins', source, currentModelName, 'fields']; - if (currentModelName && source && isEmpty(get(this.props.models.plugins, [source, 'models', currentModelName, 'attributes']))) { - if (currentModelName && isEmpty(get(this.props.models.models, [currentModelName, 'attributes']))) { - return ; - } + if (currentModelName && source && isEmpty(get(this.props.schema, attrPath))) { + return ; } return (
    + + -
    ); @@ -70,7 +71,7 @@ App.propTypes = { loadModels: PropTypes.func.isRequired, location: PropTypes.object.isRequired, modelEntries: PropTypes.number.isRequired, - models: PropTypes.oneOfType([ + schema: PropTypes.oneOfType([ PropTypes.bool, PropTypes.object, ]).isRequired, @@ -88,7 +89,7 @@ export function mapDispatchToProps(dispatch) { const mapStateToProps = createStructuredSelector({ loading: makeSelectLoading(), modelEntries: makeSelectModelEntries(), - models: makeSelectModels(), + schema: makeSelectSchema(), }); const withConnect = connect(mapStateToProps, mapDispatchToProps); diff --git a/packages/strapi-plugin-content-manager/admin/src/containers/App/reducer.js b/packages/strapi-plugin-content-manager/admin/src/containers/App/reducer.js index 9ed373bfe9..3ec801256b 100755 --- a/packages/strapi-plugin-content-manager/admin/src/containers/App/reducer.js +++ b/packages/strapi-plugin-content-manager/admin/src/containers/App/reducer.js @@ -1,39 +1,392 @@ /* * - * List reducer + * App reducer * */ import { fromJS, List } from 'immutable'; - -import { EMPTY_STORE, GET_MODEL_ENTRIES_SUCCEEDED, LOAD_MODELS, LOADED_MODELS, UPDATE_SCHEMA } from './constants'; +import { difference, findIndex, get, range, upperFirst } from 'lodash'; +import Manager from 'utils/Manager'; +import { + BEGIN_MOVE, + EMPTY_STORE, + END_MOVE, + GET_MODEL_ENTRIES_SUCCEEDED, + LOAD_MODELS, + LOADED_MODELS, + MOVE_ATTR, + MOVE_ATTR_EDIT_VIEW, + MOVE_VARIABLE_ATTR_EDIT_VIEW, + ON_CHANGE, + ON_CHANGE_SETTINGS, + ON_CLICK_ADD_ATTR, + ON_CLICK_ADD_ATTR_FIELD, + ON_REMOVE, + ON_REMOVE_EDIT_VIEW_RELATION_ATTR, + ON_REMOVE_EDIT_VIEW_FIELD_ATTR, + ON_RESET, + SET_LAYOUT, + SUBMIT_SUCCEEDED, +} from './constants'; +import { + createManager, + getElementsOnALine, + getLines, + removeColsLine, + reorderList, +} from './helpers'; const initialState = fromJS({ - modelEntries: 0, + addedElementName: null, + addedField: false, + draggedItemName: null, + formValidations: List([]), + initDragLine: -1, loading: true, - models: false, - schema: false, - formValidations: List(), + modelEntries: 0, + modifiedSchema: fromJS({}), + hasMoved: false, + hoverIndex: -1, + schema: fromJS({}), + shouldUpdateListOnDrop: true, + submitSuccess: false, + grid: List([]), + shouldResetGrid: false, }); + function appReducer(state = initialState, action) { switch (action.type) { + case BEGIN_MOVE: + return state + .update('draggedItemName', () => action.name); case EMPTY_STORE: return state; + case END_MOVE: + return state + .updateIn(['modifiedSchema', 'models', ...action.keys.split('.'), 'fields'], list => { + const shouldUpdateListOnDrop = state.get('shouldUpdateListOnDrop'); + const dropIndex = state.get('hoverIndex'); + const toAdd = state.get('draggedItemName'); + const initDragLine = state.get('initDragLine'); + const canDrop = list.indexOf(toAdd) === -1; + const path = action.keys.split('.'); + const modelName = path.length > 2 ? path[2] : path[0]; + const layout = state.getIn(['modifiedSchema', 'layout', modelName, 'attributes']); + let newList = list; + // We don't need to update the list onDrop for the full size elements since it's already handled by the MOVE_VARIABLE_ATTR + if (shouldUpdateListOnDrop && canDrop) { + newList = list + .insert(dropIndex, toAdd); + + const addedElementName = state.get('addedElementName'); + const manager = createManager(state, newList, action.keys, dropIndex, layout); + const arrayOfLastLineElements = manager.arrayOfEndLineElements; + const nodeBound = manager.getBound(true); + const dropLine = findIndex(arrayOfLastLineElements, ['index', nodeBound.index]); + const addedElementIndex = newList.indexOf(addedElementName); + + // We need to remove the added element if dropping on the same line that the element was initially + if (dropLine === initDragLine) { + const toDropIndex = dropIndex > addedElementIndex ? dropIndex + 1 : dropIndex; + + newList = newList + .delete(dropIndex) + .insert(toDropIndex, toAdd) + .delete(addedElementIndex); + } + + const newManager = createManager(state, newList, action.keys, dropIndex, layout); + const { elements: previousStateLineEls } = getElementsOnALine(createManager(state, list, action.keys, dropIndex, layout), dropLine, list); + const { elements: currentStateLineEls } = getElementsOnALine(newManager, dropLine, newList); + + if (dropLine !== initDragLine) { + const diff = difference(previousStateLineEls, currentStateLineEls); + const diffLineSize = newManager.getLineSize(diff); + const lineToCreate = [...diff, ...manager.getColsToAdd(12 - diffLineSize)]; + let indexToInsert = dropIndex + 1; + + lineToCreate.forEach(item => { + const canAdd = newList.indexOf(item) === -1; + + if (canAdd) { + newList = newList.insert(indexToInsert, item); + } + indexToInsert += 1; + }); + } + const nextManager = createManager(state, newList, action.keys, dropIndex, layout); + newList = removeColsLine(nextManager, newList); + const lastManager = createManager(state, newList, action.keys, dropIndex, layout); + // Make sure all the lines are full + // This step is needed when we create a line before a full size element like + // The JSON input or the WYSIWYG + newList = createManager(state, reorderList(lastManager, newList), action.keys, dropIndex, layout).getLayout(); + } + + return newList; + }) + .update('draggedItemName', () => null) + .update('hasMoved', () => false) + .update('hoverIndex', () => -1) + .update('shouldUpdateListOnDrop', () => true) + .update('shouldResetGrid', v => !v); case GET_MODEL_ENTRIES_SUCCEEDED: return state.set('modelEntries', action.count); case LOAD_MODELS: return state; case LOADED_MODELS: return state - .set('models', action.models); - case UPDATE_SCHEMA: + .update('schema', () => fromJS(action.models.models)) + .update('modifiedSchema', () => fromJS(action.models.models)) + .set('loading', false); + case MOVE_ATTR: return state - .set('loading', false) - .set('schema', action.schema); + .updateIn(['modifiedSchema', 'models', ...action.keys.split('.'), 'listDisplay'], list => ( + list + .delete(action.dragIndex) + .insert(action.hoverIndex, list.get(action.dragIndex)) + )); + case MOVE_ATTR_EDIT_VIEW: + return state + .updateIn(['modifiedSchema', 'models', ...action.keys.split('.')], list => ( + list + .delete(action.dragIndex) + .insert(action.hoverIndex, list.get(action.dragIndex)) + )); + case MOVE_VARIABLE_ATTR_EDIT_VIEW: { + let updateHoverIndex = true; + let shouldUpdateListOnDrop = state.get('shouldUpdateListOnDrop'); + let addedElementName = null; + let initDragLine = state.get('initDragLine'); + + return state + .updateIn(['modifiedSchema', 'models', ...action.keys.split('.'), 'fields'], list => { + const draggedItemName = state.get('draggedItemName'); + const draggedItemIndex = list.indexOf(draggedItemName); + const path = action.keys.split('.'); + const modelName = path.length > 2 ? path[2] : path[0]; + const layout = state.getIn(['modifiedSchema', 'layout', modelName, 'attributes']); + const manager = new Manager(state, list, action.keys, draggedItemIndex, layout); + const arrayOfLastLineElements = manager.arrayOfEndLineElements; + const itemInfos = manager.getAttrInfos(draggedItemIndex); + const isFullSize = itemInfos.bootstrapCol === 12; + const dropLineBounds = { left: manager.getBound(false, action.hoverIndex), right: manager.getBound(true, action.hoverIndex) }; + const hasMoved = state.get('hasMoved'); // Used only for non full-width elements + + if (isFullSize && draggedItemIndex !== -1) { + const upwards = action.dragIndex > action.hoverIndex; + const indexToDrop = upwards ? get(dropLineBounds, 'left.index', 0) : get(dropLineBounds, 'right.index', list.size -1); + updateHoverIndex = false; + shouldUpdateListOnDrop = false; + + return list + .delete(draggedItemIndex) + .insert(indexToDrop, draggedItemName); + } + + // We allow the reorder for full width elements since they don't modify the current layout of the view. + // Allowing it for the other types will be impossible to reorder the view and keep the current layout. + if (!hasMoved && !isFullSize && draggedItemIndex !== -1) { + const nodeBound = manager.getBound(true); + const currentLine = findIndex(arrayOfLastLineElements, ['index', nodeBound.index]); + initDragLine = currentLine; + const random = Math.floor(Math.random() * 1000); + const toAdd = `__col-md-${itemInfos.bootstrapCol}__${random}`; + addedElementName = toAdd; + + return list + .delete(action.dragIndex) + .insert(action.dragIndex, toAdd); + } + + return list; + }) + .update('hoverIndex', () => { + if (updateHoverIndex) { + return action.hoverIndex; + } + + return -1; + }) + .update('addedElementName', name => { + if (addedElementName) { + return addedElementName; + } + + return name; + }) + .update('hasMoved', () => true) + .update('initDragLine', () => initDragLine) + .update('shouldUpdateListOnDrop', () => shouldUpdateListOnDrop); + } + case ON_CHANGE: + return state + .updateIn(['modifiedSchema'].concat(action.keys), () => action.value) + .updateIn(['modifiedSchema', 'models'], models => { + return models + .keySeq() + .reduce((acc, current) => { + + if (current !== 'plugins') { + return acc.setIn([current, action.keys[1]], action.value); + } + + return acc + .get(current) + .keySeq() + .reduce((acc1, curr) => { + return acc1 + .getIn([current, curr]) + .keySeq() + .reduce((acc2, curr1) => { + + return acc2.setIn([ current, curr, curr1, action.keys[1]], action.value); + }, acc1); + }, acc); + }, models); + }); + case ON_CHANGE_SETTINGS: + return state + .updateIn(['modifiedSchema', 'models'].concat(action.keys), () => action.value); + case ON_CLICK_ADD_ATTR: + return state.updateIn(['modifiedSchema', 'models', ...action.keys.split('.')], list => list.push(fromJS(action.data))); + case ON_CLICK_ADD_ATTR_FIELD: + return state + .updateIn(['modifiedSchema', 'models', ...action.keys.split('.')], list => { + return list.push(action.data); + }) + .update('addedField', v => !v); + case ON_REMOVE: + return state.updateIn(['modifiedSchema', 'models', ...action.keys.split('.'), 'listDisplay'], list => { + + // If the list is empty add the default Id attribute + if (list.size -1 === 0) { + const attrToAdd = state.getIn(['schema', 'models', ...action.keys.split('.'), 'listDisplay']) + .filter(attr => { + return attr.get('name') === '_id' || attr.get('name') === 'id'; + }); + + attrToAdd.setIn(['0', 'sortable'], () => true); + + return list + .delete(action.index) + .push(attrToAdd.get('0')); + } + + return list.delete(action.index); + }); + case ON_REMOVE_EDIT_VIEW_FIELD_ATTR: + return state + .updateIn(['modifiedSchema', 'models', ...action.keys.split('.'), 'fields'], list => { + // Don't do any check if removing the last item of the array + if (action.index === list.size - 1) { + return list.delete(action.index); + } + const path = action.keys.split('.'); + const modelName = path.length > 2 ? path[2] : path[0]; + const layout = state.getIn(['modifiedSchema', 'layout', modelName, 'attributes']); + const manager = new Manager(state, list, action.keys, action.index, layout); + const attrToRemoveInfos = manager.attrToRemoveInfos; // Retrieve the removed item infos + const arrayOfLastLineElements = manager.arrayOfEndLineElements; + const isRemovingAFullWidthNode = attrToRemoveInfos.bootstrapCol === 12; + let newList; + + if (isRemovingAFullWidthNode) { // If removing we need to add the corresponding missing col in the prev line + const currentNodeLine = findIndex(arrayOfLastLineElements, ['index', attrToRemoveInfos.index]); // Used only to know if removing a full size element on the first line + + if (currentNodeLine === 0) { + newList = list + .delete(action.index); + } else { + const previousNodeLine = currentNodeLine - 1; + const firstElementOnLine = previousNodeLine === 0 ? 0 : arrayOfLastLineElements[previousNodeLine - 1].index + 1; + const lastElementOnLine = arrayOfLastLineElements[previousNodeLine].index + 1; + const previousLineRangeIndexes = firstElementOnLine === lastElementOnLine ? [firstElementOnLine] : range(firstElementOnLine, lastElementOnLine); + const elementsOnLine = manager.getElementsOnALine(previousLineRangeIndexes); + const previousLineColNumber = manager.getLineSize(elementsOnLine); + + if (previousLineColNumber >= 10) { + newList = list + .delete(action.index); + } else { + const colNumberToAdd = 12 - previousLineColNumber; + const colsToAdd = manager.getColsToAdd(colNumberToAdd); + newList = list + .delete(attrToRemoveInfos.index) + .insert(attrToRemoveInfos.index, colsToAdd[0]); + + if (colsToAdd.length > 1) { + newList = newList + .insert(attrToRemoveInfos.index, colsToAdd[1]); + } + } + } + } else { + const nodeBounds = { left: manager.getBound(false), right: manager.getBound(true) }; // Retrieve the removed element's bounds + const leftBoundIndex = get(nodeBounds, ['left', 'index'], 0) + 1; + const rightBoundIndex = get(nodeBounds, ['right', 'index'], list.size -1); + const elementsOnLine = manager.getElementsOnALine(range(leftBoundIndex - 1, rightBoundIndex + 1)); + const currentLineColSize = manager.getLineSize(elementsOnLine); + const isRemovingLine = currentLineColSize - attrToRemoveInfos.bootstrapCol === 0; + + if (isRemovingLine) { + newList = list + .delete(attrToRemoveInfos.index); + } else { + const random = Math.floor(Math.random() * 1000); + newList = list + .delete(attrToRemoveInfos.index) + .insert(rightBoundIndex, `__col-md-${attrToRemoveInfos.bootstrapCol}__${random}`); + } + } + // This part is needed to remove the add __col-md-${something}__ that keeps the layout when removing an item + // It may happen that a line is composed by these divs therefore we need to remove them + const newManager = createManager(state, newList, action.keys, action.index, layout); + + return removeColsLine(newManager, newList); + }) + .update('shouldResetGrid', v => !v); + case ON_REMOVE_EDIT_VIEW_RELATION_ATTR: + return state + .updateIn(['modifiedSchema', 'models', ...action.keys.split('.')], relation => { + return relation + .update('description', () => '') + .update('label', () => upperFirst(relation.get('alias'))); + }) + .updateIn(['modifiedSchema', 'models'].concat(action.keys.split('.')), list => { + return list.delete(action.index); + }); + case ON_RESET: + return state.update('modifiedSchema', () => state.get('schema')); + case SUBMIT_SUCCEEDED: + return state + .update('submitSuccess', v => v = !v) + .update('schema', () => state.get('modifiedSchema')); + case SET_LAYOUT: { + let updatedList = List([]); + const path = action.keys.split('.'); + const modelName = path.length > 2 ? path[2] : path[0]; + const layout = state.getIn(['modifiedSchema', 'layout', modelName, 'attributes']); + + return state + .updateIn(['modifiedSchema', 'models', ...action.keys.split('.'), 'fields'], list => { + const manager = new Manager(state, list, action.keys, 0, layout); + const newList = manager.getLayout(); + updatedList = reorderList(manager, newList); + + return newList; + }) + .update('grid', () => { + const fields = updatedList; + const lines = getLines(new Manager(state, fields, action.keys, 0, layout), fields); + + return List(lines); + }); + } default: return state; } } -export default appReducer; +export default appReducer; \ No newline at end of file diff --git a/packages/strapi-plugin-content-manager/admin/src/containers/App/sagas.js b/packages/strapi-plugin-content-manager/admin/src/containers/App/sagas.js index 3e0651e20f..0b15e3f1e2 100755 --- a/packages/strapi-plugin-content-manager/admin/src/containers/App/sagas.js +++ b/packages/strapi-plugin-content-manager/admin/src/containers/App/sagas.js @@ -1,18 +1,15 @@ import { LOCATION_CHANGE } from 'react-router-redux'; -import { map, omit } from 'lodash'; -import { fork, put, select, call, takeLatest, take, cancel } from 'redux-saga/effects'; - +import { fork, put, call, takeLatest, take, cancel, select } from 'redux-saga/effects'; import request from 'utils/request'; -import { generateSchema } from 'utils/schema'; -import { getModelEntriesSucceeded, loadedModels, updateSchema } from './actions'; -import { GET_MODEL_ENTRIES, LOAD_MODELS, LOADED_MODELS } from './constants'; -import { makeSelectModels } from './selectors'; + +import { getModelEntriesSucceeded, loadedModels, submitSucceeded } from './actions'; +import { GET_MODEL_ENTRIES, LOAD_MODELS, ON_SUBMIT } from './constants'; +import { makeSelectModifiedSchema } from './selectors'; export function* modelEntriesGet(action) { try { const requestUrl = `/content-manager/explorer/${action.modelName}/count${action.source !== undefined ? `?source=${action.source}`: ''}`; - const response = yield call(request, requestUrl, { method: 'GET' }); yield put(getModelEntriesSucceeded(response.count)); @@ -21,26 +18,6 @@ export function* modelEntriesGet(action) { } } -export const generateMenu = function () { - return request(`/content-manager/models`, { - method: 'GET', - }) - .then(response => generateSchema(response)) - .then(displayedModels => { - return [{ - name: 'Content Types', - links: map(omit(displayedModels, 'plugins'), (model, key) => ({ - label: model.labelPlural || model.label || key, - destination: key, - })), - }]; - }) - .catch((error) => { - strapi.notification.error('content-manager.error.model.fetch'); - throw Error(error); - }); -}; - export function* getModels() { try { const response = yield call(request, `/content-manager/models`, { @@ -53,25 +30,23 @@ export function* getModels() { } } -export function* modelsLoaded() { - const models = yield select(makeSelectModels()); - let schema; - +export function* submit() { try { - schema = generateSchema(models); - } catch (err) { - strapi.notification.error('content-manager.error.schema.generation'); - throw new Error(err); - } + const schema = yield select(makeSelectModifiedSchema()); + yield call(request, '/content-manager/models', { method: 'PUT', body: { schema } }); - yield put(updateSchema(schema)); + yield put(submitSucceeded()); + } catch(err) { + // Silent + // NOTE: should we add another notification?? + } } // Individual exports for testing export function* defaultSaga() { const loadModelsWatcher = yield fork(takeLatest, LOAD_MODELS, getModels); - const loadedModelsWatcher = yield fork(takeLatest, LOADED_MODELS, modelsLoaded); const loadEntriesWatcher = yield fork(takeLatest, GET_MODEL_ENTRIES, modelEntriesGet); + yield fork(takeLatest, ON_SUBMIT, submit); yield take(LOCATION_CHANGE); diff --git a/packages/strapi-plugin-content-manager/admin/src/containers/App/selectors.js b/packages/strapi-plugin-content-manager/admin/src/containers/App/selectors.js index 85cad8f781..a88074d4e5 100755 --- a/packages/strapi-plugin-content-manager/admin/src/containers/App/selectors.js +++ b/packages/strapi-plugin-content-manager/admin/src/containers/App/selectors.js @@ -25,31 +25,49 @@ const selectLocationState = () => { }; }; -/** - * Default selector used by List - */ - -const makeSelectModels = () => +const makeSelectAddedField = () => createSelector(selectGlobalDomain(), globalState => - globalState.get('models') + globalState.get('addedField') + ); +const makeSelectDraggedItemName = () => + createSelector(selectGlobalDomain(), globalState => + globalState.get('draggedItemName') + ); +const makeSelectHoverIndex = () => + createSelector(selectGlobalDomain(), globalState => + globalState.get('hoverIndex') ); - const makeSelectModelEntries = () => createSelector(selectGlobalDomain(), globalState => globalState.get('modelEntries') ); - +const makeSelectGrid = () => + createSelector(selectGlobalDomain(), substate => substate.get('grid').toJS()); +const makeSelectInitDragLine = () => + createSelector(selectGlobalDomain(), substate => substate.get('initDragLine')); const makeSelectLoading = () => createSelector(selectGlobalDomain(), substate => substate.get('loading')); - const makeSelectSchema = () => - createSelector(selectGlobalDomain(), substate => substate.get('schema')); + createSelector(selectGlobalDomain(), substate => substate.get('schema').toJS()); +const makeSelectModifiedSchema = () => + createSelector(selectGlobalDomain(), substate => substate.get('modifiedSchema').toJS()); +const makeSelectShouldResetGrid = () => + createSelector(selectGlobalDomain(), substate => substate.get('shouldResetGrid')); +const makeSelectSubmitSuccess = () => + createSelector(selectGlobalDomain(), substate => substate.get('submitSuccess')); export { selectGlobalDomain, selectLocationState, + makeSelectAddedField, + makeSelectDraggedItemName, + makeSelectHoverIndex, + makeSelectGrid, + makeSelectInitDragLine, makeSelectLoading, makeSelectModelEntries, - makeSelectModels, + makeSelectModifiedSchema, makeSelectSchema, + makeSelectShouldResetGrid, + makeSelectSubmitSuccess, }; diff --git a/packages/strapi-plugin-content-manager/admin/src/containers/EditPage/actions.js b/packages/strapi-plugin-content-manager/admin/src/containers/EditPage/actions.js index bb6f62d9ef..7e1306a5e5 100644 --- a/packages/strapi-plugin-content-manager/admin/src/containers/EditPage/actions.js +++ b/packages/strapi-plugin-content-manager/admin/src/containers/EditPage/actions.js @@ -8,13 +8,15 @@ import { get } from 'lodash'; import { getValidationsFromForm } from 'utils/formValidations'; import { + ADD_RELATION_ITEM, CHANGE_DATA, GET_DATA, GET_DATA_SUCCEEDED, - GET_LAYOUT, - GET_LAYOUT_SUCCEEDED, INIT_MODEL_PROPS, + MOVE_ATTR, + MOVE_ATTR_END, ON_CANCEL, + ON_REMOVE_RELATION_ITEM, RESET_PROPS, SET_FILE_RELATIONS, SET_LOADER, @@ -24,6 +26,14 @@ import { UNSET_LOADER, } from './constants'; +export function addRelationItem({ key, value }) { + return { + type: ADD_RELATION_ITEM, + key, + value, + }; +} + export function changeData({ target }) { return { type: CHANGE_DATA, @@ -50,29 +60,25 @@ export function getDataSucceeded(id, data, pluginHeaderTitle) { }; } -export function getLayout(source) { - return { - type: GET_LAYOUT, - source, - }; -} - -export function getLayoutSucceeded(layout) { - return { - type: GET_LAYOUT_SUCCEEDED, - layout, - }; -} - -export function initModelProps(modelName, isCreating, source, attributes) { +export function initModelProps(modelName, isCreating, source, attributes, displayedAttributes) { const formValidations = getValidationsFromForm( Object.keys(attributes).map(attr => ({ name: attr, validations: get(attributes, attr, {}) })), [], - ); + ).filter(field => { + if (get(field, ['validations', 'required'], false) === true) { + return displayedAttributes.indexOf(field.name) !== -1; + } + + return true; + }); + const record = Object.keys(attributes).reduce((acc, current) => { if (attributes[current].default) { acc[current] = attributes[current].default; + } else if (attributes[current].type === 'json') { + acc[current] = {}; } + return acc; }, {}); @@ -86,12 +92,35 @@ export function initModelProps(modelName, isCreating, source, attributes) { }; } +export function moveAttr(dragIndex, hoverIndex, keys) { + return { + type: MOVE_ATTR, + dragIndex, + hoverIndex, + keys, + }; +} + +export function moveAttrEnd() { + return { + type: MOVE_ATTR_END, + }; +} + export function onCancel() { return { type: ON_CANCEL, }; } +export function onRemoveRelationItem({ key, index }) { + return { + type: ON_REMOVE_RELATION_ITEM, + key, + index, + }; +} + export function resetProps() { return { type: RESET_PROPS, diff --git a/packages/strapi-plugin-content-manager/admin/src/containers/EditPage/constants.js b/packages/strapi-plugin-content-manager/admin/src/containers/EditPage/constants.js index 20184feecd..6bf60c3159 100644 --- a/packages/strapi-plugin-content-manager/admin/src/containers/EditPage/constants.js +++ b/packages/strapi-plugin-content-manager/admin/src/containers/EditPage/constants.js @@ -4,13 +4,15 @@ * */ +export const ADD_RELATION_ITEM = 'ContentManager/EditPage/ADD_RELATION_ITEM'; export const CHANGE_DATA = 'ContentManager/EditPage/CHANGE_DATA'; export const GET_DATA = 'ContentManager/EditPage/GET_DATA'; export const GET_DATA_SUCCEEDED = 'ContentManager/EditPage/GET_DATA_SUCCEEDED'; -export const GET_LAYOUT = 'ContentManager/EditPage/GET_LAYOUT'; -export const GET_LAYOUT_SUCCEEDED = 'ContentManager/EditPage/GET_LAYOUT_SUCCEEDED'; export const INIT_MODEL_PROPS = 'ContentManager/EditPage/INIT_MODEL_PROPS'; +export const MOVE_ATTR = 'ContentManager/EditPage/MOVE_ATTR'; +export const MOVE_ATTR_END = 'ContentManager/EditPage/MOVE_ATTR_END'; export const ON_CANCEL = 'ContentManager/EditPage/ON_CANCEL'; +export const ON_REMOVE_RELATION_ITEM = 'ContentManager/EditPage/ON_REMOVE_RELATION_ITEM'; export const RESET_PROPS = 'ContentManager/EditPage/RESET_PROPS'; export const SET_FILE_RELATIONS = 'ContentManager/EditPage/SET_FILE_RELATIONS'; export const SET_LOADER = 'ContentManager/EditPage/SET_LOADER'; diff --git a/packages/strapi-plugin-content-manager/admin/src/containers/EditPage/index.js b/packages/strapi-plugin-content-manager/admin/src/containers/EditPage/index.js index 798ea607e6..cdae807520 100644 --- a/packages/strapi-plugin-content-manager/admin/src/containers/EditPage/index.js +++ b/packages/strapi-plugin-content-manager/admin/src/containers/EditPage/index.js @@ -11,78 +11,62 @@ import { bindActionCreators, compose } from 'redux'; import { createStructuredSelector } from 'reselect'; import PropTypes from 'prop-types'; import { cloneDeep, findIndex, get, includes, isEmpty, isObject, toNumber, toString, replace } from 'lodash'; +import HTML5Backend from 'react-dnd-html5-backend'; +import { DragDropContext } from 'react-dnd'; import cn from 'classnames'; - // You can find these components in either // ./node_modules/strapi-helper-plugin/lib/src // or strapi/packages/strapi-helper-plugin/lib/src import BackHeader from 'components/BackHeader'; +import EmptyAttributesBlock from 'components/EmptyAttributesBlock'; import LoadingIndicator from 'components/LoadingIndicator'; import PluginHeader from 'components/PluginHeader'; - +import PopUpWarning from 'components/PopUpWarning'; // Plugin's components +import CustomDragLayer from 'components/CustomDragLayer'; import Edit from 'components/Edit'; import EditRelations from 'components/EditRelations'; - // App selectors -import { makeSelectModels, makeSelectSchema } from 'containers/App/selectors'; - +import { makeSelectSchema } from 'containers/App/selectors'; import injectReducer from 'utils/injectReducer'; import injectSaga from 'utils/injectSaga'; import getQueryParameters from 'utils/getQueryParameters'; import { bindLayout } from 'utils/bindLayout'; import inputValidations from 'utils/inputsValidations'; - +import { generateRedirectURI } from 'containers/ListPage/utils'; import { checkFormValidity } from 'utils/formValidations'; - import { + addRelationItem, changeData, getData, - getLayout, initModelProps, + moveAttr, + moveAttrEnd, onCancel, + onRemoveRelationItem, resetProps, setFileRelations, setFormErrors, submit, } from './actions'; - import reducer from './reducer'; import saga from './saga'; import makeSelectEditPage from './selectors'; import styles from './styles.scss'; export class EditPage extends React.Component { + state = { showWarning: false }; + componentDidMount() { - this.props.initModelProps(this.getModelName(), this.isCreating(), this.getSource(), this.getModelAttributes()); - - if (!this.isCreating()) { - const mainField = get(this.getModel(), 'info.mainField') || this.getModel().primaryKey; - this.props.getData(this.props.match.params.id, this.getSource(), mainField); - } else { - this.props.getLayout(this.getSource()); - } - - // Get all relations made with the upload plugin - const fileRelations = Object.keys(get(this.getSchema(), 'relations', {})).reduce((acc, current) => { - const association = get(this.getSchema(), ['relations', current], {}); - - if (association.plugin === 'upload' && association[association.type] === 'file') { - const relation = { - name: current, - multiple: association.nature === 'manyToManyMorph', - }; - - acc.push(relation); - } - return acc; - }, []); - - // Update the reducer so we can use it to create the appropriate FormData in the saga - this.props.setFileRelations(fileRelations); + this.initComponent(this.props); } componentDidUpdate(prevProps) { + if (prevProps.location.pathname !== this.props.location.pathname) { + this.props.resetProps(); + this.initComponent(this.props); + } + if (prevProps.editPage.submitSuccess !== this.props.editPage.submitSuccess) { if (!isEmpty(this.props.location.search) && includes(this.props.location.search, '?redirectUrl')) { const redirectUrl = this.props.location.search.split('?redirectUrl=')[1]; @@ -105,11 +89,19 @@ export class EditPage extends React.Component { } /** - * Retrive the model's custom layout - * @return {[type]} [description] + * Retrieve the model's displayed relations + * @return {Array} + */ + getDisplayedRelations = () => { + return get(this.getSchema(), ['editDisplay', 'relations'], []); + } + + /** + * Retrieve the model's custom layout + * */ getLayout = () => ( - bindLayout.call(this, get(this.props.editPage, ['layout', this.getModelName()], {})) + bindLayout.call(this, get(this.props.schema, ['layout', this.getModelName()], {})) ) /** @@ -119,11 +111,13 @@ export class EditPage extends React.Component { */ getAttributeValidations = (name) => get(this.props.editPage.formValidations, [findIndex(this.props.editPage.formValidations, ['name', name]), 'validations'], {}) + getDisplayedFields = () => get(this.getSchema(), ['editDisplay', 'fields'], []); + /** * Retrieve the model * @type {Object} */ - getModel = () => get(this.props.models, ['models', this.getModelName()]) || get(this.props.models, ['plugins', this.getSource(), 'models', this.getModelName()]); + getModel = () => get(this.props.schema, ['models', this.getModelName()]) || get(this.props.schema, ['models', 'plugins', this.getSource(), this.getModelName()]); /** * Retrieve specific attribute @@ -148,8 +142,8 @@ export class EditPage extends React.Component { * @return {Object} */ getSchema = () => this.getSource() !== 'content-manager' ? - get(this.props.schema, ['plugins', this.getSource(), this.getModelName()]) - : get(this.props.schema, [this.getModelName()]); + get(this.props.schema, ['models', 'plugins', this.getSource(), this.getModelName()]) + : get(this.props.schema, ['models', this.getModelName()]); getPluginHeaderTitle = () => { if (this.isCreating()) { @@ -165,6 +159,43 @@ export class EditPage extends React.Component { */ getSource = () => getQueryParameters(this.props.location.search, 'source'); + /** + * Initialize component + */ + initComponent = (props) => { + this.props.initModelProps(this.getModelName(), this.isCreating(), this.getSource(), this.getModelAttributes(), this.getDisplayedFields()); + + if (!this.isCreating()) { + const mainField = get(this.getModel(), 'info.mainField') || this.getModel().primaryKey; + this.props.getData(props.match.params.id, this.getSource(), mainField); + } + + // Get all relations made with the upload plugin + const fileRelations = Object.keys(get(this.getSchema(), 'relations', {})).reduce((acc, current) => { + const association = get(this.getSchema(), ['relations', current], {}); + + if (association.plugin === 'upload' && association[association.type] === 'file') { + const relation = { + name: current, + multiple: association.nature === 'manyToManyMorph', + }; + + acc.push(relation); + } + return acc; + }, []); + + // Update the reducer so we can use it to create the appropriate FormData in the saga + this.props.setFileRelations(fileRelations); + } + + handleAddRelationItem = ({ key, value }) => { + this.props.addRelationItem({ + key, + value, + }); + } + handleBlur = ({ target }) => { const defaultValue = get(this.getModelAttribute(target.name), 'default'); @@ -196,7 +227,7 @@ export class EditPage extends React.Component { let value = e.target.value; // Check if date if (isObject(e.target.value) && e.target.value._isAMomentObject === true) { - value = moment(e.target.value, 'YYYY-MM-DD HH:mm:ss').format(); + value = moment(e.target.value).format('YYYY-MM-DD HH:mm:ss'); } else if (['float', 'integer', 'biginteger', 'decimal'].indexOf(get(this.getSchema(), ['fields', e.target.name, 'type'])) !== -1) { value = toNumber(e.target.value); } @@ -209,6 +240,34 @@ export class EditPage extends React.Component { this.props.changeData({ target }); } + handleConfirm = () => { + this.props.onCancel(); + this.toggle(); + } + + handleGoBack = () => this.props.history.goBack(); + + handleRedirect = ({ model, id, source = 'content-manager'}) => { + /* eslint-disable */ + switch (model) { + case 'permission': + case 'role': + case 'file': + // Exclude special models which are handled by plugins. + if (source !== 'content-manager') { + break; + } + default: + const pathname = `${this.props.match.path.replace(':slug', model).replace(':id', id)}`; + + this.props.history.push({ + pathname, + search: `?source=${source}&redirectURI=${generateRedirectURI({ model, search: `?source=${source}` })}`, + }); + } + /* eslint-enable */ + } + handleSubmit = (e) => { e.preventDefault(); const formErrors = checkFormValidity(this.generateFormFromRecord(), this.props.editPage.formValidations); @@ -220,6 +279,14 @@ export class EditPage extends React.Component { this.props.setFormErrors(formErrors); } + hasDisplayedRelations = () => { + return this.getDisplayedRelations().length > 0; + } + + hasDisplayedFields = () => { + return get(this.getModel(), ['editDisplay', 'fields'], []).length > 0; + } + isCreating = () => this.props.match.params.id === 'create'; isRelationComponentNull = () => ( @@ -243,7 +310,7 @@ export class EditPage extends React.Component { { label: 'content-manager.containers.Edit.reset', kind: 'secondary', - onClick: this.props.onCancel, + onClick: this.toggle, type: 'button', disabled: this.showLoaders(), }, @@ -260,53 +327,107 @@ export class EditPage extends React.Component { ); showLoaders = () => { - const { editPage: { isLoading, layout } } = this.props; + const { editPage: { isLoading }, schema: { layout } } = this.props; return isLoading && !this.isCreating() || isLoading && get(layout, this.getModelName()) === undefined; } + toggle = () => this.setState(prevState => ({ showWarning: !prevState.showWarning })); + + renderEdit = () => { + const { editPage, location: { search } } = this.props; + const source = getQueryParameters(search, 'source'); + const basePath = '/plugins/content-manager/ctm-configurations'; + const pathname = source !== 'content-manager' + ? `${basePath}/plugins/${source}/${this.getModelName()}` + : `${basePath}/${this.getModelName()}`; + + if (this.showLoaders()) { + return ( +
    +
    + +
    +
    + ); + } + + if (!this.hasDisplayedFields()) { + return ( +
    + this.props.history.push(pathname)} + /> +
    + ); + } + + return ( +
    +
    + +
    +
    + ); + } + render() { - const { editPage } = this.props; + const { editPage, moveAttr, moveAttrEnd } = this.props; + const { showWarning } = this.state; return (
    - this.props.history.goBack()} /> + +
    +
    -
    -
    - {this.showLoaders() ? ( - - ) : ( - - )} -
    -
    - {!this.isRelationComponentNull() && ( + {this.renderEdit()} + {this.hasDisplayedRelations() && (
    - {!this.isRelationComponentNull() && ( + {this.hasDisplayedRelations() && ( @@ -327,22 +448,24 @@ EditPage.contextTypes = { }; EditPage.defaultProps = { - models: {}, + schema: {}, }; EditPage.propTypes = { + addRelationItem: PropTypes.func.isRequired, changeData: PropTypes.func.isRequired, editPage: PropTypes.object.isRequired, getData: PropTypes.func.isRequired, - getLayout: PropTypes.func.isRequired, history: PropTypes.object.isRequired, initModelProps: PropTypes.func.isRequired, location: PropTypes.object.isRequired, match: PropTypes.object.isRequired, - models: PropTypes.object, + moveAttr: PropTypes.func.isRequired, + moveAttrEnd: PropTypes.func.isRequired, onCancel: PropTypes.func.isRequired, + onRemoveRelationItem: PropTypes.func.isRequired, resetProps: PropTypes.func.isRequired, - schema: PropTypes.object.isRequired, + schema: PropTypes.object, setFileRelations: PropTypes.func.isRequired, setFormErrors: PropTypes.func.isRequired, submit: PropTypes.func.isRequired, @@ -351,11 +474,14 @@ EditPage.propTypes = { function mapDispatchToProps(dispatch) { return bindActionCreators( { + addRelationItem, changeData, getData, - getLayout, initModelProps, + moveAttr, + moveAttrEnd, onCancel, + onRemoveRelationItem, resetProps, setFileRelations, setFormErrors, @@ -367,7 +493,6 @@ function mapDispatchToProps(dispatch) { const mapStateToProps = createStructuredSelector({ editPage: makeSelectEditPage(), - models: makeSelectModels(), schema: makeSelectSchema(), }); @@ -380,6 +505,4 @@ export default compose( withReducer, withSaga, withConnect, -)(EditPage); - - +)(DragDropContext(HTML5Backend)(EditPage)); diff --git a/packages/strapi-plugin-content-manager/admin/src/containers/EditPage/reducer.js b/packages/strapi-plugin-content-manager/admin/src/containers/EditPage/reducer.js index a31d78605d..7842f18de0 100644 --- a/packages/strapi-plugin-content-manager/admin/src/containers/EditPage/reducer.js +++ b/packages/strapi-plugin-content-manager/admin/src/containers/EditPage/reducer.js @@ -6,11 +6,14 @@ import { fromJS, Map, List } from 'immutable'; import { + ADD_RELATION_ITEM, CHANGE_DATA, GET_DATA_SUCCEEDED, - GET_LAYOUT_SUCCEEDED, INIT_MODEL_PROPS, + MOVE_ATTR, + MOVE_ATTR_END, ON_CANCEL, + ON_REMOVE_RELATION_ITEM, RESET_PROPS, SET_FILE_RELATIONS, SET_FORM_ERRORS, @@ -27,11 +30,11 @@ const initialState = fromJS({ isCreating: false, id: '', initialRecord: Map({}), + isDraggingSibling: false, isLoading: true, - layout: fromJS({}), modelName: '', pluginHeaderTitle: 'New Entry', - record: Map({}), + record: fromJS({}), resetProps: false, showLoader: false, source: 'content-manager', @@ -40,34 +43,57 @@ const initialState = fromJS({ function editPageReducer(state = initialState, action) { switch (action.type) { + case ADD_RELATION_ITEM: + return state + .updateIn(['record', action.key], (list) => { + if (List.isList(list)) { + return list + .push(action.value); + } + + return List([]) + .push(action.value); + }); case CHANGE_DATA: return state.updateIn(action.keys, () => action.value); case GET_DATA_SUCCEEDED: return state .update('id', () => action.id) .update('isLoading', () => false) - .update('initialRecord', () => Map(action.data)) + .update('initialRecord', () => fromJS(action.data)) .update('pluginHeaderTitle', () => action.pluginHeaderTitle) - .update('record', () => Map(action.data)); - case GET_LAYOUT_SUCCEEDED: - return state - .update('isLoading', () => false) - .updateIn(['layout', state.get('modelName')], () => Map(action.layout)); + .update('record', () => fromJS(action.data)); case INIT_MODEL_PROPS: return state .update('formValidations', () => List(action.formValidations)) .update('isCreating', () => action.isCreating) .update('modelName', () => action.modelName) - .update('record', () => Map(action.record)) + .update('record', () => fromJS(action.record)) .update('source', () => action.source); + case MOVE_ATTR: + return state + .updateIn(['record', action.keys], list => { + return list + .delete(action.dragIndex) + .insert(action.hoverIndex, list.get(action.dragIndex)); + }) + .update('isDraggingSibling', () => true); + case MOVE_ATTR_END: + return state.update('isDraggingSibling', () => false); case ON_CANCEL: return state .update('didCheckErrors', (v) => v = !v) .update('formErrors', () => List([])) .update('record', () => state.get('initialRecord')) .update('resetProps', (v) => v = !v); + case ON_REMOVE_RELATION_ITEM: + return state + .updateIn(['record', action.key], (list) => { + return list + .delete(action.index); + }); case RESET_PROPS: - return initialState.update('layout', () => state.get('layout')); + return initialState; case SET_FILE_RELATIONS: return state.set('fileRelations', List(action.fileRelations)); case SET_FORM_ERRORS: diff --git a/packages/strapi-plugin-content-manager/admin/src/containers/EditPage/saga.js b/packages/strapi-plugin-content-manager/admin/src/containers/EditPage/saga.js index 96cf507608..83651c127b 100644 --- a/packages/strapi-plugin-content-manager/admin/src/containers/EditPage/saga.js +++ b/packages/strapi-plugin-content-manager/admin/src/containers/EditPage/saga.js @@ -19,13 +19,12 @@ import templateObject from 'utils/templateObject'; import { getDataSucceeded, - getLayoutSucceeded, setFormErrors, setLoader, submitSuccess, unsetLoader, } from './actions'; -import { GET_DATA, GET_LAYOUT, SUBMIT } from './constants'; +import { GET_DATA, SUBMIT } from './constants'; import { makeSelectFileRelations, makeSelectIsCreating, @@ -38,42 +37,17 @@ function* dataGet(action) { try { const modelName = yield select(makeSelectModelName()); const params = { source: action.source }; - const [response, layout] = yield [ + const [response] = yield [ call(request, `/content-manager/explorer/${modelName}/${action.id}`, { method: 'GET', params }), - call(request, '/content-manager/layout', { method: 'GET', params }), ]; const pluginHeaderTitle = yield call(templateObject, { mainField: action.mainField }, response); - // Remove the updated_at & created_at fields so it is updated correctly when using Postgres or MySQL db - if (response.updated_at) { - delete response.created_at; - delete response.updated_at; - } - - // Remove the updatedAt & createdAt fields so it is updated correctly when using MongoDB - if (response.updatedAt) { - delete response.createdAt; - delete response.updatedAt; - - } - yield put(getDataSucceeded(action.id, response, pluginHeaderTitle.mainField)); - yield put(getLayoutSucceeded(layout)); } catch(err) { strapi.notification.error('content-manager.error.record.fetch'); } } -function* layoutGet(action) { - try { - const params = { source: action.source }; - const response = yield call(request, '/content-manager/layout', { method: 'GET', params }); - yield put(getLayoutSucceeded(response)); - } catch(err) { - strapi.notification('notification.error'); - } -} - export function* submit() { const currentModelName = yield select(makeSelectModelName()); const fileRelations = yield select(makeSelectFileRelations()); @@ -82,14 +56,26 @@ export function* submit() { const source = yield select(makeSelectSource()); const schema = yield select(makeSelectSchema()); let shouldAddTranslationSuffix = false; + // Remove the updated_at & created_at fields so it is updated correctly when using Postgres or MySQL db + if (record.updated_at) { + delete record.created_at; + delete record.updated_at; + } + + // Remove the updatedAt & createdAt fields so it is updated correctly when using MongoDB + if (record.updatedAt) { + delete record.createdAt; + delete record.updatedAt; + } try { // Show button loader yield put(setLoader()); const recordCleaned = Object.keys(record).reduce((acc, current) => { - const attrType = source !== 'content-manager' ? get(schema, ['plugins', source, currentModelName, 'fields', current, 'type'], null) : get(schema, [currentModelName, 'fields', current, 'type'], null); + const attrType = source !== 'content-manager' ? get(schema, ['models', 'plugins', source, currentModelName, 'fields', current, 'type'], null) : get(schema, ['models', currentModelName, 'fields', current, 'type'], null); const cleanedData = attrType === 'json' ? record[current] : cleanData(record[current], 'value', 'id'); + if (isString(cleanedData) || isNumber(cleanedData)) { acc.append(current, cleanedData); } else if (findIndex(fileRelations, ['name', current]) !== -1) { @@ -114,6 +100,11 @@ export function* submit() { return acc; }, new FormData()); + // Helper to visualize FormData + // for(var pair of recordCleaned.entries()) { + // console.log(pair[0]+ ', '+ pair[1]); + // } + const id = isCreating ? '' : record.id || record._id; const params = { source }; // Change the request helper default headers so we can pass a FormData @@ -170,7 +161,6 @@ export function* submit() { function* defaultSaga() { const loadDataWatcher = yield fork(takeLatest, GET_DATA, dataGet); - yield fork(takeLatest, GET_LAYOUT, layoutGet); yield fork(takeLatest, SUBMIT, submit); yield take(LOCATION_CHANGE); diff --git a/packages/strapi-plugin-content-manager/admin/src/containers/EditPage/styles.scss b/packages/strapi-plugin-content-manager/admin/src/containers/EditPage/styles.scss index c98a875acf..74019ded0c 100644 --- a/packages/strapi-plugin-content-manager/admin/src/containers/EditPage/styles.scss +++ b/packages/strapi-plugin-content-manager/admin/src/containers/EditPage/styles.scss @@ -11,7 +11,7 @@ .sub_wrapper{ background: #ffffff; - padding: 0 25px 1px; + padding: 0 20px 1px; border-radius: 2px; box-shadow: 0 2px 4px #E3E9F3; } diff --git a/packages/strapi-plugin-content-manager/admin/src/containers/ListPage/actions.js b/packages/strapi-plugin-content-manager/admin/src/containers/ListPage/actions.js index 430ac54b19..3728270107 100644 --- a/packages/strapi-plugin-content-manager/admin/src/containers/ListPage/actions.js +++ b/packages/strapi-plugin-content-manager/admin/src/containers/ListPage/actions.js @@ -5,6 +5,7 @@ */ import { + ADD_ATTR, ADD_FILTER, CHANGE_PARAMS, DELETE_DATA, @@ -21,11 +22,22 @@ import { ON_TOGGLE_FILTERS, OPEN_FILTERS_WITH_SELECTION, REMOVE_ALL_FILTERS, + REMOVE_ATTR, REMOVE_FILTER, + RESET_DISPLAYED_FIELDS, + SET_DISPLAYED_FIELDS, SET_PARAMS, SUBMIT, } from './constants'; +export function addAttr(attr, index) { + return { + type: ADD_ATTR, + attr, + index, + }; +} + export function addFilter(filter) { return { type: ADD_FILTER, @@ -142,6 +154,13 @@ export function removeAllFilters() { }; } +export function removeAttr(index) { + return { + type: REMOVE_ATTR, + index, + }; +} + export function removeFilter(index) { return { type: REMOVE_FILTER, @@ -149,6 +168,20 @@ export function removeFilter(index) { }; } +export function resetDisplayedFields(fields) { + return { + type: RESET_DISPLAYED_FIELDS, + fields, + }; +} + +export function setDisplayedFields(fields) { + return { + type: SET_DISPLAYED_FIELDS, + fields, + }; +} + export function setParams(params, filters) { return { type: SET_PARAMS, diff --git a/packages/strapi-plugin-content-manager/admin/src/containers/ListPage/constants.js b/packages/strapi-plugin-content-manager/admin/src/containers/ListPage/constants.js index e064a575fe..66b48a4a68 100644 --- a/packages/strapi-plugin-content-manager/admin/src/containers/ListPage/constants.js +++ b/packages/strapi-plugin-content-manager/admin/src/containers/ListPage/constants.js @@ -5,6 +5,7 @@ * */ +export const ADD_ATTR = 'ContentManager/ListPage/ADD_ATTR'; export const ADD_FILTER = 'ContentManager/ListPage/ADD_FILTER'; export const CHANGE_PARAMS = 'ContentManager/ListPage/CHANGE_PARAMS'; export const DELETE_DATA = 'ContentManager/ListPage/DELETE_DATA'; @@ -21,6 +22,9 @@ export const ON_TOGGLE_DELETE_ALL = 'ContentManager/ListPage/ON_TOGGLE_DELETE_AL export const ON_TOGGLE_FILTERS = 'ContentManager/ListPage/ON_TOGGLE_FILTERS'; export const OPEN_FILTERS_WITH_SELECTION = 'ContentManager/ListPage/OPEN_FILTERS_WITH_SELECTION'; export const REMOVE_ALL_FILTERS = 'ContentManager/ListPage/REMOVE_ALL_FILTERS'; +export const REMOVE_ATTR = 'ContentManager/ListPage/REMOVE_ATTR'; export const REMOVE_FILTER = 'ContentManager/ListPage/REMOVE_FILTER'; +export const RESET_DISPLAYED_FIELDS = 'ContentManager/ListPage/RESET_DISPLAYED_FIELDS'; +export const SET_DISPLAYED_FIELDS = 'ContentManager/ListPage/SET_DISPLAYED_FIELDS'; export const SET_PARAMS = 'ContentManager/ListPage/SET_PARAMS'; export const SUBMIT = 'ContentManager/ListPage/SUBMIT'; diff --git a/packages/strapi-plugin-content-manager/admin/src/containers/ListPage/index.js b/packages/strapi-plugin-content-manager/admin/src/containers/ListPage/index.js index 07d392eb55..34158944f3 100644 --- a/packages/strapi-plugin-content-manager/admin/src/containers/ListPage/index.js +++ b/packages/strapi-plugin-content-manager/admin/src/containers/ListPage/index.js @@ -9,34 +9,33 @@ import PropTypes from 'prop-types'; import { connect } from 'react-redux'; import { bindActionCreators, compose } from 'redux'; import { createStructuredSelector } from 'reselect'; -import { capitalize, get, isUndefined, map, toInteger } from 'lodash'; +import { capitalize, findIndex, get, isUndefined, toInteger, upperFirst } from 'lodash'; +import { ButtonDropdown, DropdownToggle, DropdownMenu, DropdownItem } from 'reactstrap'; +import { FormattedMessage } from 'react-intl'; import cn from 'classnames'; - // App selectors -import { makeSelectModels, makeSelectSchema } from 'containers/App/selectors'; - +import { makeSelectSchema } from 'containers/App/selectors'; // You can find these components in either // ./node_modules/strapi-helper-plugin/lib/src // or strapi/packages/strapi-helper-plugin/lib/src import PageFooter from 'components/PageFooter'; import PluginHeader from 'components/PluginHeader'; import PopUpWarning from 'components/PopUpWarning'; - +import InputCheckbox from 'components/InputCheckbox'; // Components from the plugin itself import AddFilterCTA from 'components/AddFilterCTA'; import FiltersPickWrapper from 'components/FiltersPickWrapper/Loadable'; import Filter from 'components/Filter/Loadable'; import Search from 'components/Search'; import Table from 'components/Table'; - // Utils located in `strapi/packages/strapi-helper-plugin/lib/src/utils`; import getQueryParameters from 'utils/getQueryParameters'; import injectReducer from 'utils/injectReducer'; import injectSaga from 'utils/injectSaga'; - +import storeData from 'utils/storeData'; import Div from './Div'; - import { + addAttr, addFilter, changeParams, deleteData, @@ -50,11 +49,13 @@ import { onToggleFilters, openFiltersWithSelections, removeAllFilters, + removeAttr, removeFilter, + resetDisplayedFields, + setDisplayedFields, setParams, submit, } from './actions'; - import reducer from './reducer'; import saga from './saga'; import makeSelectListPage from './selectors'; @@ -62,15 +63,16 @@ import { generateFiltersFromSearch, generateSearchFromFilters, generateSearchFromParams, + generateRedirectURI, } from './utils'; - import styles from './styles.scss'; export class ListPage extends React.Component { - state = { showWarning: false, target: '' }; + state = { isOpen: false, showWarning: false, target: '' }; componentDidMount() { this.getData(this.props); + this.setTableHeaders(); } componentDidUpdate(prevProps) { @@ -78,12 +80,13 @@ export class ListPage extends React.Component { location: { pathname, search }, } = prevProps; const { - listPage: { filtersUpdated }, + listPage: { didChangeDisplayedFields, filtersUpdated, displayedFields, params: { _sort } }, } = this.props; if (pathname !== this.props.location.pathname) { this.getData(this.props); this.shouldHideFilters(); + this.setTableHeaders(); } if (search !== this.props.location.search) { @@ -94,6 +97,16 @@ export class ListPage extends React.Component { const updatedSearch = this.generateSearch(); this.props.history.push({ pathname, search: updatedSearch }); } + + if (prevProps.listPage.didChangeDisplayedFields !== didChangeDisplayedFields) { + const dataToStore = { + [this.getCurrentModelName()]: { + displayedFields, + _sort, + }, + }; + storeData.set(this.getCurrentModelName(), dataToStore); + } } componentWillUnmount() { @@ -102,13 +115,42 @@ export class ListPage extends React.Component { } } + getAllModelFields = () => { + const attributes = this.getCurrentModelAttributes(); + + return Object.keys(attributes) + .filter(attr => { + return !attributes[attr].hasOwnProperty('collection') && !attributes[attr].hasOwnProperty('model'); + }); + } + /** * Helper to retrieve the current model data * @return {Object} the current model */ - getCurrentModel = () => - get(this.props.models, ['models', this.getCurrentModelName()]) || - get(this.props.models, ['plugins', this.getSource(), 'models', this.getCurrentModelName()]); + getCurrentModel = () => ( + get(this.props.schema, ['models', this.getCurrentModelName()]) || + get(this.props.schema, ['models', 'plugins', this.getSource(), this.getCurrentModelName()]) + ); + + getCurrentModelAttributes = () => { + const primaryKey = this.getModelPrimaryKey(); + const defaultAttr = { name: primaryKey, label: 'Id', type: 'string', searchable: true, sortable: true }; + const attributes = Object.assign({ [primaryKey]: defaultAttr }, get(this.getCurrentModel(), ['attributes'], {})); + + return attributes; + } + + getCurrentModelDefaultLimit = () => ( + get(this.getCurrentModel(), 'pageEntries', 10) + ); + + getCurrentModelDefaultSort = () => { + const sortAttr = get(this.getCurrentModel(), 'defaultSort', 'id'); + const order = get(this.getCurrentModel(), 'sort', 'ASC'); + + return order === 'ASC' ? sortAttr : `-${sortAttr}`; + }; /** * Helper to retrieve the current model name @@ -122,9 +164,9 @@ export class ListPage extends React.Component { */ getData = (props, setUpdatingParams = false) => { const source = getQueryParameters(props.location.search, 'source'); - const _limit = toInteger(getQueryParameters(props.location.search, '_limit')) || 10; + const _limit = toInteger(getQueryParameters(props.location.search, '_limit')) || this.getCurrentModelDefaultLimit(); const _page = toInteger(getQueryParameters(props.location.search, '_page')) || 1; - const _sort = this.findPageSort(props); + const _sort = this.findPageSort(props); // TODO sort const _q = getQueryParameters(props.location.search, '_q') || ''; const params = { _limit, _page, _sort, _q }; const filters = generateFiltersFromSearch(props.location.search); @@ -133,6 +175,10 @@ export class ListPage extends React.Component { this.props.getData(props.match.params.slug, source, setUpdatingParams); }; + getDataFromStore = (key) => { + return get(storeData.get(this.getCurrentModelName()),[this.getCurrentModelName(), key]); + } + /** * Helper to retrieve the model's source * @return {String} the model's source @@ -143,9 +189,9 @@ export class ListPage extends React.Component { * Retrieve the model's schema * @return {Object} Fields */ - getCurrentSchema = () => - get(this.props.schema, [this.getCurrentModelName(), 'fields']) || - get(this.props.schema, ['plugins', this.getSource(), this.getCurrentModelName(), 'fields']); + getCurrentSchema = () => + get(this.props.schema, ['models', this.getCurrentModelName(), 'fields']) || + get(this.props.schema, ['models', 'plugins', this.getSource(), this.getCurrentModelName(), 'fields']); getPopUpDeleteAllMsg = () => ( this.props.listPage.entriesToDelete.length > 1 ? @@ -153,13 +199,24 @@ export class ListPage extends React.Component { : 'content-manager.popUpWarning.bodyMessage.contentType.delete' ); + getModelPrimaryKey = () => ( + get(this.getCurrentModel(), ['primaryKey'], '_id') + ); + + getTableHeaders = () => ( + get(this.props.listPage, ['displayedFields'], []) + ); + + setTableHeaders = () => { + const defaultTableHeaders = this.getDataFromStore('displayedFields') || get(this.getCurrentModel(), ['listDisplay'], []); + this.props.setDisplayedFields(defaultTableHeaders); + } + /** * Generate the redirect URI when editing an entry * @type {String} */ - generateRedirectURI = () => ( - `?redirectUrl=/plugins/content-manager/${this.getCurrentModelName().toLowerCase()}${this.generateSearch()}` - ); + generateRedirectURI = generateRedirectURI.bind(this); generateSearch = () => { const { @@ -169,64 +226,67 @@ export class ListPage extends React.Component { return `?${generateSearchFromParams(params)}&source=${this.getSource()}${generateSearchFromFilters(filters)}`; } - /** - * Function to generate the Table's headers - * @return {Array} - */ - generateTableHeaders = () => { - const currentSchema = - get(this.props.schema, [this.getCurrentModelName()]) || - get(this.props.schema, ['plugins', this.getSource(), this.getCurrentModelName()]); - const tableHeaders = map(currentSchema.list, value => ({ - name: value, - label: currentSchema.fields[value].label, - type: currentSchema.fields[value].type, - })); - - tableHeaders.splice(0, 0, { - name: this.getCurrentModel().primaryKey || 'id', - label: 'Id', - type: 'string', - }); - - return tableHeaders; - }; - areAllEntriesSelected = () => { const { listPage: { entriesToDelete, records } } = this.props; return entriesToDelete.length === get(records, this.getCurrentModelName(), []).length && get(records, this.getCurrentModelName(), []).length > 0; }; + findAttrIndex = attr => { + return findIndex(this.props.listPage.displayedFields, ['name', attr]); + } + /** * [findPageSort description] * @param {Object} props [description] * @return {String} the model's primaryKey */ findPageSort = props => { - const { - match: { - params: { slug }, - }, - } = props; - const source = this.getSource(); - const modelPrimaryKey = get(props.models, ['models', slug.toLowerCase(), 'primaryKey']); - // Check if the model is in a plugin - const pluginModelPrimaryKey = get(props.models.plugins, [ - source, - 'models', - slug.toLowerCase(), - 'primaryKey', - ]); - return ( getQueryParameters(props.location.search, '_sort') || - modelPrimaryKey || - pluginModelPrimaryKey || - 'id' + this.getDataFromStore('_sort') || + this.getCurrentModelDefaultSort() ); }; + handleChangeHeader = ({ target }) => { + const defaultSettingsDisplay = get(this.getCurrentModel(), ['listDisplay']); + const attrIndex = this.findAttrIndex(target.name); + const defaultSettingsAttrIndex = findIndex(defaultSettingsDisplay, ['name', target.name]); + + if (attrIndex !== -1) { + if (get(this.props.listPage, 'displayedFields', []).length === 1) { + strapi.notification.error('content-manager.notification.error.displayedFields'); + } else { + const isRemovingDefaultSort = get(this.props.listPage, ['params', '_sort']) === target.name; + let newDefaultSort; + + if (isRemovingDefaultSort) { + this.props.listPage.displayedFields + .filter(attr => attr.name !== target.name) + .forEach(attr => { + if (attr.sortable && !newDefaultSort) { + newDefaultSort = attr.name; + } + }); + + // TODO: store model default sort + + this.handleChangeSort(newDefaultSort || this.getModelPrimaryKey()); + } + this.props.removeAttr(attrIndex); + } + } else { + const attributes = this.getCurrentModelAttributes(); + const searchable = attributes[target.name].type !== 'json' && attributes[target.name] !== 'array'; + const attrToAdd = defaultSettingsAttrIndex !== -1 + ? get(defaultSettingsDisplay, [defaultSettingsAttrIndex], {}) + : Object.assign(attributes[target.name], { name: target.name, label: upperFirst(target.name), searchable, sortable: searchable }); + + this.props.addAttr(attrToAdd, defaultSettingsAttrIndex); + } + } + handleChangeParams = e => { const { history, @@ -273,6 +333,11 @@ export class ListPage extends React.Component { this.setState({ showWarning: false }); }; + handleResetDisplayedFields = () => { + storeData.clear(this.getCurrentModelName()); + this.props.resetDisplayedFields(get(this.getCurrentModel(), ['listDisplay'], [])); + } + handleSubmit = e => { try { e.preventDefault(); @@ -283,12 +348,30 @@ export class ListPage extends React.Component { } }; + isAttrInitiallyDisplayed = attr => { + return this.findAttrIndex(attr) !== -1; + } + shouldHideFilters = () => { if (this.props.listPage.showFilter) { this.props.onToggleFilters(); } }; + showLoaders = () => { + const { listPage: { isLoading, records, updatingParams } } = this.props; + + return updatingParams || isLoading && get(records, this.getCurrentModelName()) === undefined; + } + + showSearch = () => get(this.getCurrentModel(), ['search']); + + showFilters = () => get(this.getCurrentModel(), ['filters']); + + showBulkActions = () => get(this.getCurrentModel(), ['bulkActions']); + + toggle = () => this.setState(prevState => ({ isOpen: !prevState.isOpen })); + toggleModalWarning = e => { if (!isUndefined(e)) { e.preventDefault(); @@ -305,38 +388,49 @@ export class ListPage extends React.Component { }; - showLoaders = () => { - const { listPage: { isLoading, records, updatingParams } } = this.props; - - return updatingParams || isLoading && get(records, this.getCurrentModelName()) === undefined; + renderDropdown = item => { + return ( + +
    + +
    +
    + ); } - render() { - const { - addFilter, - deleteSeveralData, - listPage, - listPage: { - appliedFilters, - count, - entriesToDelete, - filters, - filterToFocus, - records, - params, - showFilter, - showWarningDeleteAll, - }, - onChange, - onClickRemove, - onClickSelect, - onClickSelectAll, - onToggleDeleteAll, - onToggleFilters, - openFiltersWithSelections, - removeAllFilters, - removeFilter, - } = this.props; + renderDropdownHeader = msg => { + return ( + +
    + + {msg} + + + {m => ( + + {m} + + )} + +
    +
    + ); + } + + renderFilter = (filter, key) => { + return ( + + ); + } + + renderPluginHeader = () => { const pluginHeaderActions = [ { label: 'content-manager.containers.List.addAnEntry', @@ -351,81 +445,144 @@ export class ListPage extends React.Component { }), }, ]; + const { listPage: { count } } = this.props; + + return ( + 1 + ? 'content-manager.containers.List.pluginHeaderDescription' + : 'content-manager.containers.List.pluginHeaderDescription.singular', + values: { + label: get(count, this.getCurrentModelName(), 0), + }, + }} + title={{ + id: this.getCurrentModelName() || 'Content Manager', + }} + withDescriptionAnim={this.showLoaders()} + /> + ); + } + + renderPopUpWarningDeleteAll = () => { + const { deleteSeveralData, listPage: { entriesToDelete, showWarningDeleteAll }, onToggleDeleteAll } = this.props; + + return ( + { + deleteSeveralData(entriesToDelete, this.getCurrentModelName(), this.getSource()); + }} + /> + ); + } + + render() { + const { + addFilter, + listPage, + listPage: { + appliedFilters, + count, + entriesToDelete, + filters, + filterToFocus, + records, + params, + showFilter, + }, + onChange, + onClickSelect, + onClickSelectAll, + onToggleDeleteAll, + onToggleFilters, + removeAllFilters, + removeFilter, + } = this.props; + const { isOpen } = this.state; return (
    - - 1 - ? 'content-manager.containers.List.pluginHeaderDescription' - : 'content-manager.containers.List.pluginHeaderDescription.singular', - values: { - label: get(count, this.getCurrentModelName(), 0), - }, - }} - title={{ - id: this.getCurrentModelName() || 'Content Manager', - }} - withDescriptionAnim={this.showLoaders()} - /> -
    - -
    -
    -
    0} - > -
    - - {filters.map((filter, key) => ( - - ))} + )} + {this.renderPluginHeader()} + +
    + {this.showFilters() && ( + + +
    +
    +
    0} + > +
    + + {filters.map(this.renderFilter)} +
    +
    -
    -
    -
    +
    +
    + + + + + {this.renderDropdownHeader} + + {this.getAllModelFields().map(this.renderDropdown)} + + +
    +
    +
    + + )}
    - - + + this.handleChangeSort(header.name)} + onClick={() => { + if (header.sortable) { + this.handleChangeSort(header.name); + } + }} > {header.label} @@ -64,7 +75,7 @@ class TableHeader extends React.Component { headers.push(
    e.stopPropagation()} key="i"> - - e.stopPropagation()} key="i"> + +
    - { - deleteSeveralData(entriesToDelete, this.getCurrentModelName(), this.getSource()); - }} - /> + {this.renderPopUpWarningDeleteAll()} { + if (action.index !== -1) { + return list.splice(action.index, 0, fromJS(action.attr)); + } + + return list.push(fromJS(action.attr)); + }) + .update('didChangeDisplayedFields', v => !v); case ADD_FILTER: return state.update('appliedFilters', list => list.push(Map(action.filter))); case DELETE_DATA_SUCCESS: @@ -147,8 +163,20 @@ function listPageReducer(state = initialState, action) { .update('appliedFilters', () => List([])) .update('filters', () => List([])) .update('filtersUpdated', v => v = !v); + case REMOVE_ATTR: + return state + .update('displayedFields', list => { + return list.splice(action.index, 1); + }) + .update('didChangeDisplayedFields', v => !v); case REMOVE_FILTER: return state.update('appliedFilters', list => list.splice(action.index, 1)); + case RESET_DISPLAYED_FIELDS: + return state + .update('displayedFields', () => fromJS(action.fields)) + .update('didChangeDisplayedFields', v => !v); + case SET_DISPLAYED_FIELDS: + return state.update('displayedFields', () => fromJS(action.fields)); case SET_PARAMS: return state .update('params', () => Map(action.params)) diff --git a/packages/strapi-plugin-content-manager/admin/src/containers/ListPage/saga.js b/packages/strapi-plugin-content-manager/admin/src/containers/ListPage/saga.js index 32497c9870..cf2db9228e 100644 --- a/packages/strapi-plugin-content-manager/admin/src/containers/ListPage/saga.js +++ b/packages/strapi-plugin-content-manager/admin/src/containers/ListPage/saga.js @@ -107,6 +107,7 @@ export function* dataDeleteAll({ entriesToDelete, model, source }) { yield put(deleteSeveralDataSuccess()); yield call(dataGet, { currentModel: model, source }); + strapi.notification.success('content-manager.success.record.delete'); } catch(err) { strapi.notification.error('content-manager.error.record.delete'); } diff --git a/packages/strapi-plugin-content-manager/admin/src/containers/ListPage/styles.scss b/packages/strapi-plugin-content-manager/admin/src/containers/ListPage/styles.scss index 8be6d6161b..c4c8a18f83 100644 --- a/packages/strapi-plugin-content-manager/admin/src/containers/ListPage/styles.scss +++ b/packages/strapi-plugin-content-manager/admin/src/containers/ListPage/styles.scss @@ -1,7 +1,6 @@ .containerFluid { /* stylelint-disable */ position: relative; padding: 18px 30px; - .modal-content{ padding: 0; } @@ -23,3 +22,149 @@ left: 30px; width: calc(100% - 60px); } + +.listPageDropdownWrapper { + display: flex; + justify-content: flex-end; + font-family: Lato; + margin-top: -2px; + -webkit-font-smoothing: antialiased; + + > div { + height: 30px; + + > button { + padding: 0 10px; + + &:hover { + cursor: pointer; + } + } + + button { + &:focus { + outline: 0; + } + } + + > div { + min-width: 230px; + top: 28px !important; left: 53px !important; + padding-bottom: 5px !important; + border-top-right-radius: 0; + border: 1px solid #E3E9F3; + box-shadow: 0px 2px 4px rgba(227, 233, 243, .5); + + > button { + &:active, :focus { + background-color: #f7f7f9 !important; + color: #333740; + font-weight: 500; + } + + &:hover{ + cursor: pointer; + } + + &:not(:first-child) { + padding: 0; + } + + &:first-of-type{ + margin-top: 2px; + } + + &:first-child { + margin-bottom: 2px; + font-weight: 600; + font-size: 1.3rem; + + &:hover { + background-color: #ffffff !important; + } + > div { + > span:last-child { + color: #007EFF; + font-weight: 400; + cursor: pointer; + } + } + } + + label{ + width: 100%; + outline: none; + } + } + } + } +} + +.listPageDropdownWrapperOpen { + > div { + > button { + background: #E6F0FB; + border: 1px solid #AED4FB !important; + border-radius: 2px; + border-bottom-right-radius: 0 !important; + border-bottom-left-radius: 0 !important; + border-top-right-radius: 2px !important; + + &:before { + content: '\f0db'; + font-family: FontAwesome; + color: #007EFF; + } + + &:after { + content: '\f0d7'; + display: inline-block; + margin-top: -1px; + margin-left: 10px; + font-family: FontAwesome; + color: #007EFF; + transform: rotateX(180deg); + transition: transform .3s ease-out; + } + + &:hover, :active, :focus { + background: #E6F0FB; + border: 1px solid #AED4FB; + } + + } + > div { + border-top-color: #AED4FB !important; + border-top-right-radius: 0; + } + } +} + +.listPageDropdownWrapperClose { + > div { + > button { + background: #ffffff !important; + border: 1px solid #E3E9F3; + border-radius: 2px !important; + + &:before { + content: '\f0db'; + font-family: FontAwesome; + color: #323740; + } + &:after { + content: '\f0d7'; + display: inline-block; + margin-top: -1px; + margin-left: 10px; + font-family: FontAwesome; + color: #323740; + transition: transform .3s ease-out; + } + &:hover, :focus, :active { + background: #ffffff !important; + border: 1px solid #E3E9F3; + } + } + } +} \ No newline at end of file diff --git a/packages/strapi-plugin-content-manager/admin/src/containers/ListPage/utils.js b/packages/strapi-plugin-content-manager/admin/src/containers/ListPage/utils.js index 67346e92bb..6ce34da080 100644 --- a/packages/strapi-plugin-content-manager/admin/src/containers/ListPage/utils.js +++ b/packages/strapi-plugin-content-manager/admin/src/containers/ListPage/utils.js @@ -50,8 +50,17 @@ const generateSearchFromParams = params => return acc; }, ''); + /** +* Generate the redirect URI when editing an entry +* @type {String} +*/ +const generateRedirectURI = function ({ model, search } = {}) { + return `?redirectUrl=/plugins/content-manager/${(model || this.getCurrentModelName()).toLowerCase()}${(search || this.generateSearch())}`; +}; + export { generateFiltersFromSearch, generateSearchFromFilters, generateSearchFromParams, + generateRedirectURI, }; diff --git a/packages/strapi-plugin-content-manager/admin/src/containers/SettingPage/actions.js b/packages/strapi-plugin-content-manager/admin/src/containers/SettingPage/actions.js new file mode 100644 index 0000000000..1ad42095b7 --- /dev/null +++ b/packages/strapi-plugin-content-manager/admin/src/containers/SettingPage/actions.js @@ -0,0 +1,26 @@ +import { + ON_CLICK_EDIT_FIELD, + ON_CLICK_EDIT_LIST_ITEM, + ON_CLICK_EDIT_RELATION, +} from './constants'; + +export function onClickEditField(fieldToEdit) { + return { + type: ON_CLICK_EDIT_FIELD, + fieldToEdit, + }; +} + +export function onClickEditListItem(listItemToEdit) { + return { + type: ON_CLICK_EDIT_LIST_ITEM, + listItemToEdit, + }; +} + +export function onClickEditRelation(relationToEdit) { + return { + type: ON_CLICK_EDIT_RELATION, + relationToEdit, + }; +} \ No newline at end of file diff --git a/packages/strapi-plugin-content-manager/admin/src/containers/SettingPage/constants.js b/packages/strapi-plugin-content-manager/admin/src/containers/SettingPage/constants.js new file mode 100644 index 0000000000..40ae4cd939 --- /dev/null +++ b/packages/strapi-plugin-content-manager/admin/src/containers/SettingPage/constants.js @@ -0,0 +1,3 @@ +export const ON_CLICK_EDIT_FIELD = 'contentManager/SettingPage/ON_CLICK_EDIT_FIELD'; +export const ON_CLICK_EDIT_LIST_ITEM = 'contentManager/SettingPage/ON_CLICK_EDIT_LIST_ITEM'; +export const ON_CLICK_EDIT_RELATION = 'contentManager/SettingPage/ON_CLICK_EDIT_RELATION'; \ No newline at end of file diff --git a/packages/strapi-plugin-content-manager/admin/src/containers/SettingPage/forms.json b/packages/strapi-plugin-content-manager/admin/src/containers/SettingPage/forms.json new file mode 100644 index 0000000000..ec29d2f99e --- /dev/null +++ b/packages/strapi-plugin-content-manager/admin/src/containers/SettingPage/forms.json @@ -0,0 +1,151 @@ +{ + "inputs": [ + { + "label": { "id": "content-manager.form.Input.search" }, + "customBootstrapClass": "col-md-4", + "didCheckErrors": false, + "errors": [], + "name": "search", + "type": "toggle", + "validations": {} + }, + { + "label": { "id": "content-manager.form.Input.filters" }, + "customBootstrapClass": "col-md-4", + "didCheckErrors": false, + "errors": [], + "name": "filters", + "type": "toggle", + "validations": {} + }, + { + "label": { "id": "content-manager.form.Input.bulkActions" }, + "customBootstrapClass": "col-md-4", + "didCheckErrors": false, + "errors": [], + "name": "bulkActions", + "type": "toggle", + "validations": {} + }, + { + "label": { "id": "content-manager.form.Input.pageEntries" }, + "customBootstrapClass": "col-md-4", + "didCheckErrors": false, + "errors": [], + "inputDescription": { "id": "content-manager.form.Input.pageEntries.inputDescription" }, + "name": "pageEntries", + "selectOptions": ["10", "20", "50", "100"], + "type": "select", + "validations": {} + }, + { + "label": { "id": "content-manager.form.Input.defaultSort" }, + "customBootstrapClass": "col-md-4 ml-md-auto", + "didCheckErrors": false, + "errors": [], + "style": { "marginRight": "-20px" }, + "name": "defaultSort", + "selectOptions": ["_id"], + "type": "select", + "validations": {} + }, + { + "label": "", + "customBootstrapClass": "col-md-1", + "didCheckErrors": false, + "errors": [], + + "name": "sort", + "selectOptions": ["ASC", "DESC"], + "type": "select", + "validations": {} + } + + ], + "editList": [ + { + "label": { "id": "content-manager.form.Input.label" }, + "customBootstrapClass": "col-md-7", + "didCheckErrors": false, + "errors": [], + "inputDescription": { "id": "content-manager.form.Input.label.inputDescription" }, + "name": "label", + "type": "string", + "validations": {} + }, + { + "label": { "id": "content-manager.form.Input.sort.field" }, + "customBootstrapClass": "col-md-6", + "didCheckErrors": false, + "errors": [], + "name": "sortable", + "style": { "marginTop": "18px" }, + "type": "toggle", + "validations": {} + } + ], + "editView": { + "relationForm": [ + { + "label": { "id": "content-manager.form.Input.label" }, + "customBootstrapClass": "col-md-8", + "didCheckErrors": false, + "errors": [], + "name": "label", + "placeholder": "SLUG (URL)", + "type": "string", + "validations": {} + }, + { + "label": { "id": "content-manager.form.Input.description" }, + "customBootstrapClass": "col-md-12", + "didCheckErrors": false, + "errors": [], + "name": "description", + "placeholder": "content-manager.form.Input.description.placeholder", + "type": "string", + "validations": {} + } + ], + "fieldForm": [ + { + "label": { "id": "content-manager.form.Input.label" }, + "customBootstrapClass": "col-md-8", + "didCheckErrors": false, + "errors": [], + "name": "label", + "placeholder": "SLUG (URL)", + "type": "string", + "validations": {} + }, + { + "label": { "id": "content-manager.form.Input.disabled" }, + "customBootstrapClass": "col-md-4", + "didCheckErrors": false, + "errors": [], + "name": "editable", + "type": "toggle", + "validations": {} + }, + { + "label": { "id": "content-manager.form.Input.description" }, + "customBootstrapClass": "col-md-12", + "didCheckErrors": false, + "errors": [], + "name": "description", + "placeholder": "content-manager.form.Input.description.placeholder", + "type": "string", + "validations": {} + }, + { + "label": { "id": "content-manager.form.Input.placeholder" }, + "customBootstrapClass": "col-md-12", + "didCheckErrors": false, + "errors": [], + "name": "placeholder", + "type": "string", + "validations": {} + } + ] + } +} \ No newline at end of file diff --git a/packages/strapi-plugin-content-manager/admin/src/containers/SettingPage/index.js b/packages/strapi-plugin-content-manager/admin/src/containers/SettingPage/index.js new file mode 100644 index 0000000000..03531d0dfb --- /dev/null +++ b/packages/strapi-plugin-content-manager/admin/src/containers/SettingPage/index.js @@ -0,0 +1,944 @@ +/** + * + * SettingPage + */ + +import React from 'react'; +import { connect } from 'react-redux'; +import { bindActionCreators, compose } from 'redux'; +import { createStructuredSelector } from 'reselect'; +import { findIndex, get, isEmpty, upperFirst } from 'lodash'; +import cn from 'classnames'; +import HTML5Backend from 'react-dnd-html5-backend'; +import { DragDropContext } from 'react-dnd'; +import { FormattedMessage } from 'react-intl'; +import { ButtonDropdown, DropdownToggle, DropdownMenu, DropdownItem } from 'reactstrap'; +import PropTypes from 'prop-types'; +import { + beginMove, + endMove, + moveAttr, + moveAttrEditView, + moveVariableAttrEditView, + onChangeSettings, + onClickAddAttr, + onClickAddAttrField, + onRemove, + onRemoveEditViewFieldAttr, + onRemoveEditViewRelationAttr, + onReset, + onSubmit, + setLayout, +} from 'containers/App/actions'; +import { + makeSelectAddedField, + makeSelectDraggedItemName, + makeSelectGrid, + makeSelectHoverIndex, + makeSelectInitDragLine, + makeSelectModifiedSchema, + makeSelectShouldResetGrid, + makeSelectSubmitSuccess, +} from 'containers/App/selectors'; +import BackHeader from 'components/BackHeader'; +import Input from 'components/InputsIndex'; +import PluginHeader from 'components/PluginHeader'; +import PopUpWarning from 'components/PopUpWarning'; +import Block from 'components/Block'; +import CustomDragLayer from 'components/CustomDragLayer'; +import DraggableAttr from 'components/DraggableAttr'; +import VariableDraggableAttr from 'components/VariableDraggableAttr'; +import injectReducer from 'utils/injectReducer'; +import injectSaga from 'utils/injectSaga'; +import { onClickEditField, onClickEditListItem, onClickEditRelation } from './actions'; +import forms from './forms.json'; +import reducer from './reducer'; +import saga from './saga'; +import makeSelectSettingPage from './selectors'; +import styles from './styles.scss'; + +class SettingPage extends React.PureComponent { + state = { + isDraggingSibling: false, + isOpen: false, + isOpenField: false, + isOpenRelation: false, + showWarning: false, + showWarningCancel: false, + shouldSelectField: false, + shouldSelectRelation: false, + }; + + componentDidMount() { + this.handleClickEditAttr(0); + const fields = this.getEditPageDisplayedFields(); + const relations = this.getEditPageDisplayedRelations(); + this.props.setLayout(`${this.getPath()}.editDisplay`); + + if (fields.length > 0) { + this.handleClickEditField(0); + } else if (relations.length > 0) { + this.handleClickEditRelation(0); + } + } + + componentDidUpdate(prevProps, prevState) { + const { schema } = prevProps; + const prevDisplayedFields = get(schema, ['models', ...this.getPath().split('.'), 'editDisplay', 'fields'], []); + const prevDisplayedRelations = get(schema, ['models', ...this.getPath().split('.'), 'editDisplay', 'relations'], []); + const currentDisplayedFields = get(this.props.schema, ['models', ...this.getPath().split('.'), 'editDisplay', 'fields'], []); + const currentDisplayedRelations = get(this.props.schema, ['models', ...this.getPath().split('.'), 'editDisplay', 'relations'], []); + + if (prevProps.submitSuccess !== this.props.submitSuccess) { + this.toggle(); + } + + if (prevDisplayedFields.length === 0 && currentDisplayedFields.length > 0 && prevState.shouldSelectField !== this.state.shouldSelectField) { + this.handleClickEditField(0); + } + + if (prevDisplayedRelations.length === 0 && currentDisplayedRelations.length > 0 && prevState.shouldSelectRelation !== this.state.shouldSelectRelation) { + this.handleClickEditRelation(0); + } + + if (prevProps.addedField !== this.props.addedField) { + this.props.setLayout(`${this.getPath()}.editDisplay`); + } + + if (prevProps.shouldResetGrid !== this.props.shouldResetGrid) { + this.props.setLayout(`${this.getPath()}.editDisplay`); + } + } + + componentWillUnmount() { + // Reset the modified data + this.props.onReset(); + } + + getAttrData = attrName => get(this.getEditPageDisplaySettings(), ['availableFields', attrName], {}); + + getDefaultSort = () => this.getValue(`${this.getPath()}.defaultSort`, 'string'); + + getDropDownItems = () => { + const name = get(this.props.schema, `models.${this.getPath()}.primaryKey`, 'id' ); + // The id attribute is not present on the schema so we need to add it manually + const defaultAttr = { [name]: { name, label: 'Id', type: 'string', searchable: true, sortable: true } }; + const attributes = Object.assign(get(this.props.schema, `models.${this.getPath()}.attributes`, {}), defaultAttr); + + return Object.keys(attributes) + .filter(attr => { + return findIndex(this.getListDisplay(), ['name', attr]) === -1 && !attributes[attr].hasOwnProperty('collection') && !attributes[attr].hasOwnProperty('model'); + }) + .map(attr => { + const searchable = attributes[attr].type !== 'json' && attributes[attr].type !== 'array'; + const obj = Object.assign(attributes[attr], { name: attr, label: upperFirst(attr), searchable, sortable: searchable }); + + return obj; + }); + } + + getDropDownFieldItems = () => { + const currentDisplayedFields = this.getEditPageDisplayedFields(); + const availableFields = get(this.props.schema, ['models', ...this.getPath().split('.'), 'editDisplay', 'availableFields'], {}); + + return Object.keys(availableFields) + .filter(field => { + return currentDisplayedFields.indexOf(field) === -1; + }); + } + + getDropDownRelationsItems = () => { + const currentDisplayedRelations = this.getEditPageDisplayedRelations(); + + return this.getRelations() + .filter(relation => { + return currentDisplayedRelations.indexOf(relation) === -1; + }); + } + + getEditPageDisplaySettings = () => { + return get(this.props.schema, 'models.'.concat(this.getPath().concat('.editDisplay')), { fields: [], relations: [] }); + } + + getEditPageDisplayedFields = () => get(this.getEditPageDisplaySettings(), ['fields'], []); + + getEditPageDisplayedRelations = () => get(this.getEditPageDisplaySettings(), ['relations'], []); + + getLayout = () => { + const { match: { params: { slug, endPoint } }, schema: { layout } } = this.props; + + return get(layout, [endPoint || slug, 'attributes' ], {}); + } + + getListDisplay = () => get(this.props.schema, `models.${this.getPath()}.listDisplay`, []); + + getModelName = () => { + const { match: { params: { slug, endPoint } } } = this.props; + + return endPoint || slug; + } + + getPath = () => { + const { match: { params: { slug, source, endPoint } } } = this.props; + + return [slug, source, endPoint] + .filter(param => param !== undefined) + .join('.'); + } + + getRelationLabel = (attrName) => { + const attrLabel = get(this.props.schema, ['models', ...this.getPath().split('.'), 'relations', attrName, 'label'], 'iii'); + + return attrLabel; + } + + getRelations = () => { + const relations = get(this.props.schema, 'models.'.concat(this.getPath()).concat('.relations'), {}); + + return Object.keys(relations) + .filter(relation => { + const isUploadRelation = get(relations, [relation, 'plugin'], '') === 'upload'; + const isMorphSide = get(relations, [relation, 'nature'], '').toLowerCase().includes('morph') && get(relations, [relation, relation]) !== undefined; + + return !isUploadRelation && !isMorphSide; + }); + } + + getSelectOptions = (input) => { + const selectOptions = this.getListDisplay().reduce((acc, curr) => { + + if (curr.sortable === true) { + return acc.concat([curr.name]); + } + + return acc; + }, []); + + if (selectOptions.length === 0) { + selectOptions.push(this.getPrimaryKey()); + } + + return input.name === 'defaultSort' ? selectOptions : input.selectOptions; + } + + getPluginHeaderActions = () => { + return ( + [ + { + label: 'content-manager.popUpWarning.button.cancel', + kind: 'secondary', + onClick: this.handleReset, + type: 'button', + }, + { + kind: 'primary', + label: 'content-manager.containers.Edit.submit', + onClick: this.handleSubmit, + type: 'submit', + }, + ] + ); + } + + getPrimaryKey = () => get(this.props.schema, ['models', this.getModelName()].concat(['primaryKey']), 'id'); + + getValue = (keys, type) => { + const value = get(this.props.schema, ['models'].concat(keys.split('.'))); + + return type === 'toggle' ? value : value.toString(); + } + + handleChange = (e) => { + const defaultSort = this.getDefaultSort(); + const name = e.target.name.split('.'); + name.pop(); + const attrName = get(this.props.schema.models, name.concat(['name'])); + const isDisablingDefaultSort = attrName === defaultSort && e.target.value === false; + + if (isDisablingDefaultSort) { + const enableAttrsSort = this.getSelectOptions({ name: 'defaultSort' }).filter(attr => attr !== attrName); + + if (enableAttrsSort.length === 0) { + strapi.notification.info('content-manager.notification.info.SettingPage.disableSort'); + } else { + const newDefaultSort = enableAttrsSort.length === 0 ? this.getPrimaryKey() : enableAttrsSort[0]; + const target = { name: `${this.getPath()}.defaultSort`, value: newDefaultSort }; + this.props.onChangeSettings({ target }); + this.props.onChangeSettings(e); + } + } else { + this.props.onChangeSettings(e); + } + } + + handleClickEditAttr = index => { + const attrToEdit = get(this.props.schema, ['models'].concat(this.getPath().split('.')).concat(['listDisplay', index]), {}); + this.props.onClickEditListItem(attrToEdit); + } + + handleClickEditField = index => { + const fieldToEditName = get(this.props.schema, ['models', ...this.getPath().split('.'), 'editDisplay', 'fields', index], ''); + const fieldToEdit = get(this.props.schema, ['models', ...this.getPath().split('.'), 'editDisplay', 'availableFields', fieldToEditName], {}); + + return this.props.onClickEditField(fieldToEdit); + } + + handleClickEditRelation = index => { + const relationToEditName = get(this.getEditPageDisplayedRelations(), index, ''); + const relationToEdit = get(this.props.schema, ['models', ...this.getPath().split('.'), 'relations', relationToEditName]); + + return this.props.onClickEditRelation(relationToEdit); + } + + handleConfirmReset = () => { + this.props.onReset(); + this.toggleWarningCancel(); + } + + handleGoBack = () => this.props.history.goBack(); + + handleRemove = (index, keys) => { + const attrToRemove = get(this.getListDisplay(), index, {}); + const defaultSort = this.getDefaultSort(); + const isRemovingDefaultSort = defaultSort === attrToRemove.name; + + if (isRemovingDefaultSort) { + const enableAttrsSort = this.getSelectOptions({ name: 'defaultSort' }).filter(attr => attr !== attrToRemove.name); + const newDefaultSort = enableAttrsSort.length > 1 ? enableAttrsSort[0] : this.getPrimaryKey(); + const target = { name: `${this.getPath()}.defaultSort`, value: newDefaultSort }; + this.props.onChangeSettings({ target }); + } + + this.props.onRemove(index, keys); + } + + handleRemoveField = (index, keys) => { + const { settingPage: { fieldToEdit } } = this.props; + const fieldToEditName = get(this.props.schema, ['models', ...keys.split('.'), 'fields', index], ''); + this.manageRemove(index, keys, fieldToEditName, fieldToEdit, false); + } + + handleRemoveRelation = (index, keys) => { + const { settingPage: { relationToEdit } } = this.props; + const relationToRemoveName = get(this.props.schema, ['models', ...keys.split('.'), index]); + this.manageRemove(index, keys, relationToRemoveName, relationToEdit); + } + + manageRemove = (index, keys, itemName, data, isRelation = true) => { + const isRemovingSelectedItem = isRelation ? itemName === data.alias : itemName === data.name; + const displayedRelations = this.getEditPageDisplayedRelations(); + const displayedFields = this.getEditPageDisplayedFields(); + + if (isRelation) { + this.props.onRemoveEditViewRelationAttr(index, keys); + } else { + this.props.onRemoveEditViewFieldAttr(index, keys); + } + + if (isRemovingSelectedItem) { + const selectNextItemCond = isRelation ? displayedRelations.length > 2 : displayedFields.length > 2; + const selectOtherItemCond = isRelation ? displayedFields.length > 0 : displayedRelations.length > 0; + const selectNextFunc = isRelation ? this.handleClickEditRelation : this.handleClickEditField; + const selectOtherFunc = !isRelation ? this.handleClickEditRelation : this.handleClickEditField; + + if (selectNextItemCond) { + let nextIndex = index - 1 > 0 ? index - 1 : index + 1; + + if (!isRelation) { + const nextItem = get(this.getEditPageDisplayedFields(), nextIndex); + + if (nextItem.includes('__col-md')) { + nextIndex = index - 2 > 0 ? index - 2 : index + 2; + } + + } + selectNextFunc(nextIndex); + } else if (selectOtherItemCond) { + selectOtherFunc(0); + } else { + const toAdd = isRelation ? this.getDropDownFieldItems()[0] : this.getDropDownRelationsItems()[0]; + + if (isRelation) { + this.props.onClickAddAttrField(toAdd, `${this.getPath()}.editDisplay.fields`); + this.setState(prevState => ({ shouldSelectField: !prevState.shouldSelectField })); + } else { + this.props.onClickAddAttr(toAdd, `${this.getPath()}.editDisplay.relations`); + this.setState(prevState => ({ shouldSelectRelation: !prevState.shouldSelectRelation })); + } + } + } + } + + handleReset = (e) => { + e.preventDefault(); + this.setState({ showWarningCancel: true }); + } + + handleSubmit = (e) => { + e.preventDefault(); + this.setState({ showWarning: true }); + } + + findIndexFieldToEdit = () => { + const { settingPage: { fieldToEdit } } = this.props; + + if (isEmpty(fieldToEdit)) { + return -1; + } + + const index = this.getEditPageDisplayedFields().indexOf(fieldToEdit.name); + + return index; + } + + findIndexListItemToEdit = () => { + const index = findIndex(this.getListDisplay(), ['name', get(this.props.settingPage, ['listItemToEdit', 'name'])]); + + return index === -1 ? 0 : index; + } + + findIndexRelationItemToEdit = () => { + const { settingPage: { relationToEdit } } = this.props; + + if (isEmpty(relationToEdit)) { + return -1; + } + + const index = this.getEditPageDisplayedRelations().indexOf(relationToEdit.alias); + + return index; + } + + hasRelations = () => { + return this.getRelations().length > 0; + } + + shouldDisplayCursorNotAllowed = (dropdownType) => { + switch (dropdownType) { + case 'list': + return this.getDropDownItems().length === 0; + case 'relations': + return this.getDropDownRelationsItems().length === 0; + case 'fields': + return this.getDropDownFieldItems().length === 0; + default: + return false; + } + } + + toggle = () => this.setState(prevState => ({ showWarning: !prevState.showWarning })); + + toggleWarningCancel = () => this.setState(prevState => ({ showWarningCancel: !prevState.showWarningCancel })); + + toggleDropdown = () => { + if (this.getDropDownItems().length > 0) { + this.setState(prevState => ({ isOpen: !prevState.isOpen })); + } + } + + toggleDropDownFields = () => { + if (this.getDropDownFieldItems().length > 0) { + this.setState(prevState => ({ isOpenField: !prevState.isOpenField })); + } + } + + toggleDropdownRelations = () => { + if (this.getDropDownRelationsItems().length > 0) { + this.setState(prevState => ({ isOpenRelation: !prevState.isOpenRelation })); + } + } + + // We need to remove the Over state on the DraggableAttr component + updateSiblingHoverState = () => { + this.setState(prevState => ({ isDraggingSibling: !prevState.isDraggingSibling })); + }; + + renderDraggableAttrEditSettingsField = (attr, index) => { + return ( + + ); + } + + renderDraggableAttrEditSettingsRelation = (attr, index) => { + return ( + {}} + /> + ); + } + + renderDraggableAttrListSettings = (attr, index) => { + return ( +
    +
    {index + 1}.
    + +
    + ); + } + + renderDropDownItemSettingField = item => { + return ( + this.props.onClickAddAttrField(item, `${this.getPath()}.editDisplay.fields`)} + > + {item} + + ); + } + + renderDropDownItemEditSettingsRelation = item => { + return ( + this.props.onClickAddAttr(item, `${this.getPath()}.editDisplay.relations`)} + > + {item} + + ); + } + + renderDropDownItemListSettings = item => { + return ( + { + this.props.onClickAddAttr(item, `${this.getPath()}.listDisplay`); + }} + > + {item.label} + + ); + } + + renderDropDownP = msg =>

    {msg}

    ; + + renderForm = () => { + const { settingPage: { fieldToEdit, relationToEdit } } = this.props; + + if (isEmpty(fieldToEdit)) { + return forms.editView.relationForm.map(this.renderFormEditSettingsRelation); + } + + if (isEmpty(relationToEdit)) { + return forms.editView.fieldForm.map(this.renderFormEditSettingsField); + } + + return null; + } + + renderFormEditSettingsField = (input, i) => { + const { onChangeSettings, schema, settingPage: { fieldToEdit: { name } } } = this.props; + const path = [...this.getPath().split('.'), 'editDisplay', 'availableFields', name, input.name]; + const value = get(schema, ['models', ...path], ''); + + return ( + + ); + } + + renderFormEditSettingsRelation = (input, i) => { + const { onChangeSettings, schema, settingPage: { relationToEdit: { alias } } } = this.props; + const path = [...this.getPath().split('.'), 'relations', alias, input.name]; + const value = get(schema, ['models', ...path], ''); + + return ( + + ); + } + + renderFormListAttrSettings = (input, i) => { + const indexListItemToEdit = this.findIndexListItemToEdit(); + const inputName = `${this.getPath()}.listDisplay.${indexListItemToEdit}.${input.name}`; + const inputType = this.getListDisplay()[indexListItemToEdit].type; + + if (indexListItemToEdit === -1) { + return
    ; + } + + if ((inputType === 'json' || inputType === 'array') && (input.name === 'sortable' || input.name === 'searchable')) { + return null; + } + + return ( + + ); + } + + renderInputMainSettings = (input, i) => { + const inputName = `${this.getPath()}.${input.name}`; + const content = ( + + ); + + if (i === 3) { + + return ( + +
    +
    +
    + {content} + + ); + } + + return content; + } + + render() { + const { + isOpen, + isOpenField, + isOpenRelation, + showWarning, + showWarningCancel, + } = this.state; + const { + onSubmit, + } = this.props; + + return ( + + +
    + + + + +
    + +
    +
    + {/* GENERAL LIST SETTINGS FORM */} +
    +
    + {forms.inputs.map(this.renderInputMainSettings)} +
    +
    + +
    +
    +
    +
    + +
    +
    + +
    + +

    + +

    +
    + +
    + {this.getListDisplay().map(this.renderDraggableAttrListSettings)} +
    + + + + {this.renderDropDownP} + + + + {this.getDropDownItems().map(this.renderDropDownItemListSettings)} + + +
    +
    + + {/* LIST ATTR FORM */} +
    +
    +
    + {forms.editList.map(this.renderFormListAttrSettings)} +
    +
    +
    + {/* LIST ATTR FORM */} +
    +
    +
    + + + +
    +
    + +
    + +
    + {this.getEditPageDisplayedFields().map(this.renderDraggableAttrEditSettingsField)} +
    +
    + + + + {this.renderDropDownP} + + + + {this.getDropDownFieldItems().map(this.renderDropDownItemSettingField)} + + +
    +
    +
    +
    +
    + + {/* RELATIONS SORT */} + {this.hasRelations() && ( +
    + +
    +
    + {/* DRAGGABLE BLOCK */} + {this.getEditPageDisplayedRelations().map(this.renderDraggableAttrEditSettingsRelation)} + {/* DRAGGABLE BLOCK */} +
    + + + + {this.renderDropDownP} + + + + {this.getDropDownRelationsItems().map(this.renderDropDownItemEditSettingsRelation)} + + +
    +
    +
    +
    + )} + {/* RELATIONS SORT */} +
    + + {/* EDIT MAIN ATTR FORM */} +
    +
    +
    + +
    + {this.renderForm()} +
    + +
    +
    +
    + {/* EDIT MAIN ATTR FORM */} +
    +
    +
    + + ); + } +} + +SettingPage.defaultProps = { + draggedItemName: null, + grid: [], +}; + +SettingPage.propTypes = { + addedField: PropTypes.bool.isRequired, + beginMove: PropTypes.func.isRequired, + draggedItemName: PropTypes.string, + endMove: PropTypes.func.isRequired, + grid: PropTypes.array, + history: PropTypes.object.isRequired, + hoverIndex: PropTypes.number.isRequired, + initDragLine: PropTypes.number.isRequired, + match: PropTypes.object.isRequired, + moveAttr: PropTypes.func.isRequired, + moveAttrEditView: PropTypes.func.isRequired, + moveVariableAttrEditView: PropTypes.func.isRequired, + onChangeSettings: PropTypes.func.isRequired, + onClickAddAttr: PropTypes.func.isRequired, + onClickAddAttrField: PropTypes.func.isRequired, + onClickEditField: PropTypes.func.isRequired, + onClickEditListItem: PropTypes.func.isRequired, + onClickEditRelation: PropTypes.func.isRequired, + onRemove: PropTypes.func.isRequired, + onRemoveEditViewFieldAttr: PropTypes.func.isRequired, + onRemoveEditViewRelationAttr: PropTypes.func.isRequired, + onReset: PropTypes.func.isRequired, + onSubmit: PropTypes.func.isRequired, + schema: PropTypes.object.isRequired, + setLayout: PropTypes.func.isRequired, + settingPage: PropTypes.object.isRequired, + shouldResetGrid: PropTypes.bool.isRequired, + submitSuccess: PropTypes.bool.isRequired, +}; + +const mapDispatchToProps = (dispatch) => ( + bindActionCreators( + { + beginMove, + endMove, + moveAttr, + moveAttrEditView, + moveVariableAttrEditView, + onChangeSettings, + onClickAddAttr, + onClickAddAttrField, + onClickEditField, + onClickEditListItem, + onClickEditRelation, + onRemove, + onRemoveEditViewFieldAttr, + onRemoveEditViewRelationAttr, + onReset, + onSubmit, + setLayout, + }, + dispatch, + ) +); +const mapStateToProps = createStructuredSelector({ + addedField: makeSelectAddedField(), + draggedItemName: makeSelectDraggedItemName(), + grid: makeSelectGrid(), + hoverIndex: makeSelectHoverIndex(), + initDragLine: makeSelectInitDragLine(), + schema: makeSelectModifiedSchema(), + settingPage: makeSelectSettingPage(), + shouldResetGrid: makeSelectShouldResetGrid(), + submitSuccess: makeSelectSubmitSuccess(), +}); +const withConnect = connect(mapStateToProps, mapDispatchToProps); +const withReducer = injectReducer({ key: 'settingPage', reducer }); +const withSaga = injectSaga({ key: 'settingPage', saga }); + +export default compose( + withReducer, + withSaga, + withConnect, +)(DragDropContext(HTML5Backend)(SettingPage)); diff --git a/packages/strapi-plugin-content-manager/admin/src/containers/SettingPage/reducer.js b/packages/strapi-plugin-content-manager/admin/src/containers/SettingPage/reducer.js new file mode 100644 index 0000000000..b633f109d0 --- /dev/null +++ b/packages/strapi-plugin-content-manager/admin/src/containers/SettingPage/reducer.js @@ -0,0 +1,36 @@ +/** + * + * SettingPage reducer + */ + +import { fromJS } from 'immutable'; +import { + ON_CLICK_EDIT_FIELD, + ON_CLICK_EDIT_LIST_ITEM, + ON_CLICK_EDIT_RELATION, +} from './constants'; + +const initialState = fromJS({ + fieldToEdit: fromJS({}), + listItemToEdit: fromJS({}), + relationToEdit: fromJS({}), +}); + +function settingPageReducer(state = initialState, action) { + switch (action.type) { + case ON_CLICK_EDIT_FIELD: + return state + .update('fieldToEdit', () => fromJS(action.fieldToEdit)) + .update('relationToEdit', () => fromJS({})); // Both these objects will be used to set the form in order to know which form needs to be displayed + case ON_CLICK_EDIT_LIST_ITEM: + return state.update('listItemToEdit', () => fromJS(action.listItemToEdit)); + case ON_CLICK_EDIT_RELATION: + return state + .update('fieldToEdit', () => fromJS({})) + .update('relationToEdit', () => fromJS(action.relationToEdit)); + default: + return state; + } +} + +export default settingPageReducer; \ No newline at end of file diff --git a/packages/strapi-plugin-content-manager/admin/src/containers/SettingPage/saga.js b/packages/strapi-plugin-content-manager/admin/src/containers/SettingPage/saga.js new file mode 100644 index 0000000000..64a1db2ad6 --- /dev/null +++ b/packages/strapi-plugin-content-manager/admin/src/containers/SettingPage/saga.js @@ -0,0 +1,3 @@ +function* defaultSaga() {} + +export default defaultSaga; \ No newline at end of file diff --git a/packages/strapi-plugin-content-manager/admin/src/containers/SettingPage/selectors.js b/packages/strapi-plugin-content-manager/admin/src/containers/SettingPage/selectors.js new file mode 100644 index 0000000000..bb2fe4ec1d --- /dev/null +++ b/packages/strapi-plugin-content-manager/admin/src/containers/SettingPage/selectors.js @@ -0,0 +1,24 @@ +/** + * + * SettingPage selectors + */ + +import { createSelector } from 'reselect'; + +/** +* Direct selector to the settingPage state domain +*/ +const selectSettingPageDomain = () => state => state.get('settingPage'); + + +/** + * Default selector used by EditPage + */ + +const makeSelectSettingPage = () => createSelector( + selectSettingPageDomain(), + (substate) => substate.toJS() +); + + +export default makeSelectSettingPage; \ No newline at end of file diff --git a/packages/strapi-plugin-content-manager/admin/src/containers/SettingPage/styles.scss b/packages/strapi-plugin-content-manager/admin/src/containers/SettingPage/styles.scss new file mode 100644 index 0000000000..fc55593091 --- /dev/null +++ b/packages/strapi-plugin-content-manager/admin/src/containers/SettingPage/styles.scss @@ -0,0 +1,195 @@ +.containerFluid { + padding: 18px 30px; + > div:first-child { + max-height: 33px; + } +} + +.container { + padding-top: 18px; +} + +.main_wrapper{ + background: #ffffff; + padding: 22px 28px 0px; + border-radius: 2px; + box-shadow: 0 2px 4px #E3E9F3; +} + +.ctmForm { + padding-top: 2.6rem; +} + +.separator { + margin: 0 0 17px; + border-top: 1px solid #F6F6F6; +} + +.separatorHigher{ + margin-top: 19px; + border-top: 1px solid #F6F6F6; +} + +.draggedWrapper { + display: flex; + > div:first-child { + height: 30px; + width: 20px; + margin-right: 10px; + text-align: right; + line-height: 30px; + } +} + +.listDisplayWrapper { + padding-top: 21px; +} + +.draggedDescription { + color: #333740; + font-size: 13px; + font-weight: 500; + + > p { + margin-bottom: 28px; + color: #787E8F; + font-weight: 400; + } +} + +.editWrapper { + min-height: 246px; + padding: 24px 30px 0px; + background-color: #FAFAFB; + border-radius: 2px; +} + +.dropDownNotAllowed { + > div { + > button { + cursor: not-allowed !important; + } + } +} + +.dropdownWrapper { + margin-left: 29px; + > div { + height: 30px; + width: 100%; + justify-content: space-between; + background: #ffffff; + color: #333740; + border: 1px solid #E3E9F3; + border-radius: 2px; + > button { + position: relative; + cursor: pointer; + padding-left: 10px !important; + line-height: 30px; + width: 100%; + color: #333740; + text-align: left; + background-color: #ffffff; + border: none; + font-size: 13px; + font-weight: 500; + &:focus, &:active, &:hover, &:visited { + background-color: transparent!important; + box-shadow: none; + color: #333740; + } + > p { + height: 100%; + margin-left: 20px; + margin-bottom: 0; + margin-top: -1px; + color: #007EFF !important; + font-size: 13px !important; + } + &:before { + position: absolute; + top: 0px; + bottom: 0; + content: '\f067'; + font-family: FontAwesome; + font-size: 10px; + color: #007EFF; + } + } + > div { + max-height: 180px; + min-width: calc(100% + 2px); + margin-left: -1px; + margin-top: -1px; + padding: 0; + border-top-left-radius: 0 !important; + border-top-right-radius: 0; + border-color: #E3E9F3 !important; + border-top-color: #AED4FB !important; + box-shadow: 0 2px 3px rgba(227, 233, 245, .5); + + overflow: scroll; + + button { + height: 30px; + padding-left: 10px !important; + line-height: 26px; + cursor: pointer; + font-size: 13px !important; + &:hover { + background-color: #FAFAFB !important; + } + div { + margin: 0; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + } + } + } + } +} + +.dropdownWrapperOpen { + > div { + background-color: #E6F0FB !important; + border-color: #AED4FB !important; + } +} + +.edit_settings { + padding-top: 25px; +} + +.sort_wrapper { + margin-top: 7px; + margin-bottom: 10px; + padding-top: 10px; + padding-left: 15px !important; + border: 1px dashed #E3E9F3; + border-radius: 2px; + > div { + padding: 0 10px; + } +} + +.dropdownRelations { + width: 100%; + margin-left: 0 !important; + margin-bottom: 10px; +} + +.noPadding { + padding: 0 !important; + padding-right: 10px !important; + + > div { + padding-left: 10px; + padding-right: 0px; + } +} + +.padded { + padding-bottom: 1px; +} \ No newline at end of file diff --git a/packages/strapi-plugin-content-manager/admin/src/containers/SettingsPage/actions.js b/packages/strapi-plugin-content-manager/admin/src/containers/SettingsPage/actions.js new file mode 100644 index 0000000000..e69de29bb2 diff --git a/packages/strapi-plugin-content-manager/admin/src/containers/SettingsPage/constants.js b/packages/strapi-plugin-content-manager/admin/src/containers/SettingsPage/constants.js new file mode 100644 index 0000000000..e69de29bb2 diff --git a/packages/strapi-plugin-content-manager/admin/src/containers/SettingsPage/forms.json b/packages/strapi-plugin-content-manager/admin/src/containers/SettingsPage/forms.json new file mode 100644 index 0000000000..c3014ae121 --- /dev/null +++ b/packages/strapi-plugin-content-manager/admin/src/containers/SettingsPage/forms.json @@ -0,0 +1,42 @@ +{ + "inputs": [ + { + "label": { "id": "content-manager.form.Input.search" }, + "customBootstrapClass": "col-md-4", + "didCheckErrors": false, + "errors": [], + "name": "generalSettings.search", + "type": "toggle", + "validations": {} + }, + { + "label": { "id": "content-manager.form.Input.filters" }, + "customBootstrapClass": "col-md-4", + "didCheckErrors": false, + "errors": [], + "name": "generalSettings.filters", + "type": "toggle", + "validations": {} + }, + { + "label": { "id": "content-manager.form.Input.bulkActions" }, + "customBootstrapClass": "col-md-4", + "didCheckErrors": false, + "errors": [], + "name": "generalSettings.bulkActions", + "type": "toggle", + "validations": {} + }, + { + "label": { "id": "content-manager.form.Input.pageEntries" }, + "customBootstrapClass": "col-md-3", + "didCheckErrors": false, + "errors": [], + "inputDescription": { "id": "content-manager.form.Input.pageEntries.inputDescription" }, + "name": "generalSettings.pageEntries", + "selectOptions": ["10", "20", "50", "100"], + "type": "select", + "validations": {} + } + ] +} \ No newline at end of file diff --git a/packages/strapi-plugin-content-manager/admin/src/containers/SettingsPage/index.js b/packages/strapi-plugin-content-manager/admin/src/containers/SettingsPage/index.js new file mode 100644 index 0000000000..f3d041871a --- /dev/null +++ b/packages/strapi-plugin-content-manager/admin/src/containers/SettingsPage/index.js @@ -0,0 +1,219 @@ +/** + * + * SettingsPage + */ + +import React from 'react'; +import { connect } from 'react-redux'; +import { bindActionCreators, compose } from 'redux'; +import { createStructuredSelector } from 'reselect'; +import cn from 'classnames'; +import { get, sortBy } from 'lodash'; +import PropTypes from 'prop-types'; +import { onChange, onSubmit, onReset } from 'containers/App/actions'; +import { makeSelectModifiedSchema, makeSelectSubmitSuccess } from 'containers/App/selectors'; +import Input from 'components/InputsIndex'; +import PluginHeader from 'components/PluginHeader'; +import PopUpWarning from 'components/PopUpWarning'; +import Block from 'components/Block'; +import SettingsRow from 'components/SettingsRow'; +import injectReducer from 'utils/injectReducer'; +import injectSaga from 'utils/injectSaga'; +import reducer from './reducer'; +import saga from './saga'; +import styles from './styles.scss'; +import forms from './forms.json'; + +class SettingsPage extends React.PureComponent { + state = { showWarning: false, showWarningCancel: false }; + + componentDidUpdate(prevProps) { + if (prevProps.submitSuccess !== this.props.submitSuccess) { + this.toggle(); + } + } + + componentWillUnmount() { + this.props.onReset(); + } + + getModels = (data = this.props.schema.models, destination = '/') => { + const models = Object.keys(data).reduce((acc, curr) => { + if (curr !== 'plugins') { + + if (!data[curr].fields && _.isObject(data[curr])) { + return this.getModels(data[curr], `${destination}${curr}/`); + } + + return acc.concat([{ name: curr, destination: `${destination}${curr}` }]); + } + + return this.getModels(data[curr], `${destination}${curr}/`); + }, []); + + return sortBy( + models.filter(obj => obj.name !== 'permission' && obj.name !== 'role'), + ['name'], + ); + } + + getPluginHeaderActions = () => ( + [ + { + label: 'content-manager.popUpWarning.button.cancel', + kind: 'secondary', + onClick: this.handleReset, + type: 'button', + }, + { + kind: 'primary', + label: 'content-manager.containers.Edit.submit', + onClick: this.handleSubmit, + type: 'submit', + }, + ] + ); + + getValue = (input) => { + const { schema: { generalSettings } } = this.props; + const value = get(generalSettings, input.name.split('.')[1], input.type === 'toggle' ? false : 10); + + return input.type === 'toggle' ? value : value.toString(); + } + + handleClick = (destination) => { + const { location: { pathname } } = this.props; + this.props.history.push(`${pathname}${destination}`); + } + + handleConfirmReset = () => { + this.props.onReset(); + this.toggleWarningCancel(); + } + + handleReset = (e) => { + e.preventDefault(); + this.setState({ showWarningCancel: true }); + } + + handleSubmit = (e) => { + e.preventDefault(); + this.setState({ showWarning: true }); + } + + toggle = () => this.setState(prevState => ({ showWarning: !prevState.showWarning })); + + toggleWarningCancel = () => this.setState(prevState => ({ showWarningCancel: !prevState.showWarningCancel })); + + renderForm = input => ( + + ); + + renderRow = model => ; + + render() { + const { showWarning, showWarningCancel } = this.state; + const { onSubmit } = this.props; + + return ( +
    + + + +
    + +
    +
    +
    +
    + {forms.inputs.map(this.renderForm)} +
    +
    +
    + +
    + {/* LIST */} + +
    + {this.getModels().map(this.renderRow)} +
    +
    + {/* LIST */} +
    +
    + ); + } +} + +SettingsPage.defaultProps = {}; + +SettingsPage.propTypes = { + history: PropTypes.object.isRequired, + location: PropTypes.object.isRequired, + onChange: PropTypes.func.isRequired, + onReset: PropTypes.func.isRequired, + onSubmit: PropTypes.func.isRequired, + schema: PropTypes.object.isRequired, + submitSuccess: PropTypes.bool.isRequired, +}; + +const mapDispatchToProps = (dispatch) => ( + bindActionCreators( + { + onChange, + onReset, + onSubmit, + }, + dispatch, + ) +); +const mapStateToProps = createStructuredSelector({ + schema: makeSelectModifiedSchema(), + submitSuccess: makeSelectSubmitSuccess(), +}); +const withConnect = connect(mapStateToProps, mapDispatchToProps); +const withReducer = injectReducer({ key: 'settingsPage', reducer }); +const withSaga = injectSaga({ key: 'settingsPage', saga }); + +export default compose( + withReducer, + withSaga, + withConnect, +)(SettingsPage); diff --git a/packages/strapi-plugin-content-manager/admin/src/containers/SettingsPage/reducer.js b/packages/strapi-plugin-content-manager/admin/src/containers/SettingsPage/reducer.js new file mode 100644 index 0000000000..676dcdc740 --- /dev/null +++ b/packages/strapi-plugin-content-manager/admin/src/containers/SettingsPage/reducer.js @@ -0,0 +1,17 @@ +/** + * + * SettingsPage reducer + */ + +import { fromJS } from 'immutable'; + +const initialState = fromJS({}); + +function settingsPageReducer(state = initialState, action) { + switch (action.type) { + default: + return state; + } +} + +export default settingsPageReducer; \ No newline at end of file diff --git a/packages/strapi-plugin-content-manager/admin/src/containers/SettingsPage/saga.js b/packages/strapi-plugin-content-manager/admin/src/containers/SettingsPage/saga.js new file mode 100644 index 0000000000..64a1db2ad6 --- /dev/null +++ b/packages/strapi-plugin-content-manager/admin/src/containers/SettingsPage/saga.js @@ -0,0 +1,3 @@ +function* defaultSaga() {} + +export default defaultSaga; \ No newline at end of file diff --git a/packages/strapi-plugin-content-manager/admin/src/containers/SettingsPage/selectors.js b/packages/strapi-plugin-content-manager/admin/src/containers/SettingsPage/selectors.js new file mode 100644 index 0000000000..8153dd3770 --- /dev/null +++ b/packages/strapi-plugin-content-manager/admin/src/containers/SettingsPage/selectors.js @@ -0,0 +1,24 @@ +/** + * + * SettingsPage selectors + */ + +import { createSelector } from 'reselect'; + +/** +* Direct selector to the settingsPage state domain +*/ +const selectSettingsPageDomain = () => state => state.get('settingsPage'); + + +/** + * Default selector used by EditPage + */ + +const makeSelectSettingsPage = () => createSelector( + selectSettingsPageDomain(), + (substate) => substate.toJS() +); + + +export default makeSelectSettingsPage; \ No newline at end of file diff --git a/packages/strapi-plugin-content-manager/admin/src/containers/SettingsPage/styles.scss b/packages/strapi-plugin-content-manager/admin/src/containers/SettingsPage/styles.scss new file mode 100644 index 0000000000..cc3db06f18 --- /dev/null +++ b/packages/strapi-plugin-content-manager/admin/src/containers/SettingsPage/styles.scss @@ -0,0 +1,38 @@ +.containerFluid { + padding: 18px 30px; + > div:first-child { + max-height: 33px; + } +} + +.container { + padding-top: 18px; + > div:last-child { + > div { + padding-bottom: 0 !important; + } + } +} + +.main_wrapper{ + background: #ffffff; + padding: 22px 28px 0px; + border-radius: 2px; + box-shadow: 0 2px 4px #E3E9F3; +} + +.ctmForm { + padding-top: 2.6rem; +} + +.contentTypesWrapper { + padding-top: 9px; + margin-left: -28px; + margin-right: -28px; + > div:last-child { + > div { + + border-bottom: none; + } + } +} \ No newline at end of file diff --git a/packages/strapi-plugin-content-manager/admin/src/injectedComponents.js b/packages/strapi-plugin-content-manager/admin/src/injectedComponents.js new file mode 100644 index 0000000000..109fa8b38c --- /dev/null +++ b/packages/strapi-plugin-content-manager/admin/src/injectedComponents.js @@ -0,0 +1 @@ +export default []; \ No newline at end of file diff --git a/packages/strapi-plugin-content-manager/admin/src/translations/ar.json b/packages/strapi-plugin-content-manager/admin/src/translations/ar.json index 54f6e71d44..c831e99863 100644 --- a/packages/strapi-plugin-content-manager/admin/src/translations/ar.json +++ b/packages/strapi-plugin-content-manager/admin/src/translations/ar.json @@ -15,6 +15,11 @@ "components.LimitSelect.itemsPerPage": "عنصر بالصفحة", "containers.List.errorFetchRecords": "خطأ", + "containers.SettingPage.relations": "Relational fields", + + "containers.SettingPage.editSettings.description": "Drag & drop the fields to build the layout", + "containers.SettingPage.editSettings.title": "Edit - Settings", + "EditRelations.title": "البيانات العلائقية", "emptyAttributes.title": "لا توجد حقول بعد", @@ -41,6 +46,10 @@ "error.attribute.sameKeyAndName": "لا تتطابق", "error.validation.minSupMax": "لا يمكن أن تكون متفوقة", + "form.Input.disabled": "Editable field", + "form.Input.description": "Description", + "form.Input.description.placeholder": "Display name in the profile", + "notification.error.relationship.fetch": "حدث خطأ أثناء جلب العلاقة.", "success.record.delete": "حُذف", diff --git a/packages/strapi-plugin-content-manager/admin/src/translations/de.json b/packages/strapi-plugin-content-manager/admin/src/translations/de.json index 7d004c8b2c..0e57aa0a09 100644 --- a/packages/strapi-plugin-content-manager/admin/src/translations/de.json +++ b/packages/strapi-plugin-content-manager/admin/src/translations/de.json @@ -1,6 +1,7 @@ { - "plugin.description.short": "Greife blitzschnell auf alle Daten in der Datenbank zu und manipuliere sie.", - "plugin.description.long": "Greife blitzschnell auf alle Daten in der Datenbank zu und manipuliere sie.", + "plugin.description.short": "Greife blitzschnell auf alle Daten in der Datenbank zu und änder sie.", + "plugin.description.long": "Greife blitzschnell auf alle Daten in der Datenbank zu und änder sie.", + "containers.Home.pluginHeaderTitle": "Inhalts-Manager", "containers.Home.introduction": "Um deine Einträge zu verwalten, klicke auf den entsprechenden Link im Menü links. Dieses Plugin ist noch in aktiver Entwicklung und seine Einstellungen können nicht optimal angepasst werden.", "containers.Home.pluginHeaderDescription": "Verwalte deine Einträge mithilfe eines mächtigen und wunderschönen Interfaces.", @@ -14,12 +15,64 @@ "containers.List.pluginHeaderDescription.singular": "{label} Eintrag gefunden", "components.LimitSelect.itemsPerPage": "Einträge pro Seite", "containers.List.errorFetchRecords": "Fehler", + "containers.SettingPage.addField": "Neues Feld hinzufügen", + "containers.SettingPage.attributes": "Attribut Felder", + "containers.SettingPage.attributes.description": "Reihenfolge der Attribute festlegen", + "containers.SettingPage.listSettings.title": "Liste - Einstellungen", + "containers.SettingPage.listSettings.description": "Konfiguriere die Optionen für diesen Inhaltstyp.", + "containers.SettingPage.pluginHeaderDescription": "Konfiguriere die spezifischen Einstellungen für diesen Inhaltstyp.", + "containers.SettingsPage.Block.generalSettings.description": "Konfiguriere die Standardoptionen für deine Inhaltstypen.", + "containers.SettingsPage.Block.generalSettings.title" : "Allgemeines", + "containers.SettingsPage.Block.contentType.title": "Inhaltstypen", + "containers.SettingsPage.Block.contentType.description": "Konfiguriere die spezifischen Einstellungen.", + "containers.SettingsPage.pluginHeaderDescription": "Konfigurieren Sie die Standardeinstellungen für alle Ihre Inhaltstypen.", + + "components.AddFilterCTA.add": "Filter", + "components.AddFilterCTA.hide": "Filter", + "components.DraggableAttr.edit": "Klicken zum Bearbeiten", + "components.FiltersPickWrapper.PluginHeader.actions.apply": "Anwenden", + "components.FiltersPickWrapper.PluginHeader.actions.clearAll": "Alle löschen", + "components.FiltersPickWrapper.PluginHeader.description": "Lege die Bedingungen fest, unter denen die Einträge gefiltert werden sollen.", + "components.FiltersPickWrapper.PluginHeader.title.filter": "Filter", + "components.FiltersPickWrapper.hide": "Ausblenden", + "components.FilterOptions.button.apply": "Anwenden", + "components.FilterOptions.FILTER_TYPES.=": "ist", + "components.FilterOptions.FILTER_TYPES._ne": "ist nicht", + "components.FilterOptions.FILTER_TYPES._lt": "ist kleiner als", + "components.FilterOptions.FILTER_TYPES._lte": "ist kleiner oder gleich als", + "components.FilterOptions.FILTER_TYPES._gt": "ist größer als", + "components.FilterOptions.FILTER_TYPES._gte": "ist größer oder gleich als", + "components.FilterOptions.FILTER_TYPES._contains": "enthält", + "components.FilterOptions.FILTER_TYPES._containss": "enthält (Groß-/Kleinschreibung beachten)", + "components.Search.placeholder": "Suche nach einem Eintrag....", + "components.TableDelete.entries.plural": "{number} ausgewählte Einträge", + "components.TableDelete.entries.singular": "{number} ausgewählter Eintrag", + "components.TableDelete.delete": "Alle löschen", + "components.TableEmpty.withFilters": "Es gibt keinen {contentType} mit den verwendeten Filtern...", + "components.TableEmpty.withoutFilter": "Es gibt keinen {contentType}...", + "components.TableEmpty.withSearch": "Es gibt keinen {contentType}, der der Suche entspricht ({search})...", + + "form.Input.label": "Label", + "form.Input.label.inputDescription": "Dieser Wert überschreibt das im Kopf der Tabelle angezeigte Label.", + "form.Input.search": "Suche aktivieren", + "form.Input.search.field": "Suche in diesem Feld aktivieren", + "form.Input.filters": "Filter aktivieren", + "form.Input.sort.field": "Sortierung in diesem Feld aktivieren", + "form.Input.bulkActions": "Bulk-Bearbeitung aktivieren", + "form.Input.pageEntries": "Einträge pro Seite", + "form.Input.pageEntries.inputDescription": "Hinweis: Du kannst diesen Wert auf der Inhaltstypen Einstellungsseite überschreiben.", + "form.Input.defaultSort": "Standard-Sortierattribut", + + "containers.SettingPage.relations": "Relational fields", + + "containers.SettingPage.editSettings.description": "Drag & drop the fields to build the layout", + "containers.SettingPage.editSettings.title": "Edit — Settings", "EditRelations.title": "Relationale Daten", "emptyAttributes.title": "Es gibt noch keine Felder", - "emptyAttributes.description": "Füge deinem Content-Typen das erste Feld hinzu", - "emptyAttributes.button": "Den Content-Typ-Generator öffnen", + "emptyAttributes.description": "Füge deinem Inhaltstypen das erste Feld hinzu", + "emptyAttributes.button": "Den Inhaltstyp-Generator öffnen", "error.schema.generation": "Bei der Generierung des Schemas ist ein Fehler aufgetreten.", "error.records.count": "Beim Abruf von count records ist ein Fehler aufgetreten.", @@ -35,13 +88,19 @@ "error.validation.min": "Dieser Wert ist zu niedrig.", "error.validation.maxLength": "Dieser Wert ist zu lang.", "error.validation.minLength": "Dieser Wert ist zu kurz.", + "error.validation.json": "Dies ist kein JSON", "error.contentTypeName.taken": "Dieser Name existiert bereits", "error.attribute.taken": "Dieser Feldname ist bereits vergeben", "error.attribute.key.taken": "Dieser Wert existiert bereits", "error.attribute.sameKeyAndName": "Darf nicht gleich sein", "error.validation.minSupMax": "Darf nicht höher sein", + "form.Input.disabled": "Editable field", + "form.Input.description": "Description", + "form.Input.description.placeholder": "Display name in the profile", + "notification.error.relationship.fetch": "Beim Abruf von Beziehungen ist ein Fehler aufgetreten.", + "notification.info.SettingPage.disableSort": "Du musst ein Attribut mit aktivierter Sortierung haben.", "success.record.delete": "Gelöscht", "success.record.save": "Gespeichert", @@ -51,5 +110,9 @@ "popUpWarning.button.cancel": "Abbrechen", "popUpWarning.button.confirm": "Bestätigen", "popUpWarning.title": "Bitte bestätigen", - "popUpWarning.bodyMessage.contentType.delete": "Bist du sicher, dass du diesen Content-Typ löschen willst?" + "popUpWarning.bodyMessage.contentType.delete": "Bist du sicher, dass du diesen Inhaltstyp löschen willst?", + "popUpWarning.bodyMessage.contentType.delete.all": "Bist du sicher, dass du diese Einträge löschen willst?", + "popUpWarning.warning.cancelAllSettings": "Bist du sicher, dass du deine Änderungen rückgängig machen willst?", + "popUpWarning.warning.updateAllSettings": "Dadurch werden alle deine Einstellungen geändert." + } diff --git a/packages/strapi-plugin-content-manager/admin/src/translations/en.json b/packages/strapi-plugin-content-manager/admin/src/translations/en.json index a44c540879..1aba1f53aa 100755 --- a/packages/strapi-plugin-content-manager/admin/src/translations/en.json +++ b/packages/strapi-plugin-content-manager/admin/src/translations/en.json @@ -9,21 +9,49 @@ "containers.Edit.delete": "Delete", "containers.Edit.reset": "Reset", "containers.Edit.returnList": "Return to list", + "containers.Edit.addAnItem": "Add an item...", + "containers.Edit.clickToJump": "Click to jump to the entry", + "containers.Edit.seeDetails": "Details", "containers.List.addAnEntry": "Add New {entity}", "containers.List.pluginHeaderDescription": "{label} entries found", "containers.List.pluginHeaderDescription.singular": "{label} entry found", "components.LimitSelect.itemsPerPage": "Items per page", "containers.List.errorFetchRecords": "Error", + "containers.ListPage.displayedFields": "Displayed Fields", + + "containers.SettingPage.addField": "Add new field", + "containers.SettingPage.addRelationalField": "Add new relational field", + "containers.SettingPage.attributes": "Attributes fields", + "containers.SettingPage.attributes.description": "Define the order of the attributes", + "containers.SettingPage.relations": "Relational fields", + + "containers.SettingPage.editSettings.description": "Drag & drop the fields to build the layout", + "containers.SettingPage.editSettings.title": "Edit — Settings", + + "containers.SettingPage.listSettings.title": "List — Settings", + "containers.SettingPage.listSettings.description": "Configure the options for this content type", + "containers.SettingPage.pluginHeaderDescription": "Configure the specific settings for this Content Type", + "containers.SettingsPage.pluginHeaderDescription": "Configure the default settings for all your Content types", + "containers.SettingsPage.Block.generalSettings.description": "Configure the default options for your Content Types", + "containers.SettingsPage.Block.generalSettings.title": "General", + "containers.SettingsPage.Block.contentType.title": "Content Types", + "containers.SettingsPage.Block.contentType.description": "Configure the specific settings", "components.AddFilterCTA.add": "Filters", "components.AddFilterCTA.hide": "Filters", - "components.FilterOptions.button.apply": "Apply", + + "components.DraggableAttr.edit": "Click to edit", + + "components.EmptyAttributesBlock.description": "You can change your settings", + "components.EmptyAttributesBlock.button": "Go to settings page", + "components.FiltersPickWrapper.PluginHeader.actions.apply": "Apply", "components.FiltersPickWrapper.PluginHeader.actions.clearAll": "Clear all", "components.FiltersPickWrapper.PluginHeader.description": "Set the conditions to apply to filter the entries", "components.FiltersPickWrapper.PluginHeader.title.filter": "Filters", "components.FiltersPickWrapper.hide": "Hide", - + + "components.FilterOptions.button.apply": "Apply", "components.FilterOptions.FILTER_TYPES.=": "is", "components.FilterOptions.FILTER_TYPES._ne": "is not", "components.FilterOptions.FILTER_TYPES._lt": "is lower than", @@ -71,7 +99,25 @@ "error.validation.minSupMax": "Can't be superior", "error.validation.json": "This is not a JSON", + "form.Input.description": "Description", + "form.Input.placeholder": "Placeholder", + "form.Input.description.placeholder": "Display name in the profile", + "form.Input.placeholder.placeholder": "My awesome value", + "form.Input.disabled": "Editable field", + "form.Input.label.inputDescription": "This value overrides the label displayed in the table's head", + "form.Input.label": "Label", + "form.Input.search": "Enable search", + "form.Input.search.field": "Enable search on this field", + "form.Input.filters": "Enable filters", + "form.Input.sort.field": "Enable sort on this field", + "form.Input.bulkActions": "Enable bulk actions", + "form.Input.pageEntries": "Entries per page", + "form.Input.pageEntries.inputDescription": "Note: You can override this value in the Content Type settings page.", + "form.Input.defaultSort": "Default sort attribute", + + "notification.error.displayedFields": "You need at least one displayed field", "notification.error.relationship.fetch": "An error occurred during relationship fetch.", + "notification.info.SettingPage.disableSort": "You need to have one attribute with the sorting allowed", "success.record.delete": "Deleted", "success.record.save": "Saved", @@ -82,5 +128,7 @@ "popUpWarning.button.confirm": "Confirm", "popUpWarning.title": "Please confirm", "popUpWarning.bodyMessage.contentType.delete": "Are you sure you want to delete this entry?", - "popUpWarning.bodyMessage.contentType.delete.all": "Are you sure you want to delete theses entries?" + "popUpWarning.bodyMessage.contentType.delete.all": "Are you sure you want to delete theses entries?", + "popUpWarning.warning.cancelAllSettings": "Are you sure you want to cancel your modifications?", + "popUpWarning.warning.updateAllSettings": "This will modify all your settings" } diff --git a/packages/strapi-plugin-content-manager/admin/src/translations/es.json b/packages/strapi-plugin-content-manager/admin/src/translations/es.json new file mode 100755 index 0000000000..a35861d259 --- /dev/null +++ b/packages/strapi-plugin-content-manager/admin/src/translations/es.json @@ -0,0 +1,116 @@ +{ + "plugin.description.short": "Ver, editar y eliminar información de su base de datos de manera rápida.", + "plugin.description.long": "Ver, editar y eliminar información de su base de datos de manera rápida.", + "containers.Home.pluginHeaderTitle": "Gestor de Contenido", + "containers.Home.introduction": "Para editar sus registros vaya al link en específico en el menu de la izquierda. Este plugin no tiene una manera de editar configuraciones y aún esta en continuo desarrollo.", + "containers.Home.pluginHeaderDescription": "Gestiona sus registros en una bella y poderoza interfaz.", + "containers.Edit.submit": "Guardar", + "containers.Edit.editing": "Editando...", + "containers.Edit.delete": "Eliminar", + "containers.Edit.reset": "Reiniciar", + "containers.Edit.returnList": "Regresar a la lista", + "containers.List.addAnEntry": "Agregar nuevo {entity}", + "containers.List.pluginHeaderDescription": "{label} registros encontrados", + "containers.List.pluginHeaderDescription.singular": "{label} registro encontrado", + "components.LimitSelect.itemsPerPage": "registros por página", + "containers.List.errorFetchRecords": "Error", + + "containers.SettingPage.addField": "Agregar nuevo campo", + "containers.SettingPage.attributes": "Campos de atributos", + "containers.SettingPage.attributes.description": "Defina el orden de sus atributos", + + "containers.SettingPage.listSettings.title": "Lista — Configuraciones", + "containers.SettingPage.listSettings.description": "Configura las opciones para este tipo de contenido", + "containers.SettingPage.pluginHeaderDescription": "Configura las opciones específicas para este Tipo de Contenido", + "containers.SettingsPage.pluginHeaderDescription": "Configura las opciones por defecto para todos sus Tipos de Contenido", + "containers.SettingsPage.Block.generalSettings.description": "Configura las opciones por defecto para sus Tipos de Contenido", + "containers.SettingsPage.Block.generalSettings.title": "General", + "containers.SettingsPage.Block.contentType.title": "Tipos de Contenido", + "containers.SettingsPage.Block.contentType.description": "Configuraciones específicas", + + "components.AddFilterCTA.add": "Filtros", + "components.AddFilterCTA.hide": "Filtros", + + "components.DraggableAttr.edit": "Click para editar", + + "components.FiltersPickWrapper.PluginHeader.actions.apply": "Aplicar", + "components.FiltersPickWrapper.PluginHeader.actions.clearAll": "Limpiar todo", + "components.FiltersPickWrapper.PluginHeader.description": "Establece las condiciones a aplicar para filtrar registros", + "components.FiltersPickWrapper.PluginHeader.title.filter": "Filtros", + "components.FiltersPickWrapper.hide": "Ocultar", + + "components.FilterOptions.button.apply": "Aplicar", + "components.FilterOptions.FILTER_TYPES.=": "es", + "components.FilterOptions.FILTER_TYPES._ne": "no es", + "components.FilterOptions.FILTER_TYPES._lt": "es menor que", + "components.FilterOptions.FILTER_TYPES._lte": "es menor o igual que", + "components.FilterOptions.FILTER_TYPES._gt": "es mayor que", + "components.FilterOptions.FILTER_TYPES._gte": "es mayor o igual que", + "components.FilterOptions.FILTER_TYPES._contains": "contiene", + "components.FilterOptions.FILTER_TYPES._containss": "contiene (distinguiendo mayúsculas y minúsculas)", + + "components.Search.placeholder": "Buscar un registro...", + + "components.TableDelete.entries.plural": "{number} registros seleccionados", + "components.TableDelete.entries.singular": "{number} registro seleccionado", + "components.TableDelete.delete": "Eliminar todo", + + + "components.TableEmpty.withFilters": "No hay {contentType} con los filtros aplicados...", + "components.TableEmpty.withoutFilter": "No hay {contentType}...", + "components.TableEmpty.withSearch": "No hay {contentType} que coincida con la búsqueda ({search})...", + + "EditRelations.title": "Datos relacionados", + + "emptyAttributes.title": "Aún no hay campos", + "emptyAttributes.description": "Agregue su primer campo a su Tipo de Contenido", + "emptyAttributes.button": "Ir al creador de Tipos de Contenido", + + "error.schema.generation": "Ocurrió un error durante la generación de esquema.", + "error.records.count": "Ocurrió un error durante la consulta del número de registros.", + "error.records.fetch": "Ocurrió un error durante la consulta de registros.", + "error.record.fetch": "Ocurrió un error durante la consulta del registro.", + "error.record.create": "Ocurrió un error durante la creación del registro.", + "error.record.update": "Ocurrió un error durante la actualización del registro.", + "error.record.delete": "Ocurrió un error durante la eliminación del registro.", + "error.model.fetch": "Ocurrió un error durante la consulta de configuración de modelos.", + "error.validation.required": "Este dato es requerido.", + "error.validation.regex": "El valor no cumple la expresión regular.", + "error.validation.max": "El valor es muy alto.", + "error.validation.min": "El valor es muy bajo.", + "error.validation.maxLength": "El valor es muy largo.", + "error.validation.minLength": "El valor es muy corto.", + "error.contentTypeName.taken": "Este nombre ya existe", + "error.attribute.taken": "Este campo ya existe", + "error.attribute.key.taken": "Este valor ya existe", + "error.attribute.sameKeyAndName": "No pueden ser iguales", + "error.validation.minSupMax": "No puede ser superior", + "error.validation.json": "Este no es un JSON", + + "form.Input.label.inputDescription": "Este valor sobrescribe la etiqueta mostrada en la cabecera de la tabla", + "form.Input.label": "Etiqueta", + "form.Input.search": "Habilitar la búsqueda", + "form.Input.search.field": "Habilitar la búsqueda para este campo", + "form.Input.filters": "Habilitar filtros", + "form.Input.sort.field": "Habilitar ordenado para este campo", + "form.Input.bulkActions": "Habilitar acciones en bloque", + "form.Input.pageEntries": "Registros por página", + "form.Input.pageEntries.inputDescription": "Nota: Puede sobrescribir este valor en la página de configuraciones para Tipo de Contenido.", + "form.Input.defaultSort": "Atributo para ordenar por defecto", + + "notification.error.relationship.fetch": "Ocurrió un error durante la consulta de la relación.", + "notification.info.SettingPage.disableSort": "Necesita tener un habilidato el ordenado en un atributo", + + "success.record.delete": "Eliminado", + "success.record.save": "Guardado", + + "pageNotFound": "Página no encontrada", + + "popUpWarning.button.cancel": "Cancelar", + "popUpWarning.button.confirm": "Confirmar", + "popUpWarning.title": "Favor de confirmar", + "popUpWarning.bodyMessage.contentType.delete": "¿Está seguro de querer eliminar este registro?", + "popUpWarning.bodyMessage.contentType.delete.all": "¿Está seguro de querer eliminar estos registros?", + "popUpWarning.warning.cancelAllSettings": "¿Está seguro de querer cancelar sus cambios?", + "popUpWarning.warning.updateAllSettings": "Esto modificará todas sus configuraciones" +} diff --git a/packages/strapi-plugin-content-manager/admin/src/translations/fr.json b/packages/strapi-plugin-content-manager/admin/src/translations/fr.json index 6fe8272067..5c62316897 100755 --- a/packages/strapi-plugin-content-manager/admin/src/translations/fr.json +++ b/packages/strapi-plugin-content-manager/admin/src/translations/fr.json @@ -4,37 +4,66 @@ "containers.Home.pluginHeaderTitle": "Type de contenu", "containers.Home.pluginHeaderDescription": "Créer et modifier votre type de contenu", "containers.Home.introduction": "Pour éditer du contenu, choisissez un type de données dans le menu de gauche.", - "containers.Home.pluginHeaderDescription": "Un outil complet pour gérer facilement vos données.", "containers.Edit.submit": "Valider", "containers.Edit.editing": "Édition en cours...", "containers.Edit.delete": "Supprimer", "containers.Edit.reset": "Annuler", "containers.Edit.returnList": "Retourner à la liste", + "containers.Edit.addAnItem": "Ajouter un élément...", + "containers.Edit.clickToJump": "Cliquer pour voir l'entrée", + "containers.Edit.seeDetails": "Détails", "containers.List.addAnEntry": "Ajouter {entity}", "containers.List.pluginHeaderDescription": "{label} entrées trouvées", "containers.List.pluginHeaderDescription.singular": "{label} entrée trouvée", "components.LimitSelect.itemsPerPage": "Éléments par page", "containers.List.errorFetchRecords": "Erreur", + "containers.SettingsPage.Block.contentType.title": "Types de contenu", + "containers.SettingsPage.Block.contentType.description": "Configurer les paramètres spécifiques", + "containers.ListPage.displayedFields": "Champs affichés", + + "containers.SettingPage.addField": "Ajouter un nouveau champs", + "containers.SettingPage.addRelationalField": "Ajouter un nouveau champs relationnel", + "containers.SettingPage.attributes": "Attributs", + "containers.SettingPage.attributes.description": "Organiser les attributs du modèle", + "containers.SettingPage.relations": "Champs relationnels", + + "containers.SettingPage.pluginHeaderDescription": "Configurez les paramètres de ce modèle", + + "containers.SettingPage.editSettings.description": "Glissez & déposez les champs pour construire le layout", + "containers.SettingPage.editSettings.title": "Edit — Paramètres", + + "containers.SettingPage.listSettings.title": "Liste — Paramètres", + "containers.SettingPage.listSettings.description": "Configurez les options de ce modèle", + + "containers.SettingsPage.pluginHeaderDescription": "Configurez les paramètres par défaut de vos modèles", + "containers.SettingsPage.Block.generalSettings.description": "Configurez les options par défault de vos modèles", + "containers.SettingsPage.Block.generalSettings.title": "Général", "components.AddFilterCTA.add": "Filtres", "components.AddFilterCTA.hide": "Filtres", - "components.FilterOptions.button.apply": "Appliquer", + + "components.DraggableAttr.edit": "Clicquez pour modifier", + + "components.EmptyAttributesBlock.description": "Vous pouvez modifiez vos paramètres", + "components.EmptyAttributesBlock.button": "Voir la page des configurations", + "components.FiltersPickWrapper.PluginHeader.actions.apply": "Appliquer", "components.FiltersPickWrapper.PluginHeader.actions.clearAll": "Tout supprimer", "components.FiltersPickWrapper.PluginHeader.description": "Définissez les conditions des filtres à appliquer", "components.FiltersPickWrapper.PluginHeader.title.filter": "Filtres", "components.FiltersPickWrapper.hide": "Fermer", - + "components.Search.placeholder": "Rechercher une entrée...", - + "components.TableDelete.entries.plural": "{number} entrées sélectionnées", "components.TableDelete.entries.singular": "{number} entrée sélectionnée", "components.TableDelete.delete": "Tout supprimer", - + "components.TableEmpty.withFilters": "Aucun {contentType} n'a été trouvé avec ces filtres...", "components.TableEmpty.withoutFilter": "Aucun {contentType} n'a été trouvé...", "components.TableEmpty.withSearch": "Aucun {contentType} n'a été trouvé avec cette recherche ({search})...", - + + "components.FilterOptions.button.apply": "Appliquer", "components.FilterOptions.FILTER_TYPES.=": "est", "components.FilterOptions.FILTER_TYPES._ne": "n'est pas", "components.FilterOptions.FILTER_TYPES._lt": "inférieur à", @@ -71,7 +100,25 @@ "error.validation.minSupMax": "Ne peut pas être plus grand", "error.validation.json": "Le format JSON n'est pas respecté", + "form.Input.description": "Description", + "form.Input.placeholder": "Placeholder", + "form.Input.description.placeholder": "Afficher le nom dans le profile", + "form.Input.placeholder.placeholder": "Mon super placeholder", + "form.Input.disabled": "Champ editable", + "form.Input.label": "Label", + "form.Input.label.inputDescription": "Cette valeur modifie celle du champs de la table", + "form.Input.search": "Autoriser la search", + "form.Input.search.field": "Autoriser la search sur ce champs", + "form.Input.filters": "Autoriser les filtres", + "form.Input.sort.field": "Autoriser le tri sur ce champs", + "form.Input.bulkActions": "Autoriser les actions groupées", + "form.Input.pageEntries": "Nombre d'entrées par page", + "form.Input.pageEntries.inputDescription": "Note: Vous pouvez modifier ces valeurs par modèle", + "form.Input.defaultSort": "Attribut de tri par défault", + + "notification.error.displayedFields": "Vous devez avoir au moins un champ d'affiché", "notification.error.relationship.fetch": "Une erreur est survenue en récupérant les relations.", + "notification.info.SettingPage.disableSort": "Vous devez avoir au moins un attribut de tri par défaut", "success.record.delete": "Supprimé", "success.record.save": "Sauvegardé", @@ -80,5 +127,9 @@ "popUpWarning.button.confirm": "Confirmer", "popUpWarning.title": "Confirmation requise", "popUpWarning.bodyMessage.contentType.delete": "Êtes-vous sûr de vouloir supprimer cette entrée ?", - "popUpWarning.bodyMessage.contentType.delete.all": "Êtes-vous sûr de vouloir supprimer ces entrées ?" + "popUpWarning.bodyMessage.contentType.delete.all": "Êtes-vous sûr de vouloir supprimer ces entrées ?", + "popUpWarning.warning.cancelAllSettings": "Êtes-vous sûr de vouloir vos modifications?", + "popUpWarning.warning.updateAllSettings": "Cela modifiera tous vos précédents paramètres.", + + "pageNotFound" : "Page non trouvée" } diff --git a/packages/strapi-plugin-content-manager/admin/src/translations/it.json b/packages/strapi-plugin-content-manager/admin/src/translations/it.json index dff27611c5..bcaef795b0 100644 --- a/packages/strapi-plugin-content-manager/admin/src/translations/it.json +++ b/packages/strapi-plugin-content-manager/admin/src/translations/it.json @@ -14,16 +14,27 @@ "containers.List.pluginHeaderDescription.singular": "{label} voce trovati", "components.LimitSelect.itemsPerPage": "Elementi per pagina", "containers.List.errorFetchRecords": "Errore", + "containers.SettingPage.addField" : "Aggiungi un nuovo campo", + "containers.SettingPage.attributes" : "Attributi", + "containers.SettingPage.attributes.description" : "Definisci l'ordine degli attributi", + "containers.SettingPage.listSettings.title" : "Lista — Impostazioni", + "containers.SettingPage.listSettings.description" : "Configura le opzioni per questo Tipo di Contenuto", + "containers.SettingPage.pluginHeaderDescription" : "Configura le impostazioni specifiche per questo Tipo di Contenuto", + "containers.SettingsPage.pluginHeaderDescription" : "Configura le impostazioni di default per tutti i tuoi Tipi di Contenuto", + "containers.SettingsPage.Block.generalSettings.description" : "Configura le opzioni di default per i Tipi di Contenuto", + "containers.SettingsPage.Block.generalSettings.title" : "Generali", + "containers.SettingsPage.Block.contentType.title" : "Tipi di Contenuto", + "containers.SettingsPage.Block.contentType.description" : "Configura le impostazioni specifiche", "components.AddFilterCTA.add": "Filtri", "components.AddFilterCTA.hide": "Filtri", + "components.DraggableAttr.edit" : "Clicca per modificare", "components.FilterOptions.button.apply": "Applica", "components.FiltersPickWrapper.PluginHeader.actions.apply": "Applica", "components.FiltersPickWrapper.PluginHeader.actions.clearAll": "Cancella tutto", "components.FiltersPickWrapper.PluginHeader.description": "Impostare le condizioni da applicare per filtrare le voci", "components.FiltersPickWrapper.PluginHeader.title.filter": "Filtri", "components.FiltersPickWrapper.hide": "Nascondi", - "components.FilterOptions.FILTER_TYPES.=": "si", "components.FilterOptions.FILTER_TYPES._ne": "non è", "components.FilterOptions.FILTER_TYPES._lt": "è inferiore", @@ -59,6 +70,7 @@ "error.model.fetch": "Si è verificato un errore durante il caricamento dei modelli di configurazione.", "error.validation.required": "Questo valore è richiesto.", "error.validation.regex": "Il valore non corrisponde alla regex.", + "error.validation.json" : "Non è un JSON", "error.validation.max": "Il valore è troppo alto.", "error.validation.min": "Il valore è troppo basso.", "error.validation.maxLength": "Il valore è troppo lungo.", @@ -67,9 +79,25 @@ "error.attribute.taken": "Questo campo nome esiste già", "error.attribute.key.taken": "Questo valore esiste già", "error.attribute.sameKeyAndName": "Non può essere uguale", - "error.validation.minSupMax": "Non può essere superiore", + "error.validation.minSupMax": "Non può essere superiore", + + "form.Input.description": "Description", + "form.Input.description.placeholder": "Display name in the profile", + "form.Input.disabled": "Editable field", + + "form.Input.label.inputDescription" : "Questo valore sovrascrive l'etichetta mostrata nell'intestazione della tabella", + "form.Input.label" : "Etichetta", + "form.Input.search" : "Abilita ricerca", + "form.Input.search.field" : "Abilita la ricerca su questo campo", + "form.Input.filters" : "Abilita filtri", + "form.Input.sort.field" : "Abilita ordinamento su questo campo", + "form.Input.bulkActions" : "Abilita caricamento", + "form.Input.pageEntries" : "Righe per pagina", + "form.Input.pageEntries.inputDescription" : "Attenzione: Puoi sovrascrivere questo valore nella pagina delle impostazioni del Tipo di Contenuto", + "form.Input.defaultSort" : "Attributo di ordinamento di default", "notification.error.relationship.fetch": "Si è verificato un errore durante il rapporto di recupero.", + "notification.info.SettingPage.disableSort" : "E' necessario un attributo con l'ordinamento abilitato", "success.record.delete": "Eliminato", "success.record.save": "Salvato", @@ -80,5 +108,8 @@ "popUpWarning.button.confirm": "Conferma", "popUpWarning.title": "Si prega di confermare", "popUpWarning.bodyMessage.contentType.delete": "Sei sicuro di voler cancellare questa voce?", - "popUpWarning.bodyMessage.contentType.delete.all": "Sei sicuro di voler eliminare queste voci?" -} \ No newline at end of file + "popUpWarning.bodyMessage.contentType.delete.all": "Sei sicuro di voler eliminare queste voci?", + "popUpWarning.warning.cancelAllSettings" : "Sei sicuro di voler cancellare le tue modifiche?", + "popUpWarning.warning.updateAllSettings" : "Questa operazione modificherà tutte le tue impostazioni" + +} diff --git a/packages/strapi-plugin-content-manager/admin/src/translations/ko.json b/packages/strapi-plugin-content-manager/admin/src/translations/ko.json index 70e062a65e..e545c3cff5 100644 --- a/packages/strapi-plugin-content-manager/admin/src/translations/ko.json +++ b/packages/strapi-plugin-content-manager/admin/src/translations/ko.json @@ -15,15 +15,31 @@ "components.LimitSelect.itemsPerPage": "항목 수 / 페이지", "containers.List.errorFetchRecords": "에러", + "containers.SettingPage.addField": "새 필드 추가", + "containers.SettingPage.attributes": "속성", + "containers.SettingPage.attributes.description": "속성의 순서를 지정합니다", + + "containers.SettingPage.listSettings.title": "목록 — 설정", + "containers.SettingPage.listSettings.description": "이 콘텐츠 유형의 옵션을 구성합니다.", + "containers.SettingPage.pluginHeaderDescription": "이 콘텐츠 유형에 특정한 설정을 구성합니다.", + "containers.SettingsPage.pluginHeaderDescription": "모든 콘텐츠 유형에 대한 기본 설정을 구성합니다.", + "containers.SettingsPage.Block.generalSettings.description": "콘텐츠 유형에 대한 기본 옵션을 구성합니다.", + "containers.SettingsPage.Block.generalSettings.title": "일반", + "containers.SettingsPage.Block.contentType.title": "콘텐츠 유형", + "containers.SettingsPage.Block.contentType.description": "특정 설정을 구성합니다.", + "components.AddFilterCTA.add": "필터", "components.AddFilterCTA.hide": "필터", - "components.FilterOptions.button.apply": "적용", + + "components.DraggableAttr.edit": "클릭하여 수정", + "components.FiltersPickWrapper.PluginHeader.actions.apply": "적용", "components.FiltersPickWrapper.PluginHeader.actions.clearAll": "모두 재설정", "components.FiltersPickWrapper.PluginHeader.description": "필터링 조건을 설정하세요.", "components.FiltersPickWrapper.PluginHeader.title.filter": "필터", "components.FiltersPickWrapper.hide": "숨김", - + + "components.FilterOptions.button.apply": "적용", "components.FilterOptions.FILTER_TYPES.=": "같음", "components.FilterOptions.FILTER_TYPES._ne": "같지 않음", "components.FilterOptions.FILTER_TYPES._lt": "작음", @@ -44,11 +60,11 @@ "components.TableEmpty.withoutFilter": "{contentType} 목록이 없습니다.", "components.TableEmpty.withSearch": "\"{search}\" 검색. {contentType} 목록이 없습니다.", - "EditRelations.title": "Relational 데이터", + "EditRelations.title": "관계 데이터", "emptyAttributes.title": "아직 필드가 없습니다.", "emptyAttributes.description": "첫번째 필드를 추가하세요.", - "emptyAttributes.button": "콘텐츠 타입 빌더", + "emptyAttributes.button": "콘텐츠 유형 빌더", "error.schema.generation": "스키마를 생성하는 도중 에러가 발생했습니다.", "error.records.count": "데이터 수를 가져오는 도중 에러가 발생했습니다.", @@ -64,14 +80,26 @@ "error.validation.min": "입력한 내용이 너무 작습니다.", "error.validation.maxLength": "입력한 내용이 너무 깁니다.", "error.validation.minLength": "입력한 내용이 너무 짧습니다.", - "error.contentTypeName.taken": "이미 사용중인 이름입니다..", - "error.attribute.taken": "이미 사용중인 이름입니다..", + "error.contentTypeName.taken": "이미 사용중인 이름입니다.", + "error.attribute.taken": "이미 사용중인 이름입니다.", "error.attribute.key.taken": "이미 사용중인 키입니다.", "error.attribute.sameKeyAndName": "같은 값을 사용할 수 없습니다.", "error.validation.minSupMax": "이 보다 더 클 수 없습니다.", "error.validation.json": "JSON 형식이 아닙니다.", + "form.Input.label.inputDescription": "이 값은 테이블 머리에 표시된 라벨을 덮어씌웁니다.", + "form.Input.label": "라벨", + "form.Input.search": "검색 활성화", + "form.Input.search.field": "이 필드에 검색 활성화", + "form.Input.filters": "필더 활성화", + "form.Input.sort.field": "이 필드에 정렬 활성화", + "form.Input.bulkActions": "대규모 액션 활성화", + "form.Input.pageEntries": "페이지 당 요소", + "form.Input.pageEntries.inputDescription": "참고 : 콘텐츠 유형 설정에서 이 값을 덮어씌울 수 있습니다.", + "form.Input.defaultSort": "기본 정렬 속성", + "notification.error.relationship.fetch": "데이터 관계를 가져오는 도중 에러가 발생했습니다.", + "notification.info.SettingPage.disableSort": "정렬이 활성화된 한 개의 속성이 필요합니다.", "success.record.delete": "삭제", "success.record.save": "저장", @@ -81,6 +109,8 @@ "popUpWarning.button.cancel": "취소", "popUpWarning.button.confirm": "확인", "popUpWarning.title": "확인", - "popUpWarning.bodyMessage.contentType.delete": "삭제 하시겠습니까?", - "popUpWarning.bodyMessage.contentType.delete.all": "모두 삭제 하시겠습니까?" + "popUpWarning.bodyMessage.contentType.delete": "삭제하시겠습니까?", + "popUpWarning.bodyMessage.contentType.delete.all": "모두 삭제하시겠습니까?", + "popUpWarning.warning.cancelAllSettings": "수정 사항을 취소하시겠습니까?", + "popUpWarning.warning.updateAllSettings": "모든 설정에 적용됩니다." } diff --git a/packages/strapi-plugin-content-manager/admin/src/translations/nl.json b/packages/strapi-plugin-content-manager/admin/src/translations/nl.json new file mode 100644 index 0000000000..8608fcbaed --- /dev/null +++ b/packages/strapi-plugin-content-manager/admin/src/translations/nl.json @@ -0,0 +1,116 @@ +{ + "plugin.description.short": "Snelle manier om data te zien, aan te passen en verwijderen in je database.", + "plugin.description.long": "Snelle manier om data te zien, aan te passen en verwijderen in je database", + "containers.Home.pluginHeaderTitle": "Inhoud Manager", + "containers.Home.introduction": "Om items aan te passen klik je op de link in het menu links boven. Deze plugin heeft nog geen goede manier om instellingen aan te passen en is nog in ontwikkeling.", + "containers.Home.pluginHeaderDescription": "Onderhoud je data via een krachtig en mooie interface.", + "containers.Edit.submit": "Opslaan", + "containers.Edit.editing": "Aanpassen...", + "containers.Edit.delete": "Verwijderen", + "containers.Edit.reset": "Resetten", + "containers.Edit.returnList": "Terug naar lijst", + "containers.List.addAnEntry": "Nieuwe {entity}", + "containers.List.pluginHeaderDescription": "{label} item gevonden", + "containers.List.pluginHeaderDescription.singular": "{label} items gevonden", + "components.LimitSelect.itemsPerPage": "Items per pagina", + "containers.List.errorFetchRecords": "Fout", + + "containers.SettingPage.addField": "Nieuw veld toevoegen", + "containers.SettingPage.attributes": "Attribuut velden", + "containers.SettingPage.attributes.description": "Geef de volgorde van de attributen aan", + + "containers.SettingPage.listSettings.title": "Lijst — Instellingen", + "containers.SettingPage.listSettings.description": "Stel de opties voor dit Content Type in", + "containers.SettingPage.pluginHeaderDescription": "Stel de specifieke instellingen voor dit Content Type in", + "containers.SettingsPage.pluginHeaderDescription": "Stel de standaard instellingen voor alle Content Types in", + "containers.SettingsPage.Block.generalSettings.description": "Stel de standaard instellingen voor jouw Content Types in", + "containers.SettingsPage.Block.generalSettings.title": "Algemeen", + "containers.SettingsPage.Block.contentType.title": "Content Types", + "containers.SettingsPage.Block.contentType.description": "Configureer de specifieke instellingen", + + "components.AddFilterCTA.add": "Filters", + "components.AddFilterCTA.hide": "Filters", + + "components.DraggableAttr.edit": "Klik om aan te passen", + + "components.FiltersPickWrapper.PluginHeader.actions.apply": "Toepassen", + "components.FiltersPickWrapper.PluginHeader.actions.clearAll": "Alles legen", + "components.FiltersPickWrapper.PluginHeader.description": "Maak de conditionele logica om de items te kunnen filteren", + "components.FiltersPickWrapper.PluginHeader.title.filter": "Filters", + "components.FiltersPickWrapper.hide": "Verbergen", + + "components.FilterOptions.button.apply": "Toepassen", + "components.FilterOptions.FILTER_TYPES.=": "is", + "components.FilterOptions.FILTER_TYPES._ne": "is niet", + "components.FilterOptions.FILTER_TYPES._lt": "is lager dan", + "components.FilterOptions.FILTER_TYPES._lte": "is lager dan of gelijk aan", + "components.FilterOptions.FILTER_TYPES._gt": "is groter dan", + "components.FilterOptions.FILTER_TYPES._gte": "is groter dan of gelijk aan", + "components.FilterOptions.FILTER_TYPES._contains": "bevat", + "components.FilterOptions.FILTER_TYPES._containss": "bevat (hoofdletter gevoelig)", + + "components.Search.placeholder": "Zoek naar een item...", + + "components.TableDelete.entries.plural": "{number} items geselecteerd", + "components.TableDelete.entries.singular": "{number} item geselecteerd", + "components.TableDelete.delete": "Alles verwijderen", + + + "components.TableEmpty.withFilters": "Er is geen {contentType} met de gekozen filters...", + "components.TableEmpty.withoutFilter": "Er is geen {contentType}...", + "components.TableEmpty.withSearch": "Er is geen {contentType} passend bij de zoekopdracht ({search})...", + + "EditRelations.title": "Gerelateerde data", + + "emptyAttributes.title": "Er zijn nog geen velden", + "emptyAttributes.description": "Voeg je eerste veld toe aan je Content Type", + "emptyAttributes.button": "Naar de content type bouwer", + + "error.schema.generation": "Er is een fout opgetreden tijdens het maken van het schema", + "error.records.count": "Er is een fout opgetreden tijdens het tellen van de opgehaalde gegevens", + "error.records.fetch": "Er is een fout opgetreden tijdens het ophalen van de gegevens", + "error.record.fetch": "Er is een fout opgetreden tijdens het ophalen van het item.", + "error.record.create": "Er is een fout opgetreden tijdens het maken van het item.", + "error.record.update": "Er is een fout opgetreden tijdens het opslaan van het item.", + "error.record.delete": "Er is een fout opgetreden tijdens het verwijderen van het item.", + "error.model.fetch": "Er is een fout opgetreden tijdens het ophalen van de models.", + "error.validation.required": "Deze gegevens zijn verplicht.", + "error.validation.regex": "De waarde is niet gelijk aan de regex.", + "error.validation.max": "De waarde is te hoog.", + "error.validation.min": "De waarde is te laag.", + "error.validation.maxLength": "De waarde is te lang.", + "error.validation.minLength": "De waarde is te kort.", + "error.contentTypeName.taken": "Deze naam bestaat al.", + "error.attribute.taken": "Dit veld naam bestaat al.", + "error.attribute.key.taken": "Deze waarde bestaat al.", + "error.attribute.sameKeyAndName": "Mag niet gelijk zijn.", + "error.validation.minSupMax": "Mag niet superieur zijn.", + "error.validation.json": "Dit is geen JSON", + + "form.Input.label.inputDescription": "Deze waarde overschrijft het label welke weergegeven wordt in het tabel hoofd", + "form.Input.label": "Label", + "form.Input.search": "Zoeken inschakelen", + "form.Input.search.field": "Schakel zoeken in voor dit veld", + "form.Input.filters": "Filters inschakelen", + "form.Input.sort.field": "Sorteren inschakelen voor dit veld", + "form.Input.bulkActions": "Bulk acties inschakelen", + "form.Input.pageEntries": "Items per pagina", + "form.Input.pageEntries.inputDescription": "Hint: Je kunt deze waarde overschrijven in de Content Type instellingen pagina", + "form.Input.defaultSort": "Standaard sorteer attribuut", + + "notification.error.relationship.fetch": "Er is een fout opgetreden tijdens het ophalen van de relaties.", + "notification.info.SettingPage.disableSort": "Je moet één attribuut hebben met sorteren ingeschakeld", + + "success.record.delete": "Verwijderd", + "success.record.save": "Opgeslagen", + + "pageNotFound": "Pagina niet gevonden", + + "popUpWarning.button.cancel": "Annuleren", + "popUpWarning.button.confirm": "Akkoord", + "popUpWarning.title": "Ga a.u.b. akkoord", + "popUpWarning.bodyMessage.contentType.delete": "Weet je zeker dat je dit item wilt verwijderen?", + "popUpWarning.bodyMessage.contentType.delete.all": "Weet je zeker dat je deze items wilt verwijderen?", + "popUpWarning.warning.cancelAllSettings": "Weet je zeker dat je je wijzigingen wilt annuleren?", + "popUpWarning.warning.updateAllSettings": "Dit zal al je instellingen aanpassen" +} diff --git a/packages/strapi-plugin-content-manager/admin/src/translations/pl.json b/packages/strapi-plugin-content-manager/admin/src/translations/pl.json index e0a13884b8..48cd023bbf 100644 --- a/packages/strapi-plugin-content-manager/admin/src/translations/pl.json +++ b/packages/strapi-plugin-content-manager/admin/src/translations/pl.json @@ -15,6 +15,11 @@ "components.LimitSelect.itemsPerPage": "Elementów na stronę", "containers.List.errorFetchRecords": "Błąd", + "containers.SettingPage.relations": "Relational fields", + + "containers.SettingPage.editSettings.description": "Drag & drop the fields to build the layout", + "containers.SettingPage.editSettings.title": "Edit — Settings", + "components.AddFilterCTA.add": "Filtry", "components.AddFilterCTA.hide": "Filtry", "components.FilterOptions.button.apply": "Zastosuj", @@ -70,6 +75,10 @@ "error.attribute.sameKeyAndName": "Nie mogą być takie same", "error.validation.minSupMax": "Nie może być większa", + "form.Input.description": "Description", + "form.Input.description.placeholder": "Display name in the profile", + "form.Input.disabled": "Editable field", + "notification.error.relationship.fetch": "Wystąpił błąd podczas pobierania relacji.", "success.record.delete": "Usunięto", diff --git a/packages/strapi-plugin-content-manager/admin/src/translations/pt-BR.json b/packages/strapi-plugin-content-manager/admin/src/translations/pt-BR.json index 9650973ed7..894459631e 100644 --- a/packages/strapi-plugin-content-manager/admin/src/translations/pt-BR.json +++ b/packages/strapi-plugin-content-manager/admin/src/translations/pt-BR.json @@ -14,9 +14,30 @@ "containers.List.pluginHeaderDescription.singular": "{label} registro encontrado", "components.LimitSelect.itemsPerPage": "Registros por página", "containers.List.errorFetchRecords": "Erro", + + "containers.SettingPage.relations": "Relational fields", + + "containers.SettingPage.editSettings.description": "Drag & drop the fields to build the layout", + "containers.SettingPage.editSettings.title": "Edit — Settings", + + "containers.SettingPage.addField": "Adicionar campo", + "containers.SettingPage.attributes": "Atributos", + "containers.SettingPage.attributes.description": "Define a ordem dos atributos", + + "containers.SettingPage.listSettings.title": "Lista — Configurações", + "containers.SettingPage.listSettings.description": "Configure as opções para este Tipo de Conteúdo", + "containers.SettingPage.pluginHeaderDescription": "Defina as configurações específicas para este Tipo de Conteúdo", + "containers.SettingsPage.pluginHeaderDescription": "Configure as opções padrões para todos os seus Tipos de Conteúdo", + "containers.SettingsPage.Block.generalSettings.description": "Configure as opções padrões para seu Tipo de Conteúdo", + "containers.SettingsPage.Block.generalSettings.title": "Geral", + "containers.SettingsPage.Block.contentType.title": "Tipos de Conteúdo", + "containers.SettingsPage.Block.contentType.description": "Defina as configurações específicas", "components.AddFilterCTA.add": "Filtros", "components.AddFilterCTA.hide": "Filtros", + + "components.DraggableAttr.edit": "Clique para editar", + "components.FilterOptions.button.apply": "Aplicar", "components.FiltersPickWrapper.PluginHeader.actions.apply": "Aplicar", "components.FiltersPickWrapper.PluginHeader.actions.clearAll": "Limpar tudo", @@ -71,7 +92,19 @@ "error.validation.minSupMax": "Não pode ser superior", "error.validation.json": "Isto não corresponde com o formato JSON", + "form.Input.label.inputDescription": "Este valor substitui o rótulo apresentado no cabeçalho da tabela", + "form.Input.label": "Rótulo", + "form.Input.search": "Habilitar busca", + "form.Input.search.field": "Habilitar busca neste campo", + "form.Input.filters": "Habilitar filtros", + "form.Input.sort.field": "Habilitar ordenação neste campo", + "form.Input.bulkActions": "Habilitar ações em lote", + "form.Input.pageEntries": "Entradas por página", + "form.Input.pageEntries.inputDescription": "Nota: Você pode substituir este valor na página de configurações do Tipo de Conteúdo.", + "form.Input.defaultSort": "Atributo de ordenação padrão", + "notification.error.relationship.fetch": "Ocorreu um erro durante a busca da relação.", + "notification.info.SettingPage.disableSort": "Você precisa de um atributo com permissão de ordenação", "success.record.delete": "Removido", "success.record.save": "Salvo", @@ -82,5 +115,7 @@ "popUpWarning.button.confirm": "Confirmar", "popUpWarning.title": "Por favor, confirme", "popUpWarning.bodyMessage.contentType.delete": "Tem a certeza de que deseja remover este registro?", - "popUpWarning.bodyMessage.contentType.delete.all": "Tem a certeza de que deseja remover estes registros?" + "popUpWarning.bodyMessage.contentType.delete.all": "Tem a certeza de que deseja remover estes registros?", + "popUpWarning.warning.cancelAllSettings": "Você tem certeza de que deseja cancelar suas modificações?", + "popUpWarning.warning.updateAllSettings": "Isto irá modificar todas as suas configurações" } \ No newline at end of file diff --git a/packages/strapi-plugin-content-manager/admin/src/translations/pt.json b/packages/strapi-plugin-content-manager/admin/src/translations/pt.json index 9bafef08e6..fcc43b0b2d 100644 --- a/packages/strapi-plugin-content-manager/admin/src/translations/pt.json +++ b/packages/strapi-plugin-content-manager/admin/src/translations/pt.json @@ -14,6 +14,11 @@ "containers.List.pluginHeaderDescription.singular": "{label} entrada encontrada", "components.LimitSelect.itemsPerPage": "Itens por página", "containers.List.errorFetchRecords": "Erro", + + "containers.SettingPage.relations": "Relational fields", + + "containers.SettingPage.editSettings.description": "Drag & drop the fields to build the layout", + "containers.SettingPage.editSettings.title": "Edit — Settings", "components.AddFilterCTA.add": "Filtros", "components.AddFilterCTA.hide": "Filtros", @@ -70,6 +75,10 @@ "error.attribute.sameKeyAndName": "Não pode ser igual", "error.validation.minSupMax": "Não pode ser superior", "error.validation.json": "Isto não corresponde com o formato JSON", + + "form.Input.description": "Description", + "form.Input.description.placeholder": "Display name in the profile", + "form.Input.disabled": "Editable field", "notification.error.relationship.fetch": "Ocorreu um erro durante a busca da relação.", diff --git a/packages/strapi-plugin-content-manager/admin/src/translations/ru.json b/packages/strapi-plugin-content-manager/admin/src/translations/ru.json index 8faadca003..5866499e52 100644 --- a/packages/strapi-plugin-content-manager/admin/src/translations/ru.json +++ b/packages/strapi-plugin-content-manager/admin/src/translations/ru.json @@ -9,11 +9,61 @@ "containers.Edit.delete": "Удалить", "containers.Edit.reset": "Сбросить", "containers.Edit.returnList": "Вернуться к списку", - "containers.List.addAnEntry": "Добавить новую {entity}", + "containers.List.addAnEntry": "Добавить новые {entity}", "containers.List.pluginHeaderDescription": "{label} записей найдено", "containers.List.pluginHeaderDescription.singular": "{label} запись найдена", "components.LimitSelect.itemsPerPage": "Элементов на странице", "containers.List.errorFetchRecords": "Ошибка", + "containers.SettingPage.addField": "Добавить новое поле", + "containers.SettingPage.attributes": "Поля атрибутов", + "containers.SettingPage.attributes.description": "Определить порядок атребутов", + "containers.SettingPage.listSettings.description": "Указать порядок атрибутов", + "containers.SettingPage.listSettings.title": "Список — Настройки", + "containers.SettingPage.pluginHeaderDescription": "Отдельные настройки для этого Типа Данных", + "containers.SettingsPage.pluginHeaderDescription": "Настройте параметры по умолчанию для всех Типов Данных", + "containers.SettingsPage.Block.generalSettings.description": "Настройте опции по умолчанию для ваших Типов Данных", + "containers.SettingsPage.Block.generalSettings.title": "Общее", + "containers.SettingsPage.Block.contentType.title": "Типы данных", + "containers.SettingsPage.Block.contentType.description": "Настроить отдельные параметры", + "components.AddFilterCTA.add": "Фильтры", + "components.AddFilterCTA.hide": "Фильтры", + "components.DraggableAttr.edit": "Нажмите чтобы редактировать", + "components.FiltersPickWrapper.PluginHeader.actions.apply": "Применить", + "components.FiltersPickWrapper.PluginHeader.actions.clearAll": "Очистить все", + "components.FiltersPickWrapper.PluginHeader.description": "Укажите условия для фильтрации записей", + "components.FiltersPickWrapper.PluginHeader.title.filter": "Фильтры", + "components.FiltersPickWrapper.hide": "Скрыть", + "components.FilterOptions.button.apply": "Применить", + "components.FilterOptions.FILTER_TYPES.=": "равно", + "components.FilterOptions.FILTER_TYPES._ne": "не равно", + "components.FilterOptions.FILTER_TYPES._lt": "меньше чем", + "components.FilterOptions.FILTER_TYPES._lte": "меньше или равно чем", + "components.FilterOptions.FILTER_TYPES._gt": "больше чем", + "components.FilterOptions.FILTER_TYPES._gte": "равно или больше чем", + "components.FilterOptions.FILTER_TYPES._contains": "содержит", + "components.FilterOptions.FILTER_TYPES._containss": "содержит (с учетом регистра)", + "components.Search.placeholder": "Поиск записей...", + "components.TableDelete.entries.plural": "{число} записей выбрано", + "components.TableDelete.entries.singular": "{число} записей выделено", + "components.TableDelete.delete": "Удалить все", + "components.TableEmpty.withFilters": "Нет {contentType} с примененными фильтрами...", + "components.TableEmpty.withoutFilter": "Нет {contentType}...", + "components.TableEmpty.withSearch": "Нет {contentType} согласно поиску ({search})", + "error.validation.json": "Это не JSON", + "form.Input.label.inputDescription": "Это знчение переопределит метку, в заголовке таблицы", + "form.Input.label": "Метка", + "form.Input.search": "Применить поиск", + "form.Input.search.field": "Применить поиск по этому полю", + "form.Input.filters": "Применить фильтры", + "form.Input.sort.field": "Применить сортировку по этому полю", + "form.Input.bulkActions": "Применить массовые действия", + "form.Input.pageEntries": "Записей на страницу", + "form.Input.pageEntries.inputDescription": "Заметка: вы можете переопределить это значение на странице настроек Типа Данных", + "form.Input.defaultSort": "Сортировка по умолчанию", + "notification.info.SettingPage.disableSort": "У вас должен быть один атрибут с разрешенной сортировкой", + "popUpWarning.bodyMessage.contentType.delete.all": "Вы уверенны, что хотите удалить эти записи?", + "popUpWarning.warning.cancelAllSettings": "Вы уверенны, что хотите отменить ваши модификации?", + "popUpWarning.warning.updateAllSettings": "Это изменит все ваши настройки", "EditRelations.title": "Связанные данные", @@ -40,6 +90,10 @@ "error.attribute.key.taken": "Это значение уже существует", "error.attribute.sameKeyAndName": "Не может быть одинаковым", "error.validation.minSupMax": "Не может быть выше", + + "form.Input.description": "Description", + "form.Input.description.placeholder": "Display name in the profile", + "form.Input.disabled": "Editable field", "notification.error.relationship.fetch": "Возникла ошибка при получении связей.", @@ -53,4 +107,4 @@ "popUpWarning.title": "Пожалуйста подтвердите", "popUpWarning.bodyMessage.contentType.delete": "Вы уверены, что хотите удалить эту запись?" } - \ No newline at end of file + diff --git a/packages/strapi-plugin-content-manager/admin/src/translations/tr.json b/packages/strapi-plugin-content-manager/admin/src/translations/tr.json index f818897df8..038ef1eb01 100755 --- a/packages/strapi-plugin-content-manager/admin/src/translations/tr.json +++ b/packages/strapi-plugin-content-manager/admin/src/translations/tr.json @@ -15,15 +15,31 @@ "components.LimitSelect.itemsPerPage": "Sayfa başı", "containers.List.errorFetchRecords": "Hata", + "containers.SettingPage.addField": "Yeni alan ekle", + "containers.SettingPage.attributes": "Nitelik alanları", + "containers.SettingPage.attributes.description": "Niteliklerin sırasını tanımlayın", + + "containers.SettingPage.listSettings.title": "Liste — Ayarlar", + "containers.SettingPage.listSettings.description": "Bu içerik türü için seçenekleri yapılandırın", + "containers.SettingPage.pluginHeaderDescription": "Bu İçerik Türü için belirli ayarları yapılandırın", + "containers.SettingsPage.pluginHeaderDescription": "Tüm İçerik türleriniz için varsayılan ayarları yapılandırın", + "containers.SettingsPage.Block.generalSettings.description": "İçerik Türleriniz için varsayılan seçenekleri yapılandırın", + "containers.SettingsPage.Block.generalSettings.title": "Genel", + "containers.SettingsPage.Block.contentType.title": "İçerik Türleri", + "containers.SettingsPage.Block.contentType.description": "Belirli ayarları yapılandırın", + "components.AddFilterCTA.add": "Filtreler", "components.AddFilterCTA.hide": "Filtreler", - "components.FilterOptions.button.apply": "Uygula", + + "components.DraggableAttr.edit": "Düzenlemek için tıklayın", + "components.FiltersPickWrapper.PluginHeader.actions.apply": "Uygula", "components.FiltersPickWrapper.PluginHeader.actions.clearAll": "Hepsini temizle", "components.FiltersPickWrapper.PluginHeader.description": "Filtrelemek için uygulanacak şartları ayarlayın", "components.FiltersPickWrapper.PluginHeader.title.filter": "Filtreler", "components.FiltersPickWrapper.hide": "Gizle", + "components.FilterOptions.button.apply": "Uygula", "components.FilterOptions.FILTER_TYPES.=": "eşit", "components.FilterOptions.FILTER_TYPES._ne": "eşit değil", "components.FilterOptions.FILTER_TYPES._lt": "daha düşük", @@ -68,8 +84,25 @@ "error.attribute.key.taken": "Bu değer zaten var.", "error.attribute.sameKeyAndName": "Eşit olamaz", "error.validation.minSupMax": "Üstü olamaz", + "error.validation.json": "Bu JSON biçimi ile eşleşmiyor", + + "form.Input.label.inputDescription": "Bu değer, tablonun başında görüntülenen etiketi geçersiz kılar", + "form.Input.label": "Etiket", + "form.Input.search": "Aramayı etkinleştir", + "form.Input.search.field": "Bu alanda aramayı etkinleştir", + "form.Input.filters": "Filtreleri etkinleştir", + "form.Input.sort.field": "Bu alana göre sıralamayı etkinleştir", + "form.Input.bulkActions": "Toplu işlemleri etkinleştir", + "form.Input.pageEntries": "Sayfa başına kayıtlar", + "form.Input.pageEntries.inputDescription": "Not: Bu değeri İçerik Türü ayarları sayfasında geçersiz kılabilirsiniz..", + "form.Input.defaultSort": "Varsayılan sıralama özelliği", + + "form.Input.description": "Description", + "form.Input.description.placeholder": "Display name in the profile", + "form.Input.disabled": "Editable field", "notification.error.relationship.fetch": "İlişki getirme sırasında bir hata oluştu.", + "notification.info.SettingPage.disableSort": "Sıralamaya izin verilen tek bir özelliğe sahip olmanız gerekir", "success.record.delete": "Silindi", "success.record.save": "Kaydedildi", @@ -80,5 +113,7 @@ "popUpWarning.button.confirm": "Onayla", "popUpWarning.title": "Lütfen onaylayın", "popUpWarning.bodyMessage.contentType.delete": "Bu kaydı silmek istediğinizden emin misiniz?", - "popUpWarning.bodyMessage.contentType.delete.all": "Bu kayıtları silmek istediğinizden emin misiniz?" + "popUpWarning.bodyMessage.contentType.delete.all": "Bu kayıtları silmek istediğinizden emin misiniz?", + "popUpWarning.warning.cancelAllSettings": "Değişikliklerinizi iptal etmek istediğinizden emin misiniz?", + "popUpWarning.warning.updateAllSettings": "Bu bütün ayarlarınızı değiştirecektir" } diff --git a/packages/strapi-plugin-content-manager/admin/src/translations/zh-Hans.json b/packages/strapi-plugin-content-manager/admin/src/translations/zh-Hans.json index 3c5c93207d..a9cae7f62f 100644 --- a/packages/strapi-plugin-content-manager/admin/src/translations/zh-Hans.json +++ b/packages/strapi-plugin-content-manager/admin/src/translations/zh-Hans.json @@ -15,6 +15,11 @@ "components.LimitSelect.itemsPerPage": "每页显示数目", "containers.List.errorFetchRecords": "错误", + "containers.SettingPage.relations": "Relational fields", + + "containers.SettingPage.editSettings.description": "Drag & drop the fields to build the layout", + "containers.SettingPage.editSettings.title": "Edit — Settings", + "EditRelations.title": "关系数据", "emptyAttributes.title": "还没有字段", @@ -41,6 +46,10 @@ "error.attribute.sameKeyAndName": "不能相等", "error.validation.minSupMax": "最小值大于最大值。", + "form.Input.description": "Description", + "form.Input.description.placeholder": "Display name in the profile", + "form.Input.disabled": "Editable field", + "notification.error.relationship.fetch": "获取关联数据时发生错误", "success.record.delete": "删除", diff --git a/packages/strapi-plugin-content-manager/admin/src/translations/zh.json b/packages/strapi-plugin-content-manager/admin/src/translations/zh.json index 33a32f3ae1..2664dced0b 100755 --- a/packages/strapi-plugin-content-manager/admin/src/translations/zh.json +++ b/packages/strapi-plugin-content-manager/admin/src/translations/zh.json @@ -16,6 +16,11 @@ "components.LimitSelect.itemsPerPage": "每個頁面檔案數量", "containers.List.errorFetchRecords": "錯誤", + "containers.SettingPage.relations": "Relational fields", + + "containers.SettingPage.editSettings.description": "Drag & drop the fields to build the layout", + "containers.SettingPage.editSettings.title": "Edit — Settings", + "EditRelations.title": "關聯式資料", "emptyAttributes.title": "目前還沒有欄位", @@ -42,6 +47,10 @@ "error.attribute.sameKeyAndName": "不能等於", "error.validation.minSupMax": "不能大於", + "form.Input.description": "Description", + "form.Input.description.placeholder": "Display name in the profile", + "form.Input.disabled": "Editable field", + "notification.error.relationship.fetch": "讀取關聯資料時發生錯誤", "success.record.delete": "已刪除", diff --git a/packages/strapi-plugin-content-manager/admin/src/utils/ItemTypes.js b/packages/strapi-plugin-content-manager/admin/src/utils/ItemTypes.js new file mode 100644 index 0000000000..0f8c3c2b50 --- /dev/null +++ b/packages/strapi-plugin-content-manager/admin/src/utils/ItemTypes.js @@ -0,0 +1,5 @@ +export default { + NORMAL: 'normalAttr', + SORTABLEITEM: 'sortableItem', + VARIABLE: 'variableAttr', +}; \ No newline at end of file diff --git a/packages/strapi-plugin-content-manager/config/functions/bootstrap.js b/packages/strapi-plugin-content-manager/config/functions/bootstrap.js new file mode 100644 index 0000000000..f866b8cffd --- /dev/null +++ b/packages/strapi-plugin-content-manager/config/functions/bootstrap.js @@ -0,0 +1,364 @@ +const _ = require('lodash'); +const pluralize = require('pluralize'); +const { + getApis, + getApisKeys, + getApisUploadRelations, + getEditDisplayAvailableFieldsPath, + getEditDisplayFieldsPath +} = require('./utils/getters'); +const splitted = str => str.split('.'); +const pickData = (model) => _.pick(model, [ + 'info', + 'connection', + 'collectionName', + 'attributes', + 'identity', + 'globalId', + 'globalName', + 'orm', + 'loadedModel', + 'primaryKey', + 'associations' +]); + +module.exports = async cb => { + // Retrieve all layout files from the plugins config folder + const pluginsLayout = Object.keys(strapi.plugins).reduce((acc, current) => { + const models = _.get(strapi.plugins, [current, 'config', 'layout'], {}); + Object.keys(models).forEach(model => { + const layout = _.get(strapi.plugins, [current, 'config', 'layout', model], {}); + acc[model] = layout; + }); + + return acc; + }, {}); + // Remove the core_store layout since it is not needed + // And create a temporay one + const tempLayout = Object.keys(strapi.models) + .filter(m => m !== 'core_store') + .reduce((acc, current) => { + acc[current] = { attributes: {} }; + + return acc; + }, pluginsLayout); + const models = _.mapValues(strapi.models, pickData); + delete models['core_store']; + const pluginsModel = Object.keys(strapi.plugins).reduce((acc, current) => { + acc[current] = { + models: _.mapValues(strapi.plugins[current].models, pickData), + }; + + return acc; + }, {}); + // Init schema + const schema = { + generalSettings: { + search: true, + filters: true, + bulkActions: true, + pageEntries: 10, + }, + models: { + plugins: {}, + }, + layout: {} + }; + + // Populate the schema object + const buildSchema = (model, name, plugin = false) => { + // Model data + const schemaModel = Object.assign({ + label: _.upperFirst(name), + labelPlural: _.upperFirst(pluralize(name)), + orm: model.orm || 'mongoose', + search: true, + filters: true, + bulkActions: true, + pageEntries: 10, + defaultSort: model.primaryKey, + sort: 'ASC', + editDisplay: { + availableFields: {}, + fields: [], + relations: [], + }, + }, model); + const fieldsToRemove = []; + // Fields (non relation) + const fields = _.mapValues(_.pickBy(model.attributes, attribute => + !attribute.model && !attribute.collection + ), (value, attribute) => { + const fieldClassName = _.get(tempLayout, [name, 'attributes', attribute, 'className'], ''); + + if (fieldClassName === 'd-none') { + fieldsToRemove.push(attribute); + } + + return { + label: _.upperFirst(attribute), + description: '', + type: value.type || 'string', + disabled: false, + }; + }); + + // Don't display fields that are hidden by default like the resetPasswordToken for the model user + fieldsToRemove.forEach(field => { + _.unset(fields, field); + _.unset(schemaModel.attributes, field); + }); + + schemaModel.fields = fields; + schemaModel.editDisplay.availableFields = fields; + + // Select fields displayed in list view + schemaModel.listDisplay = Object.keys(schemaModel.fields) + // Construct Array of attr ex { type: 'string', label: 'Foo', name: 'Foo', description: '' } + .map(attr => { + const attrType = schemaModel.fields[attr].type; + const sortable = attrType !== 'json' && attrType !== 'array'; + + return Object.assign(schemaModel.fields[attr], { name: attr, sortable, searchable: sortable }); + }) + // Retrieve only the fourth first items + .slice(0, 4); + + schemaModel.listDisplay.splice(0, 0, { + name: model.primaryKey || 'id', + label: 'Id', + type: 'string', + sortable: true, + searchable: true, + }); + + // This object will be used to customise the label and description and so on of an input. + // TODO: maybe add the customBootstrapClass in it; + schemaModel.editDisplay.availableFields = Object.keys(schemaModel.fields).reduce((acc, current) => { + acc[current] = Object.assign( + _.pick(_.get(schemaModel, ['fields', current], {}), ['label', 'type', 'description', 'name']), + { + editable: ['updatedAt', 'createdAt', 'updated_at', 'created_at'].indexOf(current) === -1, + placeholder: '', + }); + + return acc; + }, {}); + + if (model.associations) { + // Model relations + schemaModel.relations = model.associations.reduce((acc, current) => { + const label = _.upperFirst(current.alias); + const displayedAttribute = current.plugin ? // Value to modified to custom what's displayed in the react-select + _.get(pluginsModel, [current.plugin, 'models', current.model || current.collection, 'info', 'mainField']) || + _.findKey(_.get(pluginsModel, [current.plugin, 'models', current.model || current.collection, 'attributes']), { type : 'string'}) || + 'id' : + _.get(models, [current.model || current.collection, 'info', 'mainField']) || + _.findKey(_.get(models, [current.model || current.collection, 'attributes']), { type : 'string'}) || + 'id'; + + acc[current.alias] = { + ...current, + description: '', + label, + displayedAttribute, + }; + + return acc; + }, {}); + const relationsArray = Object.keys(schemaModel.relations).filter(relation => { + const isUploadRelation = _.get(schemaModel, ['relations', relation, 'plugin'], '') === 'upload'; + const isMorphSide = _.get(schemaModel, ['relations', relation, 'nature'], '').toLowerCase().includes('morp') && _.get(schemaModel, ['relations', relation, relation]) !== undefined; + + return !isUploadRelation && !isMorphSide; + }); + + const uploadRelations = Object.keys(schemaModel.relations).reduce((acc, current) => { + if (_.get(schemaModel, ['relations', current, 'plugin']) === 'upload') { + const model = _.get(schemaModel, ['relations', current]); + + acc[current] = { + description: '', + editable: true, + label: _.upperFirst(current), + multiple: _.has(model, 'collection'), + name: current, + placeholder: '', + type: 'file', + disabled: false, + }; + } + + return acc; + }, {}); + + schemaModel.editDisplay.availableFields = _.merge(schemaModel.editDisplay.availableFields, uploadRelations); + schemaModel.editDisplay.relations = relationsArray; + } + + schemaModel.editDisplay.fields = Object.keys(schemaModel.editDisplay.availableFields); + + if (plugin) { + return _.set(schema.models.plugins, `${plugin}.${name}`, schemaModel); + } + + // Set the formatted model to the schema + schema.models[name] = schemaModel; + }; + + // For each plugin's apis populate the schema object with the needed infos + _.forEach(pluginsModel, (plugin, pluginName) => { + _.forEach(plugin.models, (model, name) => { + buildSchema(model, name, pluginName); + }); + }); + + // Generate schema for models. + _.forEach(models, (model, name) => { + buildSchema(model, name); + }); + + const pluginStore = strapi.store({ + environment: '', + type: 'plugin', + name: 'content-manager' + }); + + try { + // Retrieve the previous schema from the db + const prevSchema = await pluginStore.get({ key: 'schema' }); + + // If no schema stored + if (!prevSchema) { + _.set(schema, 'layout', tempLayout); + + pluginStore.set({ key: 'schema', value: schema }); + + return cb(); + } + + // Here we do the difference between the previous schema from the database and the new one + + // Retrieve all the api path, it generates an array + const prevSchemaApis = getApis(prevSchema.models); + const schemaApis = getApis(schema.models); + + // Array of apis to add + const apisToAdd = schemaApis.filter(api => prevSchemaApis.indexOf(api) === -1).map(splitted); + // Array of apis to remove + const apisToRemove = prevSchemaApis.filter(api => schemaApis.indexOf(api) === -1).map(splitted); + + // Retrieve the same apis by name + const sameApis = schemaApis.filter(api => prevSchemaApis.indexOf(api) !== -1).map(splitted); + // Retrieve all the field's path of the current unchanged api name + const schemaSameApisKeys = _.flattenDeep(getApisKeys(schema, sameApis)); + // Retrieve all the field's path of the previous unchanged api name + const prevSchemaSameApisKeys = _.flattenDeep(getApisKeys(prevSchema, sameApis)); + // Determine for the same api if we need to add some fields + const sameApisAttrToAdd = schemaSameApisKeys.filter(attr => prevSchemaSameApisKeys.indexOf(attr) === -1).map(splitted); + // Special case for the relations + const prevSchemaSameApisUploadRelations = _.flattenDeep(getApisUploadRelations(prevSchema, sameApis)); + const schemaSameApisUploadRelations = _.flattenDeep(getApisUploadRelations(schema, sameApis)); + const sameApisUploadRelationsToAdd = schemaSameApisUploadRelations.filter(attr => prevSchemaSameApisUploadRelations.indexOf(attr) === -1).map(splitted); + // Determine the fields to remove for the unchanged api name + const sameApisAttrToRemove = prevSchemaSameApisKeys.filter(attr => schemaSameApisKeys.indexOf(attr) === -1).map(splitted); + + // Remove api + apisToRemove.map(apiPath => { + _.unset(prevSchema.models, apiPath); + }); + + // Remove API attribute + sameApisAttrToRemove.map(attrPath => { + const editDisplayPath = getEditDisplayAvailableFieldsPath(attrPath); + // Remove the field from the available fields in the editDisplayObject + _.unset(prevSchema.models, editDisplayPath); + // Check default sort and change it if needed + _.unset(prevSchema.models, attrPath); + // Retrieve the api path in the schema Object + const apiPath = attrPath.length > 3 ? _.take(attrPath, 3) : _.take(attrPath, 1); + // Retrieve the listDisplay path in the schema Object + const listDisplayPath = apiPath.concat('listDisplay'); + const prevListDisplay = _.get(prevSchema.models, listDisplayPath); + const defaultSortPath = apiPath.concat('defaultSort'); + const currentAttr = attrPath.slice(-1); + const defaultSort = _.get(prevSchema.models, defaultSortPath); + + // If the user has deleted the default sort attribute in the content type builder + // Replace it by new generated one from the current schema + if (_.includes(currentAttr, defaultSort)) { + _.set(prevSchema.models, defaultSortPath, _.get(schema.models, defaultSortPath)); + } + + // Update the displayed fields + const updatedListDisplay = prevListDisplay.filter(obj => obj.name !== currentAttr.join()); + + if (updatedListDisplay.length === 0) { + // Update it with the one from the generated schema + _.set(prevSchema.models, listDisplayPath, _.get(schema.models, listDisplayPath, [])); + } else { + _.set(prevSchema.models, listDisplayPath, updatedListDisplay); + } + }); + + // Add API + // Here we just need to add the data from the current schema Object + apisToAdd.map(apiPath => { + const api = _.get(schema.models, apiPath); + const { search, filters, bulkActions, pageEntries } = _.get(prevSchema, 'generalSettings'); + + _.set(api, 'filters', filters); + _.set(api, 'search', search); + _.set(api, 'bulkActions', bulkActions); + _.set(api, 'pageEntries', pageEntries); + _.set(prevSchema.models, apiPath, api); + }); + + // Add attribute to an existing API + sameApisAttrToAdd.map(attrPath => { + const attr = _.get(schema.models, attrPath); + _.set(prevSchema.models, attrPath, attr); + + // Add the field in the editDisplay object + const path = getEditDisplayAvailableFieldsPath(attrPath); + const availableAttrToAdd = _.get(schema.models, path); + _.set(prevSchema.models, path, availableAttrToAdd); + + // Push the attr into the list + const fieldsPath = getEditDisplayFieldsPath(attrPath); + const currentFields = _.get(prevSchema.models, fieldsPath, []); + currentFields.push(availableAttrToAdd.name); + _.set(prevSchema.models, fieldsPath, currentFields); + }); + + // Update other keys + sameApis.map(apiPath => { + // This doesn't keep the prevSettings for the relations, the user will have to reset it. + // We might have to improve this if we want the order of the relations to be kept + const keysToUpdate = ['relations', 'loadedModel', 'associations', 'attributes', ['editDisplay', 'relations']].map(key => apiPath.concat(key)); + + keysToUpdate.map(keyPath => { + const newValue = _.get(schema.models, keyPath); + + _.set(prevSchema.models, keyPath, newValue); + }); + }); + + // Special handler for the upload relations + sameApisUploadRelationsToAdd.forEach(attrPath => { + const attr = _.get(schema.models, attrPath); + _.set(prevSchema.models, attrPath, attr); + + const fieldsPath = [..._.take(attrPath, attrPath.length -2), 'fields']; + const currentFields = _.get(prevSchema.models, fieldsPath, []); + currentFields.push(attr.name); + _.set(prevSchema.models, fieldsPath, currentFields); + }); + + await pluginStore.set({ key: 'schema', value: prevSchema }); + } catch(err) { + console.log('error', err); + } + + cb(); +}; diff --git a/packages/strapi-plugin-content-manager/config/functions/utils/getters.js b/packages/strapi-plugin-content-manager/config/functions/utils/getters.js new file mode 100644 index 0000000000..3097e8f8c4 --- /dev/null +++ b/packages/strapi-plugin-content-manager/config/functions/utils/getters.js @@ -0,0 +1,66 @@ +const _ = require('lodash'); +/** + * Retrieve the path of each API + * @param {Object}} data + * @returns {Array} Array of API path ['plugins.upload.file', 'plugins.users-permissions.user', ...] + */ +const getApis = (data) => Object.keys(data).reduce((acc, curr) => { + if (data[curr].fields) { + return acc.concat([curr]); + } + + if (curr === 'plugins') { + Object.keys(data[curr]).map(plugin => { + Object.keys(data[curr][plugin]).map(api => { + acc = acc.concat([`${curr}.${plugin}.${api}`]); + }); + }); + } + + return acc; +}, []); + + +/** + * Retrieve all the fields from an api + * @param {Object} data + * @param {Array} apis + * @returns {Array} Array composed of fields path for instance : [['plugins.users-permissions.user.fields.username', 'plugins.users-permissions.user.fields.email', 'plugins.users-permissions.user.fields.password'], [...]] + */ +const getApisKeys = (data, apis) => apis.map(apiPath => { + const fields = Object.keys(_.get(data.models, apiPath.concat(['fields']))); + + return fields.map(field => `${apiPath.join('.')}.fields.${field}`); +}); + +/** + * Same as above but only for the relations since it's custom + */ +const getApisUploadRelations = (data, sameArray) => sameArray.map(apiPath => { + const relationPath = [...apiPath, 'relations']; + const relationsObject = _.get(data.models, relationPath, {}); + const relations = Object.keys(relationsObject) + .filter(relationName => { + return _.get(data.models, [...relationPath, relationName, 'plugin' ]) === 'upload'; + }); + + return relations.map(relation => `${apiPath.join('.')}.editDisplay.availableFields.${relation}`); +}); + +/** + * + * @param {String} attrPath + * @returns {Array} + */ +const getEditDisplayAvailableFieldsPath = attrPath => [..._.take(attrPath, attrPath.length -2), 'editDisplay', 'availableFields', attrPath[attrPath.length - 1]]; +const getEditDisplayFieldsPath = attrPath => [..._.take(attrPath, attrPath.length -2), 'editDisplay', 'fields']; + + + +module.exports = { + getApis, + getApisKeys, + getApisUploadRelations, + getEditDisplayAvailableFieldsPath, + getEditDisplayFieldsPath +}; \ No newline at end of file diff --git a/packages/strapi-plugin-content-manager/config/routes.json b/packages/strapi-plugin-content-manager/config/routes.json index 9b057fa5a5..b3abe8e762 100755 --- a/packages/strapi-plugin-content-manager/config/routes.json +++ b/packages/strapi-plugin-content-manager/config/routes.json @@ -1,13 +1,5 @@ { "routes": [ - { - "method": "GET", - "path": "/layout", - "handler": "ContentManager.layout", - "config": { - "policies": [] - } - }, { "method": "GET", "path": "/models", @@ -32,6 +24,14 @@ "policies": ["routing"] } }, + { + "method": "PUT", + "path": "/models", + "handler": "ContentManager.updateSettings", + "config": { + "policies": ["routing"] + } + }, { "method": "GET", "path": "/explorer/:model/:id", diff --git a/packages/strapi-plugin-content-manager/controllers/ContentManager.js b/packages/strapi-plugin-content-manager/controllers/ContentManager.js index 3bb5684dd7..7a580cf982 100755 --- a/packages/strapi-plugin-content-manager/controllers/ContentManager.js +++ b/packages/strapi-plugin-content-manager/controllers/ContentManager.js @@ -7,39 +7,17 @@ const _ = require('lodash'); */ module.exports = { - layout: async (ctx) => { - const {source} = ctx.query; - - return ctx.send(_.get(strapi.plugins, [source, 'config', 'layout'], {})); - }, - models: async ctx => { - const pickData = (model) => _.pick(model, [ - 'info', - 'connection', - 'collectionName', - 'attributes', - 'identity', - 'globalId', - 'globalName', - 'orm', - 'loadedModel', - 'primaryKey', - 'associations' - ]); + const pluginsStore = strapi.store({ + environment: '', + type: 'plugin', + name: 'content-manager', + }); - const models = _.mapValues(strapi.models, pickData); - delete models['core_store']; + const models = await pluginsStore.get({ key: 'schema' }); ctx.body = { models, - plugins: Object.keys(strapi.plugins).reduce((acc, current) => { - acc[current] = { - models: _.mapValues(strapi.plugins[current].models, pickData) - }; - - return acc; - }, {}) }; }, @@ -105,6 +83,18 @@ module.exports = { } }, + updateSettings: async ctx => { + const { schema } = ctx.request.body; + const pluginStore = strapi.store({ + environment: '', + type: 'plugin', + name: 'content-manager' + }); + await pluginStore.set({ key: 'schema', value: schema }); + + return ctx.body = { ok: true }; + }, + delete: async ctx => { ctx.body = await strapi.plugins['content-manager'].services['contentmanager'].delete(ctx.params, ctx.request.query); }, diff --git a/packages/strapi-plugin-content-manager/package.json b/packages/strapi-plugin-content-manager/package.json index b112406143..5a31f09e50 100755 --- a/packages/strapi-plugin-content-manager/package.json +++ b/packages/strapi-plugin-content-manager/package.json @@ -1,6 +1,6 @@ { "name": "strapi-plugin-content-manager", - "version": "3.0.0-alpha.12.7.1", + "version": "3.0.0-alpha.14", "description": "A powerful UI to easily manage your data.", "strapi": { "name": "Content Manager", @@ -22,8 +22,14 @@ "prepublishOnly": "IS_MONOREPO=true npm run build" }, "devDependencies": { + "codemirror": "^5.39.0", + "draft-js": "^0.10.5", "react-select": "^1.2.1", - "strapi-helper-plugin": "3.0.0-alpha.12.7.1" + "showdown": "^1.8.6", + "strapi-helper-plugin": "3.0.0-alpha.14" + }, + "dependencies": { + "pluralize": "^7.0.0" }, "author": { "name": "Strapi team", diff --git a/packages/strapi-plugin-content-manager/services/ContentManager.js b/packages/strapi-plugin-content-manager/services/ContentManager.js index 9f001c6832..3bf15942e3 100644 --- a/packages/strapi-plugin-content-manager/services/ContentManager.js +++ b/packages/strapi-plugin-content-manager/services/ContentManager.js @@ -118,6 +118,11 @@ module.exports = { const files = values.files; + // set empty attributes if old values was cleared + _.difference(Object.keys(files), Object.keys(values.fields)).forEach(attr => { + values.fields[attr] = []; + }); + // Parse stringify JSON data. values = Object.keys(values.fields).reduce((acc, current) => { acc[current] = parser(values.fields[current]); diff --git a/packages/strapi-plugin-content-manager/test/index.test.js b/packages/strapi-plugin-content-manager/test/index.test.js index 0280307f05..74194bb832 100644 --- a/packages/strapi-plugin-content-manager/test/index.test.js +++ b/packages/strapi-plugin-content-manager/test/index.test.js @@ -1,4 +1,5 @@ // Helpers. +const {login} = require('../../../test/helpers/auth'); const form = require('../../../test/helpers/generators'); const restart = require('../../../test/helpers/restart'); const rq = require('../../../test/helpers/request'); @@ -14,18 +15,11 @@ let data; describe('App setup auth', () => { test( - 'Register admin user', + 'Login admin user', async () => { - const body = await rq({ - url: `/auth/local/register`, - method: 'POST', - body: { - username: 'admin', - email: 'admin@strapi.io', - password: 'pcw123' - }, - json: true - }); + await restart(rq); + + const body = await login(); rq.defaults({ headers: { @@ -881,8 +875,12 @@ describe('Test oneWay relation (reference - tag) with Content Manager', () => { json: true }); - if (Object.keys(referenceToGet.tag).length == 0) { - referenceToGet.tag = null; + try { + if (Object.keys(referenceToGet.tag).length == 0) { + referenceToGet.tag = null; + } + } catch(err) { + // Silent } expect(referenceToGet.tag).toBe(null); @@ -925,4 +923,14 @@ describe('Delete test APIs', () => { }); } ); + test( + 'Delete reference API', + async () => { + await rq({ + url: `/content-type-builder/models/reference`, + method: 'DELETE', + json: true + }); + } + ); }); diff --git a/packages/strapi-plugin-content-type-builder/admin/src/components/EmptyAttributesView/index.js b/packages/strapi-plugin-content-type-builder/admin/src/components/EmptyAttributesView/index.js deleted file mode 100644 index 1018249101..0000000000 --- a/packages/strapi-plugin-content-type-builder/admin/src/components/EmptyAttributesView/index.js +++ /dev/null @@ -1,41 +0,0 @@ -/** -* -* EmptyAttributesView -* -*/ - -import React from 'react'; -import PropTypes from 'prop-types'; -import { FormattedMessage } from 'react-intl'; -import Button from 'components/Button'; -import styles from './styles.scss'; - -class EmptyAttributesView extends React.Component { // eslint-disable-line react/prefer-stateless-function - render() { - return ( -
    -
    - - {(title) =>
    {title}
    } -
    - - {(description) =>
    {description}
    } -
    -
    -
    -
    -
    - ); - } -} - -EmptyAttributesView.propTypes = { - onClickAddAttribute: PropTypes.func.isRequired, -}; - -export default EmptyAttributesView; diff --git a/packages/strapi-plugin-content-type-builder/admin/src/components/PopUpForm/index.js b/packages/strapi-plugin-content-type-builder/admin/src/components/PopUpForm/index.js index e054f902cf..d555d5d20c 100644 --- a/packages/strapi-plugin-content-type-builder/admin/src/components/PopUpForm/index.js +++ b/packages/strapi-plugin-content-type-builder/admin/src/components/PopUpForm/index.js @@ -49,7 +49,7 @@ class PopUpForm extends React.Component { // eslint-disable-line react/prefer-st } handleSubmit = (e) => { - this.props.onSubmit(e, true); + this.props.onSubmit(e, false); } renderInput = (item, key) => { @@ -138,8 +138,8 @@ class PopUpForm extends React.Component { // eslint-disable-line react/prefer-st return ( - {popUpFormType !== 'contentType' && } - {' '} + {popUpFormType !== 'contentType' && } + {' '} ); } diff --git a/packages/strapi-plugin-content-type-builder/admin/src/components/PopUpRelations/index.js b/packages/strapi-plugin-content-type-builder/admin/src/components/PopUpRelations/index.js index 2061ac395b..9e5dc0b2f0 100644 --- a/packages/strapi-plugin-content-type-builder/admin/src/components/PopUpRelations/index.js +++ b/packages/strapi-plugin-content-type-builder/admin/src/components/PopUpRelations/index.js @@ -313,8 +313,8 @@ class PopUpRelations extends React.Component { - - + {' '} diff --git a/packages/strapi-plugin-content-type-builder/admin/src/containers/Form/actions.js b/packages/strapi-plugin-content-type-builder/admin/src/containers/Form/actions.js index 31abc8121c..ab129cd686 100644 --- a/packages/strapi-plugin-content-type-builder/admin/src/containers/Form/actions.js +++ b/packages/strapi-plugin-content-type-builder/admin/src/containers/Form/actions.js @@ -240,7 +240,7 @@ function setAttributeFormData(hash) { const type = formType === 'number' ? 'integer' : formType; let defaultValue = type === 'number' ? 0 : ''; - if (type === 'checkbox') { + if (type === 'boolean') { defaultValue = false; } diff --git a/packages/strapi-plugin-content-type-builder/admin/src/containers/Form/forms.json b/packages/strapi-plugin-content-type-builder/admin/src/containers/Form/forms.json index 13c8467ec6..38d1808991 100644 --- a/packages/strapi-plugin-content-type-builder/admin/src/containers/Form/forms.json +++ b/packages/strapi-plugin-content-type-builder/admin/src/containers/Form/forms.json @@ -2,18 +2,6 @@ "contentType": { "baseSettings": { "items": [ - { - "label": { - "id": "content-type-builder.form.contentType.item.connections" - }, - "name": "connection", - "type": "select", - "value": "default", - "items": [{}], - "validations": { - "required": true - } - }, { "label": { "id": "content-type-builder.form.contentType.item.name" @@ -45,6 +33,18 @@ } } }, + { + "label": { + "id": "content-type-builder.form.contentType.item.connections" + }, + "name": "connection", + "type": "select", + "value": "default", + "items": [{}], + "validations": { + "required": true + } + }, { "label": { "id": "content-type-builder.form.contentType.item.description" @@ -926,7 +926,10 @@ }, "name": "name", "type": "string", - "value": "" + "value": "", + "validations": { + "required": true + } }, { "label": { diff --git a/packages/strapi-plugin-content-type-builder/admin/src/containers/Form/index.js b/packages/strapi-plugin-content-type-builder/admin/src/containers/Form/index.js index c0513dfa95..c27c7daafe 100644 --- a/packages/strapi-plugin-content-type-builder/admin/src/containers/Form/index.js +++ b/packages/strapi-plugin-content-type-builder/admin/src/containers/Form/index.js @@ -451,7 +451,7 @@ export class Form extends React.Component { // eslint-disable-line react/prefer- } } - handleSubmit = (e, redirectToChoose = false) => { + handleSubmit = (e, redirectToChoose = true) => { e.preventDefault(); const hashArray = split(this.props.hash, ('::')); const valueToReplace = includes(this.props.hash, '#create') ? '#create' : '#edit'; @@ -514,12 +514,9 @@ export class Form extends React.Component { // eslint-disable-line react/prefer- } renderModalBodyChooseAttributes = () => { - const attributesDisplay = forms.attributesDisplay.items; - - // Don't display the media field if the upload plugin isn't installed - if (!has(this.context.plugins.toJS(), 'upload')) { - attributesDisplay.splice(8, 1); - } + const attributesDisplay = has(this.context.plugins.toJS(), 'upload') + ? forms.attributesDisplay.items + : forms.attributesDisplay.items.filter(obj => obj.type !== 'media'); // Don't display the media field if the upload plugin isn't installed return ( map(attributesDisplay, (attribute, key) => ( diff --git a/packages/strapi-plugin-content-type-builder/admin/src/containers/ModelPage/index.js b/packages/strapi-plugin-content-type-builder/admin/src/containers/ModelPage/index.js index 07ea2d1af7..53cbb4c102 100644 --- a/packages/strapi-plugin-content-type-builder/admin/src/containers/ModelPage/index.js +++ b/packages/strapi-plugin-content-type-builder/admin/src/containers/ModelPage/index.js @@ -20,7 +20,7 @@ import { makeSelectContentTypeUpdated } from 'containers/Form/selectors'; import AttributeRow from 'components/AttributeRow'; import ContentHeader from 'components/ContentHeader'; -import EmptyAttributesView from 'components/EmptyAttributesView'; +import EmptyAttributesBlock from 'components/EmptyAttributesBlock'; import Form from 'containers/Form'; import List from 'components/List'; import PluginLeftMenu from 'components/PluginLeftMenu'; @@ -265,7 +265,7 @@ export class ModelPage extends React.Component { // eslint-disable-line react/pr const addButtons = get(storeData.getContentType(), 'name') === this.props.match.params.modelName && size(get(storeData.getContentType(), 'attributes')) > 0 || this.props.modelPage.showButtons; const contentHeaderDescription = this.props.modelPage.model.description || 'content-type-builder.modelPage.contentHeader.emptyDescription.description'; const content = size(this.props.modelPage.model.attributes) === 0 ? - : + : { @@ -47,11 +49,11 @@ module.exports = { strapi.reload.isWatching = false; - await Service.appearance(formatedAttributes, name, 'content-manager'); + await Service.appearance(formatedAttributes, name); await Service.generateAPI(name, description, connection, collectionName, []); - const modelFilePath = Service.getModelPath(name, plugin); + const modelFilePath = await Service.getModelPath(name, plugin); try { const modelJSON = _.cloneDeep(require(modelFilePath)); @@ -109,7 +111,7 @@ module.exports = { await Service.generateAPI(name, description, connection, collectionName, []); } - await Service.appearance(formatedAttributes, name, plugin ? plugin : 'content-manager'); + await Service.appearance(formatedAttributes, name, plugin); try { const modelJSON = _.cloneDeep(require(modelFilePath)); @@ -181,6 +183,18 @@ module.exports = { return ctx.badRequest(null, [{ messages: removeModelErrors }]); } + const pluginStore = strapi.store({ + environment: '', + type: 'plugin', + name: 'content-manager' + }); + + const schema = await pluginStore.get({ key: 'schema' }); + + delete schema.layout[model]; + + await pluginStore.set({ key: 'schema', value: schema }); + ctx.send({ ok: true }); strapi.reload(); @@ -188,7 +202,7 @@ module.exports = { autoReload: async ctx => { ctx.send({ - autoReload: _.get(strapi.config.environments, 'development.server.autoReload', false), + autoReload: _.get(strapi.config.currentEnvironment, 'server.autoReload', { enabled: false }) }); }, @@ -207,7 +221,7 @@ module.exports = { return ctx.badRequest(null, [{ messages: [{ id: 'Connection doesn\'t exist' }] }]); } - if (connector === 'strapi-bookshelf') { + if (connector === 'strapi-hook-bookshelf') { try { const tableExists = await strapi.connections[connection].schema.hasTable(model); diff --git a/packages/strapi-plugin-content-type-builder/package.json b/packages/strapi-plugin-content-type-builder/package.json index de7cfcdd2f..27f53ace7a 100755 --- a/packages/strapi-plugin-content-type-builder/package.json +++ b/packages/strapi-plugin-content-type-builder/package.json @@ -1,6 +1,6 @@ { "name": "strapi-plugin-content-type-builder", - "version": "3.0.0-alpha.12.7.1", + "version": "3.0.0-alpha.14", "description": "Strapi plugin to create content type (API).", "strapi": { "name": "Content Type Builder", @@ -22,12 +22,13 @@ "prepublishOnly": "IS_MONOREPO=true npm run build" }, "dependencies": { + "immutable": "^3.8.2", "pluralize": "^7.0.0", - "strapi-generate": "3.0.0-alpha.12.7.1", - "strapi-generate-api": "3.0.0-alpha.12.7.1" + "strapi-generate": "3.0.0-alpha.14", + "strapi-generate-api": "3.0.0-alpha.14" }, "devDependencies": { - "strapi-helper-plugin": "3.0.0-alpha.12.7.1" + "strapi-helper-plugin": "3.0.0-alpha.14" }, "author": { "name": "Strapi team", diff --git a/packages/strapi-plugin-content-type-builder/services/ContentTypeBuilder.js b/packages/strapi-plugin-content-type-builder/services/ContentTypeBuilder.js index eebcc5d686..7070ab93fd 100755 --- a/packages/strapi-plugin-content-type-builder/services/ContentTypeBuilder.js +++ b/packages/strapi-plugin-content-type-builder/services/ContentTypeBuilder.js @@ -4,30 +4,119 @@ const path = require('path'); const fs = require('fs'); const _ = require('lodash'); const generator = require('strapi-generate'); +const { fromJS } = require('immutable'); +const Manager = require('../utils/Manager.js'); +const { + createManager, + removeColsLine, + reorderList, +} = require('../utils/helpers.js'); module.exports = { - appearance: (attributes, model, source) => { - const layoutPath = path.join(strapi.config.appPath, 'plugins', source, 'config', 'layout.json'); - let layout; + appearance: async (attributes, model, plugin) => { + const pluginStore = strapi.store({ + environment: '', + type: 'plugin', + name: 'content-manager' + }); - try { - // NOTE: do we really need to parse the JSON? - // layout = JSON.parse(layoutPath, 'utf8'); - layout = require(layoutPath); - } catch (err) { - layout = {}; + const schema = await pluginStore.get({ key: 'schema' }); + const layout = _.get(schema.layout, model, {}); + + // If updating a content-type + if (!_.isEmpty(layout)) { + const state = fromJS({ + schema: fromJS(schema), + }); + const schemaPath = plugin ? ['models', 'plugins', plugin, model] : ['models', model]; + const keys = plugin ? `plugins.${plugin}.${model}.editDisplay` : `${model}.editDisplay`; + const prevList = state.getIn(['schema', ...schemaPath, 'editDisplay', 'fields']); + const prevFields = Object.keys(state.getIn(['schema', ...schemaPath, 'editDisplay', 'availableFields']).toJS()); + const currentFields = Object.keys(attributes); + const fieldsToRemove = _.difference(prevFields, currentFields); + let newList = prevList; + + fieldsToRemove.forEach((field) => { + const index = newList.indexOf(field); + const manager = new Manager(state, prevList, keys, index, fromJS(layout.attributes || {})); + const attrToRemoveInfos = manager.attrToRemoveInfos; // Retrieve the removed item infos + const arrayOfLastLineElements = manager.arrayOfEndLineElements; + const isRemovingAFullWidthNode = attrToRemoveInfos.bootstrapCol === 12; + + if (isRemovingAFullWidthNode) { + const currentNodeLine = _.findIndex(arrayOfLastLineElements, ['index', attrToRemoveInfos.index]); // Used only to know if removing a full size element on the first line + if (currentNodeLine === 0) { + newList = newList + .delete(index); + } else { + const previousNodeLine = currentNodeLine - 1; + const firstElementOnLine = previousNodeLine === 0 ? 0 : arrayOfLastLineElements[previousNodeLine - 1].index + 1; + const lastElementOnLine = arrayOfLastLineElements[previousNodeLine].index + 1; + const previousLineRangeIndexes = firstElementOnLine === lastElementOnLine ? [firstElementOnLine] : _.range(firstElementOnLine, lastElementOnLine); + const elementsOnLine = manager.getElementsOnALine(previousLineRangeIndexes); + const previousLineColNumber = manager.getLineSize(elementsOnLine); + + if (previousLineColNumber >= 10) { + newList = newList + .delete(index); + } else { + const colNumberToAdd = 12 - previousLineColNumber; + const colsToAdd = manager.getColsToAdd(colNumberToAdd); + newList = newList + .delete(index) + .insert(index, colsToAdd[0]); + + if (colsToAdd.length > 1) { + newList = newList + .insert(index, colsToAdd[1]); + } + } + } + } else { + const nodeBounds = { left: manager.getBound(false), right: manager.getBound(true) }; // Retrieve the removed element's bounds + const leftBoundIndex = _.get(nodeBounds, ['left', 'index'], 0) + 1; + const rightBoundIndex = _.get(nodeBounds, ['right', 'index'], prevList.size -1); + const elementsOnLine = manager.getElementsOnALine(_.range(leftBoundIndex - 1, rightBoundIndex + 1)); + const currentLineColSize = manager.getLineSize(elementsOnLine); + const isRemovingLine = currentLineColSize - attrToRemoveInfos.bootstrapCol === 0; + + if (isRemovingLine) { + newList = newList + .delete(attrToRemoveInfos.index); + } else { + const random = Math.random().toString(36).substring(7); + newList = newList + .delete(attrToRemoveInfos.index) + .insert(rightBoundIndex, `__col-md-${attrToRemoveInfos.bootstrapCol}__${random}`); + } + } + + const newManager = createManager(state, newList, keys, 0, fromJS(layout.attributes)); + newList = removeColsLine(newManager, newList); + const lastManager = createManager(state, newList, keys, 0, fromJS(layout.attributes)); + newList = reorderList(lastManager, lastManager.getLayout()); + }); + + // Delete them from the available fields + fieldsToRemove.forEach(field => { + _.unset(schema, [...schemaPath, 'editDisplay', 'availableFields', field]); + }); + + _.set(schema, [...schemaPath, 'editDisplay', 'fields'], newList.toJS()); } Object.keys(attributes).map(attribute => { const appearances = _.get(attributes, [attribute, 'appearance'], {}); Object.keys(appearances).map(appearance => { - _.set(layout, [model, 'attributes', attribute, 'appearance'], appearances[appearance] ? appearance : '' ); + _.set(layout, ['attributes', attribute, 'appearance'], appearances[appearance] ? appearance : '' ); }); _.unset(attributes, [attribute, 'appearance']); }); - fs.writeFileSync(layoutPath, JSON.stringify(layout, null, 2), 'utf8'); + schema.layout[model] = layout; + + await pluginStore.set({ key: 'schema', value: schema }); }, getModels: () => { @@ -67,11 +156,19 @@ module.exports = { return models.concat(pluginModels); }, - getModel: (name, source) => { + getModel: async (name, source) => { name = _.toLower(name); const model = source ? _.get(strapi.plugins, [source, 'models', name]) : _.get(strapi.models, name); + const pluginStore = strapi.store({ + environment: '', + type: 'plugin', + name: 'content-manager' + }); + + const schema = await pluginStore.get({ key: 'schema' }); + const attributes = []; _.forEach(model.attributes, (params, attr) => { const relation = _.find(model.associations, { alias: attr }); @@ -80,7 +177,8 @@ module.exports = { if (params.plugin === 'upload' && relation.model || relation.collection === 'file') { params = { type: 'media', - multiple: params.collection ? true : false + multiple: params.collection ? true : false, + required: params.required }; } else { params = _.omit(params, ['collection', 'model', 'via']); @@ -91,7 +189,7 @@ module.exports = { } } - const appearance = _.get(strapi.plugins, [source || 'content-manager', 'config', 'layout', name, 'attributes', attr, 'appearance']); + const appearance = _.get(schema, ['layout', name, 'attributes', attr, 'appearance']); if (appearance) { _.set(params, ['appearance', appearance], true); } @@ -117,7 +215,7 @@ module.exports = { }, generateAPI: (name, description, connection, collectionName, attributes) => { - const template = _.get(strapi.config.currentEnvironment, `database.connections.${connection}.connector`, 'strapi-mongoose').split('-')[1]; + const template = _.get(strapi.config.currentEnvironment, `database.connections.${connection}.connector`, 'strapi-hook-mongoose').split('-')[2]; return new Promise((resolve, reject) => { const scope = { @@ -190,7 +288,8 @@ module.exports = { attrs[attribute.name] = { [attribute.params.multiple ? 'collection' : 'model']: 'file', via, - plugin: 'upload' + plugin: 'upload', + required: attribute.params.required === true ? true : false }; } } else if (_.has(attribute, 'params.target')) { diff --git a/packages/strapi-plugin-content-type-builder/utils/Manager.js b/packages/strapi-plugin-content-type-builder/utils/Manager.js new file mode 100644 index 0000000000..662d0e1d85 --- /dev/null +++ b/packages/strapi-plugin-content-type-builder/utils/Manager.js @@ -0,0 +1,253 @@ +// This file contains all the methods required to get the number of inputs +// that will be displayed in the content manager edit view. +// Since we want to keep the shape of the layout when we remove a field from +// the content type builder form builder we duplicated this file already used in the content manager. +const { findIndex, pullAt, range } = require('lodash'); +const { List } = require('immutable'); + +class Manager { + constructor(state, list, keys, index, layout) { + this.state = state; + this.keys = keys.split('.'); + this.layout = layout; + this.list = list; + this.index = index; + this.arrayOfEndLineElements = this.getLinesBound(); + this.attrToRemoveInfos = this.attrToRemoveInfos(); + } + + /** + * Retrieve the bootstrap col index, name and type of a field + * @param {Number} index + * @returns {Object} + */ + getAttrInfos(index) { + const name = this.getAttrName(index); + const appearance = this.layout.getIn([name, 'appearance']); + const type = appearance !== '' && appearance !== undefined ? appearance : this.getType(name); + const bootstrapCol = this.getBootStrapCol(type); + + const infos = { + bootstrapCol, + index, + name, + type, + }; + + return infos; + } + + /** + * Returns the number of divs to add to a row so each row is complete. + * @param {Number} number + * @returns {Array} Array of bootstrap cols to add to make a row of size 12. + */ + getColsToAdd(number) { + let ret; + + switch(number) { + case 12: + ret = []; + break; + case 9: + ret = ['__col-md-3__', '__col-md-6__']; + break; + case 8: + ret = ['__col-md-4__', '__col-md-4__']; + break; + case 4: + ret = ['__col-md-4__']; + break; + case 6: + ret = ['__col-md-6__']; + break; + default: + ret = ['__col-md-3__']; + } + const random = Math.floor(Math.random() * 1000); + const random1 = Math.floor(Math.random() * 1000); + + return ret.map((v, i) => { + + if (i === 0) { + return `${v}${random}`; + } + + return `${v}${random1}`; + }); + } + + /** + * Retrieve a field default bootstrap col + * NOTE: will change if we add the customisation of an input's width + * @param {String} type + * @returns {Number} + */ + getBootStrapCol(type) { + switch(type) { + case 'checkbox': + case 'boolean': + case 'date': + case 'bigint': + case 'decimal': + case 'float': + case 'integer': + case 'number': + return 4; + case 'json': + case 'wysiwyg': + case 'WYSIWYG': + return 12; + default: + return 6; + } + } + + getElementsOnALine(itemsToPull, arr = this.list) { + const array = List.isList(arr) ? arr.toJS() : arr; + + return pullAt(array, itemsToPull); + } + + /** + * Retrieve the field to remove infos + * @returns {Object} + */ + attrToRemoveInfos() { + return this.getAttrInfos(this.index); + } + + /** + * + * Retrieve the last element of each bootstrap line + * @returns {Array} + */ + getLinesBound() { // NOTE: doesn't work for the last element if the line is not full! + const array = []; + let sum = 0; + + this.list.forEach((item, i) => { + let { bootstrapCol, index, name, type } = this.getAttrInfos(i); + + if (!type && name.includes('__col')) { + bootstrapCol = parseInt(name.split('__')[1].split('-')[2], 10); + } + + sum += bootstrapCol; + + if (sum === 12 || bootstrapCol === 12) { + const isFullSize = bootstrapCol === 12; + array.push({ name, index, isFullSize }); + sum = 0; + } + + if (sum > 12) { + sum = 0; + } + + if (i < this.list.size - 1) { + let { bootstrapCol: nextBootstrapCol, name: nextName, type: nextType } = this.getAttrInfos(i + 1); + + if (!nextType && nextName.includes('__col')) { + nextBootstrapCol = parseInt(nextName.split('__')[1].split('-')[2], 10); + } + + if (sum + nextBootstrapCol > 12) { + const isFullSize = bootstrapCol === 12; + array.push({ name, index, isFullSize }); + sum = 0; + } + } + }); + + return array; + } + + /** + * + * Retrieve the field's type depending on its name + * @param {String} itemName + * @returns {String} + */ + getType(itemName) { + return this.state + .getIn(['schema', 'models', ...this.keys, 'availableFields', itemName, 'type']); + } + + /** + * Retrieve a field name depending on its index + * @param {Number} itemIndex + * @returns {String} + */ + getAttrName(itemIndex){ + return this.state + .getIn(['schema', 'models', ...this.keys, 'fields', itemIndex]); + } + + /** + * Retrieve the line bootstrap col sum + * @param {Number} leftBound + * @param {Number} rightBound + * @returns {Number} + */ + + getLineSize(elements) { + return elements.reduce((acc, current) => { + const appearance = this.layout.getIn([current, 'appearance']); + const type = appearance !== '' && appearance !== undefined ? appearance : this.getType(current); + const col = current.includes('__col') ? parseInt(current.split('__')[1].split('-')[2], 10) : this.getBootStrapCol(type); + + return acc += col; + }, 0); + } + + /** + * + * @param {Bool} dir sup or min + * @param {Number} pivot the center + * @returns {Object} the first sup or last sup + */ + getBound(dir, pivot = this.index) { + let result = {}; + let hasResult = false; + + this.arrayOfEndLineElements.forEach(item => { + const rightBondCond = findIndex(this.arrayOfEndLineElements, ['index', pivot]) !== -1 ? item.index < pivot : item.index <= pivot; + const cond = dir === true ? item.index >= pivot && !hasResult : rightBondCond; + + if (cond) { + hasResult = true; + result = dir === true ? item : { name: this.list.get(item.index + 1), index: item.index + 1, isFullSize: false }; + } + }); + + return result; + } + + getLayout() { + let newList = this.list; + let sum = 0; + + this.arrayOfEndLineElements.forEach((item, i) => { + const firstLineItem = i === 0 ? 0 : this.arrayOfEndLineElements[i - 1].index +1; + const lastLineItem = item.index + 1; + const lineRange = firstLineItem === lastLineItem ? [firstLineItem] : range(firstLineItem, lastLineItem); + const lineItems = this.getElementsOnALine(lineRange); + const lineSize = this.getLineSize(lineItems); + + if (lineSize < 10 && i < this.arrayOfEndLineElements.length - 1) { + const colsToAdd = this.getColsToAdd(12 - lineSize); + newList = newList.insert(lastLineItem + sum, colsToAdd[0]); + + if (colsToAdd.length > 1) { + newList = newList.insert(lastLineItem + sum, colsToAdd[1]); + } + sum += 1; + } + }); + + return newList; + } +} + +module.exports = Manager; \ No newline at end of file diff --git a/packages/strapi-plugin-content-type-builder/utils/helpers.js b/packages/strapi-plugin-content-type-builder/utils/helpers.js new file mode 100644 index 0000000000..023dc247f5 --- /dev/null +++ b/packages/strapi-plugin-content-type-builder/utils/helpers.js @@ -0,0 +1,70 @@ +const { List } = require('immutable'); +const { flattenDeep, get, range } = require('lodash'); +const Manager = require('./Manager'); + +const stateUpdater = (obj, array, keys) => obj.updateIn(['modifiedSchema', 'models', ...keys.split('.'), 'fields'], () => array); +const createManager = (obj, array, keys, dropIndex, layout) => new Manager(stateUpdater(obj, array, keys), array, keys, dropIndex, layout); +const getElementsOnALine = (manager, line, list) => { + const firstElIndex = line === 0 ? 0 : manager.arrayOfEndLineElements[line - 1].index + 1; + const lastElIndex = get(manager.arrayOfEndLineElements[line], 'index', list.size -1) + 1; + const elements = manager.getElementsOnALine(range(firstElIndex, lastElIndex)); + + return { elements, lastElIndex }; +}; +const createArrayOfLastEls = (manager, list) => { + const { name, index, bootstrapCol } = manager.getAttrInfos(list.size - 1); + const isFullSize = bootstrapCol === 12; + + return manager.arrayOfEndLineElements.concat({ name, index, isFullSize }); +}; +const removeColsLine = (manager, list) => { + let addedElsToRemove = []; + const arrayOfEndLineElements = createArrayOfLastEls(manager, list); + + arrayOfEndLineElements.forEach((item, i) => { + if (i < arrayOfEndLineElements.length) { + const firstElementOnLine = i === 0 ? 0 : arrayOfEndLineElements[i - 1].index + 1; + const lastElementOnLine = arrayOfEndLineElements[i].index; + const rangeIndex = range(firstElementOnLine, lastElementOnLine + 1); + const elementsOnLine = manager.getElementsOnALine(rangeIndex) + .filter(name => !name.includes('__col')); + + if (elementsOnLine.length === 0) { + addedElsToRemove = addedElsToRemove.concat(rangeIndex); + } + } + }); + + return list.filter((item, index) => { + const indexToKeep = addedElsToRemove.indexOf(index) === -1; + + return indexToKeep; + }); +}; +const reorderList = (manager, list) => { + const array = createArrayOfLastEls(manager, list); + const lines = []; + + array.forEach((item, i) => { + const { elements } = getElementsOnALine(manager, i, list); + lines.push(elements); + }); + + const reordered = lines + .reduce((acc, curr) => { + const line = curr.sort((a) => a.includes('__col-md')); + + return acc.concat(line); + }, []) + .filter(a => a !== undefined); + + return List(flattenDeep(reordered)); +}; + +module.exports = { + createArrayOfLastEls, + createManager, + getElementsOnALine, + removeColsLine, + reorderList, +}; \ No newline at end of file diff --git a/packages/strapi-plugin-email/admin/src/injectedComponents.js b/packages/strapi-plugin-email/admin/src/injectedComponents.js new file mode 100644 index 0000000000..109fa8b38c --- /dev/null +++ b/packages/strapi-plugin-email/admin/src/injectedComponents.js @@ -0,0 +1 @@ +export default []; \ No newline at end of file diff --git a/packages/strapi-plugin-email/admin/src/translations/de.json b/packages/strapi-plugin-email/admin/src/translations/de.json index 9b1be4bf5e..e7519e0639 100755 --- a/packages/strapi-plugin-email/admin/src/translations/de.json +++ b/packages/strapi-plugin-email/admin/src/translations/de.json @@ -1,4 +1,14 @@ { + "ConfigPage.title": "E-Mail - Einstellungen", + "ConfigPage.description": "E-Mail-Plugin konfigurieren", + + "EditForm.Input.number.label": "Maximal zulässige Größe (in MB)", + "EditForm.Input.select.label": "Anbieter", + "EditForm.Input.select.inputDescription": "E-Mails können mit dem Standardanbieter (Sendmail) oder einem externen Anbieter versendet werden", + "EditForm.Input.toggle.label": "E-Mail-Versand aktivieren", + + "notification.config.success": "Die Einstellungen wurden aktualisiert", + "plugin.description.short": "Zum Versand von E-Mails.", "plugin.description.long": "Zum Versand von E-Mails." } diff --git a/packages/strapi-plugin-email/admin/src/translations/es.json b/packages/strapi-plugin-email/admin/src/translations/es.json new file mode 100755 index 0000000000..0c84804ae1 --- /dev/null +++ b/packages/strapi-plugin-email/admin/src/translations/es.json @@ -0,0 +1,14 @@ +{ + "ConfigPage.title": "Correo electrónico - Configuración", + "ConfigPage.description": "Configurar el plugin de correo electrónico", + + "EditForm.Input.number.label": "Tamaño máximo permitido (en MB)", + "EditForm.Input.select.label": "Proveedores", + "EditForm.Input.select.inputDescription": "Los correos electrónicos se pueden enviar con el proveedor predeterminado (Sendmail) o con un proveedor externo.", + "EditForm.Input.toggle.label": "Habilitar envío de correo electrónico", + + "plugin.description.short": "Enviar correos electrónicos.", + "plugin.description.long": "Enviar correos electrónicos.", + + "notification.config.success": "Se ha actualizado la configuración" +} diff --git a/packages/strapi-plugin-email/admin/src/translations/fr.json b/packages/strapi-plugin-email/admin/src/translations/fr.json index f886d98035..667c832b0c 100755 --- a/packages/strapi-plugin-email/admin/src/translations/fr.json +++ b/packages/strapi-plugin-email/admin/src/translations/fr.json @@ -1,4 +1,14 @@ { + "ConfigPage.title": "E-mail - Paramètres", + "ConfigPage.description": "Configurer le plugin email", + + "EditForm.Input.number.label": "Taille maximale autorisée (en MB)", + "EditForm.Input.select.label": "Fournisseurs", + "EditForm.Input.select.inputDescription": "Les e-mails peuvent être envoyés avec le fournisseur par défaut (Sendmail) ou un fournisseur externe.", + "EditForm.Input.toggle.label": "Activer l'envoi de e-mails", + + "notification.config.success": "Les paramètres ont été mis à jour.", + "plugin.description.short": "Envoyez des emails", "plugin.description.long": "Envoyez des emails" } diff --git a/packages/strapi-plugin-email/admin/src/translations/nl.json b/packages/strapi-plugin-email/admin/src/translations/nl.json new file mode 100644 index 0000000000..04d9ef7239 --- /dev/null +++ b/packages/strapi-plugin-email/admin/src/translations/nl.json @@ -0,0 +1,14 @@ +{ + "ConfigPage.title": "E-mail - Instellingen", + "ConfigPage.description": "Configureer de e-mail extensie", + + "EditForm.Input.number.label": "Maximum grootte toegestaan (in MB)", + "EditForm.Input.select.label": "Leveranciers", + "EditForm.Input.select.inputDescription": "E-mails kunnen worden verstuurd via de standaard leverancier (Sendmail) of via een externe leverancier", + "EditForm.Input.toggle.label": "E-mail versturen inschakelen", + + "plugin.description.short": "Verstuur e-mails.", + "plugin.description.long": "Verstuur e-mails.", + + "notification.config.success": "De instellingen zijn opgeslagen." +} diff --git a/packages/strapi-plugin-email/admin/src/translations/ru.json b/packages/strapi-plugin-email/admin/src/translations/ru.json index b2d52f90f8..8733143ea1 100644 --- a/packages/strapi-plugin-email/admin/src/translations/ru.json +++ b/packages/strapi-plugin-email/admin/src/translations/ru.json @@ -1,5 +1,12 @@ { "plugin.description.short": "Отсылка почты.", - "plugin.description.long": "Отсылка почты." + "plugin.description.long": "Отсылка почты.", + "ConfigPage.title": "Email - Настройки", + "ConfigPage.description": "Настройка плагина email", + "EditForm.Input.number.label": "Максимально допустимый размер (в МБ)", + "EditForm.Input.select.label": "Провайдеры", + "EditForm.Input.select.inputDescription": "Письма могут быть отправлены стандартным провайдером (Sendmail), или внешними провайдерами", + "EditForm.Input.toggle.label": "Активировать отправку писем", + "notification.config.success": "Настройки успешно обновлены" } \ No newline at end of file diff --git a/packages/strapi-plugin-email/package.json b/packages/strapi-plugin-email/package.json index b2a931c5c0..6d59428166 100644 --- a/packages/strapi-plugin-email/package.json +++ b/packages/strapi-plugin-email/package.json @@ -1,6 +1,6 @@ { "name": "strapi-plugin-email", - "version": "3.0.0-alpha.12.7.1", + "version": "3.0.0-alpha.14", "description": "This is the description of the plugin.", "strapi": { "name": "Email", @@ -22,11 +22,11 @@ "prepublishOnly": "IS_MONOREPO=true npm run build" }, "dependencies": { - "strapi-email-sendmail": "3.0.0-alpha.12.7.1" + "strapi-email-sendmail": "3.0.0-alpha.14" }, "devDependencies": { "react-copy-to-clipboard": "5.0.1", - "strapi-helper-plugin": "3.0.0-alpha.12.7.1" + "strapi-helper-plugin": "3.0.0-alpha.14" }, "author": { "name": "Strapi team", diff --git a/packages/strapi-plugin-email/services/Email.js b/packages/strapi-plugin-email/services/Email.js index b6e3796dd6..52fe32461a 100644 --- a/packages/strapi-plugin-email/services/Email.js +++ b/packages/strapi-plugin-email/services/Email.js @@ -10,20 +10,20 @@ const _ = require('lodash'); const createDefaultEnvConfig = async (env) => { const pluginStore = strapi.store({ - environment: env, - type: 'plugin', + environment: env, + type: 'plugin', name: 'email' }); - const provider = _.find(strapi.plugins.email.config.providers, {provider: 'sendmail'}); - const value = _.assign({}, provider, {}); - - await pluginStore.set({key: 'provider', value}); + const provider = _.find(strapi.plugins.email.config.providers, {provider: 'sendmail'}); + const value = _.assign({}, provider, {}); + + await pluginStore.set({key: 'provider', value}); return await strapi.store({ - environment: env, - type: 'plugin', + environment: env, + type: 'plugin', name: 'email' - }).get({key: 'provider'}); + }).get({key: 'provider'}); }; const getProviderConfig = async (env) => { @@ -34,18 +34,18 @@ const getProviderConfig = async (env) => { }).get({key: 'provider'}); if(!config) { - config = await createDefaultEnvConfig(env); + config = await createDefaultEnvConfig(env); } - return config; + return config; }; module.exports = { - getProviderConfig, + getProviderConfig, send: async (options, config, cb) => { // Get email provider settings to configure the provider to use. if(!config) { - config = await getProviderConfig(strapi.config.environment); + config = await getProviderConfig(strapi.config.environment); } const provider = _.find(strapi.plugins.email.config.providers, { provider: config.provider }); @@ -57,6 +57,6 @@ module.exports = { const actions = provider.init(config); // Execute email function of the provider for all files. - return actions.send(options, cb); + return actions.send(options, cb); } }; diff --git a/packages/strapi-plugin-graphql/.gitignore b/packages/strapi-plugin-graphql/.gitignore index 74c9f37854..23ece8b857 100644 --- a/packages/strapi-plugin-graphql/.gitignore +++ b/packages/strapi-plugin-graphql/.gitignore @@ -3,6 +3,7 @@ coverage node_modules stats.json package-lock.json +yarn.lock # Cruft .DS_Store diff --git a/packages/strapi-plugin-graphql/package.json b/packages/strapi-plugin-graphql/package.json index 17dd3cfbc8..afd53e19a3 100644 --- a/packages/strapi-plugin-graphql/package.json +++ b/packages/strapi-plugin-graphql/package.json @@ -1,6 +1,6 @@ { "name": "strapi-plugin-graphql", - "version": "3.0.0-alpha.12.7.1", + "version": "3.0.0-alpha.14", "description": "This is the description of the plugin.", "strapi": { "name": "graphql", @@ -27,9 +27,10 @@ "graphql-depth-limit": "^1.1.0", "graphql-playground-middleware-koa": "^1.6.1", "graphql-tools": "^2.23.1", - "graphql-type-json": "^0.2.0", + "graphql-type-json": "^0.2.1", + "graphql-type-datetime": "^0.2.2", "pluralize": "^7.0.0", - "strapi-utils": "3.0.0-alpha.12.7.1" + "strapi-utils": "3.0.0-alpha.14" }, "author": { "name": "A Strapi developer", diff --git a/packages/strapi-plugin-graphql/services/GraphQL.js b/packages/strapi-plugin-graphql/services/GraphQL.js index 29f646ca2a..6e35f50a49 100644 --- a/packages/strapi-plugin-graphql/services/GraphQL.js +++ b/packages/strapi-plugin-graphql/services/GraphQL.js @@ -14,9 +14,449 @@ const pluralize = require('pluralize'); const graphql = require('graphql'); const { makeExecutableSchema } = require('graphql-tools'); const GraphQLJSON = require('graphql-type-json'); +const GraphQLDateTime = require('graphql-type-datetime'); const policyUtils = require('strapi-utils').policy; module.exports = { + /** + * Returns all fields of type primitive + * + * @returns {Boolean} + */ + isPrimitiveType: (_type) => { + const type = _type.replace('!', ''); + return ( + type === 'Int' || + type === 'Float' || + type === 'String' || + type === 'Boolean' || + type === 'DateTime' || + type === 'JSON' + ); + }, + + /** + * Checks if the field is of type enum + * + * @returns {Boolean} + */ + isEnumType: (type) => { + return type === 'enumeration'; + }, + + /** + * Returns all fields that are not of type array + * + * @returns {Boolean} + * + * @example + * + * isNotOfTypeArray([String]) + * // => false + * isNotOfTypeArray(String!) + * // => true + */ + isNotOfTypeArray: (type) => { + return !/(\[\w+!?\])/.test(type); + }, + + /** + * Returns all fields of type Integer or float + */ + isNumberType: (type) => { + return type === 'Int' || type === 'Float'; + }, + + /** + * Convert non-primitive type to string (non-primitive types corresponds to a reference to an other model) + * + * @returns {String} + * + * @example + * + * extractType(String!) + * // => String + * + * extractType(user) + * // => ID + * + * extractType(ENUM_TEST_FIELD, enumeration) + * // => String + * + */ + extractType: function (_type, attributeType) { + return this.isPrimitiveType(_type) + ? _type.replace('!', '') + : this.isEnumType(attributeType) + ? 'String' + : 'ID'; + }, + + /** + * Returns a list of fields that have type included in fieldTypes. + */ + getFieldsByTypes: (fields, typeCheck, returnType) => { + return _.reduce(fields, (acc, fieldType, fieldName) => { + if (typeCheck(fieldType)) { + acc[fieldName] = returnType(fieldType, fieldName); + } + return acc; + }, {}); + }, + + /** + * Use the field resolver otherwise fall through the field value + * + * @returns {function} + */ + fieldResolver: (field, key) => { + return (object) => { + const resolver = field.resolve || function resolver(obj, options, context) { // eslint-disable-line no-unused-vars + return obj[key]; + }; + return resolver(object); + }; + }, + + /** + * Create fields resolvers + * + * @return {Object} + */ + createFieldsResolver: function(fields, resolver, typeCheck) { + return Object.keys(fields).reduce((acc, fieldKey) => { + const field = fields[fieldKey]; + // Check if the field is of the correct type + if (typeCheck(field)) { + return _.set(acc, fieldKey, (obj, options, context) => { + return resolver(obj, options, context, this.fieldResolver(field, fieldKey), fieldKey, obj, field); + }); + } + return acc; + }, {}); + }, + + /** + * Build the mongoose aggregator by applying the filters + */ + getModelAggregator: function (model, filters = {}) { + const aggregation = model.aggregate(); + if (!_.isEmpty(filters.where)) { + aggregation.match(filters.where); + } + if (filters.limit) { + aggregation.limit(filters.limit); + } + return aggregation; + }, + + /** + * Create the resolvers for each aggregation field + * + * @return {Object} + * + * @example + * + * const model = // Strapi model + * + * const fields = { + * username: String, + * age: Int, + * } + * + * const typeCheck = (type) => type === 'Int' || type === 'Float', + * + * const fieldsResoler = createAggregationFieldsResolver(model, fields, 'sum', typeCheck); + * + * // => { + * age: function ageResolver() { .... } + * } + */ + createAggregationFieldsResolver: function (model, fields, operation, typeCheck) { + return this.createFieldsResolver(fields, async (obj, options, context, fieldResolver, fieldKey) => { // eslint-disable-line no-unused-vars + const result = await this.getModelAggregator(model, obj).group({ + _id: null, + [fieldKey]: { [`$${operation}`]: `$${fieldKey}` } + }); + return _.get(result, `0.${fieldKey}`); + }, typeCheck); + }, + + /** + * Correctly format the data returned by the group by + */ + preProcessGroupByData: function ({ result, fieldKey, filters, modelName }) { + const _result = _.toArray(result); + return _.map(_result, (value) => { + const params = Object.assign( + {}, + this.convertToParams(_.omit(filters, 'where')), + filters.where, + { + [fieldKey]: value._id, + } + ); + + return { + key: value._id, + connection: strapi.utils.models.convertParams(modelName, params), + }; + }); + }, + + /** + * Create the resolvers for each group by field + * + * @return {Object} + * + * @example + * + * const model = // Strapi model + * const fields = { + * username: [UserConnectionUsername], + * email: [UserConnectionEmail], + * } + * const fieldsResoler = createGroupByFieldsResolver(model, fields); + * + * // => { + * username: function usernameResolver() { .... } + * email: function emailResolver() { .... } + * } + */ + createGroupByFieldsResolver: function (model, fields, name) { + return this.createFieldsResolver(fields, async (obj, options, context, fieldResolver, fieldKey) => { + const result = await this.getModelAggregator(model, obj).group({ + _id: `$${fieldKey}`, + }); + + return this.preProcessGroupByData({ + result, + fieldKey, + filters: obj, + modelName: name, + }); + }, () => true); + }, + + /** + * This method is the entry point to the GraphQL's Aggregation. + * It takes as param the model and its fields and it'll create the aggregation types and resolver to it + * Example: + * type User { + * username: String, + * age: Int, + * } + * + * It'll create + * type UserConnection { + * values: [User], + * groupBy: UserGroupBy, + * aggreate: UserAggregate + * } + * + * type UserAggregate { + * count: Int + * sum: UserAggregateSum + * avg: UserAggregateAvg + * } + * + * type UserAggregateSum { + * age: Float + * } + * + * type UserAggregateAvg { + * age: Float + * } + * + * type UserGroupBy { + * username: [UserConnectionUsername] + * age: [UserConnectionAge] + * } + * + * type UserConnectionUsername { + * key: String + * connection: UserConnection + * } + * + * type UserConnectionAge { + * key: Int + * connection: UserConnection + * } + * + */ + formatModelConnectionsGQL: function(fields, model, name, modelResolver) { + const { globalId } = model; + + const connectionGlobalId = `${globalId}Connection`; + const aggregatorFormat = this.formatConnectionAggregator(fields, model, name); + const groupByFormat = this.formatConnectionGroupBy(fields, model, name); + const connectionFields = { + values: `[${globalId}]`, + groupBy: `${globalId}GroupBy`, + aggregate: `${globalId}Aggregator`, + }; + + let modelConnectionTypes = `type ${connectionGlobalId} {${this.formatGQL(connectionFields)}}\n\n`; + if (aggregatorFormat) { + modelConnectionTypes += aggregatorFormat.type; + } + modelConnectionTypes += groupByFormat.type; + + return { + globalId: connectionGlobalId, + type: modelConnectionTypes, + query: { + [`${pluralize.plural(name)}Connection(sort: String, limit: Int, start: Int, where: JSON)`]: connectionGlobalId, + }, + resolver: { + Query: { + [`${pluralize.plural(name)}Connection`]: (obj, options, context) => { // eslint-disable-line no-unused-vars + const params = Object.assign( + {}, + this.convertToParams(_.omit(options, 'where')), + options.where + ); + return strapi.utils.models.convertParams(name, params); + } + }, + [connectionGlobalId]: { + values: (obj, option, context) => { + // Object here contains the key/value of the field that has been grouped-by + // for instance obj = { where: { country: 'USA' } } so the values here needs to be filtered according to the parent value + return modelResolver(obj, obj, context); + }, + groupBy: (obj, option, context) => { // eslint-disable-line no-unused-vars + // There is noting to resolve here, it's the aggregation resolver that will take care of it + return obj; + }, + aggregate: (obj, option, context) => { // eslint-disable-line no-unused-vars + // There is noting to resolve here, it's the aggregation resolver that will take care of it + return obj; + }, + }, + ...aggregatorFormat.resolver, + ...groupByFormat.resolver, + }, + }; + }, + + /** + * Generate the connection type of each non-array field of the model + * + * @return {String} + */ + generateConnectionFieldsTypes: function (fields, model) { + const { globalId, attributes } = model; + const primitiveFields = this.getFieldsByTypes( + fields, + this.isNotOfTypeArray, + (type, name) => this.extractType(type, (attributes[name] || {}).type), + ); + + const connectionFields = _.mapValues(primitiveFields, (fieldType) => ({ + key: fieldType, + connection: `${globalId}Connection`, + })); + + return Object.keys(primitiveFields).map((fieldKey) => + `type ${globalId}Connection${_.upperFirst(fieldKey)} {${this.formatGQL(connectionFields[fieldKey])}}` + ).join('\n\n'); + }, + + formatConnectionGroupBy: function(fields, model, name) { + const { globalId } = model; + const groupByGlobalId = `${globalId}GroupBy`; + + // Extract all primitive fields and change their types + const groupByFields = this.getFieldsByTypes( + fields, + this.isNotOfTypeArray, + (fieldType, fieldName) => `[${globalId}Connection${_.upperFirst(fieldName)}]`, + ); + + // Get the generated field types + let groupByTypes = `type ${groupByGlobalId} {${this.formatGQL(groupByFields)}}\n\n`; + groupByTypes += this.generateConnectionFieldsTypes(fields, model); + + return { + globalId: groupByGlobalId, + type: groupByTypes, + resolver: { + [groupByGlobalId]: this.createGroupByFieldsResolver(model, groupByFields, name), + } + }; + }, + + formatConnectionAggregator: function(fields, model) { + const { globalId } = model; + + // Extract all fields of type Integer and Float and change their type to Float + const numericFields = this.getFieldsByTypes(fields, this.isNumberType, () => 'Float'); + + // Don't create an aggregator field if the model has not number fields + const aggregatorGlobalId = `${globalId}Aggregator`; + const initialFields = { + count: 'Int', + }; + + // Only add the aggregator's operations if there are some numeric fields + if (!_.isEmpty(numericFields)) { + ['sum', 'avg', 'min', 'max'].forEach((agg) => { + initialFields[agg] = `${aggregatorGlobalId}${_.startCase(agg)}`; + }); + } + + const gqlNumberFormat = this.formatGQL(numericFields); + let aggregatorTypes = `type ${aggregatorGlobalId} {${this.formatGQL(initialFields)}}\n\n`; + + let resolvers = { + [aggregatorGlobalId]: { + count: async (obj, options, context) => { // eslint-disable-line no-unused-vars + // Object here corresponds to the filter that needs to be applied to the aggregation + const result = await this.getModelAggregator(model, obj).group({ + _id: null, + count: { $sum: 1 } + }); + + return _.get(result, '0.count'); + }, + } + }; + + // Only add the aggregator's operations types and resolver if there are some numeric fields + if (!_.isEmpty(numericFields)) { + // Returns the actual object and handle aggregation in the query resolvers + const defaultAggregatorFunc = (obj, options, context) => { // eslint-disable-line no-unused-vars + return obj; + }; + + aggregatorTypes += `type ${aggregatorGlobalId}Sum {${gqlNumberFormat}}\n\n`; + aggregatorTypes += `type ${aggregatorGlobalId}Avg {${gqlNumberFormat}}\n\n`; + aggregatorTypes += `type ${aggregatorGlobalId}Min {${gqlNumberFormat}}\n\n`; + aggregatorTypes += `type ${aggregatorGlobalId}Max {${gqlNumberFormat}}\n\n`; + + _.merge(resolvers[aggregatorGlobalId], { + sum: defaultAggregatorFunc, + avg: defaultAggregatorFunc, + min: defaultAggregatorFunc, + max: defaultAggregatorFunc, + }); + + resolvers = { + ...resolvers, + [`${aggregatorGlobalId}Sum`]: this.createAggregationFieldsResolver(model, fields, 'sum', this.isNumberType), + [`${aggregatorGlobalId}Avg`]: this.createAggregationFieldsResolver(model, fields, 'avg', this.isNumberType), + [`${aggregatorGlobalId}Min`]: this.createAggregationFieldsResolver(model, fields, 'min', this.isNumberType), + [`${aggregatorGlobalId}Max`]: this.createAggregationFieldsResolver(model, fields, 'max', this.isNumberType) + }; + } + + return { + globalId: aggregatorGlobalId, + type: aggregatorTypes, + resolver: resolvers, + }; + }, /** * Receive an Object and return a string which is following the GraphQL specs. @@ -167,6 +607,15 @@ module.exports = { case 'float': type = 'Float'; break; + case 'json': + type = 'JSON'; + break; + case 'time': + case 'date': + case 'datetime': + case 'timestamp': + type = 'DateTime'; + break; case 'enumeration': type = this.convertEnumType(definition, modelName, attributeName); break; @@ -328,13 +777,16 @@ module.exports = { // Plural. return async (ctx, next) => { - ctx.params = this.amountLimiting(ctx.params); - ctx.query = Object.assign( + const queryOpts = {}; + queryOpts.params = this.amountLimiting(ctx.params); + // Avoid using ctx.query = ... because it converts the object values to string + queryOpts.query = Object.assign( + {}, this.convertToParams(_.omit(ctx.params, 'where')), ctx.params.where ); - return controller(ctx, next); + return controller(Object.assign({}, ctx, queryOpts, { send: ctx.send }), next); // send method doesn't get copied when using object.assign }; })(); @@ -450,8 +902,8 @@ module.exports = { // Add timestamps attributes. if (_.get(model, 'options.timestamps') === true) { Object.assign(initialState, { - createdAt: 'String!', - updatedAt: 'String!' + createdAt: 'DateTime!', + updatedAt: 'DateTime!' }); Object.assign(acc.resolver[globalId], { @@ -551,6 +1003,22 @@ module.exports = { } }); + // TODO: + // - Add support for Graphql Aggregation in Bookshelf ORM + if (model.orm === 'mongoose') { + // Generation the aggregation for the given model + const modelAggregator = this.formatModelConnectionsGQL(attributes, model, name, queries.plural); + if (modelAggregator) { + acc.definition += modelAggregator.type; + if (!acc.resolver[modelAggregator.globalId]) { + acc.resolver[modelAggregator.globalId] = {}; + } + + _.merge(acc.resolver, modelAggregator.resolver); + _.merge(acc.query, modelAggregator.query); + } + } + // Build associations queries. (model.associations || []).forEach(association => { switch (association.nature) { @@ -620,7 +1088,8 @@ module.exports = { }; if (association.type === 'model') { - params.id = obj[association.alias]; + const rel = obj[association.alias]; + params.id = typeof rel === 'object' && 'id' in rel ? rel.id : rel; } else { // Get refering model. const ref = association.plugin ? @@ -777,10 +1246,11 @@ module.exports = { addCustomScalar: (resolvers) => { Object.assign(resolvers, { - JSON: GraphQLJSON + JSON: GraphQLJSON, + DateTime: GraphQLDateTime, }); - return 'scalar JSON'; + return 'scalar JSON \n scalar DateTime'; }, /** diff --git a/packages/strapi-plugin-settings-manager/admin/src/injectedComponents.js b/packages/strapi-plugin-settings-manager/admin/src/injectedComponents.js new file mode 100644 index 0000000000..109fa8b38c --- /dev/null +++ b/packages/strapi-plugin-settings-manager/admin/src/injectedComponents.js @@ -0,0 +1 @@ +export default []; \ No newline at end of file diff --git a/packages/strapi-plugin-settings-manager/admin/src/requirements.js b/packages/strapi-plugin-settings-manager/admin/src/requirements.js index 0206a853fb..96730be6c0 100644 --- a/packages/strapi-plugin-settings-manager/admin/src/requirements.js +++ b/packages/strapi-plugin-settings-manager/admin/src/requirements.js @@ -3,7 +3,7 @@ import request from 'utils/request'; const shouldRenderCompo = (plugin) => new Promise((resolve, reject) => { request('/settings-manager/autoReload') .then(response => { - plugin.preventComponentRendering = !response.autoReload; + plugin.preventComponentRendering = !response.autoReload.enabled; plugin.blockerComponentProps = { blockerComponentTitle: 'components.AutoReloadBlocker.header', blockerComponentDescription: 'components.AutoReloadBlocker.description', diff --git a/packages/strapi-plugin-settings-manager/admin/src/translations/de.json b/packages/strapi-plugin-settings-manager/admin/src/translations/de.json index eb5a4f20fb..8128d30e42 100755 --- a/packages/strapi-plugin-settings-manager/admin/src/translations/de.json +++ b/packages/strapi-plugin-settings-manager/admin/src/translations/de.json @@ -112,6 +112,7 @@ "form.language.choose": "Wähle eine Sprache:", "request.error.database.exist": "Diese Verbindung gibt es bereits", + "request.error.database.unknow": "Keine solche Verbindung", "request.error.type.string": "Ein Text ist erforderlich.", "request.error.type.number": "Eine Nummer ist erforderlich.", "request.error.type.boolean": "Ein Boolean ist erforderlich.", @@ -141,7 +142,7 @@ "list.databases.title.plural": "Verbindungen in dieser Umgebung", "popUpWarning.title": "Bitte bestätigen", - "popUpWarning.databases.danger.message": "Es gibt noch hiermit verbundene Content-Typen. Durch das Löschen könntest du deine Anwendung beschädigen. Sei vorsichtig...", + "popUpWarning.databases.danger.message": "Es gibt noch hiermit verbundene Inhaltstypen. Durch das Löschen könntest du deine Anwendung beschädigen. Sei vorsichtig...", "popUpWarning.danger.ok.message": "Ich stimme zu", "popUpWarning.databases.delete.message": "Bist du sicher, dass du diese Datenbank löschen möchtest?", "popUpWarning.languages.delete.message": "Bist du sicher, dass du diese Sprache entfernen möchtest?", diff --git a/packages/strapi-plugin-settings-manager/admin/src/translations/en.json b/packages/strapi-plugin-settings-manager/admin/src/translations/en.json index 2f613a630e..b9365fada3 100755 --- a/packages/strapi-plugin-settings-manager/admin/src/translations/en.json +++ b/packages/strapi-plugin-settings-manager/admin/src/translations/en.json @@ -107,6 +107,12 @@ "form.server.item.port": "Port", "form.server.item.cron": "Cron", + "form.server.item.proxy": "Proxy Settings", + "form.server.item.proxy.enable": "Proxy Enable", + "form.server.item.proxy.ssl": "Proxy SSL", + "form.server.item.proxy.host": "Proxy Host", + "form.server.item.proxy.port": "Proxy Port", + "form.language.name": "Languages", "form.language.description": "Configure your languages.", "form.language.choose": "Choose a language:", diff --git a/packages/strapi-plugin-settings-manager/admin/src/translations/es.json b/packages/strapi-plugin-settings-manager/admin/src/translations/es.json new file mode 100755 index 0000000000..7c8ce57dbe --- /dev/null +++ b/packages/strapi-plugin-settings-manager/admin/src/translations/es.json @@ -0,0 +1,656 @@ +{ + "components.DownloadDb.download": "Instalación en curso...", + "components.DownloadDb.text": "Esto podría llevar un minuto más o menos. Gracias por su paciencia.", + "plugin.description.short": "Configure su proyecto en cuestión de segundos.", + "plugin.description.long": "Configure su proyecto en cuestión de segundos.", + "menu.section.global-settings": "Ajustes globales", + "menu.item.application": "Aplicación", + "menu.item.languages": "Idiomas", + "menu.item.advanced": "Avanzado", + + "menu.section.environments": "Entornos", + "menu.item.database": "Base de Datos", + "menu.item.request": "Petición", + "menu.item.response": "Respuesta", + "menu.item.security": "Seguridad", + "menu.item.server": "Servidor", + + "form.button.cancel": "Cancelar", + "form.button.save": "Guardar", + "form.button.confirm": "Confirmar", + + "form.databases.name": "Base de Datos", + "form.databases.description": "Configure las opciones de su base de datos por entorno.", + + "form.database.item.name": "Nombre de la conexión", + "form.database.item.client": "Cliente", + "form.database.item.connector": "Conector", + "form.database.item.host": "Anfitrión", + "form.database.item.port": "Puerto", + "form.database.item.username": "Nombre de Usuario", + "form.database.item.password": "Contraseña", + "form.database.item.database": "Base de Datos", + "form.database.item.ssl": "SSL", + "form.database.item.authenticationDatabase": "Base de Datos de Autenticación", + "form.database.item.default": "Establecer como conexión predeterminada", + "form.database.item.provider.mongo": "Mongo", + "form.database.item.provider.postgres": "PostgresSQL", + "form.database.item.provider.mysql": "MySQL", + "form.database.item.provider.redis": "Redis", + + "form.application.name": "Aplicación", + "form.application.description": "Configure las opciones de su aplicación.", + + "form.application.item.name": "Nombre", + "form.application.item.description": "Descripción", + "form.application.item.version": "Versión", + + "form.advanced.name": "Avanzado", + "form.advanced.description": "Configure sus opciones avanzadas.", + + "form.advanced.item.admin": "URL del panel de control del administrador", + "form.advanced.item.prefix": "API de prefijos", + + "form.request.name": "Solicitud", + "form.request.description": "Configure los ajustes de su solicitud.", + "form.request.item.parser": "Parser", + "form.request.item.parser.multipart": "Parser Multiparte", + "form.request.item.prefix": "Prefijo", + "form.request.item.prefix.prefix": "Prefijo", + "form.request.item.logger": "Logger", + "form.request.item.logger.level": "Nivel", + "form.request.item.logger.exposeInContext": "Exponer en contexto", + "form.request.item.logger.requests": "Solicitudes", + "form.request.item.router": "Router", + "form.request.item.router.prefix": "Prefijo", + + "form.response.name": "Respuesta", + "form.response.description": "Configurar las opciones de respuesta.", + "form.response.item.gzip.enabled": "Gzip", + "form.response.item.responseTime.enabled": "Tiempo de respuesta", + + "form.security.name": "Seguridad", + "form.security.description": "Configurar las opciones de seguridad.", + + "form.security.item.csrf": "CSRF", + "form.security.item.p3p": "P3P", + "form.security.item.p3p.value": "Valor", + "form.security.item.hsts": "HOSTS", + "form.security.item.csrf.key": "Clave", + "form.security.item.csrf.secret": "Secreto", + "form.security.item.csrf.cookie": "Cookie", + "form.security.item.csrf.angular": "Angular", + "form.security.item.hsts.maxAge": "Edad Máxima", + "form.security.item.hsts.includeSubDomains": "Incluir Sub Dominio", + "form.security.item.hsts.preload": "Precarga", + + "form.security.item.session": "Sesión", + "form.security.item.session.key": "Clave secreta", + "form.security.item.session.maxAge": "Edad máxima", + + "form.security.item.xframe": "Xframe", + "form.security.item.xframe.value": "Opciones", + "form.security.item.xframe.deny": "DENY", + "form.security.item.xframe.sameorigin": "SAMEORIGIN", + "form.security.item.xframe.allow-from": "ALLOW-FROM", + + "form.security.item.xssProtection": "Protección xss", + "form.security.item.xssProtection.mode": "Modo", + + "form.security.item.cors": "Cors", + "form.security.item.cors.origin": "Origen", + + "form.server.name": "Servidor", + "form.server.description": "Configure las opciones de su servidor.", + + "form.server.item.host": "Host", + "form.server.item.port": "Puerto", + "form.server.item.cron": "Cron", + + "form.language.name": "Idiomas", + "form.language.description": "Configure sus idiomas.", + "form.language.choose": "Seleccione un idioma:", + + "request.error.database.exist": "Esta conexión ya existe", + "request.error.database.unknow": "No existe dicha conexión", + "request.error.type.string": "Se requiere un texto.", + "request.error.type.number": "Se requiere un número.", + "request.error.type.boolean": "Se requiere un booleano.", + "request.error.type.select": "El valor debe estar en una lista predefinida.", + + "request.error.validation.required": "Esta entrada de valor es obligatoria.", + "request.error.validation.regex": "El valor no coincide con el valor de regex.", + "request.error.validation.max": "El valor es demasiado alto.", + "request.error.validation.min": "El valor es demasiado bajo.", + "request.error.validation.maxLength": "El valor es demasiado largo.", + "request.error.validation.minLength": "El valor es demasiado alto.", + + "request.error.config": "El archivo de configuración no existe.", + "request.error.environment.required": "Se requiere un entorno.", + "request.error.environment.unknow": "El entorno es desconocido.", + "request.error.languages.exist": "Este idioma ya existe.", + "request.error.languages.unknow": "This language doesn't exist.", + "request.error.languages.incorrect": "Este idioma no existe.", + + "list.languages.button.label": "Añadir un nuevo idioma", + "list.languages.title.singular": "idioma disponible", + "list.languages.title.plural": "idiomas disponibles", + "list.languages.default.languages": "Idioma por defecto", + "list.languages.set.languages": "Fijar como valor por defecto", + "list.databases.button.label": "Añadir una nueva conexión", + "list.databases.title.singular": "conexión en este entorno", + "list.databases.title.plural": "conexiones en este entorno", + + "popUpWarning.title": "Por favor, confirme", + "popUpWarning.databases.danger.message": "Los Tipos de Contenido todavía están vinculados a esta conexión. Al eliminarlo, podría causar problemas críticos a su aplicación. Ten cuidado...", + "popUpWarning.danger.ok.message": "Entiendo", + "popUpWarning.databases.delete.message": "¿Está seguro de que desea eliminar esta Base de Datos?", + "popUpWarning.languages.delete.message": "¿Estás seguro de que quieres eliminar este idioma?", + "strapi.notification.info.settingsEqual": "Los ajustes son idénticos", + "strapi.notification.success.databaseDelete": "La base de datos se ha borrado con éxito.", + "strapi.notification.success.languageDelete": "El idioma ha sido borrado con éxito.", + "strapi.notification.success.languageAdd": "El idioma ha sido añadido con éxito.", + "strapi.notification.success.databaseAdd": "La Base de Datos ha sido añadida con éxito.", + "strapi.notification.success.databaseEdit": "La configuración de la base de datos se ha actualizado correctamente.", + "strapi.notification.success.databaseDeleted": "La base de datos ha sido borrada.", + "strapi.notification.success.settingsEdit": "La configuración se ha actualizado correctamente.", + "strapi.notification.error": "Se ha producido un error", + "strapi.notification.info.serverRestart": "El servidor se reiniciará", + + "language.af": "Afrikaans", + "language.af_NA": "Afrikaans (Namibië)", + "language.af_ZA": "Afrikaans (Suid-Afrika)", + "language.agq": "Aghem", + "language.agq_CM": "Aghem (Kàmàlûŋ)", + "language.ak": "Akan", + "language.ak_GH": "Akan (Gaana)", + "language.am": "አማርኛ", + "language.am_ET": "አማርኛ (ኢትዮጵያ)", + "language.ar": "العربية", + "language.ar_001": "العربية (العالم)", + "language.ar_AE": "العربية (الإمارات العربية المتحدة)", + "language.ar_BH": "العربية (البحرين)", + "language.ar_DZ": "العربية (الجزائر)", + "language.ar_EG": "العربية (مصر)", + "language.ar_IQ": "العربية (العراق)", + "language.ar_JO": "العربية (الأردن)", + "language.ar_KW": "العربية (الكويت)", + "language.ar_LB": "العربية (لبنان)", + "language.ar_LY": "العربية (ليبيا)", + "language.ar_MA": "العربية (المغرب)", + "language.ar_OM": "العربية (عُمان)", + "language.ar_QA": "العربية (قطر)", + "language.ar_SA": "العربية (المملكة العربية السعودية)", + "language.ar_SD": "العربية (السودان)", + "language.ar_SY": "العربية (سوريا)", + "language.ar_TN": "العربية (تونس)", + "language.ar_YE": "العربية (اليمن)", + "language.as": "অসমীয়া", + "language.as_IN": "অসমীয়া (ভাৰত)", + "language.asa": "Kipare", + "language.asa_TZ": "Kipare (Tadhania)", + "language.az": "azərbaycanca", + "language.az_Cyrl": "Азәрбајҹан (kiril)", + "language.az_Cyrl_AZ": "Азәрбајҹан (kiril, Азәрбајҹан)", + "language.az_Latn": "azərbaycanca (latın)", + "language.az_Latn_AZ": "azərbaycanca (latın, Azərbaycan)", + "language.bas": "Ɓàsàa", + "language.bas_CM": "Ɓàsàa (Kàmɛ̀rûn)", + "language.be": "беларуская", + "language.be_BY": "беларуская (Беларусь)", + "language.bem": "Ichibemba", + "language.bem_ZM": "Ichibemba (Zambia)", + "language.bez": "Hibena", + "language.bez_TZ": "Hibena (Hutanzania)", + "language.bg": "български", + "language.bg_BG": "български (България)", + "language.bm": "bamanakan", + "language.bm_ML": "bamanakan (Mali)", + "language.bn": "বাংলা", + "language.bn_BD": "বাংলা (বাংলাদেশ)", + "language.bn_IN": "বাংলা (ভারত)", + "language.bo": "པོད་སྐད་", + "language.bo_CN": "པོད་སྐད་ (རྒྱ་ནག)", + "language.bo_IN": "པོད་སྐད་ (རྒྱ་གར་)", + "language.br": "brezhoneg", + "language.br_FR": "brezhoneg (Frañs)", + "language.brx": "बड़ो", + "language.brx_IN": "बड़ो (भारत)", + "language.bs": "bosanski", + "language.bs_BA": "bosanski (Bosna i Hercegovina)", + "language.ca": "català", + "language.ca_ES": "català (Espanya)", + "language.cgg": "Rukiga", + "language.cgg_UG": "Rukiga (Uganda)", + "language.chr": "ᏣᎳᎩ", + "language.chr_US": "ᏣᎳᎩ (ᎠᎹᏰᏟ)", + "language.cs": "čeština", + "language.cs_CZ": "čeština (Česká republika)", + "language.cy": "Cymraeg", + "language.cy_GB": "Cymraeg (Prydain Fawr)", + "language.da": "dansk", + "language.da_DK": "dansk (Danmark)", + "language.dav": "Kitaita", + "language.dav_KE": "Kitaita (Kenya)", + "language.de": "Deutsch", + "language.de_AT": "Deutsch (Österreich)", + "language.de_BE": "Deutsch (Belgien)", + "language.de_CH": "Deutsch (Schweiz)", + "language.de_DE": "Deutsch (Deutschland)", + "language.de_LI": "Deutsch (Liechtenstein)", + "language.de_LU": "Deutsch (Luxemburg)", + "language.dje": "Zarmaciine", + "language.dje_NE": "Zarmaciine (Nižer)", + "language.dua": "duálá", + "language.dua_CM": "duálá (Cameroun)", + "language.dyo": "joola", + "language.dyo_SN": "joola (Senegal)", + "language.ebu": "Kĩembu", + "language.ebu_KE": "Kĩembu (Kenya)", + "language.ee": "eʋegbe", + "language.ee_GH": "eʋegbe (Ghana nutome)", + "language.ee_TG": "eʋegbe (Togo nutome)", + "language.el": "Ελληνικά", + "language.el_CY": "Ελληνικά (Κύπρος)", + "language.el_GR": "Ελληνικά (Ελλάδα)", + "language.en": "English", + "language.en_AS": "English (American Samoa)", + "language.en_AU": "English (Australia)", + "language.en_BB": "English (Barbados)", + "language.en_BE": "English (Belgium)", + "language.en_BM": "English (Bermuda)", + "language.en_BW": "English (Botswana)", + "language.en_BZ": "English (Belize)", + "language.en_CA": "English (Canada)", + "language.en_GB": "English (United Kingdom)", + "language.en_GU": "English (Guam)", + "language.en_GY": "English (Guyana)", + "language.en_HK": "English (Hong Kong SAR China)", + "language.en_IE": "English (Ireland)", + "language.en_IN": "English (India)", + "language.en_JM": "English (Jamaica)", + "language.en_MH": "English (Marshall Islands)", + "language.en_MP": "English (Northern Mariana Islands)", + "language.en_MT": "English (Malta)", + "language.en_MU": "English (Mauritius)", + "language.en_NA": "English (Namibia)", + "language.en_NZ": "English (New Zealand)", + "language.en_PH": "English (Philippines)", + "language.en_PK": "English (Pakistan)", + "language.en_SG": "English (Singapore)", + "language.en_TT": "English (Trinidad and Tobago)", + "language.en_UM": "English (U.S. Minor Outlying Islands)", + "language.en_US": "English (United States)", + "language.en_US_POSIX": "English (United States, Computer)", + "language.en_VI": "English (U.S. Virgin Islands)", + "language.en_ZA": "English (South Africa)", + "language.en_ZW": "English (Zimbabwe)", + "language.eo": "esperanto", + "language.es": "español", + "language.es_419": "español (Latinoamérica)", + "language.es_AR": "español (Argentina)", + "language.es_BO": "español (Bolivia)", + "language.es_CL": "español (Chile)", + "language.es_CO": "español (Colombia)", + "language.es_CR": "español (Costa Rica)", + "language.es_DO": "español (República Dominicana)", + "language.es_EC": "español (Ecuador)", + "language.es_ES": "español (España)", + "language.es_GQ": "español (Guinea Ecuatorial)", + "language.es_GT": "español (Guatemala)", + "language.es_HN": "español (Honduras)", + "language.es_MX": "español (México)", + "language.es_NI": "español (Nicaragua)", + "language.es_PA": "español (Panamá)", + "language.es_PE": "español (Perú)", + "language.es_PR": "español (Puerto Rico)", + "language.es_PY": "español (Paraguay)", + "language.es_SV": "español (El Salvador)", + "language.es_US": "español (Estados Unidos)", + "language.es_UY": "español (Uruguay)", + "language.es_VE": "español (Venezuela)", + "language.et": "eesti", + "language.et_EE": "eesti (Eesti)", + "language.eu": "euskara", + "language.eu_ES": "euskara (Espainia)", + "language.ewo": "ewondo", + "language.ewo_CM": "ewondo (Kamǝrún)", + "language.fa": "فارسی", + "language.fa_AF": "دری (افغانستان)", + "language.fa_IR": "فارسی (ایران)", + "language.ff": "Pulaar", + "language.ff_SN": "Pulaar (Senegaal)", + "language.fi": "suomi", + "language.fi_FI": "suomi (Suomi)", + "language.fil": "Filipino", + "language.fil_PH": "Filipino (Pilipinas)", + "language.fo": "føroyskt", + "language.fo_FO": "føroyskt (Føroyar)", + "language.fr": "français", + "language.fr_BE": "français (Belgique)", + "language.fr_BF": "français (Burkina Faso)", + "language.fr_BI": "français (Burundi)", + "language.fr_BJ": "français (Bénin)", + "language.fr_BL": "français (Saint-Barthélémy)", + "language.fr_CA": "français (Canada)", + "language.fr_CD": "français (République démocratique du Congo)", + "language.fr_CF": "français (République centrafricaine)", + "language.fr_CG": "français (Congo-Brazzaville)", + "language.fr_CH": "français (Suisse)", + "language.fr_CI": "français (Côte d’Ivoire)", + "language.fr_CM": "français (Cameroun)", + "language.fr_DJ": "français (Djibouti)", + "language.fr_FR": "français (France)", + "language.fr_GA": "français (Gabon)", + "language.fr_GF": "français (Guyane française)", + "language.fr_GN": "français (Guinée)", + "language.fr_GP": "français (Guadeloupe)", + "language.fr_GQ": "français (Guinée équatoriale)", + "language.fr_KM": "français (Comores)", + "language.fr_LU": "français (Luxembourg)", + "language.fr_MC": "français (Monaco)", + "language.fr_MF": "français (Saint-Martin)", + "language.fr_MG": "français (Madagascar)", + "language.fr_ML": "français (Mali)", + "language.fr_MQ": "français (Martinique)", + "language.fr_NE": "français (Niger)", + "language.fr_RE": "français (Réunion)", + "language.fr_RW": "français (Rwanda)", + "language.fr_SN": "français (Sénégal)", + "language.fr_TD": "français (Tchad)", + "language.fr_TG": "français (Togo)", + "language.fr_YT": "français (Mayotte)", + "language.ga": "Gaeilge", + "language.ga_IE": "Gaeilge (Éire)", + "language.gl": "galego", + "language.gl_ES": "galego (España)", + "language.gsw": "Schwiizertüütsch", + "language.gsw_CH": "Schwiizertüütsch (Schwiiz)", + "language.gu": "ગુજરાતી", + "language.gu_IN": "ગુજરાતી (ભારત)", + "language.guz": "Ekegusii", + "language.guz_KE": "Ekegusii (Kenya)", + "language.gv": "Gaelg", + "language.gv_GB": "Gaelg (Rywvaneth Unys)", + "language.ha": "Hausa", + "language.ha_Latn": "Hausa (Latn)", + "language.ha_Latn_GH": "Hausa (Latn, Gana)", + "language.ha_Latn_NE": "Hausa (Latn, Nijar)", + "language.ha_Latn_NG": "Hausa (Latn, Najeriya)", + "language.haw": "ʻŌlelo Hawaiʻi", + "language.haw_US": "ʻŌlelo Hawaiʻi (ʻAmelika Hui Pū ʻIa)", + "language.he": "עברית", + "language.he_IL": "עברית (ישראל)", + "language.hi": "हिन्दी", + "language.hi_IN": "हिन्दी (भारत)", + "language.hr": "hrvatski", + "language.hr_HR": "hrvatski (Hrvatska)", + "language.hu": "magyar", + "language.hu_HU": "magyar (Magyarország)", + "language.hy": "Հայերէն", + "language.hy_AM": "Հայերէն (Հայաստանի Հանրապետութիւն)", + "language.id": "Bahasa Indonesia", + "language.id_ID": "Bahasa Indonesia (Indonesia)", + "language.ig": "Igbo", + "language.ig_NG": "Igbo (Nigeria)", + "language.ii": "ꆈꌠꉙ", + "language.ii_CN": "ꆈꌠꉙ (ꍏꇩ)", + "language.is": "íslenska", + "language.is_IS": "íslenska (Ísland)", + "language.it": "italiano", + "language.it_CH": "italiano (Svizzera)", + "language.it_IT": "italiano (Italia)", + "language.ja": "日本語", + "language.ja_JP": "日本語(日本)", + "language.jmc": "Kimachame", + "language.jmc_TZ": "Kimachame (Tanzania)", + "language.ka": "ქართული", + "language.ka_GE": "ქართული (საქართველო)", + "language.kab": "Taqbaylit", + "language.kab_DZ": "Taqbaylit (Lezzayer)", + "language.kam": "Kikamba", + "language.kam_KE": "Kikamba (Kenya)", + "language.kde": "Chimakonde", + "language.kde_TZ": "Chimakonde (Tanzania)", + "language.kea": "kabuverdianu", + "language.kea_CV": "kabuverdianu (Kabu Verdi)", + "language.khq": "Koyra ciini", + "language.khq_ML": "Koyra ciini (Maali)", + "language.ki": "Gikuyu", + "language.ki_KE": "Gikuyu (Kenya)", + "language.kk": "қазақ тілі", + "language.kk_Cyrl": "қазақ тілі (кириллица)", + "language.kk_Cyrl_KZ": "қазақ тілі (кириллица, Қазақстан)", + "language.kl": "kalaallisut", + "language.kl_GL": "kalaallisut (Kalaallit Nunaat)", + "language.kln": "Kalenjin", + "language.kln_KE": "Kalenjin (Emetab Kenya)", + "language.km": "ភាសាខ្មែរ", + "language.km_KH": "ភាសាខ្មែរ (កម្ពុជា)", + "language.kn": "ಕನ್ನಡ", + "language.kn_IN": "ಕನ್ನಡ (ಭಾರತ)", + "language.ko": "한국어", + "language.ko_KR": "한국어(대한민국)", + "language.kok": "कोंकणी", + "language.kok_IN": "कोंकणी (भारत)", + "language.ksb": "Kishambaa", + "language.ksb_TZ": "Kishambaa (Tanzania)", + "language.ksf": "rikpa", + "language.ksf_CM": "rikpa (kamɛrún)", + "language.kw": "kernewek", + "language.kw_GB": "kernewek (Rywvaneth Unys)", + "language.lag": "Kɨlaangi", + "language.lag_TZ": "Kɨlaangi (Taansanía)", + "language.lg": "Luganda", + "language.lg_UG": "Luganda (Yuganda)", + "language.ln": "lingála", + "language.ln_CD": "lingála (Repibiki demokratiki ya Kongó)", + "language.ln_CG": "lingála (Kongo)", + "language.lt": "lietuvių", + "language.lt_LT": "lietuvių (Lietuva)", + "language.lu": "Tshiluba", + "language.lu_CD": "Tshiluba (Ditunga wa Kongu)", + "language.luo": "Dholuo", + "language.luo_KE": "Dholuo (Kenya)", + "language.luy": "Luluhia", + "language.luy_KE": "Luluhia (Kenya)", + "language.lv": "latviešu", + "language.lv_LV": "latviešu (Latvija)", + "language.mas": "Maa", + "language.mas_KE": "Maa (Kenya)", + "language.mas_TZ": "Maa (Tansania)", + "language.mer": "Kĩmĩrũ", + "language.mer_KE": "Kĩmĩrũ (Kenya)", + "language.mfe": "kreol morisien", + "language.mfe_MU": "kreol morisien (Moris)", + "language.mg": "Malagasy", + "language.mg_MG": "Malagasy (Madagasikara)", + "language.mgh": "Makua", + "language.mgh_MZ": "Makua (Umozambiki)", + "language.mk": "македонски", + "language.mk_MK": "македонски (Македонија)", + "language.ml": "മലയാളം", + "language.ml_IN": "മലയാളം (ഇന്ത്യ)", + "language.mr": "मराठी", + "language.mr_IN": "मराठी (भारत)", + "language.ms": "Bahasa Melayu", + "language.ms_BN": "Bahasa Melayu (Brunei)", + "language.ms_MY": "Bahasa Melayu (Malaysia)", + "language.mt": "Malti", + "language.mt_MT": "Malti (Malta)", + "language.mua": "MUNDAŊ", + "language.mua_CM": "MUNDAŊ (kameruŋ)", + "language.my": "ဗမာ", + "language.my_MM": "ဗမာ (မြန်မာ)", + "language.naq": "Khoekhoegowab", + "language.naq_NA": "Khoekhoegowab (Namibiab)", + "language.nb": "norsk bokmål", + "language.nb_NO": "norsk bokmål (Norge)", + "language.nd": "isiNdebele", + "language.nd_ZW": "isiNdebele (Zimbabwe)", + "language.ne": "नेपाली", + "language.ne_IN": "नेपाली (भारत)", + "language.ne_NP": "नेपाली (नेपाल)", + "language.nl": "Nederlands", + "language.nl_AW": "Nederlands (Aruba)", + "language.nl_BE": "Nederlands (België)", + "language.nl_CW": "Nederlands (Curaçao)", + "language.nl_NL": "Nederlands (Nederland)", + "language.nl_SX": "Nederlands (Sint Maarten)", + "language.nmg": "nmg", + "language.nmg_CM": "nmg (Kamerun)", + "language.nn": "nynorsk", + "language.nn_NO": "nynorsk (Noreg)", + "language.nus": "Thok Nath", + "language.nus_SD": "Thok Nath (Sudan)", + "language.nyn": "Runyankore", + "language.nyn_UG": "Runyankore (Uganda)", + "language.om": "Oromoo", + "language.om_ET": "Oromoo (Itoophiyaa)", + "language.om_KE": "Oromoo (Keeniyaa)", + "language.or": "ଓଡ଼ିଆ", + "language.or_IN": "ଓଡ଼ିଆ (ଭାରତ)", + "language.pa": "ਪੰਜਾਬੀ", + "language.pa_Arab": "پنجاب (العربية)", + "language.pa_Arab_PK": "پنجاب (العربية, پکستان)", + "language.pa_Guru": "ਪੰਜਾਬੀ (Guru)", + "language.pa_Guru_IN": "ਪੰਜਾਬੀ (Guru, ਭਾਰਤ)", + "language.pl": "polski", + "language.pl_PL": "polski (Polska)", + "language.ps": "پښتو", + "language.ps_AF": "پښتو (افغانستان)", + "language.pt": "português", + "language.pt_AO": "português (Angola)", + "language.pt_BR": "português (Brasil)", + "language.pt_GW": "português (Guiné Bissau)", + "language.pt_MZ": "português (Moçambique)", + "language.pt_PT": "português (Portugal)", + "language.pt_ST": "português (São Tomé e Príncipe)", + "language.rm": "rumantsch", + "language.rm_CH": "rumantsch (Svizra)", + "language.rn": "Ikirundi", + "language.rn_BI": "Ikirundi (Uburundi)", + "language.ro": "română", + "language.ro_MD": "română (Republica Moldova)", + "language.ro_RO": "română (România)", + "language.rof": "Kihorombo", + "language.rof_TZ": "Kihorombo (Tanzania)", + "language.ru": "русский", + "language.ru_MD": "русский (Молдова)", + "language.ru_RU": "русский (Россия)", + "language.ru_UA": "русский (Украина)", + "language.rw": "Kinyarwanda", + "language.rw_RW": "Kinyarwanda (Rwanda)", + "language.rwk": "Kiruwa", + "language.rwk_TZ": "Kiruwa (Tanzania)", + "language.saq": "Kisampur", + "language.saq_KE": "Kisampur (Kenya)", + "language.sbp": "Ishisangu", + "language.sbp_TZ": "Ishisangu (Tansaniya)", + "language.seh": "sena", + "language.seh_MZ": "sena (Moçambique)", + "language.ses": "Koyraboro senni", + "language.ses_ML": "Koyraboro senni (Maali)", + "language.sg": "Sängö", + "language.sg_CF": "Sängö (Ködörösêse tî Bêafrîka)", + "language.shi": "tamazight", + "language.shi_Latn": "tamazight (Latn)", + "language.shi_Latn_MA": "tamazight (Latn, lmɣrib)", + "language.shi_Tfng": "ⵜⴰⵎⴰⵣⵉⵖⵜ (Tfng)", + "language.shi_Tfng_MA": "ⵜⴰⵎⴰⵣⵉⵖⵜ (Tfng, ⵍⵎⵖⵔⵉⴱ)", + "language.si": "සිංහල", + "language.si_LK": "සිංහල (ශ්‍රී ලංකාව)", + "language.sk": "slovenčina", + "language.sk_SK": "slovenčina (Slovenská republika)", + "language.sl": "slovenščina", + "language.sl_SI": "slovenščina (Slovenija)", + "language.sn": "chiShona", + "language.sn_ZW": "chiShona (Zimbabwe)", + "language.so": "Soomaali", + "language.so_DJ": "Soomaali (Jabuuti)", + "language.so_ET": "Soomaali (Itoobiya)", + "language.so_KE": "Soomaali (Kiiniya)", + "language.so_SO": "Soomaali (Soomaaliya)", + "language.sq": "shqip", + "language.sq_AL": "shqip (Shqipëria)", + "language.sr": "Српски", + "language.sr_Cyrl": "Српски (Ћирилица)", + "language.sr_Cyrl_BA": "Српски (Ћирилица, Босна и Херцеговина)", + "language.sr_Cyrl_ME": "Српски (Ћирилица, Црна Гора)", + "language.sr_Cyrl_RS": "Српски (Ћирилица, Србија)", + "language.sr_Latn": "Srpski (Latinica)", + "language.sr_Latn_BA": "Srpski (Latinica, Bosna i Hercegovina)", + "language.sr_Latn_ME": "Srpski (Latinica, Crna Gora)", + "language.sr_Latn_RS": "Srpski (Latinica, Srbija)", + "language.sv": "svenska", + "language.sv_FI": "svenska (Finland)", + "language.sv_SE": "svenska (Sverige)", + "language.sw": "Kiswahili", + "language.sw_KE": "Kiswahili (Kenya)", + "language.sw_TZ": "Kiswahili (Tanzania)", + "language.swc": "Kiswahili ya Kongo", + "language.swc_CD": "Kiswahili ya Kongo (Jamhuri ya Kidemokrasia ya Kongo)", + "language.ta": "தமிழ்", + "language.ta_IN": "தமிழ் (இந்தியா)", + "language.ta_LK": "தமிழ் (இலங்கை)", + "language.te": "తెలుగు", + "language.te_IN": "తెలుగు (భారత దేశం)", + "language.teo": "Kiteso", + "language.teo_KE": "Kiteso (Kenia)", + "language.teo_UG": "Kiteso (Uganda)", + "language.th": "ไทย", + "language.th_TH": "ไทย (ไทย)", + "language.ti": "ትግርኛ", + "language.ti_ER": "ትግርኛ (ER)", + "language.ti_ET": "ትግርኛ (ET)", + "language.to": "lea fakatonga", + "language.to_TO": "lea fakatonga (Tonga)", + "language.tr": "Türkçe", + "language.tr_TR": "Türkçe (Türkiye)", + "language.twq": "Tasawaq senni", + "language.twq_NE": "Tasawaq senni (Nižer)", + "language.tzm": "Tamaziɣt", + "language.tzm_Latn": "Tamaziɣt (Latn)", + "language.tzm_Latn_MA": "Tamaziɣt (Latn, Meṛṛuk)", + "language.uk": "українська", + "language.uk_UA": "українська (Україна)", + "language.ur": "اردو", + "language.ur_IN": "اردو (بھارت)", + "language.ur_PK": "اردو (پاکستان)", + "language.uz": "Ўзбек", + "language.uz_Arab": "اۉزبېک (Arab)", + "language.uz_Arab_AF": "اۉزبېک (Arab, افغانستان)", + "language.uz_Cyrl": "Ўзбек (Cyrl)", + "language.uz_Cyrl_UZ": "Ўзбек (Cyrl, Ўзбекистон)", + "language.uz_Latn": "oʼzbekcha (Lotin)", + "language.uz_Latn_UZ": "oʼzbekcha (Lotin, Oʼzbekiston)", + "language.vai": "ꕙꔤ", + "language.vai_Latn": "Vai (Latn)", + "language.vai_Latn_LR": "Vai (Latn, Laibhiya)", + "language.vai_Vaii": "ꕙꔤ (Vaii)", + "language.vai_Vaii_LR": "ꕙꔤ (Vaii, ꕞꔤꔫꕩ)", + "language.vi": "Tiếng Việt", + "language.vi_VN": "Tiếng Việt (Việt Nam)", + "language.vun": "Kyivunjo", + "language.vun_TZ": "Kyivunjo (Tanzania)", + "language.xog": "Olusoga", + "language.xog_UG": "Olusoga (Yuganda)", + "language.yav": "nuasue", + "language.yav_CM": "nuasue (Kemelún)", + "language.yo": "Èdè Yorùbá", + "language.yo_NG": "Èdè Yorùbá (Orílẹ́ède Nàìjíríà)", + "language.zh": "中文", + "language.zh_Hans": "中文(简体中文)", + "language.zh_Hans_CN": "中文(简体中文、中国)", + "language.zh_Hans_HK": "中文(简体中文、中国香港特别行政区)", + "language.zh_Hans_MO": "中文(简体中文、中国澳门特别行政区)", + "language.zh_Hans_SG": "中文(简体中文、新加坡)", + "language.zh_Hant": "中文(繁體中文)", + "language.zh_Hant_HK": "中文(繁體中文,中華人民共和國香港特別行政區)", + "language.zh_Hant_MO": "中文(繁體中文,中華人民共和國澳門特別行政區)", + "language.zh_Hant_TW": "中文(繁體中文,台灣)", + "language.zu": "isiZulu", + "language.zu_ZA": "isiZulu (iNingizimu Afrika)", + + "pageNotFound": "Página no encontrada" +} diff --git a/packages/strapi-plugin-settings-manager/admin/src/translations/fr.json b/packages/strapi-plugin-settings-manager/admin/src/translations/fr.json index 0cb9b25171..cc336ffc9a 100755 --- a/packages/strapi-plugin-settings-manager/admin/src/translations/fr.json +++ b/packages/strapi-plugin-settings-manager/admin/src/translations/fr.json @@ -35,6 +35,8 @@ "form.database.item.provider.postgres": "PostgresSQL", "form.database.item.provider.mysql": "MySQL", "form.database.item.provider.redis": "Redis", + "form.database.item.ssl": "SSL", + "form.database.item.authenticationDatabase": "Base de données d'authentification", "form.application.name": "Application", "form.application.description": "Modification des configurations de l'application.", @@ -110,6 +112,7 @@ "form.language.choose": "Choisissez un langage", "request.error.database.exist": "Cette connexion est déjà utilisée", + "request.error.database.unknow": "Il n'y a pas de connexion de ce genre", "request.error.type.string": "Un texte est demandé.", "request.error.type.number": "Un nombre est demandé.", "request.error.type.boolean": "Un boolean est demandé.", @@ -141,6 +144,8 @@ "popUpWarning.title": "Merci de confirmer", "popUpWarning.databases.delete.message": "Etes-vous sûre de vouloir supprimer cette connexion ?", "popUpWarning.danger.message": "Des models sont toujours reliés à cette connexion.....", + "popUpWarning.databases.danger.message": "Les types de contenu sont toujours liés à cette connexion. En le supprimant, vous pourriez causer des problèmes critiques à votre application. Soyez prudent...", + "popUpWarning.danger.ok.message": "Je comprends", "popUpWarning.databases.danger.ok.message": "J'ai compris", "popUpWarning.languages.delete.message": "Etes-vous sûre de vouloir supprimer ce language ?", diff --git a/packages/strapi-plugin-settings-manager/admin/src/translations/nl.json b/packages/strapi-plugin-settings-manager/admin/src/translations/nl.json new file mode 100644 index 0000000000..c07100a025 --- /dev/null +++ b/packages/strapi-plugin-settings-manager/admin/src/translations/nl.json @@ -0,0 +1,656 @@ +{ + "components.DownloadDb.download": "Installatie is bezig...", + "components.DownloadDb.text": "Dit kan even duren. Bedankt voor je geduld.", + "plugin.description.short": "Configureer je project binnen enkele seconden.", + "plugin.description.long": "Configureer je project binnen enkele seconden.", + "menu.section.global-settings": "Globale instellingen", + "menu.item.application": "Applicatie", + "menu.item.languages": "Talen", + "menu.item.advanced": "Geavanceerd", + + "menu.section.environments": "Omgevingen", + "menu.item.database": "Database", + "menu.item.request": "Request", + "menu.item.response": "Response", + "menu.item.security": "Beveiliging", + "menu.item.server": "Server", + + "form.button.cancel": "Annuleren", + "form.button.save": "Opslaan", + "form.button.confirm": "Akkoord", + + "form.databases.name": "Database", + "form.databases.description": "Configureer je databse instellingen via omgeving", + + "form.database.item.name": "Connectie Naam", + "form.database.item.client": "Client", + "form.database.item.connector": "Connector", + "form.database.item.host": "Host", + "form.database.item.port": "Poort", + "form.database.item.username": "Gebruikersnaam", + "form.database.item.password": "Wachtwoord", + "form.database.item.database": "Database", + "form.database.item.ssl": "SSL", + "form.database.item.authenticationDatabase": "Authenticatie Database", + "form.database.item.default": "Zet een standaard connectie", + "form.database.item.provider.mongo": "Mongo", + "form.database.item.provider.postgres": "PostgresSQL", + "form.database.item.provider.mysql": "MySQL", + "form.database.item.provider.redis": "Redis", + + "form.application.name": "Applicatie", + "form.application.description": "Configureer jouw applicatie instellingen", + + "form.application.item.name": "Naam", + "form.application.item.description": "Beschrijving", + "form.application.item.version": "Versie", + + "form.advanced.name": "Geavanceerd", + "form.advanced.description": "Configureer je geavanceerde instellingen", + + "form.advanced.item.admin": "Admin dashboard url", + "form.advanced.item.prefix": "Prefix API", + + "form.request.name": "Request", + "form.request.description": "Configureer je request instellingen.", + "form.request.item.parser": "Parser", + "form.request.item.parser.multipart": "Parser Multipart", + "form.request.item.prefix": "Prefix", + "form.request.item.prefix.prefix": "Prefix", + "form.request.item.logger": "Logger", + "form.request.item.logger.level": "Level", + "form.request.item.logger.exposeInContext": "Expose in context", + "form.request.item.logger.requests": "Requests", + "form.request.item.router": "Router", + "form.request.item.router.prefix": "Prefix", + + "form.response.name": "Response", + "form.response.description": "Configureer je response instellingen.", + "form.response.item.gzip.enabled": "Gzip", + "form.response.item.responseTime.enabled": "Response Tijd", + + "form.security.name": "Beveiliging", + "form.security.description": "Configureer je beveiliging instellingen.", + + "form.security.item.csrf": "CSRF", + "form.security.item.p3p": "P3P", + "form.security.item.p3p.value": "Waarde", + "form.security.item.hsts": "HOSTS", + "form.security.item.csrf.key": "Key", + "form.security.item.csrf.secret": "Secret", + "form.security.item.csrf.cookie": "Cookie", + "form.security.item.csrf.angular": "Angular", + "form.security.item.hsts.maxAge": "Max Leeftijd", + "form.security.item.hsts.includeSubDomains": "Inclusief Sub Domain", + "form.security.item.hsts.preload": "Preload", + + "form.security.item.session": "Sessie", + "form.security.item.session.key": "Secret key", + "form.security.item.session.maxAge": "Maximum leeftijd", + + "form.security.item.xframe": "Xframe", + "form.security.item.xframe.value": "Options", + "form.security.item.xframe.deny": "DENY", + "form.security.item.xframe.sameorigin": "SAMEORIGIN", + "form.security.item.xframe.allow-from": "ALLOW-FROM", + + "form.security.item.xssProtection": "xss Protection", + "form.security.item.xssProtection.mode": "Mode", + + "form.security.item.cors": "Cors", + "form.security.item.cors.origin": "Origin", + + "form.server.name": "Server", + "form.server.description": "Configureer je server instellingen.", + + "form.server.item.host": "Host", + "form.server.item.port": "Poort", + "form.server.item.cron": "Cron", + + "form.language.name": "Talen", + "form.language.description": "Configureer je talen.", + "form.language.choose": "Kies een taal:", + + "request.error.database.exist": "Deze verbinding bestaat al", + "request.error.database.unknow": "Geen verbinding gevonden", + "request.error.type.string": "Een tekst is verplicht.", + "request.error.type.number": "Een getal is verplicht.", + "request.error.type.boolean": "Een boolean is verplicht.", + "request.error.type.select": "De waarde moet in de vooraangegeven lijst zitten.", + + "request.error.validation.required": "Deze waarde is verplicht.", + "request.error.validation.regex": "De waarde voldoet niet aan de regex.", + "request.error.validation.max": "De waarde is te hoog.", + "request.error.validation.min": "De waarde is te laag.", + "request.error.validation.maxLength": "De waarde is te lang.", + "request.error.validation.minLength": "De waarde is te kort.", + + "request.error.config": "Configuratie bestand bestaat niet.", + "request.error.environment.required": "Omgeving is verplicht.", + "request.error.environment.unknow": "Omgeving is onbekend.", + "request.error.languages.exist": "Deze taal bestaat al.", + "request.error.languages.unknow": "Deze taal bestaat niet.", + "request.error.languages.incorrect": "Deze taal is incorrect.", + + "list.languages.button.label": "Nieuwe taal toevoegen", + "list.languages.title.singular": "taal is beschikbaar", + "list.languages.title.plural": "talen zijn beschikbaar", + "list.languages.default.languages": "Standaard taal", + "list.languages.set.languages": "Zet als standaard", + "list.databases.button.label": "Nieuwe verbinding", + "list.databases.title.singular": "verbinding in deze omgeving", + "list.databases.title.plural": "verbindingen in deze omgeving", + + "popUpWarning.title": "Ga a.u.b. akkoord", + "popUpWarning.databases.danger.message": "Content Types zijn nog gelinked met deze omgeving. Door deze te verwijderen kunnen er fouten ontstaan. Pas op...", + "popUpWarning.danger.ok.message": "Ik begrijp het", + "popUpWarning.databases.delete.message": "Weet je zeker dat je deze database wilt verwijderen?", + "popUpWarning.languages.delete.message": "Weet je zeker dat je deze taal wilt verwijderen?", + "strapi.notification.info.settingsEqual": "Instellingen zijn gelijk", + "strapi.notification.success.databaseDelete": "De database is succesvol verwijderd.", + "strapi.notification.success.languageDelete": "De taal is succesvol verwijderd.", + "strapi.notification.success.languageAdd": "De taal is succesvol toegevoegd.", + "strapi.notification.success.databaseAdd": "De database is succesvol toegevoegd.", + "strapi.notification.success.databaseEdit": "De database instellingen zijn succesvol aangepast.", + "strapi.notification.success.databaseDeleted": "De database is verwijderd.", + "strapi.notification.success.settingsEdit": "De instellingen zijn succesvol geüpdatet", + "strapi.notification.error": "Er is een fout opgetreden", + "strapi.notification.info.serverRestart": "De server zal opnieuw opstarten", + + "language.af": "Afrikaans", + "language.af_NA": "Afrikaans (Namibië)", + "language.af_ZA": "Afrikaans (Suid-Afrika)", + "language.agq": "Aghem", + "language.agq_CM": "Aghem (Kàmàlûŋ)", + "language.ak": "Akan", + "language.ak_GH": "Akan (Gaana)", + "language.am": "አማርኛ", + "language.am_ET": "አማርኛ (ኢትዮጵያ)", + "language.ar": "العربية", + "language.ar_001": "العربية (العالم)", + "language.ar_AE": "العربية (الإمارات العربية المتحدة)", + "language.ar_BH": "العربية (البحرين)", + "language.ar_DZ": "العربية (الجزائر)", + "language.ar_EG": "العربية (مصر)", + "language.ar_IQ": "العربية (العراق)", + "language.ar_JO": "العربية (الأردن)", + "language.ar_KW": "العربية (الكويت)", + "language.ar_LB": "العربية (لبنان)", + "language.ar_LY": "العربية (ليبيا)", + "language.ar_MA": "العربية (المغرب)", + "language.ar_OM": "العربية (عُمان)", + "language.ar_QA": "العربية (قطر)", + "language.ar_SA": "العربية (المملكة العربية السعودية)", + "language.ar_SD": "العربية (السودان)", + "language.ar_SY": "العربية (سوريا)", + "language.ar_TN": "العربية (تونس)", + "language.ar_YE": "العربية (اليمن)", + "language.as": "অসমীয়া", + "language.as_IN": "অসমীয়া (ভাৰত)", + "language.asa": "Kipare", + "language.asa_TZ": "Kipare (Tadhania)", + "language.az": "azərbaycanca", + "language.az_Cyrl": "Азәрбајҹан (kiril)", + "language.az_Cyrl_AZ": "Азәрбајҹан (kiril, Азәрбајҹан)", + "language.az_Latn": "azərbaycanca (latın)", + "language.az_Latn_AZ": "azərbaycanca (latın, Azərbaycan)", + "language.bas": "Ɓàsàa", + "language.bas_CM": "Ɓàsàa (Kàmɛ̀rûn)", + "language.be": "беларуская", + "language.be_BY": "беларуская (Беларусь)", + "language.bem": "Ichibemba", + "language.bem_ZM": "Ichibemba (Zambia)", + "language.bez": "Hibena", + "language.bez_TZ": "Hibena (Hutanzania)", + "language.bg": "български", + "language.bg_BG": "български (България)", + "language.bm": "bamanakan", + "language.bm_ML": "bamanakan (Mali)", + "language.bn": "বাংলা", + "language.bn_BD": "বাংলা (বাংলাদেশ)", + "language.bn_IN": "বাংলা (ভারত)", + "language.bo": "པོད་སྐད་", + "language.bo_CN": "པོད་སྐད་ (རྒྱ་ནག)", + "language.bo_IN": "པོད་སྐད་ (རྒྱ་གར་)", + "language.br": "brezhoneg", + "language.br_FR": "brezhoneg (Frañs)", + "language.brx": "बड़ो", + "language.brx_IN": "बड़ो (भारत)", + "language.bs": "bosanski", + "language.bs_BA": "bosanski (Bosna i Hercegovina)", + "language.ca": "català", + "language.ca_ES": "català (Espanya)", + "language.cgg": "Rukiga", + "language.cgg_UG": "Rukiga (Uganda)", + "language.chr": "ᏣᎳᎩ", + "language.chr_US": "ᏣᎳᎩ (ᎠᎹᏰᏟ)", + "language.cs": "čeština", + "language.cs_CZ": "čeština (Česká republika)", + "language.cy": "Cymraeg", + "language.cy_GB": "Cymraeg (Prydain Fawr)", + "language.da": "dansk", + "language.da_DK": "dansk (Danmark)", + "language.dav": "Kitaita", + "language.dav_KE": "Kitaita (Kenya)", + "language.de": "Deutsch", + "language.de_AT": "Deutsch (Österreich)", + "language.de_BE": "Deutsch (Belgien)", + "language.de_CH": "Deutsch (Schweiz)", + "language.de_DE": "Deutsch (Deutschland)", + "language.de_LI": "Deutsch (Liechtenstein)", + "language.de_LU": "Deutsch (Luxemburg)", + "language.dje": "Zarmaciine", + "language.dje_NE": "Zarmaciine (Nižer)", + "language.dua": "duálá", + "language.dua_CM": "duálá (Cameroun)", + "language.dyo": "joola", + "language.dyo_SN": "joola (Senegal)", + "language.ebu": "Kĩembu", + "language.ebu_KE": "Kĩembu (Kenya)", + "language.ee": "eʋegbe", + "language.ee_GH": "eʋegbe (Ghana nutome)", + "language.ee_TG": "eʋegbe (Togo nutome)", + "language.el": "Ελληνικά", + "language.el_CY": "Ελληνικά (Κύπρος)", + "language.el_GR": "Ελληνικά (Ελλάδα)", + "language.en": "English", + "language.en_AS": "English (American Samoa)", + "language.en_AU": "English (Australia)", + "language.en_BB": "English (Barbados)", + "language.en_BE": "English (Belgium)", + "language.en_BM": "English (Bermuda)", + "language.en_BW": "English (Botswana)", + "language.en_BZ": "English (Belize)", + "language.en_CA": "English (Canada)", + "language.en_GB": "English (United Kingdom)", + "language.en_GU": "English (Guam)", + "language.en_GY": "English (Guyana)", + "language.en_HK": "English (Hong Kong SAR China)", + "language.en_IE": "English (Ireland)", + "language.en_IN": "English (India)", + "language.en_JM": "English (Jamaica)", + "language.en_MH": "English (Marshall Islands)", + "language.en_MP": "English (Northern Mariana Islands)", + "language.en_MT": "English (Malta)", + "language.en_MU": "English (Mauritius)", + "language.en_NA": "English (Namibia)", + "language.en_NZ": "English (New Zealand)", + "language.en_PH": "English (Philippines)", + "language.en_PK": "English (Pakistan)", + "language.en_SG": "English (Singapore)", + "language.en_TT": "English (Trinidad and Tobago)", + "language.en_UM": "English (U.S. Minor Outlying Islands)", + "language.en_US": "English (United States)", + "language.en_US_POSIX": "English (United States, Computer)", + "language.en_VI": "English (U.S. Virgin Islands)", + "language.en_ZA": "English (South Africa)", + "language.en_ZW": "English (Zimbabwe)", + "language.eo": "esperanto", + "language.es": "español", + "language.es_419": "español (Latinoamérica)", + "language.es_AR": "español (Argentina)", + "language.es_BO": "español (Bolivia)", + "language.es_CL": "español (Chile)", + "language.es_CO": "español (Colombia)", + "language.es_CR": "español (Costa Rica)", + "language.es_DO": "español (República Dominicana)", + "language.es_EC": "español (Ecuador)", + "language.es_ES": "español (España)", + "language.es_GQ": "español (Guinea Ecuatorial)", + "language.es_GT": "español (Guatemala)", + "language.es_HN": "español (Honduras)", + "language.es_MX": "español (México)", + "language.es_NI": "español (Nicaragua)", + "language.es_PA": "español (Panamá)", + "language.es_PE": "español (Perú)", + "language.es_PR": "español (Puerto Rico)", + "language.es_PY": "español (Paraguay)", + "language.es_SV": "español (El Salvador)", + "language.es_US": "español (Estados Unidos)", + "language.es_UY": "español (Uruguay)", + "language.es_VE": "español (Venezuela)", + "language.et": "eesti", + "language.et_EE": "eesti (Eesti)", + "language.eu": "euskara", + "language.eu_ES": "euskara (Espainia)", + "language.ewo": "ewondo", + "language.ewo_CM": "ewondo (Kamǝrún)", + "language.fa": "فارسی", + "language.fa_AF": "دری (افغانستان)", + "language.fa_IR": "فارسی (ایران)", + "language.ff": "Pulaar", + "language.ff_SN": "Pulaar (Senegaal)", + "language.fi": "suomi", + "language.fi_FI": "suomi (Suomi)", + "language.fil": "Filipino", + "language.fil_PH": "Filipino (Pilipinas)", + "language.fo": "føroyskt", + "language.fo_FO": "føroyskt (Føroyar)", + "language.fr": "français", + "language.fr_BE": "français (Belgique)", + "language.fr_BF": "français (Burkina Faso)", + "language.fr_BI": "français (Burundi)", + "language.fr_BJ": "français (Bénin)", + "language.fr_BL": "français (Saint-Barthélémy)", + "language.fr_CA": "français (Canada)", + "language.fr_CD": "français (République démocratique du Congo)", + "language.fr_CF": "français (République centrafricaine)", + "language.fr_CG": "français (Congo-Brazzaville)", + "language.fr_CH": "français (Suisse)", + "language.fr_CI": "français (Côte d’Ivoire)", + "language.fr_CM": "français (Cameroun)", + "language.fr_DJ": "français (Djibouti)", + "language.fr_FR": "français (France)", + "language.fr_GA": "français (Gabon)", + "language.fr_GF": "français (Guyane française)", + "language.fr_GN": "français (Guinée)", + "language.fr_GP": "français (Guadeloupe)", + "language.fr_GQ": "français (Guinée équatoriale)", + "language.fr_KM": "français (Comores)", + "language.fr_LU": "français (Luxembourg)", + "language.fr_MC": "français (Monaco)", + "language.fr_MF": "français (Saint-Martin)", + "language.fr_MG": "français (Madagascar)", + "language.fr_ML": "français (Mali)", + "language.fr_MQ": "français (Martinique)", + "language.fr_NE": "français (Niger)", + "language.fr_RE": "français (Réunion)", + "language.fr_RW": "français (Rwanda)", + "language.fr_SN": "français (Sénégal)", + "language.fr_TD": "français (Tchad)", + "language.fr_TG": "français (Togo)", + "language.fr_YT": "français (Mayotte)", + "language.ga": "Gaeilge", + "language.ga_IE": "Gaeilge (Éire)", + "language.gl": "galego", + "language.gl_ES": "galego (España)", + "language.gsw": "Schwiizertüütsch", + "language.gsw_CH": "Schwiizertüütsch (Schwiiz)", + "language.gu": "ગુજરાતી", + "language.gu_IN": "ગુજરાતી (ભારત)", + "language.guz": "Ekegusii", + "language.guz_KE": "Ekegusii (Kenya)", + "language.gv": "Gaelg", + "language.gv_GB": "Gaelg (Rywvaneth Unys)", + "language.ha": "Hausa", + "language.ha_Latn": "Hausa (Latn)", + "language.ha_Latn_GH": "Hausa (Latn, Gana)", + "language.ha_Latn_NE": "Hausa (Latn, Nijar)", + "language.ha_Latn_NG": "Hausa (Latn, Najeriya)", + "language.haw": "ʻŌlelo Hawaiʻi", + "language.haw_US": "ʻŌlelo Hawaiʻi (ʻAmelika Hui Pū ʻIa)", + "language.he": "עברית", + "language.he_IL": "עברית (ישראל)", + "language.hi": "हिन्दी", + "language.hi_IN": "हिन्दी (भारत)", + "language.hr": "hrvatski", + "language.hr_HR": "hrvatski (Hrvatska)", + "language.hu": "magyar", + "language.hu_HU": "magyar (Magyarország)", + "language.hy": "Հայերէն", + "language.hy_AM": "Հայերէն (Հայաստանի Հանրապետութիւն)", + "language.id": "Bahasa Indonesia", + "language.id_ID": "Bahasa Indonesia (Indonesia)", + "language.ig": "Igbo", + "language.ig_NG": "Igbo (Nigeria)", + "language.ii": "ꆈꌠꉙ", + "language.ii_CN": "ꆈꌠꉙ (ꍏꇩ)", + "language.is": "íslenska", + "language.is_IS": "íslenska (Ísland)", + "language.it": "italiano", + "language.it_CH": "italiano (Svizzera)", + "language.it_IT": "italiano (Italia)", + "language.ja": "日本語", + "language.ja_JP": "日本語(日本)", + "language.jmc": "Kimachame", + "language.jmc_TZ": "Kimachame (Tanzania)", + "language.ka": "ქართული", + "language.ka_GE": "ქართული (საქართველო)", + "language.kab": "Taqbaylit", + "language.kab_DZ": "Taqbaylit (Lezzayer)", + "language.kam": "Kikamba", + "language.kam_KE": "Kikamba (Kenya)", + "language.kde": "Chimakonde", + "language.kde_TZ": "Chimakonde (Tanzania)", + "language.kea": "kabuverdianu", + "language.kea_CV": "kabuverdianu (Kabu Verdi)", + "language.khq": "Koyra ciini", + "language.khq_ML": "Koyra ciini (Maali)", + "language.ki": "Gikuyu", + "language.ki_KE": "Gikuyu (Kenya)", + "language.kk": "қазақ тілі", + "language.kk_Cyrl": "қазақ тілі (кириллица)", + "language.kk_Cyrl_KZ": "қазақ тілі (кириллица, Қазақстан)", + "language.kl": "kalaallisut", + "language.kl_GL": "kalaallisut (Kalaallit Nunaat)", + "language.kln": "Kalenjin", + "language.kln_KE": "Kalenjin (Emetab Kenya)", + "language.km": "ភាសាខ្មែរ", + "language.km_KH": "ភាសាខ្មែរ (កម្ពុជា)", + "language.kn": "ಕನ್ನಡ", + "language.kn_IN": "ಕನ್ನಡ (ಭಾರತ)", + "language.ko": "한국어", + "language.ko_KR": "한국어(대한민국)", + "language.kok": "कोंकणी", + "language.kok_IN": "कोंकणी (भारत)", + "language.ksb": "Kishambaa", + "language.ksb_TZ": "Kishambaa (Tanzania)", + "language.ksf": "rikpa", + "language.ksf_CM": "rikpa (kamɛrún)", + "language.kw": "kernewek", + "language.kw_GB": "kernewek (Rywvaneth Unys)", + "language.lag": "Kɨlaangi", + "language.lag_TZ": "Kɨlaangi (Taansanía)", + "language.lg": "Luganda", + "language.lg_UG": "Luganda (Yuganda)", + "language.ln": "lingála", + "language.ln_CD": "lingála (Repibiki demokratiki ya Kongó)", + "language.ln_CG": "lingála (Kongo)", + "language.lt": "lietuvių", + "language.lt_LT": "lietuvių (Lietuva)", + "language.lu": "Tshiluba", + "language.lu_CD": "Tshiluba (Ditunga wa Kongu)", + "language.luo": "Dholuo", + "language.luo_KE": "Dholuo (Kenya)", + "language.luy": "Luluhia", + "language.luy_KE": "Luluhia (Kenya)", + "language.lv": "latviešu", + "language.lv_LV": "latviešu (Latvija)", + "language.mas": "Maa", + "language.mas_KE": "Maa (Kenya)", + "language.mas_TZ": "Maa (Tansania)", + "language.mer": "Kĩmĩrũ", + "language.mer_KE": "Kĩmĩrũ (Kenya)", + "language.mfe": "kreol morisien", + "language.mfe_MU": "kreol morisien (Moris)", + "language.mg": "Malagasy", + "language.mg_MG": "Malagasy (Madagasikara)", + "language.mgh": "Makua", + "language.mgh_MZ": "Makua (Umozambiki)", + "language.mk": "македонски", + "language.mk_MK": "македонски (Македонија)", + "language.ml": "മലയാളം", + "language.ml_IN": "മലയാളം (ഇന്ത്യ)", + "language.mr": "मराठी", + "language.mr_IN": "मराठी (भारत)", + "language.ms": "Bahasa Melayu", + "language.ms_BN": "Bahasa Melayu (Brunei)", + "language.ms_MY": "Bahasa Melayu (Malaysia)", + "language.mt": "Malti", + "language.mt_MT": "Malti (Malta)", + "language.mua": "MUNDAŊ", + "language.mua_CM": "MUNDAŊ (kameruŋ)", + "language.my": "ဗမာ", + "language.my_MM": "ဗမာ (မြန်မာ)", + "language.naq": "Khoekhoegowab", + "language.naq_NA": "Khoekhoegowab (Namibiab)", + "language.nb": "norsk bokmål", + "language.nb_NO": "norsk bokmål (Norge)", + "language.nd": "isiNdebele", + "language.nd_ZW": "isiNdebele (Zimbabwe)", + "language.ne": "नेपाली", + "language.ne_IN": "नेपाली (भारत)", + "language.ne_NP": "नेपाली (नेपाल)", + "language.nl": "Nederlands", + "language.nl_AW": "Nederlands (Aruba)", + "language.nl_BE": "Nederlands (België)", + "language.nl_CW": "Nederlands (Curaçao)", + "language.nl_NL": "Nederlands (Nederland)", + "language.nl_SX": "Nederlands (Sint Maarten)", + "language.nmg": "nmg", + "language.nmg_CM": "nmg (Kamerun)", + "language.nn": "nynorsk", + "language.nn_NO": "nynorsk (Noreg)", + "language.nus": "Thok Nath", + "language.nus_SD": "Thok Nath (Sudan)", + "language.nyn": "Runyankore", + "language.nyn_UG": "Runyankore (Uganda)", + "language.om": "Oromoo", + "language.om_ET": "Oromoo (Itoophiyaa)", + "language.om_KE": "Oromoo (Keeniyaa)", + "language.or": "ଓଡ଼ିଆ", + "language.or_IN": "ଓଡ଼ିଆ (ଭାରତ)", + "language.pa": "ਪੰਜਾਬੀ", + "language.pa_Arab": "پنجاب (العربية)", + "language.pa_Arab_PK": "پنجاب (العربية, پکستان)", + "language.pa_Guru": "ਪੰਜਾਬੀ (Guru)", + "language.pa_Guru_IN": "ਪੰਜਾਬੀ (Guru, ਭਾਰਤ)", + "language.pl": "polski", + "language.pl_PL": "polski (Polska)", + "language.ps": "پښتو", + "language.ps_AF": "پښتو (افغانستان)", + "language.pt": "português", + "language.pt_AO": "português (Angola)", + "language.pt_BR": "português (Brasil)", + "language.pt_GW": "português (Guiné Bissau)", + "language.pt_MZ": "português (Moçambique)", + "language.pt_PT": "português (Portugal)", + "language.pt_ST": "português (São Tomé e Príncipe)", + "language.rm": "rumantsch", + "language.rm_CH": "rumantsch (Svizra)", + "language.rn": "Ikirundi", + "language.rn_BI": "Ikirundi (Uburundi)", + "language.ro": "română", + "language.ro_MD": "română (Republica Moldova)", + "language.ro_RO": "română (România)", + "language.rof": "Kihorombo", + "language.rof_TZ": "Kihorombo (Tanzania)", + "language.ru": "русский", + "language.ru_MD": "русский (Молдова)", + "language.ru_RU": "русский (Россия)", + "language.ru_UA": "русский (Украина)", + "language.rw": "Kinyarwanda", + "language.rw_RW": "Kinyarwanda (Rwanda)", + "language.rwk": "Kiruwa", + "language.rwk_TZ": "Kiruwa (Tanzania)", + "language.saq": "Kisampur", + "language.saq_KE": "Kisampur (Kenya)", + "language.sbp": "Ishisangu", + "language.sbp_TZ": "Ishisangu (Tansaniya)", + "language.seh": "sena", + "language.seh_MZ": "sena (Moçambique)", + "language.ses": "Koyraboro senni", + "language.ses_ML": "Koyraboro senni (Maali)", + "language.sg": "Sängö", + "language.sg_CF": "Sängö (Ködörösêse tî Bêafrîka)", + "language.shi": "tamazight", + "language.shi_Latn": "tamazight (Latn)", + "language.shi_Latn_MA": "tamazight (Latn, lmɣrib)", + "language.shi_Tfng": "ⵜⴰⵎⴰⵣⵉⵖⵜ (Tfng)", + "language.shi_Tfng_MA": "ⵜⴰⵎⴰⵣⵉⵖⵜ (Tfng, ⵍⵎⵖⵔⵉⴱ)", + "language.si": "සිංහල", + "language.si_LK": "සිංහල (ශ්‍රී ලංකාව)", + "language.sk": "slovenčina", + "language.sk_SK": "slovenčina (Slovenská republika)", + "language.sl": "slovenščina", + "language.sl_SI": "slovenščina (Slovenija)", + "language.sn": "chiShona", + "language.sn_ZW": "chiShona (Zimbabwe)", + "language.so": "Soomaali", + "language.so_DJ": "Soomaali (Jabuuti)", + "language.so_ET": "Soomaali (Itoobiya)", + "language.so_KE": "Soomaali (Kiiniya)", + "language.so_SO": "Soomaali (Soomaaliya)", + "language.sq": "shqip", + "language.sq_AL": "shqip (Shqipëria)", + "language.sr": "Српски", + "language.sr_Cyrl": "Српски (Ћирилица)", + "language.sr_Cyrl_BA": "Српски (Ћирилица, Босна и Херцеговина)", + "language.sr_Cyrl_ME": "Српски (Ћирилица, Црна Гора)", + "language.sr_Cyrl_RS": "Српски (Ћирилица, Србија)", + "language.sr_Latn": "Srpski (Latinica)", + "language.sr_Latn_BA": "Srpski (Latinica, Bosna i Hercegovina)", + "language.sr_Latn_ME": "Srpski (Latinica, Crna Gora)", + "language.sr_Latn_RS": "Srpski (Latinica, Srbija)", + "language.sv": "svenska", + "language.sv_FI": "svenska (Finland)", + "language.sv_SE": "svenska (Sverige)", + "language.sw": "Kiswahili", + "language.sw_KE": "Kiswahili (Kenya)", + "language.sw_TZ": "Kiswahili (Tanzania)", + "language.swc": "Kiswahili ya Kongo", + "language.swc_CD": "Kiswahili ya Kongo (Jamhuri ya Kidemokrasia ya Kongo)", + "language.ta": "தமிழ்", + "language.ta_IN": "தமிழ் (இந்தியா)", + "language.ta_LK": "தமிழ் (இலங்கை)", + "language.te": "తెలుగు", + "language.te_IN": "తెలుగు (భారత దేశం)", + "language.teo": "Kiteso", + "language.teo_KE": "Kiteso (Kenia)", + "language.teo_UG": "Kiteso (Uganda)", + "language.th": "ไทย", + "language.th_TH": "ไทย (ไทย)", + "language.ti": "ትግርኛ", + "language.ti_ER": "ትግርኛ (ER)", + "language.ti_ET": "ትግርኛ (ET)", + "language.to": "lea fakatonga", + "language.to_TO": "lea fakatonga (Tonga)", + "language.tr": "Türkçe", + "language.tr_TR": "Türkçe (Türkiye)", + "language.twq": "Tasawaq senni", + "language.twq_NE": "Tasawaq senni (Nižer)", + "language.tzm": "Tamaziɣt", + "language.tzm_Latn": "Tamaziɣt (Latn)", + "language.tzm_Latn_MA": "Tamaziɣt (Latn, Meṛṛuk)", + "language.uk": "українська", + "language.uk_UA": "українська (Україна)", + "language.ur": "اردو", + "language.ur_IN": "اردو (بھارت)", + "language.ur_PK": "اردو (پاکستان)", + "language.uz": "Ўзбек", + "language.uz_Arab": "اۉزبېک (Arab)", + "language.uz_Arab_AF": "اۉزبېک (Arab, افغانستان)", + "language.uz_Cyrl": "Ўзбек (Cyrl)", + "language.uz_Cyrl_UZ": "Ўзбек (Cyrl, Ўзбекистон)", + "language.uz_Latn": "oʼzbekcha (Lotin)", + "language.uz_Latn_UZ": "oʼzbekcha (Lotin, Oʼzbekiston)", + "language.vai": "ꕙꔤ", + "language.vai_Latn": "Vai (Latn)", + "language.vai_Latn_LR": "Vai (Latn, Laibhiya)", + "language.vai_Vaii": "ꕙꔤ (Vaii)", + "language.vai_Vaii_LR": "ꕙꔤ (Vaii, ꕞꔤꔫꕩ)", + "language.vi": "Tiếng Việt", + "language.vi_VN": "Tiếng Việt (Việt Nam)", + "language.vun": "Kyivunjo", + "language.vun_TZ": "Kyivunjo (Tanzania)", + "language.xog": "Olusoga", + "language.xog_UG": "Olusoga (Yuganda)", + "language.yav": "nuasue", + "language.yav_CM": "nuasue (Kemelún)", + "language.yo": "Èdè Yorùbá", + "language.yo_NG": "Èdè Yorùbá (Orílẹ́ède Nàìjíríà)", + "language.zh": "中文", + "language.zh_Hans": "中文(简体中文)", + "language.zh_Hans_CN": "中文(简体中文、中国)", + "language.zh_Hans_HK": "中文(简体中文、中国香港特别行政区)", + "language.zh_Hans_MO": "中文(简体中文、中国澳门特别行政区)", + "language.zh_Hans_SG": "中文(简体中文、新加坡)", + "language.zh_Hant": "中文(繁體中文)", + "language.zh_Hant_HK": "中文(繁體中文,中華人民共和國香港特別行政區)", + "language.zh_Hant_MO": "中文(繁體中文,中華人民共和國澳門特別行政區)", + "language.zh_Hant_TW": "中文(繁體中文,台灣)", + "language.zu": "isiZulu", + "language.zu_ZA": "isiZulu (iNingizimu Afrika)", + + "pageNotFound": "Pagina niet gevonden" +} diff --git a/packages/strapi-plugin-settings-manager/admin/src/translations/ru.json b/packages/strapi-plugin-settings-manager/admin/src/translations/ru.json index f731174184..88519ec44c 100644 --- a/packages/strapi-plugin-settings-manager/admin/src/translations/ru.json +++ b/packages/strapi-plugin-settings-manager/admin/src/translations/ru.json @@ -35,6 +35,8 @@ "form.database.item.provider.postgres": "PostgresSQL", "form.database.item.provider.mysql": "MySQL", "form.database.item.provider.redis": "Redis", + "form.database.item.ssl": "SSL", + "form.database.item.authenticationDatabase": "База данных аутентификации", "form.application.name": "Приложение", "form.application.description": "Зайдайте настройки вашего приложения.", diff --git a/packages/strapi-plugin-settings-manager/controllers/SettingsManager.js b/packages/strapi-plugin-settings-manager/controllers/SettingsManager.js index a5f29d7458..247f84551d 100755 --- a/packages/strapi-plugin-settings-manager/controllers/SettingsManager.js +++ b/packages/strapi-plugin-settings-manager/controllers/SettingsManager.js @@ -334,8 +334,8 @@ module.exports = { autoReload: async ctx => { ctx.send({ - autoReload: _.get(strapi.config.environments, 'development.server.autoReload', false), - environment: strapi.config.environment, + autoReload: _.get(strapi.config.currentEnvironment, 'server.autoReload', { enabled: false }), + environment: strapi.config.environment }); } }; diff --git a/packages/strapi-plugin-settings-manager/package.json b/packages/strapi-plugin-settings-manager/package.json index 32b00f8dac..b09204640b 100755 --- a/packages/strapi-plugin-settings-manager/package.json +++ b/packages/strapi-plugin-settings-manager/package.json @@ -1,6 +1,6 @@ { "name": "strapi-plugin-settings-manager", - "version": "3.0.0-alpha.12.7.1", + "version": "3.0.0-alpha.14", "description": "Strapi plugin to manage settings.", "strapi": { "name": "Settings Manager", @@ -25,7 +25,7 @@ "devDependencies": { "flag-icon-css": "^2.8.0", "react-select": "^1.0.0-rc.5", - "strapi-helper-plugin": "3.0.0-alpha.12.7.1" + "strapi-helper-plugin": "3.0.0-alpha.14" }, "author": { "name": "Strapi team", diff --git a/packages/strapi-plugin-settings-manager/services/SettingsManager.js b/packages/strapi-plugin-settings-manager/services/SettingsManager.js index 530b272d3b..8821a267d3 100755 --- a/packages/strapi-plugin-settings-manager/services/SettingsManager.js +++ b/packages/strapi-plugin-settings-manager/services/SettingsManager.js @@ -3,7 +3,7 @@ const fs = require('fs'); const path = require('path'); const _ = require('lodash'); -const exec = require('child_process').execSync; +const exec = require('child_process').spawnSync; module.exports = { menu: { @@ -452,6 +452,41 @@ module.exports = { value: _.get(strapi.config, `environments.${env}.server.cron.enabled`, null) } ] + }, + { + name: 'form.server.item.proxy', + items: [ + { + name: 'form.server.item.proxy.enable', + target: 'server.proxy.enabled', + type: 'boolean', + value: _.get(strapi.config, `environments.${env}.server.proxy.enabled`, null), + items: [ + { + name: 'form.server.item.proxy.host', + target: 'server.proxy.host', + type: 'string', + value: _.get(strapi.config, `environments.${env}.server.proxy.host`, null), + validations: {} + }, + { + name: 'form.server.item.proxy.port', + target: 'server.proxy.port', + type: 'number', + value: _.get(strapi.config, `environments.${env}.server.proxy.port`, null), + validations: {} + }, + { + name: 'form.server.item.proxy.ssl', + target: 'server.proxy.ssl', + type: 'boolean', + value: _.get(strapi.config, `environments.${env}.server.proxy.ssl`, null), + validations: {} + } + ], + validations: {} + } + ] } ] }), @@ -650,9 +685,9 @@ module.exports = { const redisClients = ['redis']; let connector; - if (_.indexOf(bookshelfClients, client) !== -1) connector = 'strapi-bookshelf'; - if (_.indexOf(mongooseClients, client) !== -1) connector = 'strapi-mongoose'; - if (_.indexOf(redisClients, client) !== -1) connector = 'strapi-redis'; + if (_.indexOf(bookshelfClients, client) !== -1) connector = 'strapi-hook-bookshelf'; + if (_.indexOf(mongooseClients, client) !== -1) connector = 'strapi-hook-mongoose'; + if (_.indexOf(redisClients, client) !== -1) connector = 'strapi-hook-redis'; return connector; }, @@ -901,17 +936,17 @@ module.exports = { if (connector && !installedConnector) { strapi.log.info(`Installing ${connector} dependency ...`); - exec(`npm install ${connector}@alpha`); + exec('npm', ['install', `${connector}@alpha`]); } if (client && !installedClient) { strapi.log.info(`Installing ${client} dependency ...`); - exec(`npm install ${client}`); + exec('npm', ['install', client]); } }, cleanDependency: (env, config) => { - const availableConnectors = ['strapi-mongoose', 'strapi-bookshelf', 'strapi-redis']; + const availableConnectors = ['strapi-hook-mongoose', 'strapi-hook-bookshelf', 'strapi-hook-redis']; let usedConnectors = []; const errors = []; diff --git a/packages/strapi-plugin-upload/.gitignore b/packages/strapi-plugin-upload/.gitignore index afe256bf30..2e39eb54ca 100644 --- a/packages/strapi-plugin-upload/.gitignore +++ b/packages/strapi-plugin-upload/.gitignore @@ -3,6 +3,7 @@ coverage node_modules stats.json package-lock.json +yarn.lock # Cruft .DS_Store diff --git a/packages/strapi-plugin-upload/admin/src/components/Li/index.js b/packages/strapi-plugin-upload/admin/src/components/Li/index.js index e602c7ea77..9d7be4bcfd 100644 --- a/packages/strapi-plugin-upload/admin/src/components/Li/index.js +++ b/packages/strapi-plugin-upload/admin/src/components/Li/index.js @@ -34,13 +34,13 @@ class Li extends React.Component { let divider; switch (true) { - case value > 10000: + case value > 1000000: unit = 'GB'; - divider = 1000; + divider = 1000000; break; case value < 1: unit = 'B'; - divider = 1; + divider = .001; break; case value > 1000: unit = 'MB'; diff --git a/packages/strapi-plugin-upload/admin/src/injectedComponents.js b/packages/strapi-plugin-upload/admin/src/injectedComponents.js new file mode 100644 index 0000000000..109fa8b38c --- /dev/null +++ b/packages/strapi-plugin-upload/admin/src/injectedComponents.js @@ -0,0 +1 @@ +export default []; \ No newline at end of file diff --git a/packages/strapi-plugin-upload/admin/src/translations/de.json b/packages/strapi-plugin-upload/admin/src/translations/de.json index 60e21c6cf5..75c6e44d12 100644 --- a/packages/strapi-plugin-upload/admin/src/translations/de.json +++ b/packages/strapi-plugin-upload/admin/src/translations/de.json @@ -13,8 +13,8 @@ "EntriesNumber.number": "{number} Datei gefunden", "EntriesNumber.number.plural": "{number} Dateien gefunden", - "HomePage.title": "Hochladen", - "HomePage.description": "Übersicht über alle hochgeladenen Dateien", + "HomePage.title": "Dateien hochladen", + "HomePage.description": "Übersicht über alle hochgeladenen Dateien.", "HomePage.InputSearch.placeholder": "Suche nach einer Datei...", "Li.linkCopied": "Link in die Zwischenablage kopiert", @@ -28,8 +28,10 @@ "PluginInputFile.text": "Ziehe eine Datei hierher oder {link} eine Datei zum hochladen aus", "PluginInputFile.link": "wähle", + "PluginInputFile.loading": "Deine Dateien werden hochgeladen...", "notification.delete.success": "Die Datei wurde gelöscht", - "notification.dropFile.success": "Ihre Datei wurde hochgeladen", - "notification.dropFiles.success": "{number} Dateien wurden hochgeladen" + "notification.dropFile.success": "Deine Datei wurde hochgeladen", + "notification.dropFiles.success": "{number} Dateien wurden hochgeladen", + "notification.config.success": "Die Einstellungen wurden aktualisiert" } diff --git a/packages/strapi-plugin-upload/admin/src/translations/es.json b/packages/strapi-plugin-upload/admin/src/translations/es.json new file mode 100755 index 0000000000..e0bac6a16e --- /dev/null +++ b/packages/strapi-plugin-upload/admin/src/translations/es.json @@ -0,0 +1,36 @@ +{ + "ConfigPage.title": "Cargar - Configuración", + "ConfigPage.description": "Configurar el plugin de carga", + + "EditForm.Input.number.label": "Tamaño máximo permitido (en MB)", + "EditForm.Input.select.label": "Proveedores", + "EditForm.Input.select.inputDescription": "Los archivos se pueden cargar en su servidor o en proveedores externos.", + "EditForm.Input.toggle.label": "Habilitar la carga de archivos", + + "EmptyLi.message": "No hay archivos cargados", + + "EntriesNumber.number": "{number} archivo encontrado", + "EntriesNumber.number.plural": "{number} archivos encontrados", + + "HomePage.title": "Cargar", + "HomePage.description": "Descubre todos los archivos subidos", + "HomePage.InputSearch.placeholder": "Buscar un archivo...", + + "Li.linkCopied": "Enlace copiado en el portapapeles", + + "ListHeader.type": "Tipo", + "ListHeader.hash": "Hash", + "ListHeader.name": "Nombre", + "ListHeader.updated": "Actualizado", + "ListHeader.size": "Tamaño", + "ListHeader.related": "Relacionado con", + + "PluginInputFile.text": "Arrastre y suelte sus archivos en esta área o {link} desde un archivo para cargarlo", + "PluginInputFile.link": "buscar", + "PluginInputFile.loading": "Tus archivos están cargando....", + + "notification.config.success": "Se ha actualizado la configuración", + "notification.delete.success": "El archivo ha sido borrado", + "notification.dropFile.success": "Su archivo esta cargado", + "notification.dropFiles.success": "{number} archivos han sido cargados" +} diff --git a/packages/strapi-plugin-upload/admin/src/translations/fr.json b/packages/strapi-plugin-upload/admin/src/translations/fr.json index 5633e32553..562fb658ba 100755 --- a/packages/strapi-plugin-upload/admin/src/translations/fr.json +++ b/packages/strapi-plugin-upload/admin/src/translations/fr.json @@ -29,6 +29,7 @@ "PluginInputFile.link": "recherchez", "PluginInputFile.loading": "Vos fichiers sont en train d'être téléchargés...", + "notification.config.success": "Les paramètres ont été mis à jour.", "notification.delete.success": "Le fichier a bien été supprimé", "notification.dropFile.success": "Votre fichier a été téléchargé", "notification.dropFiles.success": "{number} fichiers ont été téléchargées" diff --git a/packages/strapi-plugin-upload/admin/src/translations/nl.json b/packages/strapi-plugin-upload/admin/src/translations/nl.json new file mode 100644 index 0000000000..3caa72c7d5 --- /dev/null +++ b/packages/strapi-plugin-upload/admin/src/translations/nl.json @@ -0,0 +1,36 @@ +{ + "ConfigPage.title": "Upload - Instellingen", + "ConfigPage.description": "Configureer de upload extensie", + + "EditForm.Input.number.label": "Maximale toegestane grootte (in MB)", + "EditForm.Input.select.label": "Leveranciers", + "EditForm.Input.select.inputDescription": "Bestanden kunnen of geüpload worden op jouw server of bij een externe leverancier", + "EditForm.Input.toggle.label": "Bestand upload inschakelen", + + "EmptyLi.message": "Er zijn geen geüploade bestanden", + + "EntriesNumber.number": "{number} bestand gevonden", + "EntriesNumber.number.plural": "{number} bestanden gevonden", + + "HomePage.title": "Upload", + "HomePage.description": "Bekijk alle geüploade bestanden", + "HomePage.InputSearch.placeholder": "Naar een bestand zoeken...", + + "Li.linkCopied": "Link gekopieerd naar klembord", + + "ListHeader.type": "Type", + "ListHeader.hash": "Hash", + "ListHeader.name": "Naam", + "ListHeader.updated": "Geüpdatet", + "ListHeader.size": "Grootte", + "ListHeader.related": "Gerelateerd aan", + + "PluginInputFile.text": "Sleep bestanden in dit gebied of {link} van een bestand om te uploaden", + "PluginInputFile.link": "doorbladeren", + "PluginInputFile.loading": "Je bestanden worden geüpload...", + + "notification.config.success": "De instellingen zijn geüpdatet", + "notification.delete.success": "Het bestand is verwijderd", + "notification.dropFile.success": "Je bestand is geüpload", + "notification.dropFiles.success": "{number} bestanden zijn geüpload" +} diff --git a/packages/strapi-plugin-upload/config/queries/bookshelf.js b/packages/strapi-plugin-upload/config/queries/bookshelf.js index a216816473..f5efcaabd4 100644 --- a/packages/strapi-plugin-upload/config/queries/bookshelf.js +++ b/packages/strapi-plugin-upload/config/queries/bookshelf.js @@ -3,8 +3,16 @@ const _ = require('lodash'); module.exports = { find: async function (params = {}, populate) { const records = await this.query(function(qb) { - _.forEach(params.where, (where, key) => { - qb.where(key, where[0].symbol, where[0].value); + Object.keys(params.where).forEach((key) => { + const where = params.where[key]; + + if (Array.isArray(where.value)) { + for (const value in where.value) { + qb[value ? 'where' : 'orWhere'](key, where.symbol, where.value[value]); + } + } else { + qb.where(key, where.symbol, where.value); + } }); if (params.sort) { diff --git a/packages/strapi-plugin-upload/controllers/Upload.js b/packages/strapi-plugin-upload/controllers/Upload.js index 28f6a22721..d811b5f510 100644 --- a/packages/strapi-plugin-upload/controllers/Upload.js +++ b/packages/strapi-plugin-upload/controllers/Upload.js @@ -24,7 +24,7 @@ module.exports = { } // Extract optional relational data. - const { refId, ref, source, field } = ctx.request.body.fields; + const { refId, ref, source, field, path } = ctx.request.body.fields; const { files = {} } = ctx.request.body.files; if (_.isEmpty(files)) { @@ -50,6 +50,13 @@ module.exports = { }); } + // Update uploading folder path for the file. + if (path) { + Object.assign(file, { + path + }); + } + return file; }); diff --git a/packages/strapi-plugin-upload/models/File.settings.json b/packages/strapi-plugin-upload/models/File.settings.json index 545aa63617..d0111a0d33 100644 --- a/packages/strapi-plugin-upload/models/File.settings.json +++ b/packages/strapi-plugin-upload/models/File.settings.json @@ -18,6 +18,10 @@ "configurable": false, "required": true }, + "sha256": { + "type": "string", + "configurable": false + }, "ext": { "type": "string", "configurable": false @@ -48,4 +52,4 @@ "configurable": false } } -} +} \ No newline at end of file diff --git a/packages/strapi-plugin-upload/package.json b/packages/strapi-plugin-upload/package.json index f61b96fc80..2b30707029 100644 --- a/packages/strapi-plugin-upload/package.json +++ b/packages/strapi-plugin-upload/package.json @@ -1,6 +1,6 @@ { "name": "strapi-plugin-upload", - "version": "3.0.0-alpha.12.7.1", + "version": "3.0.0-alpha.14", "description": "This is the description of the plugin.", "strapi": { "name": "Files Upload", @@ -23,12 +23,12 @@ }, "dependencies": { "react-copy-to-clipboard": "^5.0.1", - "strapi-upload-local": "3.0.0-alpha.12.7.1", + "strapi-upload-local": "3.0.0-alpha.14", "stream-to-array": "^2.3.0", "uuid": "^3.2.1" }, "devDependencies": { - "strapi-helper-plugin": "3.0.0-alpha.12.7.1" + "strapi-helper-plugin": "3.0.0-alpha.14" }, "author": { "name": "A Strapi developer", diff --git a/packages/strapi-plugin-upload/services/Upload.js b/packages/strapi-plugin-upload/services/Upload.js index cef660041e..b641ec3e1d 100644 --- a/packages/strapi-plugin-upload/services/Upload.js +++ b/packages/strapi-plugin-upload/services/Upload.js @@ -7,10 +7,20 @@ */ const fs = require('fs'); +const crypto = require('crypto'); const _ = require('lodash'); const toArray = require('stream-to-array'); const uuid = require('uuid/v4'); +function niceHash(buffer) { + return crypto.createHash('sha256') + .update(buffer) + .digest('base64') + .replace(/=/g, '') + .replace(/\//g, '-') + .replace(/\+/, '_'); +} + module.exports = { bufferize: async files => { if (_.isEmpty(files) === 0) { @@ -28,11 +38,14 @@ module.exports = { part => _.isBuffer(part) ? part : Buffer.from(part) ); + const buffer = Buffer.concat(buffers); + return { name: stream.name, + sha256: niceHash(buffer), hash: uuid().replace(/-/g, ''), ext: stream.name.split('.').length > 1 ? `.${_.last(stream.name.split('.'))}` : '', - buffer: Buffer.concat(buffers), + buffer, mime: stream.type, size: (stream.size / 1000).toFixed(2) }; @@ -101,6 +114,8 @@ module.exports = { }, remove: async (params, config) => { + params.id = (params._id || params.id); + const file = await strapi.plugins['upload'].services.upload.fetch(params); // get upload provider settings to configure the provider to use @@ -116,7 +131,6 @@ module.exports = { // Use Content Manager business logic to handle relation. if (strapi.plugins['content-manager']) { params.model = 'file'; - params.id = (params._id || params.id); await strapi.plugins['content-manager'].services['contentmanager'].delete(params, {source: 'upload'}); } diff --git a/packages/strapi-plugin-users-permissions/admin/src/components/EditForm/index.js b/packages/strapi-plugin-users-permissions/admin/src/components/EditForm/index.js index 52a607a4bc..6553f5fa0c 100644 --- a/packages/strapi-plugin-users-permissions/admin/src/components/EditForm/index.js +++ b/packages/strapi-plugin-users-permissions/admin/src/components/EditForm/index.js @@ -34,7 +34,7 @@ class EditForm extends React.Component { // eslint-disable-line react/prefer-sta
    ); } - + return (
    @@ -42,7 +42,7 @@ class EditForm extends React.Component { // eslint-disable-line react/prefer-sta inputDescription={{ id: 'users-permissions.EditForm.inputSelect.description.role' }} inputClassName={styles.inputStyle} label={{ id: 'users-permissions.EditForm.inputSelect.label.role' }} - name="settings.default_role" + name="advanced.settings.default_role" onChange={this.props.onChange} selectOptions={this.generateSelectOptions()} type="select" @@ -54,7 +54,7 @@ class EditForm extends React.Component { // eslint-disable-line react/prefer-sta
    +
    +
    + +
    +
    + +
    ); } diff --git a/packages/strapi-plugin-users-permissions/admin/src/components/PopUpForm/index.js b/packages/strapi-plugin-users-permissions/admin/src/components/PopUpForm/index.js index 7f9ccbb40c..8a981114a7 100644 --- a/packages/strapi-plugin-users-permissions/admin/src/components/PopUpForm/index.js +++ b/packages/strapi-plugin-users-permissions/admin/src/components/PopUpForm/index.js @@ -43,12 +43,16 @@ class PopUpForm extends React.Component { // eslint-disable-line react/prefer-st getRedirectURIProviderConf = () => { // NOTE: Still testings providers so the switch statement is likely to change switch (this.props.dataToEdit) { + case 'discord': + return `${strapi.backendURL}/connect/discord/callback`; case 'facebook': return `${strapi.backendURL}/connect/facebook/callback`; case 'google': return `${strapi.backendURL}/connect/google/callback`; case 'github': return get(this.props.values, 'redirect_uri', ''); + case 'microsoft': + return `${strapi.backendURL}/connect/microsoft/callback`; default: { const value = get(this.props.values, 'callback', ''); diff --git a/packages/strapi-plugin-users-permissions/admin/src/containers/AuthPage/actions.js b/packages/strapi-plugin-users-permissions/admin/src/containers/AuthPage/actions.js index 53d0a91bce..b9e70f52be 100644 --- a/packages/strapi-plugin-users-permissions/admin/src/containers/AuthPage/actions.js +++ b/packages/strapi-plugin-users-permissions/admin/src/containers/AuthPage/actions.js @@ -50,7 +50,7 @@ export function setForm(formType, email) { data = { identifier: '', password: '', - rememberMe: false, + rememberMe: true, }; break; @@ -60,7 +60,7 @@ export function setForm(formType, email) { password: '', confirmPassword: '', email: '', - news: true, + news: false, }; break; case 'register-success': diff --git a/packages/strapi-plugin-users-permissions/admin/src/containers/AuthPage/form.json b/packages/strapi-plugin-users-permissions/admin/src/containers/AuthPage/form.json index 075de9f189..08a23dbdc2 100644 --- a/packages/strapi-plugin-users-permissions/admin/src/containers/AuthPage/form.json +++ b/packages/strapi-plugin-users-permissions/admin/src/containers/AuthPage/form.json @@ -80,7 +80,7 @@ }, "name": "news", "type": "checkbox", - "value": true + "value": false } ], "register-success": [ diff --git a/packages/strapi-plugin-users-permissions/admin/src/containers/HomePage/saga.js b/packages/strapi-plugin-users-permissions/admin/src/containers/HomePage/saga.js index 0c3e887fd4..c5ff308f0e 100755 --- a/packages/strapi-plugin-users-permissions/admin/src/containers/HomePage/saga.js +++ b/packages/strapi-plugin-users-permissions/admin/src/containers/HomePage/saga.js @@ -1,5 +1,5 @@ import { LOCATION_CHANGE } from 'react-router-redux'; -import { findIndex } from 'lodash'; +import { findIndex, get } from 'lodash'; import { takeLatest, put, fork, take, cancel, select, call } from 'redux-saga/effects'; import request from 'utils/request'; @@ -64,7 +64,7 @@ export function* dataFetch(action) { export function* submitData(action) { try { const body = yield select(makeSelectModifiedData()); - const opts = { method: 'PUT', body: (action.endPoint === 'advanced') ? body.settings : body }; + const opts = { method: 'PUT', body: (action.endPoint === 'advanced') ? get(body, ['advanced', 'settings'], {}) : body }; yield call(request, `/users-permissions/${action.endPoint}`, opts); yield put(submitSucceeded()); diff --git a/packages/strapi-plugin-users-permissions/admin/src/injectedComponents.js b/packages/strapi-plugin-users-permissions/admin/src/injectedComponents.js new file mode 100644 index 0000000000..109fa8b38c --- /dev/null +++ b/packages/strapi-plugin-users-permissions/admin/src/injectedComponents.js @@ -0,0 +1 @@ +export default []; \ No newline at end of file diff --git a/packages/strapi-plugin-users-permissions/admin/src/translations/de.json b/packages/strapi-plugin-users-permissions/admin/src/translations/de.json index 26cc5fa1cf..4e304d04a4 100755 --- a/packages/strapi-plugin-users-permissions/admin/src/translations/de.json +++ b/packages/strapi-plugin-users-permissions/admin/src/translations/de.json @@ -28,6 +28,7 @@ "Auth.form.register.username.placeholder": "John Doe", "Auth.form.register.password.label": "Passwort", "Auth.form.register.confirmPassword.label": "Passwort-Bestätigung", + "Auth.form.register.news.label": "Haltet mich auf dem Laufenden über die neuen Funktionen und kommenden Verbesserungen.", "Auth.form.register-success.email.label": "Eine E-Mail wurde erfolgreich verschickt an", "Auth.form.register-success.email.placeholder": "mysuperemail@gmail.com", @@ -88,8 +89,8 @@ "HeaderNav.link.providers": "Methoden", "HeaderNav.link.roles": "Rollen", - "HomePage.header.title": "Benutzer & Befugnisse", - "HomePage.header.description": "Lege Rollen und deren Befugnisse fest", + "HomePage.header.title": "Benutzer & Berechtigungen", + "HomePage.header.description": "Lege Rollen und deren Berechtigungen fest.", "InputSearch.placeholder": "Suche nach einem Benutzer", diff --git a/packages/strapi-plugin-users-permissions/admin/src/translations/en.json b/packages/strapi-plugin-users-permissions/admin/src/translations/en.json index f97bf1e2fc..4a0b65d607 100755 --- a/packages/strapi-plugin-users-permissions/admin/src/translations/en.json +++ b/packages/strapi-plugin-users-permissions/admin/src/translations/en.json @@ -6,6 +6,7 @@ "Auth.form.button.login": "Log in", "Auth.form.button.register": "Ready to start", "Auth.form.error.noAdminAccess": "You can't access the administration panel.", + "Auth.form.error.confirmed": "Your account email is not confirmed.", "Auth.form.forgot-password.email.label": "Enter your email", "Auth.form.forgot-password.email.label.success": "Email successfully sent to", @@ -43,8 +44,10 @@ "Auth.form.error.code.provide": "Incorrect code provided.", "Auth.form.error.password.matching": "Passwords do not match.", "Auth.form.error.params.provide": "Incorrect params provided.", - "Auth.form.error.username.taken": "Username is already taken", - "Auth.form.error.email.taken": "Email is already taken", + "Auth.form.error.username.taken": "Username is already taken.", + "Auth.form.error.email.taken": "Email is already taken.", + "Auth.form.error.blocked": "Your account has been blocked by the administrator.", + "Auth.form.error.ratelimit": "Too many attempts, please try again in a minute.", "Auth.link.forgot-password": "Forgot your password?", "Auth.link.ready": "Ready to sign in?", @@ -65,8 +68,12 @@ "EditForm.inputToggle.label.email": "One account per email address", "EditForm.inputToggle.label.sign-up": "Enable sign-ups", + "EditForm.inputToggle.label.email-confirmation": "Enable email confirmation", + "EditForm.inputToggle.label.email-confirmation-redirection": "Redirection url", "EditForm.inputToggle.description.email": "Disallow the user to create multiple accounts using the same email address with different authentication providers.", "EditForm.inputToggle.description.sign-up": "When disabled (OFF), the registration process is forbidden. No one can subscribe anymore no matter the used provider.", + "EditForm.inputToggle.description.email-confirmation": "When enabled (ON), new registred users receive a confirmation email.", + "EditForm.inputToggle.description.email-confirmation-redirection": "After confirmed your email, chose where you will be redirected.", "EditPage.cancel": "Cancel", "EditPage.submit": "Save", @@ -129,7 +136,7 @@ "Policies.header.hint": "Select the application's actions or the plugin's actions and click on the cog icon to display the bound route", "Policies.header.title": "Advanced settings", - "Email.template.validation_email": "Email address validation", + "Email.template.email_confirmation": "Email address confirmation", "Email.template.reset_password": "Reset password", "Email.template.success_register": "Registration successful", @@ -163,9 +170,11 @@ "PopUpForm.Providers.secret.placeholder": "TEXT", "PopUpForm.Providers.redirectURL.front-end.label": "The redirect URL to your front-end app", + "PopUpForm.Providers.discord.providerConfig.redirectURL": "The redirect URL to add in your Discord application configurations", "PopUpForm.Providers.facebook.providerConfig.redirectURL": "The redirect URL to add in your Facebook application configurations", "PopUpForm.Providers.google.providerConfig.redirectURL": "The redirect URL to add in your Google application configurations", "PopUpForm.Providers.github.providerConfig.redirectURL": "The redirect URL to add in your GitHub application configurations", + "PopUpForm.Providers.microsoft.providerConfig.redirectURL": "The redirect URL to add in your Microsoft application configurations", "PopUpForm.Providers.linkedin2.providerConfig.redirectURL": "The redirect URL to add in your Linkedin application configurations", "PopUpForm.Providers.twitter.providerConfig.redirectURL": "The redirect URL to add in your Twitter application configurations", diff --git a/packages/strapi-plugin-users-permissions/admin/src/translations/es.json b/packages/strapi-plugin-users-permissions/admin/src/translations/es.json new file mode 100755 index 0000000000..1279d42b23 --- /dev/null +++ b/packages/strapi-plugin-users-permissions/admin/src/translations/es.json @@ -0,0 +1,175 @@ +{ + "Auth.form.button.register-success": "Enviar nuevamente", + "Auth.form.button.forgot-password.success": "Enviar nuevamente", + "Auth.form.button.forgot-password": "Enviar Email", + "Auth.form.button.reset-password": "Cambiar contraseña", + "Auth.form.button.login": "Iniciar sesión", + "Auth.form.button.register": "Listo para comenzar", + "Auth.form.error.noAdminAccess": "No puedes acceder al panel de administración.", + + "Auth.form.forgot-password.email.label": "Introduce tu email", + "Auth.form.forgot-password.email.label.success": "Email enviado con éxito a", + "Auth.form.forgot-password.email.placeholder": "mysuperemail@gmail.com", + + "Auth.header.register.description": "Para terminar de configurar y asegurar su aplicación, por favor cree el primer usuario (administrador root) ingresando la información necesaria a continuación.", + "Auth.form.header.login": "strapi", + "Auth.form.header.forgot-password": "strapi", + "Auth.form.header.register": "Bienvenidos!", + "Auth.form.header.register-success": "strapi", + + "Auth.form.login.password.label": "Contraseña", + "Auth.form.login.rememberMe.label": "Recuérdame", + "Auth.form.login.username.label": "Nombre de usuario", + "Auth.form.login.username.placeholder": "John Doe", + + "Auth.form.register.email.label": "Email", + "Auth.form.register.email.placeholder": "johndoe@gmail.com", + "Auth.form.register.username.label": "Nombre de usuario", + "Auth.form.register.username.placeholder": "John Doe", + "Auth.form.register.password.label": "Contraseña", + "Auth.form.register.confirmPassword.label": "Contraseña de confirmación", + "Auth.form.register.news.label": "Manténgame informado sobre las nuevas características y las próximas mejoras.", + + "Auth.form.register-success.email.label": "Email enviado con éxito a", + "Auth.form.register-success.email.placeholder": "mysuperemail@gmail.com", + + "Auth.form.error.email.provide": "Por favor proporcione su nombre de usuario o su correo electrónico.", + "Auth.form.error.email.invalid": "Este email es inválido.", + "Auth.form.error.password.provide": "Por favor, introduzca su contraseña.", + "Auth.form.error.invalid": "Identificador o contraseña inválidos.", + "Auth.form.error.password.local": "Este usuario nunca estableció una contraseña local, por favor ingrese a través de su proveedor utilizado durante la creación de la cuenta.", + "Auth.form.error.password.format": "Su contraseña no puede contener el símbolo `$` más de tres veces.", + "Auth.form.error.user.not-exist": "Este email no existe.", + "Auth.form.error.code.provide": "Código incorrecto proporcionado.", + "Auth.form.error.password.matching": "Las contraseñas no coinciden.", + "Auth.form.error.params.provide": "Parametros incorrectos proporcionados.", + "Auth.form.error.username.taken": "El nombre de usuario ya está registrado", + "Auth.form.error.email.taken": "El email ya está registrado", + + "Auth.link.forgot-password": "¿Olvidó su contraseña?", + "Auth.link.ready": "¿Listo para iniciar sesión?", + + "BoundRoute.title": "Ruta enlazada a", + + "components.Input.error.password.noMatch": "Las contraseñas no coinciden", + + "Controller.input.label": "{label} ", + "Controller.selectAll": "Seleccionar todo", + + "EditForm.inputSelect.label.role": "Rol predeterminado para usuarios autenticados", + "EditForm.inputSelect.description.role": "Adjuntará el nuevo usuario autenticado al rol seleccionado.", + "EditForm.inputSelect.subscriptions.label": "Gestionar cuotas de suscripción", + "EditForm.inputSelect.subscriptions.description": "Limite el número de suscripciones de IP por hora.", + "EditForm.inputSelect.durations.label": "Duración", + "EditForm.inputSelect.durations.description": "Número de horas durante las cuales el usuario no puede suscribirse.", + + "EditForm.inputToggle.label.email": "Una cuenta por dirección de correo electrónico", + "EditForm.inputToggle.label.sign-up": "Habilitar inscripciones", + "EditForm.inputToggle.description.email": "No permita que el usuario cree varias cuentas utilizando la misma dirección de correo electrónico con distintos proveedores de autenticación.", + "EditForm.inputToggle.description.sign-up": "Cuando está desactivado (OFF), el proceso de registro está prohibido. Nadie puede suscribirse sin importar el proveedor utilizado.", + + "EditPage.cancel": "Cancelar", + "EditPage.submit": "Guardar", + "EditPage.form.roles": "Detalles del rol", + "EditPage.form.roles.label.description": "Descripción", + "EditPage.form.roles.label.name": "Nombre", + "EditPage.form.roles.label.users": "Usuarios asociados a este rol ({number})", + "EditPage.form.roles.name.error": "Este valor es obligatorio.", + "EditPage.header.title": "{name} ", + "EditPage.header.title.create": "Crear un nuevo rol", + "EditPage.header.description": "{description} ", + "EditPage.header.description.create": " ", + + "EditPage.notification.permissions.error": "Se ha producido un error al obtener las autorizaciones", + "EditPage.notification.policies.error": "Se ha producido un error al recuperar las políticas", + "EditPage.notification.role.error": "Se ha producido un error al recuperar el rol", + + "HeaderNav.link.advancedSettings": "Ajustes avanzados", + "HeaderNav.link.emailTemplates": "Plantillas de email", + "HeaderNav.link.providers": "Proveedores", + "HeaderNav.link.roles": "Roles y Permisos", + + "HomePage.header.title": "Roles y Permisos", + "HomePage.header.description": "Defina los roles y permisos para sus usuarios.", + + "InputSearch.placeholder": "Buscar un usuario", + + "List.button.roles": "Añadir nuevo rol", + "List.button.providers": "Añadir nuevo proveedor", + + "List.title.emailTemplates.singular": "{number} plantilla de email está disponible", + "List.title.emailTemplates.plural": "{number} plantillas de email disponibles", + + "List.title.providers.disabled.singular": "{number} está desactivado", + "List.title.providers.disabled.plural": "{number} están desactivados", + "List.title.providers.enabled.singular": "{number} está habilitado y", + "List.title.providers.enabled.plural": "{number} proveedores están habilitados y", + + "List.title.roles.singular": "{number} rol disponible", + "List.title.roles.plural": "{number} roles disponibles", + + "notification.error.delete": "Se ha producido un error al intentar borrar el elemento", + "notification.error.fetch": "Se ha producido un error al intentar recuperar los datos", + "notification.error.fetchUser": "Se ha producido un error al intentar buscar usuarios", + "notification.info.emailSent": "El email ha sido enviado", + "notification.success.delete": "El elemento ha sido borrado", + "notification.success.submit": "Los ajustes se han actualizado", + + "plugin.description.short": "Proteja su API con un proceso de autenticación completo basado en JWT", + "plugin.description.long": "Proteja su API con un proceso de autenticación completo basado en JWT. Este plugin viene también con una estrategia ACL que le permite administrar los permisos entre los grupos de usuarios.", + + "Plugin.permissions.application.description": "Defina todas las acciones permitidas de su proyecto.", + "Plugin.permissions.plugins.description": "Defina todas las acciones permitidas para el plugin {name}.", + + "Plugins.header.title": "Permisos", + "Plugins.header.description": "Sólo las acciones vinculadas a una ruta se enumeran a continuación.", + + "Policies.InputSelect.empty": "Ninguno", + "Policies.InputSelect.label": "Permita que se realice esta acción para:", + "Policies.header.hint": "Seleccione las acciones de la aplicación o las acciones del plugin y haga clic en el icono del engranaje para ver la ruta vinculada", + "Policies.header.title": "Ajustes avanzados", + + "Email.template.validation_email": "Validación de la dirección de email", + "Email.template.reset_password": "Restablecer contraseña", + "Email.template.success_register": "Inscripción exitosa", + + "Auth.advanced.allow_register": "", + + "PopUpForm.button.cancel": "Cancelar", + "PopUpForm.button.save": "Guardar", + "PopUpForm.header.add.providers": "Añadir nuevo proveedor", + "PopUpForm.header.edit.email-templates": "Editar Plantillas de Email", + "PopUpForm.header.edit.providers": "Editar Proveedor {provider}", + "PopUpForm.inputSelect.providers.label": "Elija el proveedor", + "PopUpForm.Email.options.from.name.label": "Nombre del remitente", + "PopUpForm.Email.options.from.email.label": "Email del remitente", + "PopUpForm.Email.options.response_email.label": "Email de respuesta", + "PopUpForm.Email.options.object.label": "Tema", + "PopUpForm.Email.options.message.label": "Mensaje", + "PopUpForm.Email.validation_email.options.object.placeholder": "Por favor, confirme su dirección de email para %APP_NAME%", + "PopUpForm.Email.reset_password.options.object.placeholder": "Confirme su dirección de email para %APP_NAME%", + "PopUpForm.Email.success_register.options.object.placeholder": "Confirme su dirección de email para %APP_NAME%", + "PopUpForm.Email.validation_email.options.message.placeholder": "

    Haga clic en este enlace para validar su cuenta

    ", + "PopUpForm.Email.reset_password.options.message.placeholder": "

    Haga clic en este enlace para validar su cuenta

    ", + "PopUpForm.Email.success_register.options.message.placeholder": "

    Haga clic en este enlace para validar su cuenta

    ", + "PopUpForm.Email.options.from.email.placeholder": "johndoe@gmail.com", + "PopUpForm.Email.options.response_email.placeholder": "johndoe@gmail.com", + "PopUpForm.Email.options.from.name.placeholder": "John Doe", + "PopUpForm.Providers.enabled.label": "Habilitar", + "PopUpForm.Providers.enabled.description": "Si está desactivado, los usuarios no podrán utilizar este proveedor.", + "PopUpForm.Providers.key.label": "ID de cliente", + "PopUpForm.Providers.key.placeholder": "TEXTO", + "PopUpForm.Providers.secret.label": "Secreto Cliente", + "PopUpForm.Providers.secret.placeholder": "TEXTO", + "PopUpForm.Providers.redirectURL.front-end.label": "La URL de redireccionamiento a su aplicación front-end", + + "PopUpForm.Providers.facebook.providerConfig.redirectURL": "La URL de redirección que se debe agregar en la configuración de la aplicación Facebook", + "PopUpForm.Providers.google.providerConfig.redirectURL": "La URL de redirección que se debe agregar en la configuración de la aplicación Google", + "PopUpForm.Providers.github.providerConfig.redirectURL": "La URL de redirección que se debe agregar en la configuración de la aplicación GitHub", + "PopUpForm.Providers.linkedin2.providerConfig.redirectURL": "La URL de redirección que se debe agregar en la configuración de la aplicación Linkedin", + "PopUpForm.Providers.twitter.providerConfig.redirectURL": "La URL de redirección que se debe agregar en la configuración de la aplicación Twitter", + + "PopUpForm.Providers.callback.placeholder": "TEXTO", + "PopUpForm.Email.email_templates.inputDescription": "Si no estás seguro de cómo usar las variables, {link}", + "PopUpForm.Email.link.documentation": "consulte nuestra documentación." +} diff --git a/packages/strapi-plugin-users-permissions/admin/src/translations/fr.json b/packages/strapi-plugin-users-permissions/admin/src/translations/fr.json index 5def04f578..f12484c033 100755 --- a/packages/strapi-plugin-users-permissions/admin/src/translations/fr.json +++ b/packages/strapi-plugin-users-permissions/admin/src/translations/fr.json @@ -1,6 +1,7 @@ { "Auth.form.button.register-success": "Envoyer à nouveau", "Auth.form.button.forgot-password": "Envoyer à nouveau", + "Auth.form.button.forgot-password.success": "Envoyer à nouveau", "Auth.form.button.reset-password": "Changez votre mot de passe", "Auth.form.button.login": "Se connecter", "Auth.form.button.register": "Prêt à commencer", diff --git a/packages/strapi-plugin-users-permissions/admin/src/translations/it.json b/packages/strapi-plugin-users-permissions/admin/src/translations/it.json new file mode 100644 index 0000000000..f845251dda --- /dev/null +++ b/packages/strapi-plugin-users-permissions/admin/src/translations/it.json @@ -0,0 +1,144 @@ +{ + "Auth.form.button.register-success": "Invia di nuovo", + "Auth.form.button.forgot-password.success": "Invia di nuovo", + "Auth.form.button.forgot-password": "Invia Email", + "Auth.form.button.reset-password": "Cambia password", + "Auth.form.button.login": "Accedi", + "Auth.form.button.register": "Inizia adesso", + "Auth.form.error.noAdminAccess": "Non puoi accedere al pannello di amministrazione.", + "Auth.form.forgot-password.email.label": "Inserisci la tua email", + "Auth.form.forgot-password.email.label.success": "Email inviata correttamente", + "Auth.form.forgot-password.email.placeholder": "mysuperemail@gmail.com", + "Auth.header.register.description": "Per terminare l'installazione e mettere in sicurezza la tua applicazione è necessario creare il primo utente (root admin) inserendo le informazioni necessarie di seguito riportate", + "Auth.form.header.login": "strapi", + "Auth.form.header.forgot-password": "strapi", + "Auth.form.header.register": "Benvenuto!", + "Auth.form.header.register-success": "strapi", + "Auth.form.login.password.label": "Password", + "Auth.form.login.rememberMe.label": "Ricordati di me", + "Auth.form.login.username.label": "Username", + "Auth.form.login.username.placeholder": "John Doe", + "Auth.form.register.email.label": "Email", + "Auth.form.register.email.placeholder": "johndoe@gmail.com", + "Auth.form.register.username.label": "Username", + "Auth.form.register.username.placeholder": "John Doe", + "Auth.form.register.password.label": "Password", + "Auth.form.register.confirmPassword.label": "Password di conferma", + "Auth.form.register.news.label": "Tienimi aggiornato su nuove funzionalità e futuri miglioramenti", + "Auth.form.register-success.email.label": "Email inviata con successo a", + "Auth.form.register-success.email.placeholder": "mysuperemail@gmail.com", + "Auth.form.error.email.provide": "Per favore fornisci il tuo username o la tua password", + "Auth.form.error.email.invalid": "Questa email non è valida.", + "Auth.form.error.password.provide": "Per favore fornisci la tua password", + "Auth.form.error.invalid": "Identificatore o password non valida.", + "Auth.form.error.password.local": "Questo utente non ha mai impostato una password locale, accedi gentilmente tramite il provider usato durante la creazione dell'account", + "Auth.form.error.password.format": "La tua password non può contenere il simbolo $ per più di tre volte.", + "Auth.form.error.user.not-exist": "Questa email non esiste.", + "Auth.form.error.code.provide": "Codice fornito non corretto.", + "Auth.form.error.password.matching": "La password non corrisponde.", + "Auth.form.error.params.provide": "I parametri forniti non sono corretti.", + "Auth.form.error.username.taken": "Username in uso", + "Auth.form.error.email.taken": "Email in uso", + "Auth.link.forgot-password": "Password dimenticata?", + "Auth.link.ready": "Sei pronto per accedere?", + "BoundRoute.title": "Percorso vincolato a", + "components.Input.error.password.noMatch": "La password non corrisponde", + "Controller.input.label": "{label}", + "Controller.selectAll": "Seleziona tutto", + "EditForm.inputSelect.label.role": "Ruolo di default per gli utenti autenticati", + "EditForm.inputSelect.description.role": "Questa operazione associerà i nuovi utenti autenticati al ruolo selezionato.", + "EditForm.inputSelect.subscriptions.label": "Gestisci le sottoscrizioni di quota", + "EditForm.inputSelect.subscriptions.description": "Limita il numero di sottoscrizioni per IP per ora.", + "EditForm.inputSelect.durations.label": "Durata", + "EditForm.inputSelect.durations.description": "Numero di ore in cui l'utente non può registrarsi", + "EditForm.inputToggle.label.email": "Un account per indirizzo email", + "EditForm.inputToggle.label.sign-up": "Abilita iscrizioni", + "EditForm.inputToggle.description.email": "Non consentire all'utente di creare account multipli usando lo stesso indirizzo email con fornitori di autenticazione diversi.", + "EditForm.inputToggle.description.sign-up": "Quando disabilitata (OFF), il processo di registrazione è proibito. Nessuno può iscriversi indipendentemente dal fornitore utilizzato.", + "EditPage.cancel": "Cancella", + "EditPage.submit": "Salva", + "EditPage.form.roles": "Dettagli del ruolo", + "EditPage.form.roles.label.description": "Descrizione", + "EditPage.form.roles.label.name": "Nome", + "EditPage.form.roles.label.users": "Utente associato con questo ruolo ({number})", + "EditPage.form.roles.name.error": "Questo valore è obbligatorio", + "EditPage.header.title": "{name}", + "EditPage.header.title.create": "Crea un nuovo ruolo", + "EditPage.header.description": "{description}", + "EditPage.header.description.create": "Crea", + "EditPage.notification.permissions.error": "Si è verificato un errore durante il recupero dei permessi", + "EditPage.notification.policies.error": "Si è verificato un errore durante il recupero delle policy", + "EditPage.notification.role.error": "Si è verificato un errore durante il recupero del ruolo", + "eaderNav.link.advancedSettings": "Impostazioni avanzate", + "HeaderNav.link.emailTemplates": "Template delle Email", + "HeaderNav.link.providers": "Providers", + "HeaderNav.link.roles": "Ruoli e permessi", + "HomePage.header.title": "Ruoli e permessi", + "HomePage.header.description": "Definisci i ruoli e i permessi per i tuoi utenti.", + "InputSearch.placeholder": "Cerca un utente", + "List.button.roles": "Aggiungi un ruolo", + "List.button.providers": "Aggiungi un nuovo Provider", + "List.title.emailTemplates.singular": "{number} template per le email disponibile", + "List.title.emailTemplates.plural": "{number} template per le email disponibili", + "List.title.providers.disabled.singular": "{number} disabilitato", + "List.title.providers.disabled.plural": "{number} sono disabilitati", + "List.title.providers.enabled.singular": "{number} provider è abilitato e", + "List.title.providers.enabled.plural": "{number} providers sono disponibili e", + "List.title.roles.singular": "{number} ruolo disponibile", + "List.title.roles.plural": "{number} ruoli disponibili", + "notification.error.delete": "Si è verificato un errore mentre si stava cercando di eliminare l'elemento", + "notification.error.fetch": "Si è verificato un errore mentre si stava cercando di recuperare i dati", + "notification.error.fetchUser": "Si è verificato un errore mentre si stava cercando di recuperare gli utenti", + "notification.info.emailSent": "Email inviata", + "notification.success.delete": "L'elemento è stato eliminato", + "notification.success.submit": "Impostazioni aggiornate", + "plugin.description.short": "Proteggi le tue API con un processo completo di autenticazione basato su JWT", + "plugin.description.long": "Proteggi le tue API con un processo completo di autenticazione basato su JWT. Questo plugin è implementato con una strategia ACL che ti consente di gestire i permessi tra i gruppi di utenti.", + "Plugin.permissions.application.description": "Definire tutte le azioni consentite per il tuo progetto.", + "Plugin.permissions.plugins.description": "Definire tutte le azioni consentite per il plugin {name}.", + "Plugins.header.title": "Permessi", + "Plugins.header.description": "Solo le azioni guidate da un percorso sono elencate sotto.", + "Policies.InputSelect.empty": "Nessuno", + "Policies.InputSelect.label": "Consenti di eseguire questa azione per:", + "Policies.header.hint": "Seleziona le azioni dell'applicazione o del plugin e clicca sull'icona cog per mostrare il percorso corrispondente", + "Policies.header.title": "Impostazioni avanzate", + "Email.template.validation_email": "Validazione dell'indirizzo Email", + "Email.template.reset_password": "Reset password", + "Email.template.success_register": "Registrazione avvenuta", + "Auth.advanced.allow_register": "Registrazione consentita", + "PopUpForm.button.cancel": "Cancella", + "PopUpForm.button.save": "Salva", + "PopUpForm.header.add.providers": "Aggiungi nuovo Provider", + "PopUpForm.header.edit.email-templates": "Modifica il template delle Email", + "PopUpForm.header.edit.providers": "Modifica {provider} Provider", + "PopUpForm.inputSelect.providers.label": "Scegli il provider", + "PopUpForm.Email.options.from.name.label": "Nome del mittente", + "PopUpForm.Email.options.from.email.label": "Email del mittente", + "PopUpForm.Email.options.response_email.label": "Email di risposta", + "PopUpForm.Email.options.object.label": "Soggetto", + "PopUpForm.Email.options.message.label": "Messaggio", + "PopUpForm.Email.validation_email.options.object.placeholder": "Conferma gentilmente il tuo indirizzo email per %APP_NAME%", + "PopUpForm.Email.reset_password.options.object.placeholder": "Conferma gentilmente il tuo indirizzo email per %APP_NAME%", + "PopUpForm.Email.success_register.options.object.placeholder": "Conferma gentilmente il tuo indirizzo email per %APP_NAME%", + "PopUpForm.Email.validation_email.options.message.placeholder": "Clicca su questo link per validare il tuo account", + "PopUpForm.Email.reset_password.options.message.placeholder": "Clicca su questo link per validare il tuo account", + "PopUpForm.Email.success_register.options.message.placeholder": "Clicca su questo link per validare il tuo account", + "PopUpForm.Email.options.from.email.placeholder": "johndoe@gmail.com", + "PopUpForm.Email.options.response_email.placeholder": "johndoe@gmail.com", + "PopUpForm.Email.options.from.name.placeholder": "John Doe", + "PopUpForm.Providers.enabled.label": "Abilita", + "PopUpForm.Providers.enabled.description": "Se disabilitato, gli utenti non potranno usare questo provider.", + "opUpForm.Providers.key.label": "Client ID", + "PopUpForm.Providers.key.placeholder": "TEXT", + "PopUpForm.Providers.secret.label": "Client Secret", + "PopUpForm.Providers.secret.placeholder": "TEXT", + "PopUpForm.Providers.redirectURL.front-end.label": "L'URL di redirect per la tua app di front-end", + "PopUpForm.Providers.facebook.providerConfig.redirectURL": "L'URL di redirect per aggiungere la tua configurazione dell'applicazione Facebook", + "PopUpForm.Providers.google.providerConfig.redirectURL": "L'URL di redirect per aggiungere la tua configurazione dell'applicazione Google", + "PopUpForm.Providers.github.providerConfig.redirectURL": "L'URL di redirect per aggiungere la tua configurazione dell'applicazione Github", + "PopUpForm.Providers.linkedin2.providerConfig.redirectURL": "L'URL di redirect per aggiungere la tua configurazione dell'applicazione Linkdin", + "PopUpForm.Providers.twitter.providerConfig.redirectURL": "L'URL di redirect per aggiungere la tua configurazione dell'applicazione Twitter", + "PopUpForm.Providers.callback.placeholder": "TEXT", + "PopUpForm.Email.email_templates.inputDescription": "Se non sai bene come usare le variabili, {link}", + "PopUpForm.Email.link.documentation": "controlla la documentazione" +} diff --git a/packages/strapi-plugin-users-permissions/admin/src/translations/nl.json b/packages/strapi-plugin-users-permissions/admin/src/translations/nl.json new file mode 100644 index 0000000000..7c794e4db4 --- /dev/null +++ b/packages/strapi-plugin-users-permissions/admin/src/translations/nl.json @@ -0,0 +1,175 @@ +{ + "Auth.form.button.register-success": "Opnieuw versturen", + "Auth.form.button.forgot-password.success": "Opnieuw versturen", + "Auth.form.button.forgot-password": "E-mail versturen", + "Auth.form.button.reset-password": "Wachtwoord wijzigen", + "Auth.form.button.login": "Inloggen", + "Auth.form.button.register": "Klaar om te beginnen", + "Auth.form.error.noAdminAccess": "Je hebt geen toegang tot het administrator paneel", + + "Auth.form.forgot-password.email.label": "Voer je e-mail in", + "Auth.form.forgot-password.email.label.success": "E-mail succesvol verstuurt naar", + "Auth.form.forgot-password.email.placeholder": "mijnsuperemail@gmail.com", + + "Auth.header.register.description": "Om de set-up af te maken en je app te beveiligen, maak a.u.b. een eerste gebruiker (root admin) door alle velden hier beneden in te vullen.", + "Auth.form.header.login": "strapi", + "Auth.form.header.forgot-password": "strapi", + "Auth.form.header.register": "Welkom!", + "Auth.form.header.register-success": "strapi", + + "Auth.form.login.password.label": "Wachtwoord", + "Auth.form.login.rememberMe.label": "Onthoudt mij", + "Auth.form.login.username.label": "Gebruikersnaam", + "Auth.form.login.username.placeholder": "John Doe", + + "Auth.form.register.email.label": "E-mail", + "Auth.form.register.email.placeholder": "johndoe@gmail.com", + "Auth.form.register.username.label": "Gebruikersnaam", + "Auth.form.register.username.placeholder": "John Doe", + "Auth.form.register.password.label": "Wachtwoord", + "Auth.form.register.confirmPassword.label": "Wachtwoord Bevestigen", + "Auth.form.register.news.label": "Houd mij op de hoogte van nieuwe functies en nog komende verbeteringen.", + + "Auth.form.register-success.email.label": "E-mail met succes verzonden aan", + "Auth.form.register-success.email.placeholder": "mijnsuperemail@gmail.com", + + "Auth.form.error.email.provide": "Voer a.u.b. je gebruikersnaam of je e-mail in.", + "Auth.form.error.email.invalid": "Dit e-mailadres is onjuist", + "Auth.form.error.password.provide": "Voer a.u.b je wachtwoord in.", + "Auth.form.error.invalid": "Gebruikersnaam of wachtwoord onjuist.", + "Auth.form.error.password.local": "Deze gebruiker heeft nooit een lokaal wachtwoord ingesteld, gebruik de leverancier welke gebruikt is tijdens het maken van het account om in te loggen/", + "Auth.form.error.password.format": "Het wachtwoord mag niet het `$` symbool meer dan drie keer bevatten.", + "Auth.form.error.user.not-exist": "Dit e-mailadres bestaat niet.", + "Auth.form.error.code.provide": "Incorrecte code ingevoerd.", + "Auth.form.error.password.matching": "Wachtwoorden komen niet overeen.", + "Auth.form.error.params.provide": "Incorrecte parameters ingevoerd.", + "Auth.form.error.username.taken": "Gebruikersnaam is al in gebruik", + "Auth.form.error.email.taken": "E-mailadres is al in gebruik", + + "Auth.link.forgot-password": "Wachtwoord vergeten?", + "Auth.link.ready": "Klaar om in te loggen?", + + "BoundRoute.title": "Gebonden route naar", + + "components.Input.error.password.noMatch": "Wachtwoorden komen niet overeen", + + "Controller.input.label": "{label} ", + "Controller.selectAll": "Alles selecteren", + + "EditForm.inputSelect.label.role": "Standaard rol voor geautoriseerde gebruikers", + "EditForm.inputSelect.description.role": "Het zal de nieuwe geautoriseerde gebruiker aan de geselecteerde rol verbinden.", + "EditForm.inputSelect.subscriptions.label": "Beheer abonnementen quotas", + "EditForm.inputSelect.subscriptions.description": "Stel een limiet in voor het aantal abonnementen per IP per uur", + "EditForm.inputSelect.durations.label": "Tijdsduur", + "EditForm.inputSelect.durations.description": "Aantal uur welke de gebruiker niet kan abonneren.", + + "EditForm.inputToggle.label.email": "Één account per e-mailadres.", + "EditForm.inputToggle.label.sign-up": "Registratie inschakelen", + "EditForm.inputToggle.description.email": "Zorg ervoor dat de gebruiker niet meerdere accounts kan maken met hetzelfde e-mailadres maar met verschillende leveranciers.", + "EditForm.inputToggle.description.sign-up": "Wanneer uitgeschakeld (OFF), is registratie verboden. Niemand kan abonneren ongeacht de leverancier", + + "EditPage.cancel": "Annuleren", + "EditPage.submit": "Opslaan", + "EditPage.form.roles": "Rol details", + "EditPage.form.roles.label.description": "Beschrijving", + "EditPage.form.roles.label.name": "Naam", + "EditPage.form.roles.label.users": "Gebruikers geassocieerd met deze rol ({number})", + "EditPage.form.roles.name.error": "Deze waarde is verplicht.", + "EditPage.header.title": "{name} ", + "EditPage.header.title.create": "Nieuwe rol aanmaken", + "EditPage.header.description": "{description} ", + "EditPage.header.description.create": " ", + + "EditPage.notification.permissions.error": "Er is een fout opgetreden tijdens het ophalen van de permissies", + "EditPage.notification.policies.error": "Er is een fout opgetreden tijdens het ophalen van het beleid", + "EditPage.notification.role.error": "Er is een fout opgetreden tijdens het ophalen van de rol", + + "HeaderNav.link.advancedSettings": "Geavanceerde instellingen", + "HeaderNav.link.emailTemplates": "E-mail sjabloon", + "HeaderNav.link.providers": "Leveranciers", + "HeaderNav.link.roles": "Rollen & Permissies", + + "HomePage.header.title": "Rollen & Permissies", + "HomePage.header.description": "Geef de rollen en permissies aan voor je gebruikers.", + + "InputSearch.placeholder": "Zoek naar een gebruiker", + + "List.button.roles": "Nieuwe rol toevoegen", + "List.button.providers": "Nieuwe leverancier toevoegen", + + "List.title.emailTemplates.singular": "{number} e-mail sjabloon beschikbaar", + "List.title.emailTemplates.plural": "{number} e-mail sjablonen beschikbaar", + + "List.title.providers.disabled.singular": "{number} is uitgeschakeld", + "List.title.providers.disabled.plural": "{number} zijn uitgeschakeld", + "List.title.providers.enabled.singular": "{number} leverancier is ingeschakeld en", + "List.title.providers.enabled.plural": "{number} leveranciers zijn ingeschakeld en", + + "List.title.roles.singular": "{number} rol is beschikbaar", + "List.title.roles.plural": "{number} rollen zijn beschikbaar", + + "notification.error.delete": "Er is een fout opgetreden tijdens het verwijderen van dit item", + "notification.error.fetch": "Er is een fout opgetreden tijdens het ophalen van de data", + "notification.error.fetchUser": "Er is een fout opgetreden tijdens het ophalen van de gebruikers", + "notification.info.emailSent": "De e-mail is verstuurd", + "notification.success.delete": "Het item is verwijderd", + "notification.success.submit": "Instellingen zijn geüpdatet", + + "plugin.description.short": "Beveilig je API met een volledig authenticatie proces op JWT", + "plugin.description.long": "Beveilig je API met een volledig authenticatie proces op JWT. Deze extensie komt ook met een ACL strategie welke ervoor zorgt dat je de permissies tussen groepen van gebruikers kan beheren.", + + "Plugin.permissions.application.description": "Voer alle toegestane acties van je project in.", + "Plugin.permissions.plugins.description": "Voer alle toegestane acties in voor extensie {name}.", + + "Plugins.header.title": "Permissies", + "Plugins.header.description": "Alleen acties gekoppeld aan een route worden hieronder weergegeven.", + + "Policies.InputSelect.empty": "Geen", + "Policies.InputSelect.label": "Deze actie toestaan voor:", + "Policies.header.hint": "Selecteer de actie van de applicatie of de acties van de extensie en klik op het tandwiel icoontje om de gekoppelde route weer te geven", + "Policies.header.title": "Geavanceerde instellingen", + + "Email.template.validation_email": "E-mailadres validatie", + "Email.template.reset_password": "Wachtwoord herstellen", + "Email.template.success_register": "Registratie gelukt", + + "Auth.advanced.allow_register": "", + + "PopUpForm.button.cancel": "Annuleren", + "PopUpForm.button.save": "Opslaan", + "PopUpForm.header.add.providers": "Nieuwe leverancier toevoegen", + "PopUpForm.header.edit.email-templates": "E-mail sjablonen aanpassen", + "PopUpForm.header.edit.providers": "Leverancier {provider} aanpassen", + "PopUpForm.inputSelect.providers.label": "Kies een leverancier", + "PopUpForm.Email.options.from.name.label": "Afzender naam", + "PopUpForm.Email.options.from.email.label": "Afzender e-mail", + "PopUpForm.Email.options.response_email.label": "Antwoord e-mail", + "PopUpForm.Email.options.object.label": "Onderwerp", + "PopUpForm.Email.options.message.label": "Bericht", + "PopUpForm.Email.validation_email.options.object.placeholder": "Bevestig a.u.b. het e-mailadres voor %APP_NAME%", + "PopUpForm.Email.reset_password.options.object.placeholder": "Bevestig a.u.b. het e-mailadres voor %APP_NAME%", + "PopUpForm.Email.success_register.options.object.placeholder": "Bevestig a.u.b. het e-mailadres voor %APP_NAME%", + "PopUpForm.Email.validation_email.options.message.placeholder": "

    Klik op deze link om je account te valideren

    ", + "PopUpForm.Email.reset_password.options.message.placeholder": "

    Klik op deze link om je account te valideren

    ", + "PopUpForm.Email.success_register.options.message.placeholder": "

    Klik op deze link om je account te valideren

    ", + "PopUpForm.Email.options.from.email.placeholder": "johndoe@gmail.com", + "PopUpForm.Email.options.response_email.placeholder": "johndoe@gmail.com", + "PopUpForm.Email.options.from.name.placeholder": "John Doe", + "PopUpForm.Providers.enabled.label": "Inschakelen", + "PopUpForm.Providers.enabled.description": "Als deze uitgeschakeld is kunnen gebruikers geen gebruik maken van deze leverancier.", + "PopUpForm.Providers.key.label": "Client ID", + "PopUpForm.Providers.key.placeholder": "TEXT", + "PopUpForm.Providers.secret.label": "Client Secret", + "PopUpForm.Providers.secret.placeholder": "TEXT", + "PopUpForm.Providers.redirectURL.front-end.label": "De doorstuur URL voor jouw front-end app", + + "PopUpForm.Providers.facebook.providerConfig.redirectURL": "De doorstuur URL om in je Facebook applicatie configuratie te zetten", + "PopUpForm.Providers.google.providerConfig.redirectURL": "De doorstuur URL om in je Google applicatie configuratie te zetten", + "PopUpForm.Providers.github.providerConfig.redirectURL": "De doorstuur URL om in je GitHub applicatie configuratie te zetten", + "PopUpForm.Providers.linkedin2.providerConfig.redirectURL": "De doorstuur URL om in je LinkedIn applicatie configuratie te zetten", + "PopUpForm.Providers.twitter.providerConfig.redirectURL": "De doorstuur URL om in je Twitter applicatie configuratie te zetten", + + "PopUpForm.Providers.callback.placeholder": "TEXT", + "PopUpForm.Email.email_templates.inputDescription": "Als je niet zeker weet hoe je variabelen moet gebruiken, {link}", + "PopUpForm.Email.link.documentation": "bekijk onze documentatie." +} diff --git a/packages/strapi-plugin-users-permissions/admin/src/translations/pt.json b/packages/strapi-plugin-users-permissions/admin/src/translations/pt.json new file mode 100644 index 0000000000..9e26dfeeb6 --- /dev/null +++ b/packages/strapi-plugin-users-permissions/admin/src/translations/pt.json @@ -0,0 +1 @@ +{} \ No newline at end of file diff --git a/packages/strapi-plugin-users-permissions/config/functions/bootstrap.js b/packages/strapi-plugin-users-permissions/config/functions/bootstrap.js index bbca66962f..b04e2e2c39 100644 --- a/packages/strapi-plugin-users-permissions/config/functions/bootstrap.js +++ b/packages/strapi-plugin-users-permissions/config/functions/bootstrap.js @@ -39,6 +39,17 @@ module.exports = async cb => { enabled: true, icon: 'envelope' }, + discord: { + enabled: false, + icon: 'comments', + key: '', + secret: '', + callback: '/auth/discord/callback', + scope: [ + 'identify', + 'email' + ] + }, facebook: { enabled: false, icon: 'facebook-official', @@ -66,6 +77,14 @@ module.exports = async cb => { 'user:email' ] }, + microsoft: { + enabled: false, + icon: 'windows', + key: '', + secret: '', + callback: '/auth/microsoft/callback', + scope: ['user.read'] + }, twitter: { enabled: false, icon: 'twitter', @@ -106,6 +125,25 @@ module.exports = async cb => {

    <%= URL %>?code=<%= TOKEN %>

    +

    Thanks.

    ` + } + }, + 'email_confirmation': { + display: 'Email.template.email_confirmation', + icon: 'check-square-o', + options: { + from: { + name: 'Administration Panel', + email: 'no-reply@strapi.io' + }, + response_email: '', + object: 'Account confirmation', + message: `

    Thank you for registering!

    + +

    You have to confirm your email address. Please click on the link below.

    + +

    <%= URL %>?confirmation=<%= CODE %>

    +

    Thanks.

    ` } } @@ -118,11 +156,13 @@ module.exports = async cb => { const value = { unique_email: true, allow_register: true, + email_confirmation: false, + email_confirmation_redirection: `http://${strapi.config.currentEnvironment.server.host}:${strapi.config.currentEnvironment.server.port}/admin`, default_role: 'authenticated' }; await pluginStore.set({key: 'advanced', value}); } - + strapi.plugins['users-permissions'].services.userspermissions.initialize(cb); }; diff --git a/packages/strapi-plugin-users-permissions/config/policies/permissions.js b/packages/strapi-plugin-users-permissions/config/policies/permissions.js index 3407b5ee41..58731485f2 100644 --- a/packages/strapi-plugin-users-permissions/config/policies/permissions.js +++ b/packages/strapi-plugin-users-permissions/config/policies/permissions.js @@ -1,3 +1,5 @@ +const _ = require('lodash'); + module.exports = async (ctx, next) => { let role; @@ -23,6 +25,20 @@ module.exports = async (ctx, next) => { if (role.type === 'root') { return await next(); } + + const store = await strapi.store({ + environment: '', + type: 'plugin', + name: 'users-permissions' + }); + + if (_.get(await store.get({key: 'advanced'}), 'email_confirmation') && ctx.state.user.confirmed !== true) { + return ctx.unauthorized('Your account email is not confirmed.'); + } + + if (ctx.state.user.blocked === true) { + return ctx.unauthorized(`Your account has been blocked by the administrator.`); + } } // Retrieve `public` role. if (!role) { @@ -43,7 +59,7 @@ module.exports = async (ctx, next) => { return ctx.request.graphql = strapi.errors.forbidden(); } - ctx.forbidden(); + return ctx.forbidden(); } // Execute the policies. diff --git a/packages/strapi-plugin-users-permissions/config/policies/rateLimit.js b/packages/strapi-plugin-users-permissions/config/policies/rateLimit.js new file mode 100644 index 0000000000..e5267d2c69 --- /dev/null +++ b/packages/strapi-plugin-users-permissions/config/policies/rateLimit.js @@ -0,0 +1,12 @@ +const RateLimit = require('koa2-ratelimit').RateLimit; + +module.exports = async (ctx, next) => { + const message = ctx.request.admin ? [{ messages: [{ id: 'Auth.form.error.ratelimit' }] }] : 'Too many attempts, please try again in a minute.'; + + return RateLimit.middleware(Object.assign({}, { + interval: 1*60*1000, + max: 5, + prefixKey: `${ctx.request.url}:${ctx.request.ip}`, + message + }, strapi.plugins['users-permissions'].config.ratelimit))(ctx, next); +}; diff --git a/packages/strapi-plugin-users-permissions/config/request.json b/packages/strapi-plugin-users-permissions/config/request.json new file mode 100644 index 0000000000..7a74471551 --- /dev/null +++ b/packages/strapi-plugin-users-permissions/config/request.json @@ -0,0 +1,6 @@ +{ + "ratelimit": { + "interval": 60000, + "max": 10 + } +} diff --git a/packages/strapi-plugin-users-permissions/config/routes.json b/packages/strapi-plugin-users-permissions/config/routes.json index 5d43993b0d..b9881d1e9d 100644 --- a/packages/strapi-plugin-users-permissions/config/routes.json +++ b/packages/strapi-plugin-users-permissions/config/routes.json @@ -153,7 +153,7 @@ "path": "/connect/*", "handler": "Auth.connect", "config": { - "policies": [], + "policies": ["plugins.users-permissions.ratelimit"], "prefix": "" } }, @@ -162,7 +162,7 @@ "path": "/auth/local", "handler": "Auth.callback", "config": { - "policies": [], + "policies": ["plugins.users-permissions.ratelimit"], "prefix": "" } }, @@ -171,7 +171,7 @@ "path": "/auth/local/register", "handler": "Auth.register", "config": { - "policies": [], + "policies": ["plugins.users-permissions.ratelimit"], "prefix": "" } }, @@ -189,7 +189,7 @@ "path": "/auth/forgot-password", "handler": "Auth.forgotPassword", "config": { - "policies": [], + "policies": ["plugins.users-permissions.ratelimit"], "prefix": "" } }, @@ -197,6 +197,15 @@ "method": "POST", "path": "/auth/reset-password", "handler": "Auth.changePassword", + "config": { + "policies": ["plugins.users-permissions.ratelimit"], + "prefix": "" + } + }, + { + "method": "GET", + "path": "/auth/email-confirmation", + "handler": "Auth.emailConfirmation", "config": { "policies": [], "prefix": "" diff --git a/packages/strapi-plugin-users-permissions/controllers/Auth.js b/packages/strapi-plugin-users-permissions/controllers/Auth.js index da1edb2375..b88b7c02a6 100644 --- a/packages/strapi-plugin-users-permissions/controllers/Auth.js +++ b/packages/strapi-plugin-users-permissions/controllers/Auth.js @@ -52,10 +52,18 @@ module.exports = { // Check if the user exists. const user = await strapi.query('user', 'users-permissions').findOne(query, ['role']); + if (_.get(await store.get({key: 'advanced'}), 'email_confirmation') && user.confirmed !== true) { + return ctx.badRequest(null, ctx.request.admin ? [{ messages: [{ id: 'Auth.form.error.confirmed' }] }] : 'Your account email is not confirmed.'); + } + if (!user) { return ctx.badRequest(null, ctx.request.admin ? [{ messages: [{ id: 'Auth.form.error.invalid' }] }] : 'Identifier or password invalid.'); } + if (user.blocked === true) { + return ctx.badRequest(null, ctx.request.admin ? [{ messages: [{ id: 'Auth.form.error.blocked' }] }] : 'Your account has been blocked by the administrator.'); + } + if (user.role.type !== 'root' && ctx.request.admin) { return ctx.badRequest(null, ctx.request.admin ? [{ messages: [{ id: 'Auth.form.error.noAdminAccess' }] }] : `You're not an administrator.`); } @@ -216,12 +224,15 @@ module.exports = { }, register: async (ctx) => { - const settings = await strapi.store({ + const pluginStore = await strapi.store({ environment: '', type: 'plugin', - name: 'users-permissions', + name: 'users-permissions' + }); + + const settings = await pluginStore.get({ key: 'advanced' - }).get(); + }); if (!settings.allow_register) { return ctx.badRequest(null, ctx.request.admin ? [{ messages: [{ id: 'Auth.advanced.allow_register' }] }] : 'Register action is currently disabled.'); @@ -274,15 +285,53 @@ module.exports = { return ctx.badRequest(null, ctx.request.admin ? [{ messages: [{ id: 'Auth.form.error.email.taken' }] }] : 'Email is already taken.'); } - if (user && user.provider !== params.provider && strapi.plugins['users-permissions'].config.advanced.unique_email) { + if (user && user.provider !== params.provider && settings.unique_email) { return ctx.badRequest(null, ctx.request.admin ? [{ messages: [{ id: 'Auth.form.error.email.taken' }] }] : 'Email is already taken.'); } try { + if (!settings.email_confirmation) { + params.confirmed = true; + } + const user = await strapi.query('user', 'users-permissions').create(params); + const jwt = strapi.plugins['users-permissions'].services.jwt.issue(_.pick(user.toJSON ? user.toJSON() : user, ['_id', 'id'])); + + if (settings.email_confirmation) { + const storeEmail = (await pluginStore.get({ + key: 'email' + })) || {}; + + const settings = storeEmail['email_confirmation'] ? storeEmail['email_confirmation'].options : {}; + + settings.message = await strapi.plugins['users-permissions'].services.userspermissions.template(settings.message, { + URL: `http://${strapi.config.currentEnvironment.server.host}:${strapi.config.currentEnvironment.server.port}/auth/email-confirmation`, + USER: _.omit(user.toJSON ? user.toJSON() : user, ['password', 'resetPasswordToken', 'role', 'provider']), + CODE: jwt + }); + + settings.object = await strapi.plugins['users-permissions'].services.userspermissions.template(settings.object, { + USER: _.omit(user.toJSON ? user.toJSON() : user, ['password', 'resetPasswordToken', 'role', 'provider']) + }); + + try { + // Send an email to the user. + await strapi.plugins['email'].services.email.send({ + to: user.email, + from: (settings.from.email && settings.from.name) ? `"${settings.from.name}" <${settings.from.email}>` : undefined, + replyTo: settings.response_email, + subject: settings.object, + text: settings.message, + html: settings.message + }); + } catch (err) { + return ctx.badRequest(null, err); + } + } + ctx.send({ - jwt: strapi.plugins['users-permissions'].services.jwt.issue(_.pick(user.toJSON ? user.toJSON() : user, ['_id', 'id'])), + jwt, user: _.omit(user.toJSON ? user.toJSON() : user, ['password', 'resetPasswordToken']) }); } catch(err) { @@ -290,5 +339,22 @@ module.exports = { ctx.badRequest(null, ctx.request.admin ? [{ messages: [{ id: adminError }] }] : err.message); } + }, + + emailConfirmation: async (ctx) => { + const params = ctx.query; + + const user = await strapi.plugins['users-permissions'].services.jwt.verify(params.confirmation); + + await strapi.plugins['users-permissions'].services.user.edit(_.pick(user, ['_id', 'id']), {confirmed: true}); + + const settings = await strapi.store({ + environment: '', + type: 'plugin', + name: 'users-permissions', + key: 'advanced' + }).get(); + + ctx.redirect(settings.email_confirmation_redirection || '/'); } }; diff --git a/packages/strapi-plugin-users-permissions/controllers/UsersPermissions.js b/packages/strapi-plugin-users-permissions/controllers/UsersPermissions.js index 568e33155e..d3a13ebfa0 100644 --- a/packages/strapi-plugin-users-permissions/controllers/UsersPermissions.js +++ b/packages/strapi-plugin-users-permissions/controllers/UsersPermissions.js @@ -186,7 +186,7 @@ module.exports = { type: 'plugin', name: 'users-permissions', key: 'email' - }).set({value: ctx.request.body}); + }).set({value: ctx.request.body['email-templates']}); ctx.send({ ok: true }); }, @@ -237,7 +237,7 @@ module.exports = { type: 'plugin', name: 'users-permissions', key: 'grant' - }).set({value: ctx.request.body}); + }).set({value: ctx.request.body.providers}); ctx.send({ ok: true }); } diff --git a/packages/strapi-plugin-users-permissions/middlewares/users-permissions/index.js b/packages/strapi-plugin-users-permissions/middlewares/users-permissions/index.js index 7e9d41dd19..988995824c 100644 --- a/packages/strapi-plugin-users-permissions/middlewares/users-permissions/index.js +++ b/packages/strapi-plugin-users-permissions/middlewares/users-permissions/index.js @@ -14,6 +14,12 @@ module.exports = strapi => { }, initialize: function(cb) { + _.forEach(strapi.admin.config.routes, value => { + if (_.get(value.config, 'policies')) { + value.config.policies.unshift('plugins.users-permissions.permissions'); + } + }); + _.forEach(strapi.config.routes, value => { if (_.get(value.config, 'policies')) { value.config.policies.unshift('plugins.users-permissions.permissions'); diff --git a/packages/strapi-plugin-users-permissions/models/Permission.settings.json b/packages/strapi-plugin-users-permissions/models/Permission.settings.json index 2d037f3c70..e6e40033af 100644 --- a/packages/strapi-plugin-users-permissions/models/Permission.settings.json +++ b/packages/strapi-plugin-users-permissions/models/Permission.settings.json @@ -36,4 +36,4 @@ "configurable": false } } -} +} \ No newline at end of file diff --git a/packages/strapi-plugin-users-permissions/models/Role.settings.json b/packages/strapi-plugin-users-permissions/models/Role.settings.json index 66fa504376..7549710040 100644 --- a/packages/strapi-plugin-users-permissions/models/Role.settings.json +++ b/packages/strapi-plugin-users-permissions/models/Role.settings.json @@ -20,16 +20,18 @@ "unique": true, "configurable": false }, - "users": { - "collection": "user", - "via": "role", - "plugin": "users-permissions" - }, "permissions": { "collection": "permission", "via": "role", "plugin": "users-permissions", - "configurable": false + "configurable": false, + "isVirtual": true + }, + "users": { + "collection": "user", + "via": "role", + "plugin": "users-permissions" } - } + }, + "collectionName": "users-permissions_role" } \ No newline at end of file diff --git a/packages/strapi-plugin-users-permissions/models/User.settings.json b/packages/strapi-plugin-users-permissions/models/User.settings.json index da72db76e3..e166ceabde 100644 --- a/packages/strapi-plugin-users-permissions/models/User.settings.json +++ b/packages/strapi-plugin-users-permissions/models/User.settings.json @@ -18,10 +18,6 @@ "configurable": false, "required": true }, - "provider": { - "type": "string", - "configurable": false - }, "password": { "type": "password", "minLength": 6, @@ -31,7 +27,17 @@ "resetPasswordToken": { "type": "string", "configurable": false, - "private": true + " ": true + }, + "confirmed": { + "type": "boolean", + "default": false, + "configurable": false + }, + "blocked": { + "type": "boolean", + "default": false, + "configurable": false }, "role": { "model": "role", @@ -40,4 +46,4 @@ "configurable": false } } -} \ No newline at end of file +} diff --git a/packages/strapi-plugin-users-permissions/package.json b/packages/strapi-plugin-users-permissions/package.json index 4cbca8c236..af58f296b2 100644 --- a/packages/strapi-plugin-users-permissions/package.json +++ b/packages/strapi-plugin-users-permissions/package.json @@ -1,6 +1,6 @@ { "name": "strapi-plugin-users-permissions", - "version": "3.0.0-alpha.12.7.1", + "version": "3.0.0-alpha.14", "description": "Protect your API with a full-authentication process based on JWT", "strapi": { "name": "Roles & Permissions", @@ -26,12 +26,13 @@ "grant-koa": "^3.8.1", "jsonwebtoken": "^8.1.0", "koa": "^2.1.0", + "koa2-ratelimit": "^0.6.1", "purest": "^2.0.1", "request": "^2.83.0", "uuid": "^3.1.0" }, "devDependencies": { - "strapi-helper-plugin": "3.0.0-alpha.12.7.1" + "strapi-helper-plugin": "3.0.0-alpha.14" }, "author": { "name": "Strapi team", diff --git a/packages/strapi-plugin-users-permissions/services/Providers.js b/packages/strapi-plugin-users-permissions/services/Providers.js index a7f32886a1..e278a1eadf 100644 --- a/packages/strapi-plugin-users-permissions/services/Providers.js +++ b/packages/strapi-plugin-users-permissions/services/Providers.js @@ -46,7 +46,9 @@ exports.connect = (provider, query) => { try { const users = await strapi.query('user', 'users-permissions').find({ - email: profile.email + where: { + email: profile.email + } }); const advanced = await strapi.store({ @@ -107,6 +109,40 @@ const getProfile = async (provider, query, callback) => { }).get(); switch (provider) { + case 'discord': { + const discord = new Purest({ + provider: 'discord', + config: { + 'discord': { + 'https://discordapp.com/api/': { + '__domain': { + 'auth': { + 'auth': {'bearer': '[0]'} + } + }, + '{endpoint}': { + '__path': { + 'alias': '__default' + } + } + } + } + } + }); + discord.query().get('users/@me').auth(access_token).request((err, res, body) => { + if (err) { + callback(err); + } else { + // Combine username and discriminator because discord username is not unique + var username = `${body.username}#${body.discriminator}`; + callback(null, { + username: username, + email: body.email + }); + } + }); + break; + } case 'facebook': { const facebook = new Purest({ provider: 'facebook' @@ -172,6 +208,40 @@ const getProfile = async (provider, query, callback) => { }); break; } + case 'microsoft': { + const microsoft = new Purest({ + provider: 'microsoft', + config:{ + 'microsoft': { + 'https://graph.microsoft.com': { + '__domain': { + 'auth': { + 'auth': {'bearer': '[0]'} + } + }, + '[version]/{endpoint}': { + '__path': { + 'alias': '__default', + 'version': 'v1.0' + } + } + } + } + } + }); + + microsoft.query().get('me').auth(access_token).request((err, res, body) => { + if (err) { + callback(err); + } else { + callback(null, { + username: body.userPrincipalName, + email: body.userPrincipalName + }); + } + }); + break; + } case 'twitter': { const twitter = new Purest({ provider: 'twitter', diff --git a/packages/strapi-plugin-users-permissions/services/UsersPermissions.js b/packages/strapi-plugin-users-permissions/services/UsersPermissions.js index 4e5fc46f2f..ac6f6f92f0 100644 --- a/packages/strapi-plugin-users-permissions/services/UsersPermissions.js +++ b/packages/strapi-plugin-users-permissions/services/UsersPermissions.js @@ -88,7 +88,7 @@ module.exports = { }, getPlugins: (plugin, lang = 'en') => { - return new Promise((resolve, reject) => { + return new Promise((resolve) => { request({ uri: `https://marketplace.strapi.io/plugins?lang=${lang}`, json: true, @@ -97,7 +97,7 @@ module.exports = { } }, (err, response, body) => { if (err) { - return reject(err); + return resolve([]); } resolve(body); @@ -116,7 +116,9 @@ module.exports = { }, {})); const appControllers = Object.keys(strapi.api || {}).reduce((acc, key) => { - acc.controllers[key] = generateActions(strapi.api[key].controllers[key]); + Object.keys(strapi.api[key].controllers).forEach((controller) => { + acc.controllers[controller] = generateActions(strapi.api[key].controllers[controller]); + }); return acc; }, { controllers: {} }); @@ -199,7 +201,11 @@ module.exports = { }, updatePermissions: async function (cb) { - const actions = strapi.plugins['users-permissions'].config.actions || []; + // fetch all the current permissions from the database, and format them into an array of actions. + const databasePermissions = await strapi.query('permission', 'users-permissions').find(); + const actions = databasePermissions + .map(permission => `${permission.type}.${permission.controller}.${permission.action}`); + // Aggregate first level actions. const appActions = Object.keys(strapi.api || {}).reduce((acc, api) => { @@ -232,7 +238,7 @@ module.exports = { // Merge array into one. const currentActions = appActions.concat(pluginsActions); // Count permissions available. - const permissions = await strapi.query('permission', 'users-permissions').count(); + const permissions = databasePermissions.length; // Compare to know if actions have been added or removed from controllers. if (!_.isEqual(actions, currentActions) || permissions < 1) { @@ -245,13 +251,14 @@ module.exports = { const defaultPolicy = (obj, role) => { const isCallback = obj.action === 'callback' && obj.controller === 'auth' && obj.type === 'users-permissions' && role.type === 'public'; const isConnect = obj.action === 'connect' && obj.controller === 'auth' && obj.type === 'users-permissions'; - const isRegister = obj.action === 'register' && obj.controller === 'auth' && obj.type === 'users-permissions' && role.type === 'public'; const isPassword = obj.action === 'forgotpassword' && obj.controller === 'auth' && obj.type === 'users-permissions' && role.type === 'public'; + const isRegister = obj.action === 'register' && obj.controller === 'auth' && obj.type === 'users-permissions' && role.type === 'public'; + const isConfirmation = obj.action === 'emailconfirmation' && obj.controller === 'auth' && obj.type === 'users-permissions' && role.type === 'public'; const isNewPassword = obj.action === 'changepassword' && obj.controller === 'auth' && obj.type === 'users-permissions' && role.type === 'public'; const isInit = obj.action === 'init' && obj.controller === 'userspermissions'; const isMe = obj.action === 'me' && obj.controller === 'user' && obj.type === 'users-permissions'; const isReload = obj.action === 'autoreload'; - const enabled = isCallback || isRegister || role.type === 'root' || isInit || isPassword || isNewPassword || isMe || isReload || isConnect; + const enabled = isCallback || isRegister || role.type === 'root' || isInit || isPassword || isNewPassword || isMe || isReload || isConnect || isConfirmation; return Object.assign(obj, { enabled, policy: '' }); }; diff --git a/packages/strapi-redis/.gitignore b/packages/strapi-redis/.gitignore deleted file mode 100755 index 218a5700e9..0000000000 --- a/packages/strapi-redis/.gitignore +++ /dev/null @@ -1,95 +0,0 @@ -############################ -# OS X -############################ - -.DS_Store -.AppleDouble -.LSOverride -Icon -.Spotlight-V100 -.Trashes -._* - - -############################ -# Linux -############################ - -*~ - - -############################ -# Windows -############################ - -Thumbs.db -ehthumbs.db -Desktop.ini -$RECYCLE.BIN/ -*.cab -*.msi -*.msm -*.msp - - -############################ -# Packages -############################ - -*.7z -*.csv -*.dat -*.dmg -*.gz -*.iso -*.jar -*.rar -*.tar -*.zip -*.com -*.class -*.dll -*.exe -*.o -*.seed -*.so -*.swo -*.swp -*.swn -*.swm -*.out -*.pid - - -############################ -# Logs and databases -############################ - -.tmp -*.log -*.sql -*.sqlite - - -############################ -# Misc. -############################ - -*# -.idea -nbproject - - -############################ -# Node.js -############################ - -lib-cov -lcov.info -pids -logs -results -build -node_modules -.node_history -package-lock.json diff --git a/packages/strapi-upload-aws-s3/.gitignore b/packages/strapi-upload-aws-s3/.gitignore index 218a5700e9..ab74240ce1 100644 --- a/packages/strapi-upload-aws-s3/.gitignore +++ b/packages/strapi-upload-aws-s3/.gitignore @@ -93,3 +93,4 @@ build node_modules .node_history package-lock.json +yarn.lock diff --git a/packages/strapi-upload-aws-s3/lib/index.js b/packages/strapi-upload-aws-s3/lib/index.js index 0545af0b36..447c5f9d42 100644 --- a/packages/strapi-upload-aws-s3/lib/index.js +++ b/packages/strapi-upload-aws-s3/lib/index.js @@ -70,10 +70,12 @@ module.exports = { upload: (file) => { return new Promise((resolve, reject) => { // upload file on S3 bucket + const path = file.path ? `${file.path}/` : ''; S3.upload({ - Key: `${file.hash}${file.ext}`, + Key: `${path}${file.hash}${file.ext}`, Body: new Buffer(file.buffer, 'binary'), - ACL: 'public-read' + ACL: 'public-read', + ContentType: file.mime, }, (err, data) => { if (err) { return reject(err); @@ -89,12 +91,9 @@ module.exports = { delete: (file) => { return new Promise((resolve, reject) => { // delete file on S3 bucket + const path = file.path ? `${file.path}/` : ''; S3.deleteObjects({ - Delete: { - Objects: [{ - Key: `${file.hash}${file.ext}` - }] - } + Key: `${path}${file.hash}${file.ext}` }, (err, data) => { if (err) { return reject(err); diff --git a/packages/strapi-upload-aws-s3/package.json b/packages/strapi-upload-aws-s3/package.json index 0517d57175..cdfeb2706f 100644 --- a/packages/strapi-upload-aws-s3/package.json +++ b/packages/strapi-upload-aws-s3/package.json @@ -1,6 +1,6 @@ { "name": "strapi-upload-aws-s3", - "version": "3.0.0-alpha.12.7.1", + "version": "3.0.0-alpha.14", "description": "AWS S3 provider for strapi upload", "homepage": "http://strapi.io", "keywords": [ diff --git a/packages/strapi-upload-cloudinary/.gitignore b/packages/strapi-upload-cloudinary/.gitignore index 784dbb5950..bd2f239807 100644 --- a/packages/strapi-upload-cloudinary/.gitignore +++ b/packages/strapi-upload-cloudinary/.gitignore @@ -1,6 +1,7 @@ # Don't check auto-generated stuff into git node_modules package-lock.json +yarn.lock # Cruft .DS_Store diff --git a/packages/strapi-upload-cloudinary/package.json b/packages/strapi-upload-cloudinary/package.json index 46eb0a008e..96c150ed83 100644 --- a/packages/strapi-upload-cloudinary/package.json +++ b/packages/strapi-upload-cloudinary/package.json @@ -1,6 +1,6 @@ { "name": "strapi-upload-cloudinary", - "version": "3.0.0-alpha.12.7.1", + "version": "3.0.0-alpha.14", "description": "Cloudinary provider for strapi upload", "homepage": "http://strapi.io", "keywords": [ diff --git a/packages/strapi-upload-local/.gitignore b/packages/strapi-upload-local/.gitignore index 218a5700e9..ab74240ce1 100644 --- a/packages/strapi-upload-local/.gitignore +++ b/packages/strapi-upload-local/.gitignore @@ -93,3 +93,4 @@ build node_modules .node_history package-lock.json +yarn.lock diff --git a/packages/strapi-upload-local/package.json b/packages/strapi-upload-local/package.json index a6fedda903..bb37131e96 100644 --- a/packages/strapi-upload-local/package.json +++ b/packages/strapi-upload-local/package.json @@ -1,6 +1,6 @@ { "name": "strapi-upload-local", - "version": "3.0.0-alpha.12.7.1", + "version": "3.0.0-alpha.14", "description": "Local provider for strapi upload", "homepage": "http://strapi.io", "keywords": [ diff --git a/packages/strapi-upload-rackspace/package.json b/packages/strapi-upload-rackspace/package.json index 37f37c3597..1567f9c611 100644 --- a/packages/strapi-upload-rackspace/package.json +++ b/packages/strapi-upload-rackspace/package.json @@ -1,6 +1,6 @@ { "name": "strapi-upload-rackspace", - "version": "3.0.0-alpha.12.7.1", + "version": "3.0.0-alpha.14", "description": "Rackspace provider for strapi upload", "main": "./lib", "scripts": { diff --git a/packages/strapi-utils/.gitignore b/packages/strapi-utils/.gitignore index 218a5700e9..ab74240ce1 100755 --- a/packages/strapi-utils/.gitignore +++ b/packages/strapi-utils/.gitignore @@ -93,3 +93,4 @@ build node_modules .node_history package-lock.json +yarn.lock diff --git a/packages/strapi-utils/lib/index.js b/packages/strapi-utils/lib/index.js index ae57227e18..7c9ce99755 100755 --- a/packages/strapi-utils/lib/index.js +++ b/packages/strapi-utils/lib/index.js @@ -15,5 +15,6 @@ module.exports = { models: require('./models'), packageManager: require('./packageManager'), policy: require('./policy'), - regex: require('./regex') + regex: require('./regex'), + templateConfiguration: require('./templateConfiguration') }; diff --git a/packages/strapi-utils/lib/knex.js b/packages/strapi-utils/lib/knex.js index 03b4c8affa..66d4621aa2 100755 --- a/packages/strapi-utils/lib/knex.js +++ b/packages/strapi-utils/lib/knex.js @@ -21,10 +21,10 @@ module.exports = scope => { // First, make sure the application we have access to // the migration generator. try { - require.resolve(path.resolve(scope.rootPath, 'node_modules', 'strapi-knex')); + require.resolve(path.resolve(scope.rootPath, 'node_modules', 'strapi-hook-knex')); } catch (err) { console.error('Impossible to call the Knex migration tool.'); - console.error('You can install it with `$ npm install strapi-knex --save`.'); + console.error('You can install it with `$ npm install strapi-hook-knex --save`.'); process.exit(1); } diff --git a/packages/strapi-utils/lib/models.js b/packages/strapi-utils/lib/models.js old mode 100755 new mode 100644 index 4f416be13a..0cfebd0248 --- a/packages/strapi-utils/lib/models.js +++ b/packages/strapi-utils/lib/models.js @@ -457,11 +457,28 @@ module.exports = { _.forEach(params, (value, key) => { let result; let formattedValue; - - // Check if the value if a valid candidate to be converted to a number value - formattedValue = isNumeric(value) - ? _.toNumber(value) - : value; + let modelAttributes = models[model]['attributes']; + let fieldType; + // Get the field type to later check if it's a string before number conversion + if (modelAttributes[key]) { + fieldType = modelAttributes[key]['type']; + } else { + // Remove the filter keyword at the end + let splitKey = key.split('_').slice(0,-1); + splitKey = splitKey.join('_'); + + if (modelAttributes[splitKey]) { + fieldType = modelAttributes[splitKey]['type']; + } + } + // Check if the value is a valid candidate to be converted to a number value + if (fieldType !== 'string') { + formattedValue = isNumeric(value) + ? _.toNumber(value) + : value; + } else { + formattedValue = value; + } if (_.includes(['_start', '_limit'], key)) { result = convertor(formattedValue, key); diff --git a/packages/strapi-utils/lib/policy.js b/packages/strapi-utils/lib/policy.js index b7756bd6c2..e7496d1265 100644 --- a/packages/strapi-utils/lib/policy.js +++ b/packages/strapi-utils/lib/policy.js @@ -12,16 +12,21 @@ module.exports = { // Looking for global policy or namespaced. if ( _.startsWith(policy, globalPolicyPrefix, 0) && - !_.isEmpty( - strapi.config.policies, - policy.replace(globalPolicyPrefix, '') + !_.isUndefined( + _.get( + strapi.config.policies, + policy.replace(globalPolicyPrefix, '').toLowerCase() + ) ) ) { // Global policy. return policies.push( - this.parsePolicy(strapi.config.policies[ - policy.replace(globalPolicyPrefix, '').toLowerCase() - ]) + this.parsePolicy( + _.get( + strapi.config.policies, + policy.replace(globalPolicyPrefix, '').toLowerCase() + ) + ) ); } else if ( _.startsWith(policy, pluginPolicyPrefix, 0) && diff --git a/packages/strapi-utils/lib/templateConfiguration.js b/packages/strapi-utils/lib/templateConfiguration.js new file mode 100644 index 0000000000..810a7352a8 --- /dev/null +++ b/packages/strapi-utils/lib/templateConfiguration.js @@ -0,0 +1,26 @@ +const { isString, isPlainObject } = require('lodash'); + +const regex = /\$\{[^()]*\}/g; + +/** + * Allow dynamic config values through the native ES6 template string function. + */ +const templateConfiguration = (obj) => { + // Allow values which looks like such as an ES6 literal string without parenthesis inside (aka function call). + return Object.keys(obj).reduce((acc, key) => { + if (isPlainObject(obj[key]) && !isString(obj[key])) { + acc[key] = templateConfiguration(obj[key]); + + } else if (isString(obj[key]) && obj[key].match(regex) !== null) { + // eslint-disable-next-line prefer-template + acc[key] = eval('`' + obj[key] + '`'); + + } else { + acc[key] = obj[key]; + } + + return acc; + }, {}); +}; + +module.exports = templateConfiguration; diff --git a/packages/strapi-utils/package.json b/packages/strapi-utils/package.json index e40163b296..0e01f898bf 100755 --- a/packages/strapi-utils/package.json +++ b/packages/strapi-utils/package.json @@ -1,6 +1,6 @@ { "name": "strapi-utils", - "version": "3.0.0-alpha.12.7.1", + "version": "3.0.0-alpha.14", "description": "Shared utilities for the Strapi packages", "homepage": "http://strapi.io", "keywords": [ @@ -21,7 +21,7 @@ "commander": "^2.11.0", "joi-json": "^2.0.1", "knex": "^0.13.0", - "lodash": "^4.17.4", + "lodash": "^4.17.5", "pino": "^4.7.1", "shelljs": "^0.7.7" }, diff --git a/packages/strapi/.gitignore b/packages/strapi/.gitignore index 84684d7459..a495c3dd62 100755 --- a/packages/strapi/.gitignore +++ b/packages/strapi/.gitignore @@ -93,6 +93,7 @@ build node_modules .node_history package-lock.json +yarn.lock ############################ diff --git a/packages/strapi/bin/strapi-install.js b/packages/strapi/bin/strapi-install.js index 156266ca4a..5571ae845b 100755 --- a/packages/strapi/bin/strapi-install.js +++ b/packages/strapi/bin/strapi-install.js @@ -60,7 +60,7 @@ module.exports = function (plugin, cliArguments) { if (!isStrapiInstalledWithNPM) { // Create the directory yarn doesn't do it it - shell.exec(`mkdir ${pluginPath}`); + shell.exec('mkdir', [pluginPath]); // Add a package.json so it installs the dependencies shell.touch(`${pluginPath}/package.json`); fs.writeFileSync(`${pluginPath}/package.json`, JSON.stringify({}), 'utf8'); diff --git a/packages/strapi/bin/strapi-new.js b/packages/strapi/bin/strapi-new.js index c939bccb83..6c475c28f7 100755 --- a/packages/strapi/bin/strapi-new.js +++ b/packages/strapi/bin/strapi-new.js @@ -63,7 +63,10 @@ module.exports = function (name, cliArguments) { username: cliArguments.dbusername, password: cliArguments.dbpassword }, - options: {} + options: { + authenticationDatabase: cliArguments.dbauth, + ssl: cliArguments.dbssl + } }; } diff --git a/packages/strapi/bin/strapi-start.js b/packages/strapi/bin/strapi-start.js index 9f109f43c7..fb0989895c 100755 --- a/packages/strapi/bin/strapi-start.js +++ b/packages/strapi/bin/strapi-start.js @@ -13,7 +13,7 @@ const cluster = require('cluster'); // Public dependencies const fs = require('fs'); const _ = require('lodash'); -const {cyan} = require('chalk'); +const { cyan } = require('chalk'); // Logger. const { cli, logger } = require('strapi-utils'); @@ -66,7 +66,8 @@ module.exports = function(appPath = '') { }; const setFilesToWatch = (src) => { - var files = fs.readdirSync(src); + let files = _.includes(src, '/admin') || _.includes(src, 'components') ? [] : fs.readdirSync(src); + _.forEach(files, file => { if ( _.startsWith(file, '.') || @@ -86,8 +87,6 @@ module.exports = function(appPath = '') { setFilesToWatch(appPath); - - if (cluster.isMaster) { cluster.on('message', (worker, message) => { switch (message) { diff --git a/packages/strapi/bin/strapi.js b/packages/strapi/bin/strapi.js index 7d497e2517..453a7705eb 100755 --- a/packages/strapi/bin/strapi.js +++ b/packages/strapi/bin/strapi.js @@ -59,6 +59,8 @@ program .option('--dbname ', 'Database name') .option('--dbusername ', 'Database username') .option('--dbpassword ', 'Database password') + .option('--dbssl ', 'Database SSL') + .option('--dbauth ', 'Authentication Database') .description('create a new application') .action(require('./strapi-new')); diff --git a/packages/strapi/lib/Strapi.js b/packages/strapi/lib/Strapi.js index d5b7c44b21..3f6e4bea0d 100755 --- a/packages/strapi/lib/Strapi.js +++ b/packages/strapi/lib/Strapi.js @@ -70,6 +70,7 @@ class Strapi extends EventEmitter { port: process.env.PORT || 1337, environment: toLower(process.env.NODE_ENV) || 'development', environments: {}, + admin: {}, paths: { admin: 'admin', api: 'api', @@ -110,7 +111,7 @@ class Strapi extends EventEmitter { // Update source admin. await admin.call(this); // Launch server. - this.server.listen(this.config.port, err => { + this.server.listen(this.config.port, async (err) => { if (err) { this.log.debug(`⚠️ Server wasn't able to start properly.`); this.log.error(err); @@ -124,7 +125,7 @@ class Strapi extends EventEmitter { this.log.info(`Version: ${this.config.info.strapi} (node v${this.config.info.node})`); this.log.info('To shut down your server, press + C at any time'); console.log(); - this.log.info(`☄️ Admin panel: ${this.config.url}/admin`); + this.log.info(`☄️ Admin panel: ${this.config.admin.url}`); this.log.info(`⚡️ Server: ${this.config.url}`); console.log(); @@ -134,10 +135,15 @@ class Strapi extends EventEmitter { if (cb && typeof cb === 'function') { cb(); } + + if (this.config.environment === 'development' && get(this.config.currentEnvironment, 'server.admin.autoOpen', true) !== false) { + await utils.openBrowser.call(this); + } }); } catch (err) { this.log.debug(`⛔️ Server wasn't able to start properly.`); this.log.error(err); + console.log(err); this.stop(); } } @@ -228,11 +234,14 @@ class Strapi extends EventEmitter { reload() { const state = { - shouldReload: true, + shouldReload: 0 }; const reload = function() { - if (state.shouldReload === false) { + if (state.shouldReload > 0) { + // Reset the reloading state + state.shouldReload -= 1; + reload.isReloading = false; return; } @@ -250,7 +259,9 @@ class Strapi extends EventEmitter { enumerable: true, set: value => { // Special state when the reloader is disabled temporarly (see GraphQL plugin example). - state.shouldReload = !(state.isWatching === false && value === true); + if (state.isWatching === false && value === true) { + state.shouldReload += 1; + } state.isWatching = value; }, get: () => { diff --git a/packages/strapi/lib/core/configurations.js b/packages/strapi/lib/core/configurations.js index ae39c6b89c..02afee46a7 100755 --- a/packages/strapi/lib/core/configurations.js +++ b/packages/strapi/lib/core/configurations.js @@ -1,9 +1,11 @@ 'use strict'; // Dependencies. +const fs = require('fs'); const path = require('path'); const glob = require('glob'); -const { merge, setWith, get, upperFirst, isString, isEmpty, isObject, pullAll, defaults, isPlainObject, assign, clone, cloneDeep, camelCase } = require('lodash'); +const { merge, setWith, get, upperFirst, isEmpty, isObject, pullAll, defaults, assign, clone, cloneDeep, camelCase } = require('lodash'); +const { templateConfiguration } = require('strapi-utils'); const utils = require('../utils'); module.exports.nested = function() { @@ -84,14 +86,14 @@ module.exports.app = async function() { this.config.currentEnvironment = this.config.environments[this.config.environment] || {}; // Set current connections. - this.config.connections = get(this.config.currentEnvironment, `database.connections`, {}); + this.config.connections = get(this.config.currentEnvironment, 'database.connections', {}); if (get(this.config, 'language.enabled')) { this.config.language.locales = Object.keys(get(strapi.config, 'locales', {})); } // Template literal string. - this.config = templateConfigurations(this.config); + this.config = templateConfiguration(this.config); // Initialize main router to use it in middlewares. this.router = this.koaMiddlewares.routerJoi(); @@ -281,7 +283,7 @@ module.exports.app = async function() { // Enable hooks and dependencies related to the connections. for (let name in this.config.connections) { const connection = this.config.connections[name]; - const connector = connection.connector.replace('strapi-', ''); + const connector = connection.connector.replace('strapi-hook-', ''); enableHookNestedDependencies.call(this, connector, flattenHooksConfig); } @@ -304,36 +306,47 @@ module.exports.app = async function() { this.config.hook.settings = Object.keys(this.hook).reduce((acc, current) => { // Try to find the settings in the current environment, then in the main configurations. - const currentSettings = flattenHooksConfig[current] || this.config[current]; + const currentSettings = merge(get(cloneDeep(this.hook[current]), ['defaults', current], {}), flattenHooksConfig[current] || get(this.config.currentEnvironment, ['hook', current]) || get(this.config, ['hook', current])); + acc[current] = !isObject(currentSettings) ? {} : currentSettings; - if (isString(currentSettings)) { - acc[current] = currentSettings; - } else { - acc[current] = !isObject(currentSettings) ? {} : currentSettings; - - if (this.hook[current].isPlugin) { - acc[current].enabled = true; - } - - if (!acc[current].hasOwnProperty('enabled')) { - this.log.warn(`(hook:${current}) wasn't loaded due to missing key \`enabled\` in the configuration`); - } - - // Ensure that enabled key exist by forcing to false. - defaults(acc[current], { enabled : false }); + if (!acc[current].hasOwnProperty('enabled')) { + this.log.warn(`(hook:${current}) wasn't loaded due to missing key \`enabled\` in the configuration`); } + // Ensure that enabled key exist by forcing to false. + defaults(acc[current], { enabled : false }); + return acc; }, {}); + // default settings this.config.port = get(this.config.currentEnvironment, 'server.port') || this.config.port; this.config.host = get(this.config.currentEnvironment, 'server.host') || this.config.host; this.config.url = `http://${this.config.host}:${this.config.port}`; + + // Admin. + this.config.admin.devMode = isAdminInDevMode.call(this); + this.config.admin.url = this.config.admin.devMode ? + `http://${this.config.host}:4000/${get(this.config.currentEnvironment.server, 'admin.path', 'admin')}`: + `${this.config.url}/${get(this.config.currentEnvironment.server, 'admin.path', 'admin')}`; + + // proxy settings + this.config.proxy = get(this.config.currentEnvironment, 'server.proxy' || {}); + + // check if SSL enabled and construct proxy url + function getProxyUrl(ssl, url) { + return `http${ssl ? 's' : ''}://${url}`; + } + + // check if proxy is enabled and construct url + if (get(this.config, 'proxy.enabled')) { + this.config.url = getProxyUrl(this.config.proxy.ssl, `${this.config.proxy.host}:${this.config.proxy.port}`); + } }; const enableHookNestedDependencies = function (name, flattenHooksConfig, force = false) { if (!this.hook[name]) { - this.log.warn(`(hook:${name}) \`strapi-${name}\` is missing in your dependencies. Please run \`npm install strapi-${name}\``); + this.log.warn(`(hook:${name}) \`strapi-hook-${name}\` is missing in your dependencies. Please run \`npm install strapi-hook-${name}\``); } // Couldn't find configurations for this hook. @@ -347,7 +360,7 @@ const enableHookNestedDependencies = function (name, flattenHooksConfig, force = const connector = get(this.config.connections, models[model].connection, {}).connector; if (connector) { - return connector.replace('strapi-', '') === name; + return connector.replace('strapi-hook-', '') === name; } return false; @@ -363,29 +376,18 @@ const enableHookNestedDependencies = function (name, flattenHooksConfig, force = // Enabled dependencies. if (get(this.hook, `${name}.dependencies`, []).length > 0) { this.hook[name].dependencies.forEach(dependency => { - enableHookNestedDependencies.call(this, dependency.replace('strapi-', ''), flattenHooksConfig, true); + enableHookNestedDependencies.call(this, dependency.replace('strapi-hook-', ''), flattenHooksConfig, true); }); } } }; -/** - * Allow dynamic config values through - * the native ES6 template string function. - */ -const regex = /\$\{[^()]*\}/g; -const templateConfigurations = function (obj) { - // Allow values which looks like such as - // an ES6 literal string without parenthesis inside (aka function call). - return Object.keys(obj).reduce((acc, key) => { - if (isPlainObject(obj[key]) && !isString(obj[key])) { - acc[key] = templateConfigurations(obj[key]); - } else if (isString(obj[key]) && obj[key].match(regex) !== null) { - acc[key] = eval('`' + obj[key] + '`'); // eslint-disable-line prefer-template - } else { - acc[key] = obj[key]; - } - - return acc; - }, {}); +const isAdminInDevMode = function () { + try { + fs.accessSync(path.resolve(this.config.appPath, 'admin', 'admin', 'build', 'index.html'), fs.constants.R_OK | fs.constants.W_OK); + + return true; + } catch (e) { + return false; + } }; diff --git a/packages/strapi/lib/core/hooks.js b/packages/strapi/lib/core/hooks.js index e03c210761..5416b503ed 100755 --- a/packages/strapi/lib/core/hooks.js +++ b/packages/strapi/lib/core/hooks.js @@ -2,46 +2,34 @@ // Dependencies. const path = require('path'); -const fs = require('fs'); const glob = require('glob'); const { parallel } = require('async'); -const { upperFirst, lowerFirst, get } = require('lodash'); +const { endsWith, get } = require('lodash'); module.exports = function() { this.hook = {}; return Promise.all([ new Promise((resolve, reject) => { - const cwd = ''; + const cwd = this.config.appPath; // Load configurations. - glob('./node_modules/strapi-*', { - ignore: [ - './node_modules/strapi-admin', - './node_modules/strapi-utils', - './node_modules/strapi-generate*', - './node_modules/strapi-plugin-*', - './node_modules/strapi-helper-*', - './node_modules/strapi-middleware-*', - './node_modules/strapi-upload-*', - './node_modules/strapi-email-*', - './node_modules/strapi-lint' - ], - cwd: this.config.appPath + glob('./node_modules/*(strapi-hook-*)/*/*(index|defaults).*(js|json)', { + cwd }, (err, files) => { if (err) { return reject(err); } - mountHooks.call(this, files, cwd)(resolve, reject); + mountHooks.call(this, files, cwd, 'node_modules')(resolve, reject); }); }), new Promise((resolve, reject) => { - const cwd = 'hooks'; + const cwd = path.resolve(this.config.appPath, 'hooks'); // Load configurations. - glob('./*', { - cwd: path.resolve(this.config.appPath, 'hooks') + glob('./*/*(index|defaults).*(js|json)', { + cwd }, (err, files) => { if (err) { return reject(err); @@ -54,59 +42,55 @@ module.exports = function() { const cwd = path.resolve(this.config.appPath, 'plugins'); // Load configurations. - glob('./*/hooks/*', { + glob('./*/hooks/*/*(index|defaults).*(js|json)', { cwd }, (err, files) => { if (err) { return reject(err); } - mountHooks.call(this, files, cwd, true)(resolve, reject); + mountHooks.call(this, files, cwd, 'plugins')(resolve, reject); }); }) ]); }; -const mountHooks = function (files, cwd, isPlugin) { +const mountHooks = function (files, cwd, source) { return (resolve, reject) => parallel( files.map(p => cb => { - const extractStr = p - .split('/') - .pop() - .replace(/^strapi(-|\.)/, '') - .split('-'); + const folders = p.replace(/^.\/node_modules\/strapi-hook-/, './') + .split('/'); + const name = source === 'plugins' ? folders[folders.length - 2] : folders[1]; - const name = lowerFirst( - extractStr.length === 1 - ? extractStr[0] - : extractStr.map(p => upperFirst(p)).join('') - ); + this.hook[name] = this.hook[name] || { + loaded: false + }; - fs.readFile(path.resolve(this.config.appPath, cwd, p, 'package.json'), (err, content) => { + let dependencies = []; + if (source === 'node_modules') { try { - const pkg = isPlugin ? {} : JSON.parse(content); - - this.hook[name] = { - isPlugin, - loaded: false, - identity: name, - dependencies: get(pkg, 'strapi.dependencies') || [] - }; - - // Lazy loading. - Object.defineProperty(this.hook[name], 'load', { - configurable: false, - enumerable: true, - get: () => require(path.resolve(this.config.appPath, cwd, p)) - }); - - cb(); - } catch (e) { - cb(e); + dependencies = get(require(path.resolve(this.config.appPath, 'node_modules', `strapi-hook-${name}`, 'package.json')), 'strapi.dependencies', []); + } catch(err) { + // Silent } + } - }); + if (endsWith(p, 'index.js') && !this.hook[name].load) { + // Lazy loading. + Object.defineProperty(this.hook[name], 'load', { + configurable: false, + enumerable: true, + get: () => require(path.resolve(cwd, p)), + dependencies + }); + + this.hook[name].dependencies = dependencies; + } else if (endsWith(p, 'defaults.json')) { + this.hook[name].defaults = require(path.resolve(cwd, p)); + } + + cb(); }), (err) => { if (err) { diff --git a/packages/strapi/lib/core/plugins.js b/packages/strapi/lib/core/plugins.js index 2425051cdf..8bf1b615f6 100644 --- a/packages/strapi/lib/core/plugins.js +++ b/packages/strapi/lib/core/plugins.js @@ -2,187 +2,131 @@ // Dependencies. const path = require('path'); -const fs = require('fs'); +const fs = require('fs-extra'); const _ = require('lodash'); -module.exports = function() { - return new Promise((resolve, reject) => { - const folder = ((url = _.get(strapi.config.currentEnvironment.server, 'admin.path', 'admin')) => - url[0] === '/' ? url.substring(1) : url)().replace(/\/$/, ''); +module.exports = async function() { + const folder = ((url = _.get(strapi.config.currentEnvironment.server, 'admin.path', 'admin')) => + url[0] === '/' ? url.substring(1) : url)().replace(/\/$/, ''); - const configuratePlugin = (acc, current, source, name) => { - switch (source) { - case 'host': { - const host = - _.get(this.config.environments[current].server, 'admin.build.host').replace(/\/$/, '') || '/'; + const configuratePlugin = (acc, current, source, name) => { + switch (source) { + case 'host': { + const host = + _.get(this.config.environments[current].server, 'admin.build.host').replace(/\/$/, '') || '/'; - if (!host) { - throw new Error(`You can't use \`remote\` as a source without set the \`host\` configuration.`); - } - - const folder = _.get(this.config.environments[current].server, 'admin.build.plugins.folder', null); - - if (_.isString(folder)) { - const cleanFolder = folder[0] === '/' ? folder.substring(1) : folder; - - return `/${host}/${cleanFolder}/${name}/main.js`.replace('//', '/'); - } - - return `/${host}/${name}/main.js`.replace('//', '/'); + if (!host) { + throw new Error(`You can't use \`remote\` as a source without set the \`host\` configuration.`); } - case 'custom': - if (!_.isEmpty(_.get(this.plugins[name].config, `sources.${current}`, {}))) { - return (acc[current] = this.plugins[name].config.sources[current]); - } - throw new Error( - `You have to define the source URL for each environment in \`./plugins/**/config/sources.json\``, - ); - case 'backend': { - const backend = _.get( - this.config.environments[current], - 'server.admin.build.backend', - `http://${this.config.environments[current].server.host}:${ - this.config.environments[current].server.port - }`, - ).replace(/\/$/, ''); + const folder = _.get(this.config.environments[current].server, 'admin.build.plugins.folder', null); - return `${backend}/${folder.replace(/\/$/, '')}/${name}/main.js`; + if (_.isString(folder)) { + const cleanFolder = folder[0] === '/' ? folder.substring(1) : folder; + + return `/${host}/${cleanFolder}/${name}/main.js`.replace('//', '/'); } - default: - return `/${name}/main.js`; + + return `/${host}/${name}/main.js`.replace('//', '/'); } - }; - - const sourcePath = - this.config.environment !== 'test' - ? path.resolve(this.config.appPath, 'admin', 'admin', 'src', 'config', 'plugins.json') - : path.resolve( - this.config.appPath, - 'packages', - 'strapi-admin', - 'admin', - 'src', - 'config', - 'plugins.json', - ); - const buildPath = - this.config.environment !== 'test' - ? path.resolve(this.config.appPath, 'admin', 'admin', 'build', 'config', 'plugins.json') - : path.resolve( - this.config.appPath, - 'packages', - 'strapi-admin', - 'admin', - 'build', - 'config', - 'plugins.json', - ); - - try { - fs.access(path.resolve(this.config.appPath, 'admin', 'admin'), err => { - if (err && err.code !== 'ENOENT') { - return reject(err); + case 'custom': + if (!_.isEmpty(_.get(this.plugins[name].config, `sources.${current}`, {}))) { + return (acc[current] = this.plugins[name].config.sources[current]); } - // No admin. - if (err && err.code === 'ENOENT') { - return resolve(); - } + throw new Error( + `You have to define the source URL for each environment in \`./plugins/**/config/sources.json\``, + ); + case 'backend': { + const backend = _.get( + this.config.environments[current], + 'server.admin.build.backend', + `http://${this.config.environments[current].server.host}:${ + this.config.environments[current].server.port + }`, + ).replace(/\/$/, ''); - // Try to access to path. - fs.access(sourcePath, err => { - if (err && err.code !== 'ENOENT') { - this.log.error(`Impossible to access to ${sourcePath}`); - - return reject(err); - } - - if (!err) { - // Delete source file. - fs.unlinkSync(sourcePath); - } - - // Try to access to path. - fs.access(buildPath, err => { - if (err && err.code !== 'ENOENT') { - this.log.error(`Impossible to access to ${buildPath}`); - - return reject(err); - } - - if (!err) { - // Delete build file. - fs.unlinkSync(buildPath); - } - - if (err && err.code === 'ENOENT') { - try { - fs.accessSync(path.resolve(buildPath, '..', '..')); - } catch (err) { - if (err && err.code !== 'ENOENT') { - return reject(err); - } - - fs.mkdirSync(path.resolve(buildPath, '..', '..')); - } - } - - // Create `./config` folder - try { - fs.accessSync(path.resolve(buildPath, '..')); - } catch (err) { - if (err && err.code !== 'ENOENT') { - return reject(err); - } - - fs.mkdirSync(path.resolve(buildPath, '..')); - } - - // Create `plugins.json` file. - // Don't inject the plugins without an Admin - const data = Object.keys(this.plugins) - .filter(plugin => { - let hasAdminFolder; - - try { - fs.accessSync( - path.resolve(this.config.appPath, 'plugins', plugin, 'admin', 'src', 'containers', 'App'), - ); - hasAdminFolder = true; - } catch (err) { - hasAdminFolder = false; - } - return hasAdminFolder; - }) - .map(name => ({ - id: name, - source: Object.keys(this.config.environments).reduce((acc, current) => { - const source = _.get( - this.config.environments[current].server, - 'admin.build.plugins.source', - 'default', - ); - - if (_.isString(source)) { - acc[current] = configuratePlugin(acc, current, source, name); - } else if (_.isOject(source)) { - acc[current] = configuratePlugin(acc, current, source[current], name); - } - - return acc; - }, {}), - })); - - fs.writeFileSync(sourcePath, JSON.stringify(data, null, 2), 'utf8'); - fs.writeFileSync(buildPath, JSON.stringify(data), 'utf8'); - - resolve(); - }); - }); - }); - } catch (e) { - reject(e); + return `${backend}/${folder.replace(/\/$/, '')}/${name}/main.js`; + } + default: + return `/${name}/main.js`; } - }); + }; + + const sourcePath = + this.config.environment !== 'test' + ? path.resolve(this.config.appPath, 'admin', 'admin', 'src', 'config', 'plugins.json') + : path.resolve( + this.config.appPath, + 'packages', + 'strapi-admin', + 'admin', + 'src', + 'config', + 'plugins.json', + ); + const buildPath = + this.config.environment !== 'test' + ? path.resolve(this.config.appPath, 'admin', 'admin', 'build', 'config', 'plugins.json') + : path.resolve( + this.config.appPath, + 'packages', + 'strapi-admin', + 'admin', + 'build', + 'config', + 'plugins.json', + ); + + const isAdmin = await fs.pathExists(path.resolve(this.config.appPath, 'admin', 'admin')); + if (!isAdmin) return; + + // arrange system directories + await Promise.all([ + fs.remove(sourcePath), + + (async () => { + const existBuildPath = await fs.pathExists(buildPath); + if (existBuildPath) { + await fs.remove(buildPath); + } else { + await fs.ensureDir(path.resolve(buildPath, '..', '..')); + } + })(), + + // Create `./config` folder + fs.ensureDir(path.resolve(buildPath, '..')), + ]); + + // Create `plugins.json` file. + // Don't inject the plugins without an Admin + const existingPlugins = await Object.keys(this.plugins).filter(plugin => + fs.pathExists(path.resolve(this.config.appPath, 'plugins', plugin, 'admin', 'src', 'containers', 'App')), + ); + + const existingPluginsInfo = existingPlugins.map(id => ({ + id, + source: Object.keys(this.config.environments).reduce((acc, current) => { + const source = _.get(this.config.environments[current].server, 'admin.build.plugins.source', 'default'); + + if (_.isString(source)) { + acc[current] = configuratePlugin(acc, current, source, id); + } else if (_.isOject(source)) { + acc[current] = configuratePlugin(acc, current, source[current], id); + } + + return acc; + }, {}), + })); + + await Promise.all([ + fs.writeJSON(sourcePath, existingPluginsInfo, { + spaces: 2, + encoding: 'utf8', + }), + fs.writeJSON(buildPath, existingPluginsInfo, { + spaces: 2, + encoding: 'utf8', + }), + ]); }; diff --git a/packages/strapi/lib/hooks/index.js b/packages/strapi/lib/hooks/index.js index ae3d3e1a81..7305f316fc 100755 --- a/packages/strapi/lib/hooks/index.js +++ b/packages/strapi/lib/hooks/index.js @@ -6,39 +6,35 @@ const { after, includes, indexOf, drop, dropRight, uniq, defaultsDeep, get, set, module.exports = async function() { // Method to initialize hooks and emit an event. const initialize = (module, hook) => (resolve, reject) => { - if (typeof module === 'function') { - let timeout = true; + let timeout = true; - setTimeout(() => { - if (timeout) { - reject(`(hook:${hook}) takes too long to load`); - } - }, this.config.hook.timeout || 1000); + setTimeout(() => { + if (timeout) { + reject(`(hook:${hook}) takes too long to load`); + } + }, this.config.hook.timeout || 1000); - const loadedModule = module(this); + const loadedModule = module(this); - loadedModule.initialize.call(module, err => { - timeout = false; + loadedModule.initialize.call(module, err => { + timeout = false; - if (err) { - this.emit('hook:' + hook + ':error'); + if (err) { + this.emit('hook:' + hook + ':error'); - return reject(err); - } + return reject(err); + } - this.hook[hook].loaded = true; + this.hook[hook].loaded = true; - this.hook[hook] = merge(this.hook[hook], loadedModule); + this.hook[hook] = merge(this.hook[hook], loadedModule); - this.emit('hook:' + hook + ':loaded'); - // Remove listeners. - this.removeAllListeners('hook:' + hook + ':loaded'); + this.emit('hook:' + hook + ':loaded'); + // Remove listeners. + this.removeAllListeners('hook:' + hook + ':loaded'); - resolve(); - }); - } else { resolve(); - } + }); }; await Promise.all( @@ -83,7 +79,7 @@ module.exports = async function() { } // Initialize array. - let previousDependencies = this.hook[hook].dependencies.map(x => x.replace('strapi-', '')) || []; + let previousDependencies = this.hook[hook].dependencies || []; // Add BEFORE middlewares to load and remove the current one // to avoid that it waits itself. diff --git a/packages/strapi/lib/middlewares/boom/index.js b/packages/strapi/lib/middlewares/boom/index.js index ed2d590349..eaa688165b 100644 --- a/packages/strapi/lib/middlewares/boom/index.js +++ b/packages/strapi/lib/middlewares/boom/index.js @@ -25,6 +25,11 @@ module.exports = strapi => { // App logic. await next(); } catch (error) { + // emit error if configured + if (_.get(strapi, 'config.currentEnvironment.server.emitErrors', false)) { + strapi.app.emit('error', error, ctx); + } + // Log error. console.error(error); diff --git a/packages/strapi/lib/utils/index.js b/packages/strapi/lib/utils/index.js index 9d80b46729..f24da9f757 100755 --- a/packages/strapi/lib/utils/index.js +++ b/packages/strapi/lib/utils/index.js @@ -14,6 +14,7 @@ const fetch = require('node-fetch'); const Buffer = require('buffer').Buffer; const crypto = require('crypto'); const exposer = require('./exposer'); +const openBrowser = require('./openBrowser'); module.exports = { loadFile: function(url) { @@ -149,5 +150,6 @@ module.exports = { } catch (e) { // Silent. } - } + }, + openBrowser }; diff --git a/packages/strapi/lib/utils/openBrowser.js b/packages/strapi/lib/utils/openBrowser.js new file mode 100644 index 0000000000..212cb6acf4 --- /dev/null +++ b/packages/strapi/lib/utils/openBrowser.js @@ -0,0 +1,157 @@ +/** + * Copyright (c) 2015-present, Facebook, Inc. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +'use strict'; + +var execSync = require('child_process').execSync; +var chalk = require('chalk'); +var spawn = require('cross-spawn'); +var opn = require('opn'); +const fetch = require('node-fetch'); + +// https://github.com/sindresorhus/opn#app +var OSX_CHROME = 'google chrome'; + +const Actions = Object.freeze({ + NONE: 0, + BROWSER: 1, + SCRIPT: 2, +}); + +function getBrowserEnv() { + // Attempt to honor this environment variable. + // It is specific to the operating system. + // See https://github.com/sindresorhus/opn#app for documentation. + const value = process.env.BROWSER; + let action; + if (!value) { + // Default. + action = Actions.BROWSER; + } else if (value.toLowerCase().endsWith('.js')) { + action = Actions.SCRIPT; + } else if (value.toLowerCase() === 'none') { + action = Actions.NONE; + } else { + action = Actions.BROWSER; + } + return { action, value }; +} + +function executeNodeScript(scriptPath, url) { + const extraArgs = process.argv.slice(2); + const child = spawn('node', [scriptPath, ...extraArgs, url], { + stdio: 'inherit', + }); + child.on('close', code => { + if (code !== 0) { + console.log(); + console.log( + chalk.red( + 'The script specified as BROWSER environment variable failed.' + ) + ); + console.log(`${chalk.cyan(scriptPath)} exited with code ${code}.`); + console.log(); + return; + } + }); + return true; +} + +function startBrowserProcess(browser, url) { + // If we're on OS X, the user hasn't specifically + // requested a different browser, we can try opening + // Chrome with AppleScript. This lets us reuse an + // existing tab when possible instead of creating a new one. + const shouldTryOpenChromeWithAppleScript = + process.platform === 'darwin' && + (typeof browser !== 'string' || browser === OSX_CHROME); + + if (shouldTryOpenChromeWithAppleScript) { + try { + // Try our best to reuse existing tab + // on OS X Google Chrome with AppleScript + execSync('ps cax | grep "Google Chrome"'); + execSync(`osascript resources/openChrome.applescript "${encodeURI(url)}"`, { + cwd: __dirname, + stdio: 'ignore', + }); + return true; + } catch (err) { + console.log(err); + // Ignore errors. + } + } + + // Another special case: on OS X, check if BROWSER has been set to "open". + // In this case, instead of passing `open` to `opn` (which won't work), + // just ignore it (thus ensuring the intended behavior, i.e. opening the system browser): + // https://github.com/facebook/create-react-app/pull/1690#issuecomment-283518768 + if (process.platform === 'darwin' && browser === 'open') { + browser = undefined; + } + + // Fallback to opn + // (It will always open new tab) + try { + var options = { app: browser }; + opn(url, options).catch(() => {}); // Prevent `unhandledRejection` error. + return true; + } catch (err) { + return false; + } +} + +async function pingDashboard(url, multipleTime = false) { + try { + await fetch(url, { method:'HEAD', timeout: 300, body: null }); + // Inform the user that we're going to open the administration panel. + this.log.info("⏳ Opening the admin panel..."); + } catch (e) { + if (e.code !== 'ECONNREFUSED' && e.type !== 'request-timeout') { + return console.error(e); + } + + // Only display once. + if (!multipleTime) { + this.log.warn(`⚠️ The admin panel is unavailable... Impossible to open it in the browser.`); + } + + // Only retry if the user is running the administration on another server. + if (this.config.admin.devMode) { + // Wait 1 second until the next ping. + await new Promise((resolve) => { setTimeout(resolve, 1000); }); + await pingDashboard.call(this, url, true); + } + } +} + +/** + * Reads the BROWSER evironment variable and decides what to do with it. Returns + * true if it opened a browser or ran a node.js script, otherwise false. + */ +async function openBrowser() { + const url = this.config.admin.url; + + // Ping the dashboard to ensure it's available. + await pingDashboard.call(this, url); + + const { action, value } = getBrowserEnv(); + switch (action) { + case Actions.NONE: + // Special case: BROWSER="none" will prevent opening completely. + return false; + case Actions.SCRIPT: + return executeNodeScript(value, url); + case Actions.BROWSER: + return startBrowserProcess(value, url); + default: + throw new Error('Not implemented.'); + } +} + +module.exports = openBrowser; \ No newline at end of file diff --git a/packages/strapi/lib/utils/resources/openChrome.applescript b/packages/strapi/lib/utils/resources/openChrome.applescript new file mode 100644 index 0000000000..90f080d237 --- /dev/null +++ b/packages/strapi/lib/utils/resources/openChrome.applescript @@ -0,0 +1,83 @@ +(* +Copyright (c) 2015-present, Facebook, Inc. + +This source code is licensed under the MIT license found in the +LICENSE file in the root directory of this source tree. +*) + +property targetTab: null +property targetTabIndex: -1 +property targetWindow: null + +on run argv + set theURL to item 1 of argv + + tell application "Chrome" + + if (count every window) = 0 then + make new window + end if + + -- 1: Looking for tab running debugger + -- then, Reload debugging tab if found + -- then return + set found to my lookupTabWithUrl(theURL) + if found then + set targetWindow's active tab index to targetTabIndex + tell targetTab to reload + tell targetWindow to activate + set index of targetWindow to 1 + return + end if + + -- 2: Looking for Empty tab + -- In case debugging tab was not found + -- We try to find an empty tab instead + set found to my lookupTabWithUrl("chrome://newtab/") + if found then + set targetWindow's active tab index to targetTabIndex + set URL of targetTab to theURL + tell targetWindow to activate + return + end if + + -- 3: Create new tab + -- both debugging and empty tab were not found + -- make a new tab with url + tell window 1 + activate + make new tab with properties {URL:theURL} + end tell + end tell +end run + +-- Function: +-- Lookup tab with given url +-- if found, store tab, index, and window in properties +-- (properties were declared on top of file) +on lookupTabWithUrl(lookupUrl) + tell application "Chrome" + -- Find a tab with the given url + set found to false + set theTabIndex to -1 + repeat with theWindow in every window + set theTabIndex to 0 + repeat with theTab in every tab of theWindow + set theTabIndex to theTabIndex + 1 + if (theTab's URL as string) contains lookupUrl then + -- assign tab, tab index, and window to properties + set targetTab to theTab + set targetTabIndex to theTabIndex + set targetWindow to theWindow + set found to true + exit repeat + end if + end repeat + + if found then + exit repeat + end if + end repeat + end tell + return found +end lookupTabWithUrl \ No newline at end of file diff --git a/packages/strapi/package.json b/packages/strapi/package.json index c507406ca6..1bba756bc0 100755 --- a/packages/strapi/package.json +++ b/packages/strapi/package.json @@ -1,6 +1,6 @@ { "name": "strapi", - "version": "3.0.0-alpha.12.7.1", + "version": "3.0.0-alpha.14", "description": "An open source solution to create and manage your own API. It provides a powerful dashboard and features to make your life easier.", "homepage": "http://strapi.io", "keywords": [ @@ -34,6 +34,7 @@ "boom": "^5.2.0", "cheerio": "^1.0.0-rc.2", "delegates": "^1.0.0", + "fs-extra": "^7.0.0", "glob": "^7.1.2", "kcors": "^2.2.0", "koa": "^2.1.0", @@ -49,22 +50,23 @@ "koa-router-joi": "^1.0.1", "koa-session": "^5.5.1", "koa-static": "^4.0.1", - "lodash": "^4.16.5", + "lodash": "^4.17.5", "node-fetch": "^1.7.3", "node-schedule": "^1.2.0", + "opn": "^5.3.0", "rimraf": "^2.6.2", "semver": "^5.4.1", "stack-trace": "0.0.10", - "strapi-generate": "3.0.0-alpha.12.7.1", - "strapi-generate-admin": "3.0.0-alpha.12.7.1", - "strapi-generate-api": "3.0.0-alpha.12.7.1", - "strapi-generate-controller": "3.0.0-alpha.12.7.1", - "strapi-generate-model": "3.0.0-alpha.12.7.1", - "strapi-generate-new": "3.0.0-alpha.12.7.1", - "strapi-generate-plugin": "3.0.0-alpha.12.7.1", - "strapi-generate-policy": "3.0.0-alpha.12.7.1", - "strapi-generate-service": "3.0.0-alpha.12.7.1", - "strapi-utils": "3.0.0-alpha.12.7.1" + "strapi-generate": "3.0.0-alpha.14", + "strapi-generate-admin": "3.0.0-alpha.14", + "strapi-generate-api": "3.0.0-alpha.14", + "strapi-generate-controller": "3.0.0-alpha.14", + "strapi-generate-model": "3.0.0-alpha.14", + "strapi-generate-new": "3.0.0-alpha.14", + "strapi-generate-plugin": "3.0.0-alpha.14", + "strapi-generate-policy": "3.0.0-alpha.14", + "strapi-generate-service": "3.0.0-alpha.14", + "strapi-utils": "3.0.0-alpha.14" }, "author": { "email": "hi@strapi.io", diff --git a/scripts/lint.js b/scripts/lint.js index 2124a61dc8..58e4acb764 100644 --- a/scripts/lint.js +++ b/scripts/lint.js @@ -35,13 +35,14 @@ const files = glob .sync('**/*.js', { ignore: '**/node_modules/**' }) .filter(f => changedFiles.has(f)) .filter( - package => + package => !package.includes('README.md') && !package.includes('strapi-middleware-views') && !package.includes('strapi-lint') && !package.includes('strapi-plugin-settings-manager') && !package.includes('scripts') && - !package.includes('test') + !package.includes('test') && + !package.includes('jest.config.js') ) .map(file => { const directoryArray = file.split('/'); diff --git a/scripts/setup.js b/scripts/setup.js index 908a20391d..3d58c6d9c4 100755 --- a/scripts/setup.js +++ b/scripts/setup.js @@ -99,17 +99,17 @@ shell.cd('../strapi-generate-new'); watcher('', 'npm install ../strapi-utils'); watcher('📦 Linking strapi-generate-new', 'npm link'); -shell.cd('../strapi-mongoose'); +shell.cd('../strapi-hook-mongoose'); watcher('', 'npm install ../strapi-utils'); -watcher('📦 Linking strapi-mongoose...', 'npm link'); +watcher('📦 Linking strapi-hook-mongoose...', 'npm link'); -shell.cd('../strapi-knex'); -watcher('📦 Linking strapi-knex...', 'npm link'); +shell.cd('../strapi-hook-knex'); +watcher('📦 Linking strapi-hook-knex...', 'npm link'); -shell.cd('../strapi-bookshelf'); +shell.cd('../strapi-hook-bookshelf'); watcher('', 'npm install ../strapi-utils'); -watcher('', 'npm install ../strapi-knex'); -watcher('📦 Linking strapi-bookshelf...', 'npm link'); +watcher('', 'npm install ../strapi-hook-knex'); +watcher('📦 Linking strapi-hook-bookshelf...', 'npm link'); shell.cd('../strapi'); watcher('', 'npm install ../strapi-generate ../strapi-generate-admin ../strapi-generate-api ../strapi-generate-new ../strapi-generate-plugin ../strapi-generate-policy ../strapi-generate-service ../strapi-utils'); diff --git a/test/helpers/auth.js b/test/helpers/auth.js new file mode 100644 index 0000000000..8b8f333499 --- /dev/null +++ b/test/helpers/auth.js @@ -0,0 +1,26 @@ +const rq = require('./request'); + +const auth = { + username: 'admin', + email: 'admin@strapi.io', + password: 'pcw123' +}; + +module.exports = { + auth, + login: () => { + return new Promise(async (resolve) => { + const body = await rq({ + url: `/auth/local`, + method: 'POST', + body: { + identifier: auth.email, + password: auth.password + }, + json: true + }); + + resolve(body); + }); + } +}; diff --git a/test/index.test.js b/test/index.test.js index 932f771690..0ec226a7ea 100644 --- a/test/index.test.js +++ b/test/index.test.js @@ -1,3 +1,6 @@ +const {auth} = require('./helpers/auth'); +const rq = require('./helpers/request'); + describe('Initialize', () => { test( 'Avoid failure', @@ -5,4 +8,16 @@ describe('Initialize', () => { expect(true).toBeTruthy(); } ); + + test( + 'Register admin user', + async () => { + await rq({ + url: `/auth/local/register`, + method: 'POST', + body: auth, + json: true + }); + } + ); }); diff --git a/test/start.js b/test/start.js index 46b929444e..d35f4d00bc 100644 --- a/test/start.js +++ b/test/start.js @@ -1,15 +1,16 @@ -const exec = require('child_process').exec; +const spawn = require('child_process').spawn; const fs = require('fs-extra'); const path = require('path'); const strapiBin = path.resolve('./packages/strapi/bin/strapi.js'); const appName = 'testApp'; +let testExitCode = 0; let appStart; const databases = { - mongo: `--dbclient=mongo --dbhost=127.0.0.1 --dbport=27017 --dbname=strapi-test-${new Date().getTime()} --dbusername="" --dbpassword=""`, - postgres: `--dbclient=postgres --dbhost=127.0.0.1 --dbport=5432 --dbname=strapi-test --dbusername="" --dbpassword=""`, - mysql: `--dbclient=mysql --dbhost=127.0.0.1 --dbport=3306 --dbname=strapi-test --dbusername="root" --dbpassword="root"` + mongo: `--dbclient=mongo --dbhost=127.0.0.1 --dbport=27017 --dbname=strapi-test-${new Date().getTime()} --dbusername= --dbpassword=`, + postgres: '--dbclient=postgres --dbhost=127.0.0.1 --dbport=5432 --dbname=strapi-test --dbusername= --dbpassword=', + mysql: '--dbclient=mysql --dbhost=127.0.0.1 --dbport=3306 --dbname=strapi-test --dbusername=root --dbpassword=root' }; const {runCLI: jest} = require('jest-cli/build/cli'); @@ -29,32 +30,28 @@ const main = async () => { const generate = (database) => { return new Promise((resolve, reject) => { - const appCreation = exec( - `node ${strapiBin} new ${appName} --dev ${database}`, - ); + const appCreation = spawn('node', `${strapiBin} new ${appName} --dev ${database}`.split(' '), { detached: true }); appCreation.stdout.on('data', data => { console.log(data.toString()); if (data.includes('is ready at')) { - appCreation.kill(); + process.kill(-appCreation.pid); return resolve(); } if (data.includes('Database connection has failed')) { - appCreation.kill(); - return reject(); + process.kill(-appCreation.pid); + return reject(new Error('Database connection has failed')); } }); }); }; const start = () => { - return new Promise((resolve) => { + return new Promise((resolve, reject) => { try { - appStart = exec( - `node ${strapiBin} start ${appName}`, - ); + appStart = spawn('node', `${strapiBin} start ${appName}`.split(' '), {detached: true}); appStart.stdout.on('data', data => { console.log(data.toString()); @@ -65,7 +62,10 @@ const main = async () => { }); } catch (e) { - console.error(e); + if (typeof appStart !== 'undefined') { + process.kill(-appStart.pid); + } + return reject(e); } }); }; @@ -74,7 +74,8 @@ const main = async () => { return new Promise(async (resolve) => { // Run setup tests to generate the app. await jest({ - passWithNoTests: true + passWithNoTests: true, + testURL: 'http://localhost/' }, [process.cwd()]); const packages = fs.readdirSync(path.resolve(process.cwd(), 'packages')) @@ -84,6 +85,7 @@ const main = async () => { for (let i in packages) { await jest({ passWithNoTests: true, + testURL: 'http://localhost/' }, [`${process.cwd()}/packages/${packages[i]}`]); } @@ -92,17 +94,22 @@ const main = async () => { }; const testProcess = async (database) => { - await clean(); - await generate(database); - await start(); - await test(); - - appStart.kill(); + try { + await clean(); + await generate(database); + await start(); + await test(); + process.kill(-appStart.pid); + } catch (e) { + console.error(e.message); + testExitCode = 1; + } }; await testProcess(databases.mongo); await testProcess(databases.postgres); await testProcess(databases.mysql); + process.exit(testExitCode); }; main();