Merge branch 'master' into fix/reset-image-preview

This commit is contained in:
Jim LAURIE 2018-09-06 18:15:29 +02:00 committed by GitHub
commit 3fe7f0cf2a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 125 additions and 49 deletions

View File

@ -152,7 +152,7 @@ module.exports = (scope, cb) => {
when: !hasDatabaseConfig, when: !hasDatabaseConfig,
type: 'input', type: 'input',
name: 'port', name: 'port',
message: 'Port (It will be ignored if you enable +srv):', message: `Port${scope.client.database === 'mongo' ? ' (It will be ignored if you enable +srv)' : ''}:`,
default: (answers) => { // eslint-disable-line no-unused-vars default: (answers) => { // eslint-disable-line no-unused-vars
if (_.get(scope.database, 'port')) { if (_.get(scope.database, 'port')) {
return scope.database.port; return scope.database.port;
@ -186,7 +186,7 @@ module.exports = (scope, cb) => {
when: !hasDatabaseConfig && scope.client.database === 'mongo', when: !hasDatabaseConfig && scope.client.database === 'mongo',
type: 'input', type: 'input',
name: 'authenticationDatabase', name: 'authenticationDatabase',
message: 'Authentication database:', message: 'Authentication database (Maybe "admin" or blank):',
default: _.get(scope.database, 'authenticationDatabase', undefined) default: _.get(scope.database, 'authenticationDatabase', undefined)
}, },
{ {
@ -203,7 +203,7 @@ module.exports = (scope, cb) => {
} }
scope.database.settings.host = answers.host; scope.database.settings.host = answers.host;
scope.database.settings.srv = answers.srv; scope.database.settings.srv = _.toString(answers.srv) === 'true';
scope.database.settings.port = answers.port; scope.database.settings.port = answers.port;
scope.database.settings.database = answers.database; scope.database.settings.database = answers.database;
scope.database.settings.username = answers.username; scope.database.settings.username = answers.username;

View File

@ -8,7 +8,8 @@
import React from 'react'; import React from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { FormattedMessage } from 'react-intl'; import { FormattedMessage } from 'react-intl';
import { cloneDeep } from 'lodash'; import { cloneDeep, isArray, isObject } from 'lodash';
import cn from 'classnames';
import ImgPreview from 'components/ImgPreview'; import ImgPreview from 'components/ImgPreview';
import InputFileDetails from 'components/InputFileDetails'; import InputFileDetails from 'components/InputFileDetails';
@ -59,7 +60,8 @@ class InputFile extends React.Component {
type: 'file', type: 'file',
value, value,
}; };
this.inputFile.value = '';
this.setState({ isUploading: !this.state.isUploading }); this.setState({ isUploading: !this.state.isUploading });
this.props.onChange({ target }); this.props.onChange({ target });
} }
@ -75,11 +77,12 @@ class InputFile extends React.Component {
if (this.props.multiple) { if (this.props.multiple) {
value.splice(this.state.position, 1); value.splice(this.state.position, 1);
} }
// Update the parent's props // Update the parent's props
const target = { const target = {
name: this.props.name, name: this.props.name,
type: 'file', type: 'file',
value, value: Object.keys(value).length === 0 ? '' : value,
}; };
this.props.onChange({ target }); this.props.onChange({ target });
@ -98,6 +101,19 @@ class InputFile extends React.Component {
this.setState({ position: newPosition }); 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() { render() {
const { const {
multiple, multiple,
@ -108,39 +124,43 @@ class InputFile extends React.Component {
return ( return (
<div> <div>
<ImgPreview <div className={cn("form-control", styles.inputFileControlForm, this.props.error && 'is-invalid')}>
didDeleteFile={this.state.didDeleteFile} <ImgPreview
files={value} didDeleteFile={this.state.didDeleteFile}
isUploading={this.state.isUploading} files={value}
multiple={multiple} isUploading={this.state.isUploading}
name={name}
onChange={onChange}
onBrowseClick={this.handleClick}
onDrop={this.onDrop}
position={this.state.position}
updateFilePosition={this.updateFilePosition}
/>
<label style={{ width: '100%'}}>
<input
className={styles.inputFile}
multiple={multiple} multiple={multiple}
name={name} name={name}
onChange={this.handleChange} onChange={onChange}
type="file" onBrowseClick={this.handleClick}
ref={(input) => this.inputFile = input} onDrop={this.onDrop}
position={this.state.position}
updateFilePosition={this.updateFilePosition}
/> />
<label style={{"margin-bottom": 0, width: '100%'}}>
<input
className={styles.inputFile}
multiple={multiple}
name={name}
onChange={this.handleChange}
type="file"
ref={(input) => this.inputFile = input}
/>
<div className={styles.buttonContainer}> <div className={styles.buttonContainer}>
<i className="fa fa-plus" /> <i className="fa fa-plus" />
<FormattedMessage id="app.components.InputFile.newFile" /> <FormattedMessage id="app.components.InputFile.newFile" />
</div> </div>
</label> </label>
<InputFileDetails </div>
file={value[this.state.position] || value[0] || value} {this.isVisibleDetails() && (
multiple={multiple} <InputFileDetails
number={value.length} file={value[this.state.position] || value[0] || value}
onFileDelete={this.handleFileDelete} multiple={multiple}
/> number={value.length}
onFileDelete={this.handleFileDelete}
/>
)}
</div> </div>
); );
} }
@ -150,9 +170,12 @@ InputFile.defaultProps = {
multiple: false, multiple: false,
setLabel: () => {}, setLabel: () => {},
value: [], value: [],
error: false,
}; };
InputFile.propTypes = { InputFile.propTypes = {
error: PropTypes.bool,
multiple: PropTypes.bool, multiple: PropTypes.bool,
name: PropTypes.string.isRequired, name: PropTypes.string.isRequired,
onChange: PropTypes.func.isRequired, onChange: PropTypes.func.isRequired,

View File

@ -32,3 +32,7 @@
.copy { .copy {
cursor: copy !important; cursor: copy !important;
} }
.inputFileControlForm {
padding: 0;
}

View File

@ -14,16 +14,27 @@ import Label from 'components/Label';
import InputDescription from 'components/InputDescription'; import InputDescription from 'components/InputDescription';
import InputFile from 'components/InputFile'; import InputFile from 'components/InputFile';
import InputSpacer from 'components/InputSpacer'; import InputSpacer from 'components/InputSpacer';
import InputErrors from 'components/InputErrors';
// Styles
import styles from './styles.scss'; import styles from './styles.scss';
class InputFileWithErrors extends React.PureComponent { class InputFileWithErrors extends React.PureComponent {
state = { label: null, hasValue: false }; state = { errors: [], label: null, hasValue: false };
componentDidMount() { componentDidMount() {
const { errors } = this.props;
let newState = Object.assign({}, this.state);
if (this.props.multiple && !isEmpty(this.props.value)) { 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);
} }
componentDidUpdate(prevProps) { componentDidUpdate(prevProps) {
@ -32,6 +43,12 @@ class InputFileWithErrors extends React.PureComponent {
} else if(isEmpty(this.props.value)) { } else if(isEmpty(this.props.value)) {
this.updateState({ label: null }); this.updateState({ label: null });
} }
// Check if errors have been updated during validations
if (nextProps.didCheckErrors !== this.props.didCheckErrors) {
// Remove from the state the errors that have already been set
const errors = isEmpty(nextProps.errors) ? [] : nextProps.errors;
this.setState({ errors });
}
} }
setLabel = (label) => { setLabel = (label) => {
@ -47,6 +64,9 @@ class InputFileWithErrors extends React.PureComponent {
const { const {
className, className,
customBootstrapClass, customBootstrapClass,
errorsClassName,
errorsStyle,
noErrorsDescription,
inputDescription, inputDescription,
inputDescriptionClassName, inputDescriptionClassName,
inputDescriptionStyle, inputDescriptionStyle,
@ -83,6 +103,7 @@ class InputFileWithErrors extends React.PureComponent {
)} )}
<InputFile <InputFile
multiple={multiple} multiple={multiple}
error={!isEmpty(this.state.errors)}
name={name} name={name}
onChange={onChange} onChange={onChange}
setLabel={this.setLabel} setLabel={this.setLabel}
@ -93,6 +114,11 @@ class InputFileWithErrors extends React.PureComponent {
message={inputDescription} message={inputDescription}
style={inputDescriptionStyle} style={inputDescriptionStyle}
/> />
<InputErrors
className={errorsClassName}
errors={!noErrorsDescription && this.state.errors || []}
style={errorsStyle}
/>
{spacer} {spacer}
</div> </div>
); );
@ -100,8 +126,12 @@ class InputFileWithErrors extends React.PureComponent {
} }
InputFileWithErrors.defaultProps = { InputFileWithErrors.defaultProps = {
errors: [],
errorsClassName: '',
errorsStyle: {},
className: '', className: '',
customBootstrapClass: 'col-md-6', customBootstrapClass: 'col-md-6',
didCheckErrors: false,
inputDescription: '', inputDescription: '',
inputDescriptionClassName: '', inputDescriptionClassName: '',
inputDescriptionStyle: {}, inputDescriptionStyle: {},
@ -109,6 +139,7 @@ InputFileWithErrors.defaultProps = {
labelClassName: '', labelClassName: '',
labelStyle: {}, labelStyle: {},
multiple: false, multiple: false,
noErrorsDescription: false,
style: {}, style: {},
value: [], value: [],
}; };
@ -116,6 +147,10 @@ InputFileWithErrors.defaultProps = {
InputFileWithErrors.propTypes = { InputFileWithErrors.propTypes = {
className: PropTypes.string, className: PropTypes.string,
customBootstrapClass: PropTypes.string, customBootstrapClass: PropTypes.string,
didCheckErrors: PropTypes.bool,
errors: PropTypes.array,
errorsClassName: PropTypes.string,
errorsStyle: PropTypes.object,
inputDescription: PropTypes.oneOfType([ inputDescription: PropTypes.oneOfType([
PropTypes.string, PropTypes.string,
PropTypes.func, PropTypes.func,
@ -138,6 +173,7 @@ InputFileWithErrors.propTypes = {
labelStyle: PropTypes.object, labelStyle: PropTypes.object,
multiple: PropTypes.bool, multiple: PropTypes.bool,
name: PropTypes.string.isRequired, name: PropTypes.string.isRequired,
noErrorsDescription: PropTypes.bool,
onChange: PropTypes.func.isRequired, onChange: PropTypes.func.isRequired,
style: PropTypes.object, style: PropTypes.object,
value: PropTypes.oneOfType([ value: PropTypes.oneOfType([

View File

@ -97,7 +97,7 @@ module.exports = {
module.exports.findOne module.exports.findOne
.call(model, { [model.primaryKey]: recordId }, [details.via]) .call(model, { [model.primaryKey]: recordId }, [details.via])
.then(record => { .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, { return module.exports.update.call(this, {
id: getValuePrimaryKey(record[details.via], model.primaryKey), id: getValuePrimaryKey(record[details.via], model.primaryKey),
values: { values: {

View File

@ -51,9 +51,10 @@ module.exports = function (strapi) {
initialize: cb => { initialize: cb => {
_.forEach(_.pickBy(strapi.config.connections, {connector: 'strapi-hook-mongoose'}), (connection, connectionName) => { _.forEach(_.pickBy(strapi.config.connections, {connector: 'strapi-hook-mongoose'}), (connection, connectionName) => {
const instance = new Mongoose(); 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 uriOptions = uri ? url.parse(uri, true).query : {};
const { authenticationDatabase, ssl, debug } = _.defaults(connection.options, uriOptions, strapi.config.hook.settings.mongoose); const { authenticationDatabase, ssl, debug } = _.defaults(connection.options, uriOptions, strapi.config.hook.settings.mongoose);
const isSrv = srv === true || srv === 'true';
// Connect to mongo database // Connect to mongo database
const connectOptions = {}; const connectOptions = {};
@ -73,10 +74,16 @@ module.exports = function (strapi) {
connectOptions.ssl = ssl === true || ssl === 'true'; connectOptions.ssl = ssl === true || ssl === 'true';
connectOptions.useNewUrlParser = true; connectOptions.useNewUrlParser = true;
connectOptions.dbName = database;
options.debug = debug === true || debug === 'true'; 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) { for (let key in options) {
instance.set(key, options[key]); instance.set(key, options[key]);

View File

@ -50,7 +50,7 @@ module.exports = {
.findOne({ [model.primaryKey]: value[current] }) .findOne({ [model.primaryKey]: value[current] })
.populate(details.via) .populate(details.via)
.then(record => { .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, { return module.exports.update.call(this, {
id: getValuePrimaryKey(record[details.via], model.primaryKey), id: getValuePrimaryKey(record[details.via], model.primaryKey),
values: { values: {

View File

@ -107,6 +107,7 @@ export function* dataDeleteAll({ entriesToDelete, model, source }) {
yield put(deleteSeveralDataSuccess()); yield put(deleteSeveralDataSuccess());
yield call(dataGet, { currentModel: model, source }); yield call(dataGet, { currentModel: model, source });
strapi.notification.success('content-manager.success.record.delete');
} catch(err) { } catch(err) {
strapi.notification.error('content-manager.error.record.delete'); strapi.notification.error('content-manager.error.record.delete');
} }

View File

@ -926,7 +926,10 @@
}, },
"name": "name", "name": "name",
"type": "string", "type": "string",
"value": "" "value": "",
"validations": {
"required": true
}
}, },
{ {
"label": { "label": {

View File

@ -178,7 +178,8 @@ module.exports = {
if (params.plugin === 'upload' && relation.model || relation.collection === 'file') { if (params.plugin === 'upload' && relation.model || relation.collection === 'file') {
params = { params = {
type: 'media', type: 'media',
multiple: params.collection ? true : false multiple: params.collection ? true : false,
required: params.required
}; };
} else { } else {
params = _.omit(params, ['collection', 'model', 'via']); params = _.omit(params, ['collection', 'model', 'via']);
@ -288,7 +289,8 @@ module.exports = {
attrs[attribute.name] = { attrs[attribute.name] = {
[attribute.params.multiple ? 'collection' : 'model']: 'file', [attribute.params.multiple ? 'collection' : 'model']: 'file',
via, via,
plugin: 'upload' plugin: 'upload',
required: attribute.params.required === true ? true : false
}; };
} }
} else if (_.has(attribute, 'params.target')) { } else if (_.has(attribute, 'params.target')) {

View File

@ -56,14 +56,14 @@ module.exports = {
return ctx.badRequest(null, ctx.request.admin ? [{ messages: [{ id: 'Auth.form.error.confirmed' }] }] : 'Your account email is not confirmed.'); return ctx.badRequest(null, ctx.request.admin ? [{ messages: [{ id: 'Auth.form.error.confirmed' }] }] : 'Your account email is not confirmed.');
} }
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) { if (!user) {
return ctx.badRequest(null, ctx.request.admin ? [{ messages: [{ id: 'Auth.form.error.invalid' }] }] : 'Identifier or password invalid.'); 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) { 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.`); return ctx.badRequest(null, ctx.request.admin ? [{ messages: [{ id: 'Auth.form.error.noAdminAccess' }] }] : `You're not an administrator.`);
} }
@ -285,7 +285,7 @@ module.exports = {
return ctx.badRequest(null, ctx.request.admin ? [{ messages: [{ id: 'Auth.form.error.email.taken' }] }] : 'Email is already taken.'); 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.'); return ctx.badRequest(null, ctx.request.admin ? [{ messages: [{ id: 'Auth.form.error.email.taken' }] }] : 'Email is already taken.');
} }