Fix conflicts

This commit is contained in:
soupette 2019-03-09 10:39:35 +01:00
commit bac6dad0a6
88 changed files with 424 additions and 128 deletions

View File

@ -28,6 +28,7 @@ before_script:
script:
- npm run lint
- npm run doc
- npm run test:front
cache:
directories:

View File

@ -2,7 +2,7 @@
@import '../../styles/variables/variables';
.notificationsContainer { /* stylelint-disable */
position: absolute;
position: fixed;
top: 72px;
left: 240px;
right: 0;

View File

@ -296,8 +296,8 @@ export class AdminPage extends React.Component {
}
AdminPage.childContextTypes = {
emitEvent: PropTypes.func,
currentEnvironment: PropTypes.string.isRequired,
emitEvent: PropTypes.func,
disableGlobalOverlayBlocker: PropTypes.func,
enableGlobalOverlayBlocker: PropTypes.func,
plugins: PropTypes.object,
@ -305,6 +305,7 @@ AdminPage.childContextTypes = {
};
AdminPage.contextTypes = {
emitEvent: PropTypes.func,
router: PropTypes.object.isRequired,
};

View File

@ -121,7 +121,9 @@
"components.Input.error.validation.required": "This value is required.",
"components.ListRow.empty": "There is no data to be shown.",
"components.OverlayBlocker.description": "You're using a feature that needs the server to restart. Please wait until the server is up.",
"components.OverlayBlocker.description.serverError": "The server should have restarted, please check your logs in the terminal.",
"components.OverlayBlocker.title": "Waiting for restart...",
"components.OverlayBlocker.title.serverError": "The restart takes longer than expected",
"components.PageFooter.select": "entries per page",
"components.ProductionBlocker.description": "For safety purposes we have to disable this plugin in other environments.",
"components.ProductionBlocker.header": "This plugin is only available in development.",

View File

@ -122,7 +122,9 @@
"components.Input.error.validation.required": "Ce champ est obligatoire.",
"components.ListRow.empty": "Il n'y a pas de données à afficher.",
"components.OverlayBlocker.description": "Vous utilisez une fonctionnalité qui nécessite le redémarrage du server. Merci d'attendre que celui-ci ait redémarré.",
"components.OverlayBlocker.description.serverError": "Le serveur aurait déjà du redémarrer, vous devriez regarder les messages dans le terminal.",
"components.OverlayBlocker.title": "Le serveur est en train de redémarrer",
"components.OverlayBlocker.title.serverError": "Le serveur aurait déjà du redémarrer",
"components.PageFooter.select": "entrées par page",
"components.ProductionBlocker.description": "Pour des raisons de sécurité il est désactivé dans les autres environnements.",
"components.ProductionBlocker.header": "Ce plugin est disponible uniquement en développement.",

View File

@ -55,4 +55,4 @@
"npm": ">= 6.0.0"
},
"license": "MIT"
}
}

View File

