Merge branch 'master' into fix-1885

This commit is contained in:
Jim LAURIE 2018-09-24 19:11:14 +02:00 committed by GitHub
commit c4cb9ccfab
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
19 changed files with 139 additions and 91 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 875 KiB

View File

@ -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!
<!-- #### Tests -->
<!-- TODO Add documentation about how to configure unit test for the new provider -->
@ -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)

View File

@ -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)

View File

@ -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",

View File

@ -9,5 +9,8 @@
},
"cron": {
"enabled": false
},
"admin": {
"autoOpen": true
}
}

View File

@ -9,5 +9,8 @@
},
"cron": {
"enabled": false
},
"admin": {
"autoOpen": false
}
}

View File

@ -9,5 +9,8 @@
},
"cron": {
"enabled": false
},
"admin": {
"autoOpen": false
}
}

View File

@ -260,7 +260,7 @@ module.exports = (scope, cb) => {
} catch(err) {
shell.rm('-r', scope.tmpPath);
console.log(err);
cb.success();
cb.error();
}
});
});

View File

@ -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 {

View File

@ -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"
},

View File

@ -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();
});
});
};

View File

@ -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": {

View File

@ -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();
});
};

View File

@ -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"
},

View File

@ -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();
},
);
};
/**

View File

@ -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

View File

@ -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,

View File

@ -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;

View File

@ -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);
});
[...new Set(changedDirs)]
.forEach(directory => {
watcher(`Testing ${directory}`, directory);
});