diff --git a/docs/3.x.x/en/assets/internationalization.gif b/docs/3.x.x/en/assets/internationalization.gif new file mode 100644 index 0000000000..d4dcd412e5 Binary files /dev/null and b/docs/3.x.x/en/assets/internationalization.gif differ diff --git a/docs/3.x.x/en/guides/authentication.md b/docs/3.x.x/en/guides/authentication.md index 496bcb3b07..b5340ca3e8 100644 --- a/docs/3.x.x/en/guides/authentication.md +++ b/docs/3.x.x/en/guides/authentication.md @@ -117,7 +117,8 @@ This action sends an email to a user with the link of you reset password page. T #### Usage - `email` is your user email. -- `url` is the url link that user will receive. +- `url` is the url link that user will receive. After the user triggers a new password reset, +it is used to redirect the user to the new-password form. ```js $.ajax({ @@ -125,7 +126,7 @@ $.ajax({ url: 'http://localhost:1337/auth/forgot-password', data: { email: 'user@strapi.io', - url: 'http://mon-site.com/rest-password' + url: 'http:/localhost:1337/admin/plugins/users-permissions/auth/reset-password' }, done: function() { console.log('Your user received an email'); @@ -136,8 +137,6 @@ $.ajax({ }); ``` -> Received link url format http://mon-site.com/rest-password?code=privateCode - ## Reset user password. This action will reset the user password. @@ -165,7 +164,7 @@ $.ajax({ ``` ## User Object In Strapi Context -The User object is available to successfully authenticated requests. +The `user` object is available to successfully authenticated requests. #### Usage - The authenticated `user` object is a property of `ctx.state`. @@ -189,10 +188,9 @@ 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: +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 @@ -203,7 +201,7 @@ packages/strapi-plugin-users-permissions/admin/src/translations/en.json We will go step by step. -### Configure your Provider request +### 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`. @@ -212,9 +210,10 @@ 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. +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. +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 @@ -239,9 +238,11 @@ Let's take the `discord` one as an example since it's not the easier, it should } } }); + } ``` -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. +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) @@ -265,17 +266,21 @@ You may also want to take a look onto the numerous already made configurations [ } ``` -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 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. +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. +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. +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` @@ -296,9 +301,8 @@ For our discord provider it will look like: }, ``` - -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! - +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! @@ -323,7 +327,6 @@ These two change will set up the popup message who appear on the UI when we will 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/i18n.md b/docs/3.x.x/en/guides/i18n.md index 19998eaf00..992258816a 100644 --- a/docs/3.x.x/en/guides/i18n.md +++ b/docs/3.x.x/en/guides/i18n.md @@ -60,3 +60,17 @@ You need to define the english and french translation for this key. ``` That's all! The request `GET /hello/John?locale=en_US` will return `Hello John` and `GET /hello/Tom?locale=fr_FR` will return `Bonjour Tom`. + +## Content Internationalization + +Translating content from a language to another has been requested by many of you. As you may have seen on our website, the [Internationalization plugin](https://strapi.io/marketplace/internationalization) is not available yet because we need to go out of alpha before developing new plugins. + +But, no worries, we have a good **work around to help you internationalize your content**! + +The solution is simple: **suffix your fields**. + +For example if you are building a blog with posts, you may have a Content Type `post` with two fields: `title` and `content`. To make them available in english and french for example, simply replace them by `title_en`, `title_fr`, `content_en` and `content_fr`. + +Then, when you request your API, you will get all these fields in your response payload. If you want to select only some of them (in a specific language) we recommend you to use the [GraphQL plugin](graphql.md). + +![Content Internationalization Strapi](../assets/internationalization.gif) diff --git a/package.json b/package.json index 71c1eab9f1..2bfd74985c 100755 --- a/package.json +++ b/package.json @@ -25,8 +25,8 @@ "strapi-lint": "file:packages/strapi-lint" }, "scripts": { - "clean": "npm run removesymlinkdependencies && rm -rf package-lock.json && rm -rf packages/*/package-lock.json", - "clean:all": "npm run removesymlinkdependencies && rm -rf package-lock.json && rm -rf packages/*/package-lock.json && rm -rf packages/*/node_modules", + "clean": "npm run removesymlinkdependencies && npx rimraf package-lock.json && npx rimraf packages/*/package-lock.json", + "clean:all": "npm run removesymlinkdependencies && npx rimraf package-lock.json && npx rimraf packages/*/package-lock.json && npx rimraf packages/*/node_modules", "doc": "node ./scripts/documentation.js", "release": "npm run clean:all && npm install && npm run createsymlinkdependencies && lerna exec --concurrency 1 -- npm install && npm run removesymlinkdependencies && node ./scripts/publish.js $TAG", "createsymlinkdependencies": "node ./scripts/createSymlinkDependencies.js", 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 46db2086bb..81302a2134 100755 --- a/packages/strapi-generate-new/files/config/environments/development/server.json +++ b/packages/strapi-generate-new/files/config/environments/development/server.json @@ -9,5 +9,8 @@ }, "cron": { "enabled": false + }, + "admin": { + "autoOpen": true } } 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 bc0294e31f..4e5d9d1b5e 100755 --- a/packages/strapi-generate-new/files/config/environments/production/server.json +++ b/packages/strapi-generate-new/files/config/environments/production/server.json @@ -9,5 +9,8 @@ }, "cron": { "enabled": false + }, + "admin": { + "autoOpen": false } } 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 bc0294e31f..4e5d9d1b5e 100755 --- a/packages/strapi-generate-new/files/config/environments/staging/server.json +++ b/packages/strapi-generate-new/files/config/environments/staging/server.json @@ -9,5 +9,8 @@ }, "cron": { "enabled": false + }, + "admin": { + "autoOpen": false } } diff --git a/packages/strapi-generate-new/lib/before.js b/packages/strapi-generate-new/lib/before.js index 1f6381aac8..2b576c29d4 100755 --- a/packages/strapi-generate-new/lib/before.js +++ b/packages/strapi-generate-new/lib/before.js @@ -260,7 +260,7 @@ module.exports = (scope, cb) => { } catch(err) { shell.rm('-r', scope.tmpPath); console.log(err); - cb.success(); + cb.error(); } }); }); diff --git a/packages/strapi-hook-bookshelf/lib/utils/connectivity.js b/packages/strapi-hook-bookshelf/lib/utils/connectivity.js index 077d289ebc..31cbf709cb 100644 --- a/packages/strapi-hook-bookshelf/lib/utils/connectivity.js +++ b/packages/strapi-hook-bookshelf/lib/utils/connectivity.js @@ -1,14 +1,11 @@ 'use strict'; -// Node.js core. -const execSync = require('child_process').execSync; -const path = require('path'); - // Public node modules const inquirer = require('inquirer'); +const rimraf = require('rimraf'); module.exports = (scope, success, error) => { - const knex = require(path.resolve(`${scope.tmpPath}/node_modules/knex`))({ + const knex = require('knex')({ client: scope.client.module, connection: Object.assign({}, scope.database.settings, { user: scope.database.settings.username @@ -20,9 +17,12 @@ module.exports = (scope, success, error) => { knex.destroy(); const next = () => { - execSync(`rm -r "${scope.tmpPath}"`); - - success(); + rimraf(scope.tmpPath, (err) => { + if (err) { + console.log(`Error removing connection test folder: ${scope.tmpPath}`); + } + success(); + }); }; if (tables.rows && tables.rows.length !== 0) { @@ -33,7 +33,7 @@ module.exports = (scope, success, error) => { name: 'confirm', message: `Are you sure you want to continue with the ${scope.database.settings.database} database:`, }]) - .then(({confirm}) => { + .then(({ confirm }) => { if (confirm) { next(); } else { diff --git a/packages/strapi-hook-bookshelf/package.json b/packages/strapi-hook-bookshelf/package.json index 08e3ffabd5..5b5d5dffb1 100755 --- a/packages/strapi-hook-bookshelf/package.json +++ b/packages/strapi-hook-bookshelf/package.json @@ -20,6 +20,7 @@ "inquirer": "^5.2.0", "lodash": "^4.17.5", "pluralize": "^6.0.0", + "rimraf": "^2.6.2", "strapi-hook-knex": "3.0.0-alpha.14.1.1", "strapi-utils": "3.0.0-alpha.14.1.1" }, diff --git a/packages/strapi-hook-mongoose/lib/utils/connectivity.js b/packages/strapi-hook-mongoose/lib/utils/connectivity.js index cf1164750b..214ab4196b 100644 --- a/packages/strapi-hook-mongoose/lib/utils/connectivity.js +++ b/packages/strapi-hook-mongoose/lib/utils/connectivity.js @@ -1,11 +1,10 @@ 'use strict'; -// Node.js core. -const execSync = require('child_process').execSync; -const path = require('path'); +// Public node modules +const rimraf = require('rimraf'); module.exports = (scope, success, error) => { - const Mongoose = require(path.resolve(`${scope.tmpPath}/node_modules/mongoose`)); + const Mongoose = require('mongoose'); const { username, password, srv } = scope.database.settings; const { authenticationDatabase, ssl } = scope.database.options; @@ -36,8 +35,11 @@ module.exports = (scope, success, error) => { Mongoose.connection.close(); - execSync(`rm -r "${scope.tmpPath}"`); - - success(); + rimraf(scope.tmpPath, (err) => { + if (err) { + console.log(`Error removing connection test folder: ${scope.tmpPath}`); + } + success(); + }); }); }; diff --git a/packages/strapi-hook-mongoose/package.json b/packages/strapi-hook-mongoose/package.json index 8bbabf20fd..9c78ca536c 100755 --- a/packages/strapi-hook-mongoose/package.json +++ b/packages/strapi-hook-mongoose/package.json @@ -19,6 +19,7 @@ "mongoose": "^5.0.16", "mongoose-float": "^1.0.2", "pluralize": "^6.0.0", + "rimraf": "^2.6.2", "strapi-utils": "3.0.0-alpha.14.1.1" }, "author": { diff --git a/packages/strapi-hook-redis/lib/utils/connectivity.js b/packages/strapi-hook-redis/lib/utils/connectivity.js index afee928190..0f386ee861 100644 --- a/packages/strapi-hook-redis/lib/utils/connectivity.js +++ b/packages/strapi-hook-redis/lib/utils/connectivity.js @@ -1,14 +1,13 @@ 'use strict'; -/* eslint-disable import/no-unresolved */ -// Node.js core. -const execSync = require('child_process').execSync; +// Public node modules +const rimraf = require('rimraf'); // Logger. const logger = require('strapi-utils').logger; module.exports = (scope, success, error) => { - const Redis = require(`${scope.tmpPath}/node_modules/ioredis`); + const Redis = require(`ioredis`); const redis = new Redis({ port: scope.database.settings.port, host: scope.database.settings.host, @@ -26,10 +25,14 @@ module.exports = (scope, success, error) => { logger.info('The app has been connected to the database successfully!'); - execSync(`rm -r "${scope.tmpPath}"`); + rimraf(scope.tmpPath, (err) => { + if (err) { + console.log(`Error removing connection test folder: ${scope.tmpPath}`); + } + logger.info('Copying the dashboard...'); - logger.info('Copying the dashboard...'); + success(); + }); - success(); }); }; diff --git a/packages/strapi-hook-redis/package.json b/packages/strapi-hook-redis/package.json index ed0f3b7868..07d40e4996 100755 --- a/packages/strapi-hook-redis/package.json +++ b/packages/strapi-hook-redis/package.json @@ -17,6 +17,7 @@ "dependencies": { "ioredis": "^3.1.2", "lodash": "^4.17.5", + "rimraf": "^2.6.2", "stack-trace": "0.0.10", "strapi-utils": "3.0.0-alpha.14.1.1" }, diff --git a/packages/strapi-plugin-content-manager/admin/src/components/Wysiwyg/index.js b/packages/strapi-plugin-content-manager/admin/src/components/Wysiwyg/index.js index eeead5e30c..bb2d5331d6 100644 --- a/packages/strapi-plugin-content-manager/admin/src/components/Wysiwyg/index.js +++ b/packages/strapi-plugin-content-manager/admin/src/components/Wysiwyg/index.js @@ -173,9 +173,7 @@ class Wysiwyg extends React.Component { if (selectedText !== '') { return this.setState( { - // Move the cursor to the end (this line forces the cursor to be at the end of the content) - // It may go at the end of the last block - editorState: EditorState.moveFocusToEnd(newEditorState), + editorState: newEditorState, }, () => { this.focus(); @@ -309,7 +307,14 @@ class Wysiwyg extends React.Component { const newContentState = this.createNewContentStateFromBlock(newBlock); const newEditorState = this.createNewEditorState(newContentState, text); - return this.setState({ editorState: EditorState.moveFocusToEnd(newEditorState) }); + return this.setState( + { + editorState: newEditorState, + }, + () => { + this.focus(); + }, + ); }; /** 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 c27c7daafe..11ebcc5421 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 @@ -143,7 +143,7 @@ export class Form extends React.Component { // eslint-disable-line react/prefer- } // Check if user is adding a relation with the same content type - + if (includes(this.props.hash, 'attributerelation') && this.props.modifiedDataAttribute.params.target === this.props.modelName && get(this.props.modifiedDataAttribute, ['params', 'nature'], '') !== 'oneWay') { // Insert two attributes this.props.addAttributeRelationToContentType(this.props.modifiedDataAttribute); @@ -418,7 +418,7 @@ export class Form extends React.Component { // eslint-disable-line react/prefer- if (includes(this.props.hash, 'choose')) { const { nodeToFocus } = this.state; let toAdd = 0; - + switch(e.keyCode) { case 37: // Left arrow case 39: // Right arrow @@ -446,7 +446,7 @@ export class Form extends React.Component { // eslint-disable-line react/prefer- toAdd = 0; break; } - + this.setState(prevState => ({ nodeToFocus: prevState.nodeToFocus + toAdd })); } } @@ -456,7 +456,6 @@ export class Form extends React.Component { // eslint-disable-line react/prefer- const hashArray = split(this.props.hash, ('::')); const valueToReplace = includes(this.props.hash, '#create') ? '#create' : '#edit'; const contentTypeName = replace(hashArray[0], valueToReplace, ''); - let cbSuccess; let dataSucces = null; let cbFail; @@ -466,7 +465,7 @@ export class Form extends React.Component { // eslint-disable-line react/prefer- // Check if the user is editing the attribute const isAttribute = includes(hashArray[1], 'attribute'); cbSuccess = isAttribute ? () => this.editTempContentTypeAttribute(redirectToChoose) : this.createContentType; - dataSucces = isAttribute ? null : this.props.modifiedDataEdit; + dataSucces = isAttribute ? null : this.getModelWithCamelCaseName(this.props.modifiedDataEdit); cbFail = isAttribute ? () => this.editContentTypeAttribute(redirectToChoose) : this.contentTypeEdit; return this.testContentType(contentTypeName, cbSuccess, dataSucces, cbFail); } @@ -476,11 +475,24 @@ export class Form extends React.Component { // eslint-disable-line react/prefer- return this.testContentType(contentTypeName, cbSuccess, dataSucces, cbFail); } default: { - return this.createContentType(this.props.modifiedData); + return this.createContentType( + this.getModelWithCamelCaseName(this.props.modifiedData) + ); } } } + getModelWithCamelCaseName = (model = {}) => { + if (isEmpty(model) || isEmpty(model.name)) { + return; + } + + return { + ...model, + name: camelCase(model.name), + }; + } + initComponent = (props, condition) => { if (!isEmpty(props.hash)) { this.setState({ showModal: true }); @@ -514,7 +526,7 @@ export class Form extends React.Component { // eslint-disable-line react/prefer- } renderModalBodyChooseAttributes = () => { - const attributesDisplay = has(this.context.plugins.toJS(), 'upload') + 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 diff --git a/packages/strapi-plugin-email/controllers/Email.js b/packages/strapi-plugin-email/controllers/Email.js index 95cb9073be..baf3e588fb 100644 --- a/packages/strapi-plugin-email/controllers/Email.js +++ b/packages/strapi-plugin-email/controllers/Email.js @@ -28,9 +28,9 @@ module.exports = { return; } - let options = ctx.request.body; + let options = ctx.request.body; - await strapi.plugins.email.services.send(options, config); + await strapi.plugins.email.services.email.send(options, config); // Send 200 `ok` ctx.send({}); @@ -48,7 +48,7 @@ module.exports = { }, getSettings: async (ctx) => { - let config = await strapi.plugins.email.services.email.getProviderConfig(ctx.params.environment); + let config = await strapi.plugins.email.services.email.getProviderConfig(ctx.params.environment); ctx.send({ providers: strapi.plugins.email.config.providers, diff --git a/packages/strapi-plugin-settings-manager/admin/src/components/List/styles.scss b/packages/strapi-plugin-settings-manager/admin/src/components/List/styles.scss index 5307203cc9..a2796a0c04 100755 --- a/packages/strapi-plugin-settings-manager/admin/src/components/List/styles.scss +++ b/packages/strapi-plugin-settings-manager/admin/src/components/List/styles.scss @@ -113,14 +113,16 @@ button { margin-left: 1rem; } -.flexed { +.flexed, +.label { display: flex; } .label { + align-items: center; + line-height: 1; width: 15rem; height: 5.2rem; - line-height: 5.2rem; margin-left: 5rem; color: #333740; font-weight: 600; diff --git a/scripts/lint.js b/scripts/lint.js index 58e4acb764..eb665b6711 100644 --- a/scripts/lint.js +++ b/scripts/lint.js @@ -2,24 +2,23 @@ const path = require('path'); const shell = require('shelljs'); const chalk = require('chalk'); const eslintErrorsFormatter = require('./eslintErrorsFormatter'); -const glob = require('glob'); -const fs = require('fs'); const listChangedFiles = require('../packages/strapi-lint/lib/internals/shared/listChangedFiles.js'); const changedFiles = listChangedFiles(); -const { includes, take } = require('lodash'); +const { take, template } = require('lodash'); -const frontCmd = - 'node ../../node_modules/strapi-lint/node_modules/.bin/eslint --ignore-path .gitignore --ignore-pattern \'/admin/build/\' --config ../../node_modules/strapi-lint/lib/internals/eslint/front/.eslintrc.json admin'; -const helperCmd = - 'node ../../node_modules/strapi-lint/node_modules/.bin/eslint --ignore-path .gitignore --ignore-pattern \'/admin/build/\' --config ../../node_modules/strapi-lint/lib/internals/eslint/front/.eslintrc.json lib/src'; -const backCmd = - 'node ../../node_modules/strapi-lint/node_modules/.bin/eslint --ignore-path .gitignore --ignore-pattern \'/admin\' --config ../../node_modules/strapi-lint/lib/internals/eslint/back/.eslintrc.json controllers config services bin lib'; +const cmdEslint = template( + 'node ../../node_modules/strapi-lint/node_modules/.bin/eslint --ignore-path .gitignore --ignore-pattern "${ignore}"' + + ' --config ../../node_modules/strapi-lint/lib/internals/eslint/${conf}/.eslintrc.json ${params}' +); +const cmdFront = cmdEslint({ ignore: '/admin/build/', conf: 'front', params: 'admin' }); +const cmdHelper = cmdEslint({ ignore: '/admin/build/', conf: 'front', params: 'lib/src' }); +const cmdBack = cmdEslint({ ignore: '/admin', conf: 'back', params: 'controllers config services bin lib' }); -const watcher = (label, pckgName, type = 'front') => { +const watcher = (label, pckgName) => { shell.echo(label); shell.cd(pckgName); - const cmd = includes(pckgName, 'strapi-helper-plugin') ? helperCmd : `${frontCmd} && ${backCmd}`; + const cmd = pckgName.includes('strapi-helper-plugin') ? cmdHelper : `${cmdFront} && ${cmdBack}`; const data = shell.exec(cmd, { silent: true }); shell.echo(chalk(eslintErrorsFormatter(data.stdout))); @@ -31,19 +30,17 @@ const watcher = (label, pckgName, type = 'front') => { shell.echo(''); }; -const files = glob - .sync('**/*.js', { ignore: '**/node_modules/**' }) - .filter(f => changedFiles.has(f)) - .filter( - 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('jest.config.js') - ) +const except = [ + 'jest.config.js', + 'scripts', + 'strapi-lint', + 'strapi-middleware-views', + 'strapi-plugin-settings-manager', + 'test', +]; + +const changedDirs = [...changedFiles] + .filter(file => path.extname(file) === '.js' && !except.some(path => file.includes(path))) .map(file => { const directoryArray = file.split('/'); const toTake = directoryArray.length === 2 ? 1 : 2; @@ -51,9 +48,7 @@ const files = glob return take(directoryArray, toTake).join('/'); }); - -files - .filter((directory, index) => files.indexOf(directory) === index) - .forEach(package => { - watcher(`Testing ${package}`, package); - }); \ No newline at end of file +[...new Set(changedDirs)] + .forEach(directory => { + watcher(`Testing ${directory}`, directory); + });