@ -129,7 +129,7 @@ module.exports = {
await Promise.all(
<%= globalID %>.associations.map(async association => {
if (!association.via || !data._id) {
if (!association.via || !data._id || association.dominant) {
return true;
}

View File

@ -29,6 +29,7 @@ function InputNumber(props) {
onFocus={props.onFocus}
placeholder={message}
ref={props.inputRef}
step={!Number.isNaN(Number(props.step)) ? Number(props.step) : 1}
style={props.style}
tabIndex={props.tabIndex}
type="number"
@ -49,6 +50,7 @@ InputNumber.defaultProps = {
onBlur: () => {},
onFocus: () => {},
placeholder: 'app.utils.placeholder.defaultMessage',
step: 1,
style: {},
tabIndex: '0',
};
@ -65,6 +67,7 @@ InputNumber.propTypes = {
onChange: PropTypes.func.isRequired,
onFocus: PropTypes.func,
placeholder: PropTypes.string,
step: PropTypes.number,
style: PropTypes.object,
tabIndex: PropTypes.string,
value: PropTypes.oneOfType([

View File

@ -83,6 +83,7 @@ class InputNumberWithErrors extends React.Component { // eslint-disable-line rea
placeholder,
style,
tabIndex,
step,
value,
} = this.props;
const handleBlur = isFunction(onBlur) ? onBlur : this.handleBlur;
@ -121,6 +122,7 @@ class InputNumberWithErrors extends React.Component { // eslint-disable-line rea
placeholder={placeholder}
style={inputStyle}
tabIndex={tabIndex}
step={step}
value={value}
/>
<InputDescription
@ -162,6 +164,7 @@ InputNumberWithErrors.defaultProps = {
labelStyle: {},
noErrorsDescription: false,
placeholder: 'app.utils.placeholder.defaultMessage',
step: 1,
style: {},
tabIndex: '0',
validations: {},
@ -209,6 +212,7 @@ InputNumberWithErrors.propTypes = {
onChange: PropTypes.func.isRequired,
onFocus: PropTypes.func,
placeholder: PropTypes.string,
step: PropTypes.number,
style: PropTypes.object,
tabIndex: PropTypes.string,
validations: PropTypes.object,

View File

@ -12,27 +12,78 @@ import cn from 'classnames';
import styles from './styles.scss';
const DELAY = 1000;
class OverlayBlocker extends React.Component {
constructor(props) {
super(props);
this.state = { elapsed: 0, start: 0 };
this.overlayContainer = document.createElement('div');
document.body.appendChild(this.overlayContainer);
}
componentDidUpdate(prevProps) {
const { isOpen } = this.props;
if (prevProps.isOpen !== this.props.isOpen && isOpen) {
this.startTimer();
}
if (prevProps.isOpen !== isOpen && !isOpen) {
this.stopTimer();
}
}
componentWillUnmount() {
document.body.removeChild(this.overlayContainer);
}
tick = () => {
const { elapsed } = this.state;
if (elapsed > 15) {
clearInterval(this.timer);
return;
}
this.setState(prevState => ({ elapsed: (Math.round(Date.now() - prevState.start) / 1000) }));
}
startTimer = () => {
this.setState({ start: Date.now() });
this.timer = setInterval(this.tick, DELAY);
}
stopTimer = () => {
this.setState({ elapsed: 0 });
clearInterval(this.timer);
}
render() {
const { title, description, icon } = this.props;
let { title, description, icon } = this.props;
const { elapsed } = this.state;
let button = (
<div className={styles.buttonContainer}>
<a className={cn(styles.primary, 'btn')} href="https://strapi.io/documentation/configurations/configurations.html#server" target="_blank">Read the documentation</a>
</div>
);
if (elapsed > 15) {
button = null;
icon = 'fa fa-clock-o';
description = 'components.OverlayBlocker.description.serverError';
title = 'components.OverlayBlocker.title.serverError';
}
const content = this.props.children ? (
this.props.children
) : (
<div className={styles.container}>
<div className={styles.icoContainer}>
<div className={cn(styles.icoContainer, elapsed < 15 && styles.spin)}>
<i className={icon} />
</div>
<div>
@ -42,9 +93,7 @@ class OverlayBlocker extends React.Component {
<p>
<FormattedMessage id={description} />
</p>
<div className={styles.buttonContainer}>
<a className={cn(styles.primary, 'btn')} href="https://strapi.io/documentation/configurations/configurations.html#server" target="_blank">Read the documentation</a>
</div>
{button}
</div>
</div>
);
@ -65,7 +114,7 @@ class OverlayBlocker extends React.Component {
}
OverlayBlocker.defaultProps = {
children: '',
children: null,
description: 'components.OverlayBlocker.description',
icon: 'fa fa-refresh',
isOpen: false,

View File

@ -23,6 +23,9 @@
color: #323740;
margin-right: 20px;
line-height: 9.3rem;
}
.spin {
> i {
-webkit-animation:spin 4s linear infinite;
-moz-animation:spin 4s linear infinite;

View File

@ -30,6 +30,7 @@ function PageFooter(props) {
name: 'params._limit',
value: parseInt(e.target.value, 10),
};
props.context.emitEvent('willChangeNumberOfEntriesPerPage');
props.onChangeParams({ target });
}}
value={get(props, ['params', '_limit'], 10)}
@ -54,6 +55,7 @@ function PageFooter(props) {
}
PageFooter.defaultProps = {
context: {},
count: 1,
onChangeParams: () => {},
params: {
@ -64,6 +66,7 @@ PageFooter.defaultProps = {
};
PageFooter.propTypes = {
context: PropTypes.object,
count: PropTypes.number,
onChangeParams: PropTypes.func,
params: PropTypes.object,

View File

@ -14,7 +14,7 @@ class Manager {
/**
* Retrieve the bootstrap col index, name and type of a field
* @param {Number} index
* @param {Number} index
* @returns {Object}
*/
getAttrInfos(index) {
@ -72,7 +72,7 @@ class Manager {
/**
* Retrieve a field default bootstrap col
* NOTE: will change if we add the customisation of an input's width
* @param {String} type
* @param {String} type
* @returns {Number}
*/
getBootStrapCol(type) {
@ -80,7 +80,7 @@ class Manager {
case 'checkbox':
case 'boolean':
case 'date':
case 'bigint':
case 'biginteger':
case 'decimal':
case 'float':
case 'integer':
@ -109,7 +109,7 @@ class Manager {
}
/**
*
*
* Retrieve the last element of each line of a bootstrap grid and push it into an array
* @returns {Array}
*/
@ -138,7 +138,7 @@ class Manager {
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);
}
@ -155,9 +155,9 @@ class Manager {
}
/**
*
*
* Retrieve the field's type depending on its name
* @param {String} itemName
* @param {String} itemName
* @returns {String}
*/
getType(itemName) {
@ -177,8 +177,8 @@ class Manager {
/**
* Retrieve the line bootstrap col sum
* @param {Number} leftBound
* @param {Number} rightBound
* @param {Number} leftBound
* @param {Number} rightBound
* @returns {Number}
*/
@ -237,7 +237,7 @@ class Manager {
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]);
}
@ -249,4 +249,4 @@ class Manager {
}
}
module.exports = Manager;
module.exports = Manager;

View File

@ -422,12 +422,12 @@ module.exports = function(strapi) {
case 'email':
type = 'varchar(255)';
break;
case 'biginteger':
type = definition.client === 'pg' ? 'bigint' : 'bigint(53)';
break;
case 'integer':
type = definition.client === 'pg' ? 'integer' : 'int';
break;
case 'biginteger':
type = definition.client === 'pg' ? 'bigint' : 'bigint(53)';
break;
case 'float':
type = definition.client === 'pg' ? 'double precision' : 'double';
break;

View File

@ -27,7 +27,7 @@ const getInputType = (type = '') => {
switch (type.toLowerCase()) {
case 'boolean':
return 'toggle';
case 'bigint':
case 'biginteger':
case 'decimal':
case 'float':
case 'integer':

View File

@ -20,7 +20,7 @@ const getInputType = (attrType) => {
case 'datetime':
return InputDate;
case 'integer':
case 'bigint':
case 'biginteger':
case 'decimal':
case 'float':
return InputNumber;

View File

@ -47,6 +47,7 @@ const getFilters = (type) => {
},
];
case 'integer':
case 'biginteger':
case 'float':
case 'decimal':
case 'date':

View File

@ -73,7 +73,10 @@ class FiltersPickWrapper extends React.PureComponent {
label: 'content-manager.components.FiltersPickWrapper.PluginHeader.actions.apply',
kind: 'primary',
type: 'submit',
onClick: this.props.onSubmit,
onClick: (e) => {
this.context.emitEvent('didFilterEntries');
this.props.onSubmit(e);
},
},
]);
@ -179,6 +182,10 @@ class FiltersPickWrapper extends React.PureComponent {
}
}
FiltersPickWrapper.contextTypes = {
emitEvent: PropTypes.func,
};
FiltersPickWrapper.defaultProps = {
appliedFilters: [],
filterToFocus: null,

View File

@ -68,6 +68,7 @@ class TableRow extends React.Component {
// Redirect to the edit page
handleClick() {
this.context.emitEvent('willEditEntry');
this.context.router.history.push(`${this.props.destination}${this.props.redirectUrl}`);
}
@ -128,6 +129,7 @@ class TableRow extends React.Component {
}
TableRow.contextTypes = {
emitEvent: PropTypes.func,
router: PropTypes.object.isRequired,
};

View File

@ -33,7 +33,7 @@ const getBootstrapClass = attrType => {
case 'boolean':
case 'toggle':
case 'date':
case 'bigint':
case 'biginteger':
case 'decimal':
case 'float':
case 'integer':

View File

@ -183,9 +183,10 @@ export function onReset() {
};
}
export function onSubmit() {
export function onSubmit(context) {
return {
type: ON_SUBMIT,
context,
};
}

View File

@ -28,11 +28,13 @@ export function* getModels() {
}
}
export function* submit() {
export function* submit(action) {
try {
const schema = yield select(makeSelectModifiedSchema());
yield call(request, '/content-manager/models', { method: 'PUT', body: { schema } });
action.context.emitEvent('didSaveContentTypeLayout');
yield put(submitSucceeded());
} catch(err) {
// Silent

View File

@ -154,9 +154,10 @@ export function setLoader() {
};
}
export function submit() {
export function submit(context) {
return {
type: SUBMIT,
context,
};
}

View File

@ -380,7 +380,7 @@ export class EditPage extends React.Component {
);
if (isEmpty(formErrors)) {
this.props.submit();
this.props.submit(this.context);
}
this.props.setFormErrors(formErrors);
@ -436,7 +436,7 @@ export class EditPage extends React.Component {
};
return (
<li key={`${pluginId}.link`}>
<li key={`${pluginId}.link`} onClick={() => this.context.emitEvent('willEditContentTypeLayoutFromEditView')}>
<NavLink {...message} url={url} />
</li>
);
@ -506,7 +506,7 @@ export class EditPage extends React.Component {
const Component = compo.component;
return (
<li key={compo.key}>
<li key={compo.key} onClick={() => this.context.emitEvent('willEditContentTypeFromEditView')}>
<Component {...this} {...compo.props} />
</li>
);
@ -704,6 +704,7 @@ export class EditPage extends React.Component {
}
EditPage.contextTypes = {
emitEvent: PropTypes.func,
currentEnvironment: PropTypes.string,
plugins: PropTypes.object,
};

View File

@ -68,7 +68,7 @@ function* deleteData() {
}
}
export function* submit() {
export function* submit(action) {
const currentModelName = yield select(makeSelectModelName());
const fileRelations = yield select(makeSelectFileRelations());
const isCreating = yield select(makeSelectIsCreating());
@ -140,6 +140,8 @@ export function* submit() {
const requestUrl = `/content-manager/explorer/${currentModelName}/${id}`;
action.context.emitEvent('willSaveEntry');
// Call our request helper (see 'utils/request')
// Pass false and false as arguments so the request helper doesn't stringify
// the body and doesn't watch for the server to restart
@ -150,11 +152,14 @@ export function* submit() {
params,
}, false, false);
action.context.emitEvent('didSaveEntry');
strapi.notification.success('content-manager.success.record.save');
// Redirect the user to the ListPage container
yield put(submitSuccess());
} catch(err) {
action.context.emitEvent('didNotSaveEntry', { error: err });
if (isArray(get(err, 'response.payload.message'))) {
const errors = err.response.payload.message.reduce((acc, current) => {
const error = current.messages.reduce((acc, current) => {

View File

@ -53,12 +53,13 @@ export function changeParams({ target }) {
};
}
export function deleteData(id, modelName, source) {
export function deleteData(id, modelName, source, context) {
return {
type: DELETE_DATA,
id,
modelName,
source,
context,
};
}

View File

@ -192,7 +192,7 @@ export class ListPage extends React.Component {
* Retrieve the model's schema
* @return {Object} Fields
*/
getCurrentSchema = () =>
getCurrentSchema = () =>
get(this.props.schema, ['models', this.getCurrentModelName(), 'fields']) ||
get(this.props.schema, ['models', 'plugins', this.getSource(), this.getCurrentModelName(), 'fields']);
@ -257,6 +257,8 @@ export class ListPage extends React.Component {
const attrIndex = this.findAttrIndex(target.name);
const defaultSettingsAttrIndex = findIndex(defaultSettingsDisplay, ['name', target.name]);
this.context.emitEvent('didChangeDisplayedFields');
if (attrIndex !== -1) {
if (get(this.props.listPage, 'displayedFields', []).length === 1) {
strapi.notification.error('content-manager.notification.error.displayedFields');
@ -282,10 +284,10 @@ export class ListPage extends React.Component {
} else {
const attributes = this.getCurrentModelAttributes();
const searchable = attributes[target.name].type !== 'json' && attributes[target.name] !== 'array';
const attrToAdd = defaultSettingsAttrIndex !== -1
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);
}
}
@ -332,7 +334,7 @@ export class ListPage extends React.Component {
handleDelete = e => {
e.preventDefault();
e.stopPropagation();
this.props.deleteData(this.state.target, this.getCurrentModelName(), this.getSource());
this.props.deleteData(this.state.target, this.getCurrentModelName(), this.getSource(), this.context);
this.setState({ showWarning: false });
};
@ -379,7 +381,13 @@ export class ListPage extends React.Component {
showBulkActions = () => get(this.getCurrentModel(), ['bulkActions']);
toggle = () => this.setState(prevState => ({ isOpen: !prevState.isOpen }));
toggle = () => {
if (!this.state.isOpen) {
this.context.emitEvent('willChangeDisplayedFields');
}
this.setState(prevState => ({ isOpen: !prevState.isOpen }));
}
toggleModalWarning = e => {
if (!isUndefined(e)) {
@ -446,11 +454,13 @@ export class ListPage extends React.Component {
entity: capitalize(this.props.match.params.slug) || 'Content Manager',
},
kind: 'primaryAddShape',
onClick: () =>
onClick: () => {
this.context.emitEvent('willCreateEntry');
this.props.history.push({
pathname: `${this.props.location.pathname}/create`,
search: this.generateRedirectURI(),
}),
});
},
},
];
const { listPage: { count } } = this.props;
@ -555,7 +565,7 @@ export class ListPage extends React.Component {
decreaseMarginBottom={filters.length > 0}
>
<div className="row">
<AddFilterCTA onClick={onToggleFilters} showHideText={showFilter} id="addFilterCTA" />
<AddFilterCTA onClick={(e) => this.context.emitEvent('willFilterEntries') && onToggleFilters(e)} showHideText={showFilter} id="addFilterCTA" />
{filters.map(this.renderFilter)}
</div>
</Div>
@ -613,6 +623,7 @@ export class ListPage extends React.Component {
/>
{this.renderPopUpWarningDeleteAll()}
<PageFooter
context={this.context}
count={get(count, this.getCurrentModelName(), 0)}
onChangeParams={this.handleChangeParams}
params={listPage.params}
@ -627,6 +638,10 @@ export class ListPage extends React.Component {
}
}
ListPage.contextTypes = {
emitEvent: PropTypes.func,
};
ListPage.propTypes = {
addAttr: PropTypes.func.isRequired,
addFilter: PropTypes.func.isRequired,

View File

@ -73,7 +73,7 @@ export function* dataGet(action) {
}
}
export function* dataDelete({ id, modelName, source }) {
export function* dataDelete({ id, modelName, source, context }) {
try {
const requestUrl = `/content-manager/explorer/${modelName}/${id}`;
const params = {};
@ -82,6 +82,8 @@ export function* dataDelete({ id, modelName, source }) {
params.source = source;
}
context.emitEvent('willDeleteEntry');
yield call(request, requestUrl, {
method: 'DELETE',
params,
@ -90,6 +92,8 @@ export function* dataDelete({ id, modelName, source }) {
strapi.notification.success('content-manager.success.record.delete');
yield put(deleteDataSuccess(id));
context.emitEvent('didDeleteEntry');
} catch(err) {
strapi.notification.error('content-manager.error.record.delete');
}

View File

@ -318,7 +318,10 @@ class SettingPage extends React.PureComponent {
{
kind: 'primary',
label: 'content-manager.containers.Edit.submit',
onClick: this.handleSubmit,
onClick: (e) => {
this.context.emitEvent('willSaveContentTypeLayout');
this.handleSubmit(e);
},
type: 'submit',
},
];
@ -1060,7 +1063,7 @@ class SettingPage extends React.PureComponent {
confirm: 'content-manager.popUpWarning.button.confirm',
}}
popUpWarningType="danger"
onConfirm={onSubmit}
onConfirm={() => onSubmit(this.context)}
/>
<PopUpWarning
isOpen={showWarningCancel}
@ -1085,6 +1088,10 @@ class SettingPage extends React.PureComponent {
}
}
SettingPage.contextTypes = {
emitEvent: PropTypes.func,
};
SettingPage.defaultProps = {
draggedItemName: null,
grid: [],

View File

@ -58,7 +58,7 @@ module.exports = {
const searchInt = Object.keys(this._attributes)
.filter(attribute => attribute !== this.primaryKey && !associations.includes(attribute))
.filter(attribute => ['integer', 'decimal', 'float'].includes(this._attributes[attribute].type));
.filter(attribute => ['integer','biginteger', 'decimal', 'float'].includes(this._attributes[attribute].type));
const searchBool = Object.keys(this._attributes)
.filter(attribute => attribute !== this.primaryKey && !associations.includes(attribute))
@ -129,11 +129,11 @@ module.exports = {
const searchNoText = Object.keys(this._attributes)
.filter(attribute => attribute !== this.primaryKey && !associations.includes(attribute))
.filter(attribute => !['string', 'text', 'boolean', 'integer', 'decimal', 'float'].includes(this._attributes[attribute].type));
.filter(attribute => !['string', 'text', 'boolean', 'integer', 'biginteger', 'decimal', 'float'].includes(this._attributes[attribute].type));
const searchInt = Object.keys(this._attributes)
.filter(attribute => attribute !== this.primaryKey && !associations.includes(attribute))
.filter(attribute => ['integer', 'decimal', 'float'].includes(this._attributes[attribute].type));
.filter(attribute => ['integer', 'biginteger', 'decimal', 'float'].includes(this._attributes[attribute].type));
const searchBool = Object.keys(this._attributes)
.filter(attribute => attribute !== this.primaryKey && !associations.includes(attribute))

View File

@ -22,6 +22,7 @@ module.exports = {
const $or = Object.keys(this.attributes).reduce((acc, curr) => {
switch (this.attributes[curr].type) {
case 'integer':
case 'biginteger':
case 'float':
case 'decimal':
if (!_.isNaN(_.toNumber(params.search))) {
@ -57,6 +58,7 @@ module.exports = {
const $or = Object.keys(this.attributes).reduce((acc, curr) => {
switch (this.attributes[curr].type) {
case 'integer':
case 'biginteger':
case 'float':
case 'decimal':
if (!_.isNaN(_.toNumber(params.search))) {

View File

@ -52,4 +52,4 @@
"npm": ">= 6.0.0"
},
"license": "MIT"
}
}

View File

@ -42,6 +42,7 @@ class AttributeRow extends React.Component {
string: IcoString,
text: IcoText,
integer: IcoNumber,
biginteger: IcoNumber,
float: IcoNumber,
decimal: IcoNumber,
email: IcoEmail,
@ -56,6 +57,7 @@ class AttributeRow extends React.Component {
handleEdit = () => this.props.onEditAttribute(this.props.row.name);
handleDelete = () => {
this.context.emitEvent('willDeleteFieldOfContentType');
this.props.onDelete(this.props.row.name);
this.setState({ showWarning: false });
};
@ -136,6 +138,10 @@ class AttributeRow extends React.Component {
}
}
AttributeRow.contextTypes = {
emitEvent: PropTypes.func,
};
AttributeRow.propTypes = {
onDelete: PropTypes.func.isRequired,
onEditAttribute: PropTypes.func.isRequired,

View File

@ -17,6 +17,9 @@ import styles from './styles.scss';
/* eslint-disable jsx-a11y/click-events-have-key-events */
class ContentHeader extends React.Component { // eslint-disable-line react/prefer-stateless-function
handleEdit = () => {
// Send event.
this.context.emitEvent('willEditNameOfContentType');
// Open modal.
router.push(this.props.editPath);
}
@ -41,6 +44,7 @@ class ContentHeader extends React.Component { // eslint-disable-line react/prefe
renderContentHeader = () => {
const description = isEmpty(this.props.description) ? '' : <FormattedMessage id={this.props.description} defaultMessage='{description}' values={{ description: this.props.description}} />;
const buttons = this.props.addButtons ? this.renderButtonContainer() : '';
return (
<div className={styles.contentHeader} style={this.props.styles}>
<div>
@ -74,6 +78,10 @@ class ContentHeader extends React.Component { // eslint-disable-line react/prefe
}
}
ContentHeader.contextTypes = {
emitEvent: PropTypes.func,
};
ContentHeader.propTypes = {
addButtons: PropTypes.bool,
buttonsContent: PropTypes.array,

View File

@ -15,6 +15,12 @@ import styles from './styles.scss';
class PopUpHeaderNavLink extends React.Component { // eslint-disable-line react/prefer-stateless-function
handleGoTo = () => {
if (this.props.routePath.indexOf('#create::contentType') !== -1 && this.props.name === 'advancedSettings') {
this.context.emitEvent('didSelectContentTypeSettings');
} else if (this.props.routePath.indexOf('#create') !== -1 && this.props.routePath.indexOf('::attribute') !== -1 && this.props.name === 'advancedSettings') {
this.context.emitEvent('didSelectContentTypeFieldSettings');
}
router.push(replace(this.props.routePath, this.props.nameToReplace, this.props.name));
}
@ -29,6 +35,10 @@ class PopUpHeaderNavLink extends React.Component { // eslint-disable-line react/
}
}
PopUpHeaderNavLink.contextTypes = {
emitEvent: PropTypes.func,
};
PopUpHeaderNavLink.propTypes = {
message: PropTypes.string.isRequired,
name: PropTypes.string.isRequired,

View File

@ -12,7 +12,7 @@ import { Switch, Route } from 'react-router-dom';
import pluginId from '../../pluginId';
// import HomePage from '../HomePage';
import HomePage from '../HomePage';
import ModelPage from '../ModelPage';
import NotFoundPage from '../NotFoundPage';
@ -27,10 +27,10 @@ import makeSelectApp from './selectors';
import styles from './styles.scss';
const ROUTES = [
// {
// component: HomePage,
// to: `/plugins/${pluginId}`,
// },
{
component: HomePage,
to: `/plugins/${pluginId}`,
},
{
component: ModelPage,
to: `/plugins/${pluginId}/models/:modelName`,

View File

@ -259,6 +259,7 @@
"value": "integer",
"items": [
{ "name": "content-type-builder.form.attribute.item.number.type.integer", "value": "integer" },
{ "name": "content-type-builder.form.attribute.item.number.type.biginteger", "value": "biginteger" },
{ "name": "content-type-builder.form.attribute.item.number.type.float", "value": "float" },
{ "name": "content-type-builder.form.attribute.item.number.type.decimal", "value": "decimal" }
],

View File

@ -373,6 +373,7 @@ export class Form extends React.Component { // eslint-disable-line react/prefer-
}
goToAttributeTypeView = (attributeType) => {
this.context.emitEvent('didSelectContentTypeFieldType', { type: attributeType });
const settings = attributeType === 'relation' ? 'defineRelation' : 'baseSettings';
router.push(`${this.props.routePath}#create${this.props.modelName}::attribute${attributeType}::${settings}`);
}
@ -461,6 +462,12 @@ export class Form extends React.Component { // eslint-disable-line react/prefer-
let dataSucces = null;
let cbFail;
if (redirectToChoose) {
this.context.emitEvent('willAddMoreFieldToContentType');
} else if (this.props.hash.indexOf('#edit') !== -1 && this.props.hash.indexOf('::attribute') !== -1) {
this.context.emitEvent('willEditFieldOfContentType');
}
switch (true) {
case includes(hashArray[0], '#edit'): {
// Check if the user is editing the attribute
@ -682,6 +689,7 @@ export class Form extends React.Component { // eslint-disable-line react/prefer-
}
Form.contextTypes = {
emitEvent: PropTypes.func,
plugins: PropTypes.object,
updatePlugin: PropTypes.func,
};

View File

@ -39,6 +39,8 @@ export function* editContentType(action) {
const response = yield call(request, requestUrl, opts, true);
if (response.ok) {
action.context.emitEvent('didEditNameOfContentType');
yield put(contentTypeActionSucceeded());
yield put(unsetButtonLoading());

View File

@ -35,7 +35,7 @@ export class HomePage extends React.Component { // eslint-disable-line react/pre
const title = availableNumber > 1 ? `${pluginId}.table.contentType.title.plural`
: `${pluginId}.table.contentType.title.singular`;
const renderViewContent = availableNumber === 0 ?
const renderViewContent = availableNumber === 0 ?
<EmptyContentTypeView handleButtonClick={() => {}} />
: (
<TableList

View File

@ -46,6 +46,9 @@ export class HomePage extends React.Component { // eslint-disable-line react/pre
if (storeData.getIsModelTemporary()) {
strapi.notification.info('content-type-builder.notification.info.contentType.creating.notSaved');
} else {
// Send event.
this.context.emitEvent('willCreateContentType');
// Open CT modal.
this.toggleModal();
}
}
@ -108,6 +111,7 @@ export class HomePage extends React.Component { // eslint-disable-line react/pre
}
HomePage.contextTypes = {
emitEvent: PropTypes.func,
plugins: PropTypes.object,
updatePlugin: PropTypes.func,
};

View File

@ -46,7 +46,7 @@ import styles from './styles.scss';
// Array of attributes that the ctb can handle at the moment
const availableAttributes = Object.keys(forms.attribute);
availableAttributes.push('integer', 'decimal', 'float');
availableAttributes.push('integer', 'biginteger', 'decimal', 'float');
/* eslint-disable jsx-a11y/no-static-element-interactions */
/* eslint-disable react/jsx-wrap-multilines */
@ -149,7 +149,7 @@ export class ModelPage extends React.Component { // eslint-disable-line react/pr
const attributeToRemove = get(model, ['attributes', index]);
const parallelAttributeIndex = attributeToRemove.name === attributeToRemove.params.key ?
-1 : findIndex(model.attributes, (attr) => attr.params.key === attributeName);
this.props.deleteAttribute(index, this.props.match.params.modelName, parallelAttributeIndex !== -1);
}
@ -169,6 +169,7 @@ export class ModelPage extends React.Component { // eslint-disable-line react/pr
switch (attribute.params.type) {
case 'integer':
case 'biginteger':
case 'float':
case 'decimal':
attributeType = 'number';
@ -321,6 +322,7 @@ export class ModelPage extends React.Component { // eslint-disable-line react/pr
}
ModelPage.contextTypes = {
emitEvent: PropTypes.func,
plugins: PropTypes.object,
updatePlugin: PropTypes.func,
};

View File

@ -107,15 +107,22 @@ export function* submitChanges(action) {
set(body, 'plugin', pluginModel);
}
const { emitEvent } = action.context;
const method = modelName === body.name ? 'POST' : 'PUT';
const baseUrl = '/content-type-builder/models/';
const requestUrl = method === 'POST' ? baseUrl : `${baseUrl}${body.name}`;
const opts = { method, body };
// Send event.
yield put(emitEvent('willSaveContentType'));
// Send request to save the content type.
const response = yield call(request, requestUrl, opts, true);
if (response.ok) {
if (method === 'POST') {
storeData.clearAppStorage();
yield put(emitEvent('didSaveContentType'));
yield put(temporaryContentTypePosted(size(get(body, 'attributes'))));
yield put(postContentTypeSucceeded());

View File

@ -7,6 +7,7 @@
"attribute.enumeration": "Enumeration",
"attribute.float": "Float",
"attribute.integer": "integer",
"attribute.biginteger": "big integer",
"attribute.json": "JSON",
"attribute.media": "Media",
"attribute.password": "Password",
@ -54,6 +55,7 @@
"form.attribute.item.number.type.decimal": "decimal (ex: 2.22)",
"form.attribute.item.number.type.float": "float (ex: 3.33333333)",
"form.attribute.item.number.type.integer": "integer (ex: 10)",
"form.attribute.item.number.type.biginteger": "big integer (ex: 123456789)",
"form.attribute.item.requiredField": "Required field",
"form.attribute.item.requiredField.description": "You won't be able to create an entry if this field is empty",
"form.attribute.item.settings.name": "Settings",

View File

@ -89,6 +89,8 @@ module.exports = {
if (_.isEmpty(strapi.api)) {
strapi.emit('didCreateFirstContentType');
} else {
strapi.emit('didCreateContentType');
}
ctx.send({ ok: true });

View File

@ -51,4 +51,4 @@
"npm": ">= 6.0.0"
},
"license": "MIT"
}
}

View File

@ -88,10 +88,10 @@ class Manager {
case 'checkbox':
case 'boolean':
case 'date':
case 'bigint':
case 'decimal':
case 'float':
case 'integer':
case 'biginteger':
case 'number':
return 4;
case 'json':
@ -250,4 +250,4 @@ class Manager {
}
}
module.exports = Manager;
module.exports = Manager;

View File

@ -514,7 +514,7 @@ module.exports = {
const getter =
currentAssociation.plugin !== undefined
? ['plugins', currentAssociation.plugin, 'models', name, 'attributes']
: ['models', name, 'attributes'];
: ['models', name.toLowerCase(), 'attributes'];
const associationAttributes = _.get(strapi, getter);
const associationSchema = this.generateAssociationSchema(associationAttributes, getter);
@ -1313,6 +1313,7 @@ module.exports = {
case 'double':
return 'number';
case 'integer':
case 'biginteger':
case 'long':
return 'integer';
default:

View File

@ -30,6 +30,20 @@ module.exports = async cb => {
return _.startsWith(node_module, 'strapi-provider-email') || _.startsWith(node_module, 'strapi-email');
});
node_modules.filter((node_module) => {
return node_module.startsWith('@');
})
.forEach((orga) => {
const node_modules = fs.readdirSync(path.join(basePath, 'node_modules', orga));
node_modules.forEach((node_module) => {
// DEPRECATED strapi-email-* will be remove in next version
if (_.startsWith(node_module, 'strapi-provider-email') || _.startsWith(node_module, 'strapi-email')) {
emails.push(`${orga}/${node_module}`);
}
});
});
// mount all providers to get configs
_.forEach(emails, (node_module) => {
strapi.plugins.email.config.providers.push(

View File

@ -50,4 +50,4 @@
"npm": ">= 6.0.0"
},
"license": "MIT"
}
}

View File

@ -24,7 +24,7 @@
"apollo-server-koa": "^2.0.7",
"dataloader": "^1.4.0",
"glob": "^7.1.3",
"graphql": "^14.0.2",
"graphql": "^14.1.0",
"graphql-depth-limit": "^1.1.0",
"graphql-playground-middleware-koa": "^1.6.4",
"graphql-tools": "^3.1.1",
@ -54,4 +54,4 @@
"npm": ">= 6.0.0"
},
"license": "MIT"
}
}

View File

@ -22,7 +22,9 @@ module.exports = {
convertToParams: (params, primaryKey) => {
return Object.keys(params).reduce((acc, current) => {
return Object.assign(acc, {
[`${primaryKey === current ? '' : '_'}${current}`]: params[current],
[`${
primaryKey === current || "id" === current ? "" : "_"
}${current}`]: params[current]
});
}, {});
},

View File

@ -41,6 +41,7 @@ module.exports = {
type = 'Boolean';
break;
case 'integer':
case 'biginteger':
type = 'Int';
break;
case 'decimal':

View File

@ -124,11 +124,12 @@ export function languagesFetchSucceeded(appLanguages, listLanguages) {
}
export function editSettings(newSettings, endPoint) {
export function editSettings(newSettings, endPoint, context) {
return {
type: EDIT_SETTINGS,
newSettings,
endPoint,
context
};
}
@ -230,11 +231,12 @@ export function databasesFetchSucceeded(listDatabases, availableDatabases) {
};
}
export function newDatabasePost(endPoint, data) {
export function newDatabasePost(endPoint, data, context) {
return {
type: NEW_DATABASE_POST,
endPoint,
data,
context
};
}
@ -284,11 +286,12 @@ export function specificDatabaseFetchSucceeded(db) {
};
}
export function databaseEdit(data, apiUrl) {
export function databaseEdit(data, apiUrl, context) {
return {
type: DATABASE_EDIT,
data,
apiUrl,
context
};
}

View File

@ -147,7 +147,7 @@ export class HomePage extends React.Component { // eslint-disable-line react/pre
if (isEmpty(formErrors)) {
// this.props.setErrors([]);
this.props.newDatabasePost(this.props.match.params.env, newData);
this.props.newDatabasePost(this.props.match.params.env, newData, this.context);
} else {
this.props.setErrors(formErrors);
}
@ -192,7 +192,7 @@ export class HomePage extends React.Component { // eslint-disable-line react/pre
const defaultLanguageArray = formatLanguageLocale(target.id);
// Edit the new config
this.props.editSettings({ 'language.defaultLocale': join(defaultLanguageArray, '_') }, 'i18n');
this.props.editSettings({ 'language.defaultLocale': join(defaultLanguageArray, '_') }, 'i18n', this.context);
}
handleFetch(props) {
@ -260,7 +260,7 @@ export class HomePage extends React.Component { // eslint-disable-line react/pre
if (isEmpty(body)) return strapi.notification.info('settings-manager.strapi.notification.info.settingsEqual');
if (isEmpty(formErrors)) {
this.props.editSettings(body, apiUrl);
this.props.editSettings(body, apiUrl, this.context);
} else {
this.props.setErrors(formErrors);
}
@ -278,7 +278,7 @@ export class HomePage extends React.Component { // eslint-disable-line react/pre
if (isEmpty(formErrors)) {
this.props.databaseEdit(body, apiUrl);
this.props.databaseEdit(body, apiUrl, this.context);
} else {
this.props.setErrors(formErrors);
}
@ -535,6 +535,10 @@ function mapDispatchToProps(dispatch) {
);
}
HomePage.contextTypes = {
emitEvent: PropTypes.func,
};
HomePage.propTypes = {
cancelChanges: PropTypes.func.isRequired,
changeDefaultLanguage: PropTypes.func.isRequired,

View File

@ -54,14 +54,19 @@ export function* editDatabase(action) {
body,
};
const requestUrl = `/settings-manager/configurations/databases/${action.apiUrl}`;
action.context.emitEvent('willEditDatabaseSettings');
const resp = yield call(request, requestUrl, opts, true);
if (resp.ok) {
action.context.emitEvent('didEditDatabaseSettings');
strapi.notification.success('settings-manager.strapi.notification.success.databaseEdit');
yield put(databaseActionSucceeded());
}
} catch(error) {
action.context.emitEvent('didNotEditDatabaseSettings');
const formErrors = map(error.response.payload.message, err => ({ target: err.target, errors: map(err.messages, mess => ({ id: `settings-manager.${mess.id}`})) }));
yield put(databaseActionError(formErrors));
@ -190,14 +195,17 @@ export function* postDatabase(action) {
method: 'POST',
body,
};
action.context.emitEvent('willAddDatabaseSettings');
const requestUrl = `/settings-manager/configurations/databases/${action.endPoint}`;
const resp = yield call(request, requestUrl, opts, true);
if (resp.ok) {
action.context.emitEvent('didAddDatabaseSettings');
yield put(databaseActionSucceeded());
strapi.notification.success('settings-manager.strapi.notification.success.databaseAdd');
}
} catch(error) {
action.context.emitEvent('didNotAddDatabaseSettings')
const formErrors = map(error.response.payload.message, (err) => {
const target = err.target ? replace(err.target, err.target.split('.')[2], '${name}') : 'database.connections.${name}.name';
return (
@ -219,14 +227,19 @@ export function* settingsEdit(action) {
body: action.newSettings,
method: 'PUT',
};
action.context.emitEvent('willEditSettings', { category : action.endPoint });
const requestUrl = `/settings-manager/configurations/${action.endPoint}`;
const resp = yield call(request, requestUrl, opts, true);
if (resp.ok) {
action.context.emitEvent('didEditSettings', { category : action.endPoint });
yield put(editSettingsSucceeded());
strapi.notification.success('settings-manager.strapi.notification.success.settingsEdit');
}
} catch(error) {
} catch (error) {
action.context.emitEvent('didNotEditSettings', { error });
strapi.notification.error('settings-manager.strapi.notification.error');
} finally {
yield put(unsetLoader());

View File

@ -53,4 +53,4 @@
"npm": ">= 6.0.0"
},
"license": "MIT"
}
}

View File

@ -72,6 +72,7 @@ class EditForm extends React.Component {
name="sizeLimit"
onChange={this.props.onChange}
type="number"
step={0.01}
value={get(this.props.modifiedData, 'sizeLimit', 1) / 1000}
/>
</div>

View File

@ -40,7 +40,7 @@ export function onCancel() {
export function onChange({ target }) {
const keys = ['modifiedData'].concat(target.name.split('.'));
const value = target.name === 'sizeLimit' ? parseInt(target.value, 10) * 1000 : target.value;
const value = target.name === 'sizeLimit' ? Number(target.value) * 1000 : target.value;
return {
type: ON_CHANGE,

View File

@ -1,6 +1,6 @@
// import { LOCATION_CHANGE } from 'react-router-redux';
import { Map } from 'immutable';
import { isEmpty } from 'lodash';
import { isEmpty, get, isObject } from 'lodash';
import {
all,
call,
@ -13,6 +13,9 @@ import {
} from 'redux-saga/effects';
import request from 'utils/request';
import pluginId from '../../pluginId';
import {
deleteSuccess,
dropSuccess,
@ -81,8 +84,11 @@ function* uploadFiles(action) {
strapi.notification.success({ id: 'upload.notification.dropFiles.success', values: { number: newFiles.length } });
}
} catch(err) {
strapi.notification.error('notification.error');
} catch(error) {
let message = get(error, ['response', 'payload', 'message', '0', 'messages', '0']);
if (isObject(message)) message = {...message, id: `${pluginId}.${message.id}`};
strapi.notification.error(message || 'notification.error');
} finally {
yield put(unsetLoading());
}

View File

@ -24,5 +24,7 @@
"notification.config.success": "تم تحديث الإعدادات",
"notification.delete.success": "تم حذف الملف",
"notification.dropFile.success": "تم تحميل ملفك",
"notification.dropFiles.success": "{number} ملفات تم تحميلها"
}
"notification.dropFiles.success": "{number} ملفات تم تحميلها",
"Upload.status.sizeLimit": "{file} أكبر من حجم الحد الذي تمت تهيئته",
"Upload.status.disabled" : "تم تعطيل تحميل الملف"
}

View File

@ -24,5 +24,7 @@
"notification.config.success": "Die Einstellungen wurden aktualisiert",
"notification.delete.success": "Die Datei wurde gelöscht",
"notification.dropFile.success": "Deine Datei wurde hochgeladen",
"notification.dropFiles.success": "{number} Dateien wurden hochgeladen"
}
"notification.dropFiles.success": "{number} Dateien wurden hochgeladen",
"Upload.status.sizeLimit": "{file} ist größer als die konfigurierte Begrenzungsgröße",
"Upload.status.disabled" : "Das Hochladen von Dateien ist deaktiviert"
}

View File

@ -21,8 +21,13 @@
"PluginInputFile.link": "browse",
"PluginInputFile.loading": "Your files are being uploaded...",
"PluginInputFile.text": "Drag & drop your files into this area or {link} from a file to upload",
"Upload.status.empty": "Files are empty",
"Upload.status.disabled": "File upload is disabled",
"Upload.status.sizeLimit": "{file} file is bigger than limit size!",
"notification.config.success": "The settings has been updated",
"notification.delete.success": "The file has been deleted",
"notification.dropFile.success": "Your file has been uploaded",
"notification.dropFiles.success": "{number} files have been uploaded"
}
"notification.dropFiles.success": "{number} files have been uploaded",
"Upload.status.sizeLimit": "{file} is bigger than configured limit size",
"Upload.status.disabled" : "File upload is disabled"
}

View File

@ -24,5 +24,7 @@
"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"
}
"notification.dropFiles.success": "{number} archivos han sido cargados",
"Upload.status.sizeLimit": "{archivo} es más grande que el tamaño límite configurado",
"Upload.status.disabled" : "La carga de archivos está deshabilitada"
}

View File

@ -24,5 +24,7 @@
"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"
"notification.dropFiles.success": "{number} fichiers ont été téléchargées",
"Upload.status.sizeLimit": "{file} est plus grand que la taille limite configurée",
"Upload.status.disabled" : "Le téléchargement de fichier est désactivé"
}

View File

@ -24,5 +24,7 @@
"notification.config.success": "Le impostazioni è stato aggiornato",
"notification.delete.success": "Il file è stato cancellato",
"notification.dropFile.success": "Il file è stato caricato",
"notification.dropFiles.success": "{number} file sono stati caricati"
}
"notification.dropFiles.success": "{number} file sono stati caricati",
"Upload.status.sizeLimit": "{file} è più grande della dimensione limite configurata",
"Upload.status.disabled" : "Il caricamento del file è disabilitato"
}

View File

@ -24,5 +24,7 @@
"notification.config.success": "設定が更新されました",
"notification.delete.success": "ファイルが削除されました",
"notification.dropFile.success": "ファイルがアップロードされました",
"notification.dropFiles.success": "{number}個のファイルがアップロードされました"
}
"notification.dropFiles.success": "{number}個のファイルがアップロードされました",
"Upload.status.sizeLimit": "{file}は設定された制限サイズよりも大きいです",
"Upload.status.disabled" : "ファイルのアップロードが無効になっています"
}

View File

@ -24,5 +24,7 @@
"notification.config.success": "설정을 업데이트했습니다.",
"notification.delete.success": "파일을 삭제했습니다.",
"notification.dropFile.success": "파일을 업로드했습니다.",
"notification.dropFiles.success": "{number}개의 파일을 업로드 했습니다."
}
"notification.dropFiles.success": "{number}개의 파일을 업로드 했습니다.",
"Upload.status.sizeLimit": "{file}이 (가) 구성된 제한 크기보다 큽니다.",
"Upload.status.disabled" : "파일 업로드가 사용 중지되었습니다."
}

View File

@ -24,5 +24,7 @@
"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"
}
"notification.dropFiles.success": "{number} bestanden zijn geüpload",
"Upload.status.sizeLimit": "{file} is groter dan de geconfigureerde limietgrootte",
"Upload.status.disabled" : "Bestand uploaden is uitgeschakeld"
}

View File

@ -24,5 +24,7 @@
"notification.config.success": "Ustawienia zostały zaaktualizowane",
"notification.delete.success": "Plik został usunięty",
"notification.dropFile.success": "Plik został przesłany",
"notification.dropFiles.success": "{number} plików zostało przesłanych"
}
"notification.dropFiles.success": "{number} plików zostało przesłanych",
"Upload.status.sizeLimit": "{plik} jest większy niż skonfigurowany rozmiar limitu",
"Upload.status.disabled" : "Przesyłanie plików jest wyłączone"
}

View File

@ -25,4 +25,4 @@
"notification.delete.success": "O arquivo foi removido",
"notification.dropFile.success": "Seu arquivo foi enviado com sucesso",
"notification.dropFiles.success": "{number} arquivos foram enviados com sucesso"
}
}

View File

@ -24,5 +24,7 @@
"notification.config.success": "As configurações foram actualizadas",
"notification.delete.success": "O arquivo foi apagado",
"notification.dropFile.success": "Seu arquivo foi transferido com sucesso",
"notification.dropFiles.success": "{number} arquivos foram transferidos com sucesso"
}
"notification.dropFiles.success": "{number} arquivos foram transferidos com sucesso",
"Upload.status.sizeLimit": "{file} é maior que o tamanho limite configurado",
"Upload.status.disabled" : "O upload de arquivos está desativado"
}

View File

@ -24,5 +24,7 @@
"notification.config.success": "Настройки обновлены",
"notification.delete.success": "Файл удален",
"notification.dropFile.success": "Ваш файл загружен",
"notification.dropFiles.success": "Файлов загружено: {number}"
"notification.dropFiles.success": "Файлов загружено: {number}",
"Upload.status.sizeLimit": "{file} больше настроенного предельного размера",
"Upload.status.disabled" : "Загрузка файла отключена"
}

View File

@ -24,5 +24,7 @@
"notification.config.success": "Ayarlar güncellendi.",
"notification.delete.success": "Dosya silindi",
"notification.dropFile.success": "Dosyanız yüklendi",
"notification.dropFiles.success": "{number} dosyalar yüklendi"
}
"notification.dropFiles.success": "{number} dosyalar yüklendi",
"Upload.status.sizeLimit": "{file} yapılandırılmış sınır boyutundan daha büyük",
"Upload.status.disabled" : "Dosya yükleme devre dışı"
}

View File

@ -24,5 +24,7 @@
"notification.config.success": "设置已更新",
"notification.delete.success": "文件已被删除",
"notification.dropFile.success": "您的文件已上传",
"notification.dropFiles.success": "{number} 个文件已上传"
}
"notification.dropFiles.success": "{number} 个文件已上传",
"Upload.status.sizeLimit": "{file}大于配置的限制大小",
"Upload.status.disabled" : "文件上传已禁用"
}

View File

@ -24,5 +24,7 @@
"notification.config.success": "設定已更新",
"notification.delete.success": "檔案已刪除",
"notification.dropFile.success": "您的檔案已上傳",
"notification.dropFiles.success": "{number} 個檔案已上傳"
}
"notification.dropFiles.success": "{number} 個檔案已上傳",
"Upload.status.sizeLimit": "{file}大於配置的限制大小",
"Upload.status.disabled" : "文件上傳已禁用"
}

View File

@ -30,6 +30,20 @@ module.exports = async cb => {
return _.startsWith(node_module, 'strapi-provider-upload') || _.startsWith(node_module, 'strapi-upload');
});
node_modules.filter((node_module) => {
return node_module.startsWith('@');
})
.forEach((orga) => {
const node_modules = fs.readdirSync(path.join(basePath, 'node_modules', orga));
node_modules.forEach((node_module) => {
// DEPRECATED strapi-email-* will be remove in next version
if (_.startsWith(node_module, 'strapi-provider-upload') || _.startsWith(node_module, 'strapi-upload')) {
uploads.push(`${orga}/${node_module}`);
}
});
});
// mount all providers to get configs
_.forEach(uploads, (node_module) => {
strapi.plugins.upload.config.providers.push(

View File

@ -19,7 +19,6 @@ module.exports = {
// Verify if the file upload is enable.
if (config.enabled === false) {
strapi.log.error('File upload is disabled');
return ctx.badRequest(null, ctx.request.admin ? [{ messages: [{ id: 'Upload.status.disabled' }] }] : 'File upload is disabled');
}
@ -28,7 +27,7 @@ module.exports = {
const { files = {} } = ctx.request.body.files;
if (_.isEmpty(files)) {
return ctx.send(true);
return ctx.badRequest(null, ctx.request.admin ? [{ messages: [{ id: 'Upload.status.empty' }] }] : 'Files are empty');
}
// Transform stream files to buffer

View File

@ -46,4 +46,4 @@
"npm": ">= 6.0.0"
},
"license": "MIT"
}
}

View File

@ -136,7 +136,12 @@ class ListRow extends React.Component { // eslint-disable-line react/prefer-stat
return;
}
case 'providers':
this.context.emitEvent('willEditAuthenticationProvider');
return this.context.setDataToEdit(this.props.item.name);
case 'email-templates':
this.context.emitEvent('willEditEmailTemplates');
return this.context.setDataToEdit(this.props.item.name);
default:
return;
@ -165,6 +170,7 @@ class ListRow extends React.Component { // eslint-disable-line react/prefer-stat
}
ListRow.contextTypes = {
emitEvent: PropTypes.func,
setDataToEdit: PropTypes.func.isRequired,
};

View File

@ -218,9 +218,10 @@ export function setShouldDisplayPolicieshint() {
};
}
export function submit() {
export function submit(context) {
return {
type: SUBMIT,
context,
};
}

View File

@ -103,7 +103,7 @@ export class EditPage extends React.Component { // eslint-disable-line react/pre
return this.props.setErrors([{ name: 'name', errors: [{ id: 'users-permissions.EditPage.form.roles.name.error' }] }]);
}
this.props.submit();
this.props.submit(this.context);
}
showLoaderForm = () => {
@ -159,7 +159,10 @@ export class EditPage extends React.Component { // eslint-disable-line react/pre
number: size(get(this.props.editPage, ['modifiedData', 'users'])),
},
}}
onClickAdd={this.props.onClickAdd}
onClickAdd={() => {
this.context.emitEvent('didAssociateUserToRole');
this.props.onClickAdd();
}}
onClickDelete={this.props.onClickDelete}
name="users"
type="text"
@ -268,6 +271,10 @@ EditPage.childContextTypes = {
resetShouldDisplayPoliciesHint: PropTypes.func.isRequired,
};
EditPage.contextTypes = {
emitEvent: PropTypes.func,
};
EditPage.propTypes = {
addUser: PropTypes.func.isRequired,
editPage: PropTypes.object.isRequired,

View File

@ -85,7 +85,7 @@ export function* roleGet(action) {
}
}
export function* submit() {
export function* submit(action) {
try {
const actionType = yield select(makeSelectActionType());
const body = yield select(makeSelectModifiedData());
@ -97,7 +97,11 @@ export function* submit() {
const requestURL = actionType === 'POST' ? '/users-permissions/roles' : `/users-permissions/roles/${roleId}`;
const response = yield call(request, requestURL, opts);
if (actionType === 'POST') {
action.context.emitEvent('didCreateRole');
}
if (response.ok) {
yield put(submitSucceeded());
}

View File

@ -107,10 +107,11 @@ export function setFormErrors(formErrors) {
};
}
export function submit(endPoint) {
export function submit(endPoint, context) {
return {
type: SUBMIT,
endPoint,
context,
};
}

View File

@ -115,6 +115,7 @@ export class HomePage extends React.Component {
handleButtonClick = () => {
// TODO change open modal URL
if (this.props.match.params.settingType === 'roles') {
this.context.emitEvent('willCreateRole');
this.props.history.push(`${this.props.location.pathname}/create`);
} else if (this.props.match.params.settingType === 'providers') {
this.props.history.push(`${this.props.location.pathname}#add::${this.props.match.params.settingType}`);
@ -133,7 +134,7 @@ export class HomePage extends React.Component {
if (isEmpty(formErrors)) {
this.setState({ showModalEdit: false });
this.props.submit(this.props.match.params.settingType);
this.props.submit(this.props.match.params.settingType, this.context);
} else {
this.props.setFormErrors(formErrors);
}
@ -176,7 +177,7 @@ export class HomePage extends React.Component {
showLoaders = () => {
const { data, isLoading, modifiedData } = this.props;
const isAdvanded = this.getEndPoint() === 'advanced';
return isLoading && get(data, this.getEndPoint()) === undefined && !isAdvanded || isLoading && isAdvanded && get(modifiedData, this.getEndPoint()) === undefined;
}
@ -197,7 +198,7 @@ export class HomePage extends React.Component {
values={get(modifiedData, this.getEndPoint(), {})}
/>
);
return (
<div>
<form onSubmit={(e) => e.preventDefault()}>
@ -232,6 +233,10 @@ HomePage.childContextTypes = {
unsetDataToEdit: PropTypes.func,
};
HomePage.contextTypes = {
emitEvent: PropTypes.func,
};
HomePage.defaultProps = {};
HomePage.propTypes = {

View File

@ -75,6 +75,13 @@ export function* submitData(action) {
const opts = { method: 'PUT', body: (action.endPoint === 'advanced') ? get(body, ['advanced', 'settings'], {}) : body };
yield call(request, `/users-permissions/${action.endPoint}`, opts);
if (action.endPoint === 'email-templates') {
action.context.emitEvent('didEditEmailTemplates');
} else if (action.endPoint === 'providers') {
action.context.emitEvent('didEditAuthenticationProvider');
}
yield put(submitSucceeded());
strapi.notification.success('users-permissions.notification.success.submit');
} catch(error) {

View File

@ -56,7 +56,7 @@ module.exports = {
if (!user) {
return ctx.badRequest(null, ctx.request.admin ? [{ messages: [{ id: 'Auth.form.error.invalid' }] }] : 'Identifier or password invalid.');
}
if (_.get(await store.get({key: 'advanced'}), 'email_confirmation') && !user.confirmed) {
return ctx.badRequest(null, ctx.request.admin ? [{ messages: [{ id: 'Auth.form.error.confirmed' }] }] : 'Your account email is not confirmed.');
}
@ -196,7 +196,7 @@ module.exports = {
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({
@ -332,7 +332,7 @@ module.exports = {
}
ctx.send({
jwt,
jwt: !settings.email_confirmation ? jwt : undefined,
user: _.omit(user.toJSON ? user.toJSON() : user, ['password', 'resetPasswordToken'])
});
} catch(err) {
@ -345,7 +345,12 @@ module.exports = {
emailConfirmation: async (ctx) => {
const params = ctx.query;
const user = await strapi.plugins['users-permissions'].services.jwt.verify(params.confirmation);
let user;
try {
user = await strapi.plugins['users-permissions'].services.jwt.verify(params.confirmation);
} catch (err) {
return ctx.badRequest(null, 'This confirmation token is invalid.');
}
await strapi.plugins['users-permissions'].services.user.edit(_.pick(user, ['_id', 'id']), {confirmed: true});

View File

@ -56,4 +56,4 @@
"npm": ">= 6.0.0"
},
"license": "MIT"
}
}