@@ -327,7 +348,7 @@ EditPage.contextTypes = {
};
EditPage.defaultProps = {
- models: {},
+ schema: {},
};
EditPage.propTypes = {
@@ -339,10 +360,9 @@ EditPage.propTypes = {
initModelProps: PropTypes.func.isRequired,
location: PropTypes.object.isRequired,
match: PropTypes.object.isRequired,
- models: PropTypes.object,
onCancel: PropTypes.func.isRequired,
resetProps: PropTypes.func.isRequired,
- schema: PropTypes.object.isRequired,
+ schema: PropTypes.object,
setFileRelations: PropTypes.func.isRequired,
setFormErrors: PropTypes.func.isRequired,
submit: PropTypes.func.isRequired,
@@ -367,7 +387,6 @@ function mapDispatchToProps(dispatch) {
const mapStateToProps = createStructuredSelector({
editPage: makeSelectEditPage(),
- models: makeSelectModels(),
schema: makeSelectSchema(),
});
diff --git a/packages/strapi-plugin-content-manager/admin/src/containers/ListPage/index.js b/packages/strapi-plugin-content-manager/admin/src/containers/ListPage/index.js
index 07d392eb55..12cfe08b99 100644
--- a/packages/strapi-plugin-content-manager/admin/src/containers/ListPage/index.js
+++ b/packages/strapi-plugin-content-manager/admin/src/containers/ListPage/index.js
@@ -9,11 +9,11 @@ import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import { bindActionCreators, compose } from 'redux';
import { createStructuredSelector } from 'reselect';
-import { capitalize, get, isUndefined, map, toInteger } from 'lodash';
+import { capitalize, get, isUndefined, toInteger } from 'lodash';
import cn from 'classnames';
// App selectors
-import { makeSelectModels, makeSelectSchema } from 'containers/App/selectors';
+import { makeSelectSchema } from 'containers/App/selectors';
// You can find these components in either
// ./node_modules/strapi-helper-plugin/lib/src
@@ -106,9 +106,21 @@ export class ListPage extends React.Component {
* Helper to retrieve the current model data
* @return {Object} the current model
*/
- getCurrentModel = () =>
- get(this.props.models, ['models', this.getCurrentModelName()]) ||
- get(this.props.models, ['plugins', this.getSource(), 'models', this.getCurrentModelName()]);
+ getCurrentModel = () => (
+ get(this.props.schema, ['models', this.getCurrentModelName()]) ||
+ get(this.props.schema, ['models', 'plugins', this.getSource(), this.getCurrentModelName()])
+ );
+
+ getCurrentModelDefaultLimit = () => (
+ get(this.getCurrentModel(), 'pageEntries', 10)
+ );
+
+ getCurrentModelDefaultSort = () => {
+ const sortAttr = get(this.getCurrentModel(), 'defaultSort', 'id');
+ const order = get(this.getCurrentModel(), 'sort', 'ASC');
+
+ return order === 'ASC' ? sortAttr : `-${sortAttr}`;
+ };
/**
* Helper to retrieve the current model name
@@ -122,7 +134,7 @@ export class ListPage extends React.Component {
*/
getData = (props, setUpdatingParams = false) => {
const source = getQueryParameters(props.location.search, 'source');
- const _limit = toInteger(getQueryParameters(props.location.search, '_limit')) || 10;
+ const _limit = toInteger(getQueryParameters(props.location.search, '_limit')) || this.getCurrentModelDefaultLimit();
const _page = toInteger(getQueryParameters(props.location.search, '_page')) || 1;
const _sort = this.findPageSort(props);
const _q = getQueryParameters(props.location.search, '_q') || '';
@@ -144,8 +156,8 @@ export class ListPage extends React.Component {
* @return {Object} Fields
*/
getCurrentSchema = () =>
- get(this.props.schema, [this.getCurrentModelName(), 'fields']) ||
- get(this.props.schema, ['plugins', this.getSource(), this.getCurrentModelName(), 'fields']);
+ get(this.props.schema, ['models', this.getCurrentModelName(), 'fields']) ||
+ get(this.props.schema, ['models', 'plugins', this.getSource(), this.getCurrentModelName(), 'fields']);
getPopUpDeleteAllMsg = () => (
this.props.listPage.entriesToDelete.length > 1 ?
@@ -153,6 +165,14 @@ export class ListPage extends React.Component {
: 'content-manager.popUpWarning.bodyMessage.contentType.delete'
);
+ getModelPrimaryKey = () => (
+ get(this.getCurrentModel(), ['primaryKey'], '_id')
+ );
+
+ getTableHeaders = () => (
+ get(this.getCurrentModel(), ['listDisplay'], [])
+ );
+
/**
* Generate the redirect URI when editing an entry
* @type {String}
@@ -169,29 +189,6 @@ export class ListPage extends React.Component {
return `?${generateSearchFromParams(params)}&source=${this.getSource()}${generateSearchFromFilters(filters)}`;
}
- /**
- * Function to generate the Table's headers
- * @return {Array}
- */
- generateTableHeaders = () => {
- const currentSchema =
- get(this.props.schema, [this.getCurrentModelName()]) ||
- get(this.props.schema, ['plugins', this.getSource(), this.getCurrentModelName()]);
- const tableHeaders = map(currentSchema.list, value => ({
- name: value,
- label: currentSchema.fields[value].label,
- type: currentSchema.fields[value].type,
- }));
-
- tableHeaders.splice(0, 0, {
- name: this.getCurrentModel().primaryKey || 'id',
- label: 'Id',
- type: 'string',
- });
-
- return tableHeaders;
- };
-
areAllEntriesSelected = () => {
const { listPage: { entriesToDelete, records } } = this.props;
@@ -204,26 +201,9 @@ export class ListPage extends React.Component {
* @return {String} the model's primaryKey
*/
findPageSort = props => {
- const {
- match: {
- params: { slug },
- },
- } = props;
- const source = this.getSource();
- const modelPrimaryKey = get(props.models, ['models', slug.toLowerCase(), 'primaryKey']);
- // Check if the model is in a plugin
- const pluginModelPrimaryKey = get(props.models.plugins, [
- source,
- 'models',
- slug.toLowerCase(),
- 'primaryKey',
- ]);
-
return (
getQueryParameters(props.location.search, '_sort') ||
- modelPrimaryKey ||
- pluginModelPrimaryKey ||
- 'id'
+ this.getCurrentModelDefaultSort()
);
};
@@ -311,6 +291,12 @@ export class ListPage extends React.Component {
return updatingParams || isLoading && get(records, this.getCurrentModelName()) === undefined;
}
+ showSearch = () => get(this.getCurrentModel(), ['search']);
+
+ showFilters = () => get(this.getCurrentModel(), ['filters']);
+
+ showBulkActions = () => get(this.getCurrentModel(), ['bulkActions']);
+
render() {
const {
addFilter,
@@ -337,6 +323,7 @@ export class ListPage extends React.Component {
removeAllFilters,
removeFilter,
} = this.props;
+
const pluginHeaderActions = [
{
label: 'content-manager.containers.List.addAnEntry',
@@ -355,12 +342,14 @@ export class ListPage extends React.Component {
return (
-
+ {this.showSearch() && (
+
+ )}
-
-
-
-
0}
- >
-
-
- {filters.map((filter, key) => (
-
- ))}
+ {this.showFilters() && (
+
+
+
+
+
0}
+ >
+
+
+ {filters.map((filter, key) => (
+
+ ))}
+
+
-
-
-
+
+
+ )}
this.getValue(`${this.getPath()}.defaultSort`, 'string');
+
+ getDropDownItems = () => {
+ const name = get(this.props.schema, `models.${this.getPath()}.primaryKey`, 'id' );
+ // The id attribute is not present on schema so we need to add it manually
+ const defaultAttr = { [name]: { name, label: 'Id', type: 'string', searchable: true, sortable: true } };
+ const attributes = Object.assign(get(this.props.schema, `models.${this.getPath()}.attributes`, {}), defaultAttr);
+
+ return Object.keys(attributes)
+ .filter(attr => {
+ return findIndex(this.getListDisplay(), ['name', attr]) === -1 && !attributes[attr].hasOwnProperty('collection') && !attributes[attr].hasOwnProperty('model');
+ })
+ .map(attr => {
+ const searchable = attributes[attr].type !== 'json' && attributes[attr].type !== 'array';
+ const obj = Object.assign(attributes[attr], { name: attr, label: upperFirst(attr), searchable, sortable: searchable });
+
+ return obj;
+ });
+ }
+
+ getListDisplay = () => (
+ get(this.props.schema, `models.${this.getPath()}.listDisplay`, [])
+ );
+
+ getModelName = () => {
+ const { match: { params: { slug, endPoint } } } = this.props;
+
+ return endPoint || slug;
+ }
+
+ getPath = () => {
+ const { match: { params: { slug, source, endPoint } } } = this.props;
+
+ return [slug, source, endPoint]
+ .filter(param => param !== undefined)
+ .join('.');
+ }
+
+ getSelectOptions = (input) => {
+ const selectOptions = this.getListDisplay().reduce((acc, curr) => {
+
+ if (curr.sortable === true) {
+ return acc.concat([curr.name]);
+ }
+
+ return acc;
+ }, []);
+
+ if (selectOptions.length === 0) {
+ selectOptions.push(this.getPrimaryKey());
+ }
+
+ return input.name === 'defaultSort' ? selectOptions : input.selectOptions;
+ }
+
+ getPluginHeaderActions = () => (
+ [
+ {
+ label: 'content-manager.popUpWarning.button.cancel',
+ kind: 'secondary',
+ onClick: this.handleReset,
+ type: 'button',
+ },
+ {
+ kind: 'primary',
+ label: 'content-manager.containers.Edit.submit',
+ onClick: this.handleSubmit,
+ type: 'submit',
+ },
+ ]
+ );
+
+ getPrimaryKey = () => get(this.props.schema, ['models', this.getModelName()].concat(['primaryKey']), 'id');
+
+ getValue = (keys, type) => {
+ const value = get(this.props.schema, ['models'].concat(keys.split('.')));
+
+ return type === 'toggle' ? value : value.toString();
+ }
+
+ handleChange = (e) => {
+ const defaultSort = this.getDefaultSort();
+ const name = e.target.name.split('.');
+ name.pop();
+ const attrName = get(this.props.schema.models, name.concat(['name']));
+ const isDisablingDefaultSort = attrName === defaultSort && e.target.value === false;
+
+ if (isDisablingDefaultSort) {
+ const enableAttrsSort = this.getSelectOptions({ name: 'defaultSort' }).filter(attr => attr !== attrName);
+
+ if (enableAttrsSort.length === 0) {
+ strapi.notification.info('content-manager.notification.info.SettingPage.disableSort');
+ } else {
+ const newDefaultSort = enableAttrsSort.length === 0 ? this.getPrimaryKey() : enableAttrsSort[0];
+ const target = { name: `${this.getPath()}.defaultSort`, value: newDefaultSort };
+ this.props.onChangeSettings({ target });
+ this.props.onChangeSettings(e);
+ }
+ } else {
+ this.props.onChangeSettings(e);
+ }
+ }
+
+ handleClickEditAttr = (index) => {
+ const attrToEdit = get(this.props.schema, ['models'].concat(this.getPath().split('.')).concat(['listDisplay', index]), {});
+ this.props.onClickEditListItem(attrToEdit);
+ }
+
+ handleRemove = (index, keys) => {
+ const attrToRemove = get(this.getListDisplay(), index, {});
+ const defaultSort = this.getDefaultSort();
+ const isRemovingDefaultSort = defaultSort === attrToRemove.name;
+
+ if (isRemovingDefaultSort) {
+ const enableAttrsSort = this.getSelectOptions({ name: 'defaultSort' }).filter(attr => attr !== attrToRemove.name);
+ const newDefaultSort = enableAttrsSort.length > 1 ? enableAttrsSort[0] : this.getPrimaryKey();
+ const target = { name: `${this.getPath()}.defaultSort`, value: newDefaultSort };
+ this.props.onChangeSettings({ target });
+ }
+
+ this.props.onRemove(index, keys);
+ }
+
+ handleReset = (e) => {
+ e.preventDefault();
+ this.setState({ showWarningCancel: true });
+ }
+
+ handleSubmit = (e) => {
+ e.preventDefault();
+ this.setState({ showWarning: true });
+ }
+
+ findIndexListItemToEdit = () => {
+ const index = findIndex(this.getListDisplay(), ['name', get(this.props.settingPage, ['listItemToEdit', 'name'])]);
+
+ return index === -1 ? 0 : index;
+ }
+
+ // We need to remove the Over state on the DraggableAttr component
+ updateSiblingHoverState = () => {
+ this.setState(prevState => ({ isDraggingSibling: !prevState.isDraggingSibling }));
+ };
+
+ toggle = () => this.setState(prevState => ({ showWarning: !prevState.showWarning }));
+
+ toggleWarningCancel = () => this.setState(prevState => ({ showWarningCancel: !prevState.showWarningCancel }));
+
+ toggleDropdown = () => {
+ if (this.getDropDownItems().length > 0) {
+ this.setState(prevState => ({ isOpen: !prevState.isOpen }));
+ }
+ }
+
+ render() {
+ const { isDraggingSibling, isOpen, showWarning, showWarningCancel } = this.state;
+ const {
+ moveAttr,
+ onChangeSettings,
+ onClickAddAttr,
+ onReset,
+ onSubmit,
+ } = this.props;
+ const namePath = this.getPath();
+
+ return (
+
+ this.props.history.goBack()} />
+
+
+
{
+ onSubmit();
+ }}
+ />
+ {
+ onReset();
+ this.toggleWarningCancel();
+ }}
+ />
+
+
+
+ );
+ }
+}
+
+SettingPage.defaultProps = {};
+
+SettingPage.propTypes = {
+ history: PropTypes.object.isRequired,
+ match: PropTypes.object.isRequired,
+ moveAttr: PropTypes.func.isRequired,
+ onChangeSettings: PropTypes.func.isRequired,
+ onClickAddAttr: PropTypes.func.isRequired,
+ onClickEditListItem: PropTypes.func.isRequired,
+ onRemove: PropTypes.func.isRequired,
+ onReset: PropTypes.func.isRequired,
+ onSubmit: PropTypes.func.isRequired,
+ schema: PropTypes.object.isRequired,
+ settingPage: PropTypes.object.isRequired,
+ submitSuccess: PropTypes.bool.isRequired,
+};
+
+const mapDispatchToProps = (dispatch) => (
+ bindActionCreators(
+ {
+ moveAttr,
+ onChangeSettings,
+ onClickAddAttr,
+ onClickEditListItem,
+ onRemove,
+ onReset,
+ onSubmit,
+ },
+ dispatch,
+ )
+);
+
+const mapStateToProps = createStructuredSelector({
+ schema: makeSelectModifiedSchema(),
+ settingPage: makeSelectSettingPage(),
+ submitSuccess: makeSelectSubmitSuccess(),
+});
+
+const withConnect = connect(mapStateToProps, mapDispatchToProps);
+const withReducer = injectReducer({ key: 'settingPage', reducer });
+const withSaga = injectSaga({ key: 'settingPage', saga });
+
+export default compose(
+ withReducer,
+ withSaga,
+ withConnect,
+)(DragDropContext(HTML5Backend)(SettingPage));
diff --git a/packages/strapi-plugin-content-manager/admin/src/containers/SettingPage/reducer.js b/packages/strapi-plugin-content-manager/admin/src/containers/SettingPage/reducer.js
new file mode 100644
index 0000000000..2cd54cc754
--- /dev/null
+++ b/packages/strapi-plugin-content-manager/admin/src/containers/SettingPage/reducer.js
@@ -0,0 +1,24 @@
+/**
+ *
+ * SettingPage reducer
+ */
+
+import { fromJS } from 'immutable';
+import { ON_CLICK_EDIT_LIST_ITEM } from './constants';
+
+const initialState = fromJS({
+ listItemToEdit: fromJS({}),
+ indexListItemToEdit: 0,
+});
+
+function settingPageReducer(state = initialState, action) {
+ switch (action.type) {
+ case ON_CLICK_EDIT_LIST_ITEM:
+ return state
+ .update('listItemToEdit', () => fromJS(action.listItemToEdit));
+ default:
+ return state;
+ }
+}
+
+export default settingPageReducer;
\ No newline at end of file
diff --git a/packages/strapi-plugin-content-manager/admin/src/containers/SettingPage/saga.js b/packages/strapi-plugin-content-manager/admin/src/containers/SettingPage/saga.js
new file mode 100644
index 0000000000..64a1db2ad6
--- /dev/null
+++ b/packages/strapi-plugin-content-manager/admin/src/containers/SettingPage/saga.js
@@ -0,0 +1,3 @@
+function* defaultSaga() {}
+
+export default defaultSaga;
\ No newline at end of file
diff --git a/packages/strapi-plugin-content-manager/admin/src/containers/SettingPage/selectors.js b/packages/strapi-plugin-content-manager/admin/src/containers/SettingPage/selectors.js
new file mode 100644
index 0000000000..bb2fe4ec1d
--- /dev/null
+++ b/packages/strapi-plugin-content-manager/admin/src/containers/SettingPage/selectors.js
@@ -0,0 +1,24 @@
+/**
+ *
+ * SettingPage selectors
+ */
+
+import { createSelector } from 'reselect';
+
+/**
+* Direct selector to the settingPage state domain
+*/
+const selectSettingPageDomain = () => state => state.get('settingPage');
+
+
+/**
+ * Default selector used by EditPage
+ */
+
+const makeSelectSettingPage = () => createSelector(
+ selectSettingPageDomain(),
+ (substate) => substate.toJS()
+);
+
+
+export default makeSelectSettingPage;
\ No newline at end of file
diff --git a/packages/strapi-plugin-content-manager/admin/src/containers/SettingPage/styles.scss b/packages/strapi-plugin-content-manager/admin/src/containers/SettingPage/styles.scss
new file mode 100644
index 0000000000..f403c79e06
--- /dev/null
+++ b/packages/strapi-plugin-content-manager/admin/src/containers/SettingPage/styles.scss
@@ -0,0 +1,128 @@
+.containerFluid {
+ padding: 18px 30px;
+ > div:first-child {
+ max-height: 33px;
+ }
+}
+
+.container {
+ padding-top: 18px;
+}
+
+.main_wrapper{
+ background: #ffffff;
+ padding: 22px 28px 0px;
+ border-radius: 2px;
+ box-shadow: 0 2px 4px #E3E9F3;
+}
+
+.ctmForm {
+ padding-top: 2.6rem;
+}
+
+.separator {
+ margin-top: 19px;
+ border-top: 1px solid #F6F6F6;
+}
+
+.draggedWrapper {
+ display: flex;
+ > div:first-child {
+ height: 30px;
+ width: 20px;
+ margin-right: 10px;
+ text-align: right;
+ line-height: 30px;
+ }
+}
+
+.listDisplayWrapper {
+ padding-top: 21px;
+}
+
+.draggedDescription {
+ color: #333740;
+ font-size: 13px;
+ font-weight: 500;
+
+ > p {
+ margin-bottom: 28px;
+ color: #787E8F;
+ }
+}
+
+.editWrapper {
+ min-height: 246px;
+ padding: 24px 30px;
+ background-color: #FAFAFB;
+ border-radius: 2px;
+}
+
+.dropdownWrapper {
+ margin-left: 30px;
+ > div {
+ height: 30px;
+ width: 100%;
+ justify-content: space-between;
+ background: #ffffff;
+ color: #333740;
+ border: 1px solid #E3E9F3;
+ border-radius: 2px;
+ > button {
+ position: relative;
+ cursor: pointer;
+ padding-left: 10px !important;
+ line-height: 30px;
+ width: 100%;
+ color: #333740;
+ text-align: left;
+ background-color: #ffffff;
+ border: none;
+ font-size: 13px;
+ font-weight: 500;
+ &:focus, &:active, &:hover, &:visited {
+ background-color: transparent!important;
+ box-shadow: none;
+ color: #333740;
+ }
+ > p {
+ height: 100%;
+ margin-left: 20px;
+ margin-bottom: 0;
+ margin-top: -1px;
+ color: #007EFF !important;
+ font-size: 13px !important;
+ }
+ &:before {
+ position: absolute;
+ top: 0;
+ bottom: 0;
+ content: '\f067';
+ font-family: FontAwesome;
+ font-size: 10px;
+ color: #007EFF;
+ }
+ }
+ > div {
+ max-height: 180px;
+ min-width: calc(100% + 2px);
+ margin-left: -1px;
+ margin-top: -1px;
+ border-top-left-radius: 0 !important;
+ border-top-right-radius: 0;
+
+ overflow: scroll;
+
+ button {
+ height: 30px;
+ line-height: 30px;
+ div {
+ margin: 0;
+ white-space: nowrap;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/packages/strapi-plugin-content-manager/admin/src/containers/SettingsPage/actions.js b/packages/strapi-plugin-content-manager/admin/src/containers/SettingsPage/actions.js
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/packages/strapi-plugin-content-manager/admin/src/containers/SettingsPage/constants.js b/packages/strapi-plugin-content-manager/admin/src/containers/SettingsPage/constants.js
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/packages/strapi-plugin-content-manager/admin/src/containers/SettingsPage/forms.json b/packages/strapi-plugin-content-manager/admin/src/containers/SettingsPage/forms.json
new file mode 100644
index 0000000000..93392fa681
--- /dev/null
+++ b/packages/strapi-plugin-content-manager/admin/src/containers/SettingsPage/forms.json
@@ -0,0 +1,42 @@
+{
+ "inputs": [
+ {
+ "label": { "id": "content-manager.form.Input.search" },
+ "customBootstrapClass": "col-md-5",
+ "didCheckErrors": false,
+ "errors": [],
+ "name": "generalSettings.search",
+ "type": "toggle",
+ "validations": {}
+ },
+ {
+ "label": { "id": "content-manager.form.Input.filters" },
+ "customBootstrapClass": "col-md-5",
+ "didCheckErrors": false,
+ "errors": [],
+ "name": "generalSettings.filters",
+ "type": "toggle",
+ "validations": {}
+ },
+ {
+ "label": { "id": "content-manager.form.Input.bulkActions" },
+ "customBootstrapClass": "col-md-2",
+ "didCheckErrors": false,
+ "errors": [],
+ "name": "generalSettings.bulkActions",
+ "type": "toggle",
+ "validations": {}
+ },
+ {
+ "label": { "id": "content-manager.form.Input.pageEntries" },
+ "customBootstrapClass": "col-md-3",
+ "didCheckErrors": false,
+ "errors": [],
+ "inputDescription": { "id": "content-manager.form.Input.pageEntries.inputDescription" },
+ "name": "generalSettings.pageEntries",
+ "selectOptions": ["10", "20", "50", "100"],
+ "type": "select",
+ "validations": {}
+ }
+ ]
+}
\ No newline at end of file
diff --git a/packages/strapi-plugin-content-manager/admin/src/containers/SettingsPage/index.js b/packages/strapi-plugin-content-manager/admin/src/containers/SettingsPage/index.js
new file mode 100644
index 0000000000..23e2734fd8
--- /dev/null
+++ b/packages/strapi-plugin-content-manager/admin/src/containers/SettingsPage/index.js
@@ -0,0 +1,221 @@
+/**
+ *
+ * SettingsPage
+ */
+
+import React from 'react';
+import { connect } from 'react-redux';
+import { bindActionCreators, compose } from 'redux';
+import { createStructuredSelector } from 'reselect';
+import cn from 'classnames';
+import { get, sortBy } from 'lodash';
+import PropTypes from 'prop-types';
+
+import { onChange, onSubmit, onReset } from 'containers/App/actions';
+import { makeSelectModifiedSchema, makeSelectSubmitSuccess } from 'containers/App/selectors';
+
+import Input from 'components/InputsIndex';
+import PluginHeader from 'components/PluginHeader';
+import PopUpWarning from 'components/PopUpWarning';
+
+import Block from 'components/Block';
+import SettingsRow from 'components/SettingsRow';
+
+import injectReducer from 'utils/injectReducer';
+import injectSaga from 'utils/injectSaga';
+
+import reducer from './reducer';
+import saga from './saga';
+import styles from './styles.scss';
+
+import forms from './forms.json';
+
+class SettingsPage extends React.PureComponent {
+ state = { showWarning: false, showWarningCancel: false };
+
+ componentDidUpdate(prevProps) {
+ if (prevProps.submitSuccess !== this.props.submitSuccess) {
+ this.toggle();
+ }
+ }
+
+ componentWillUnmount() {
+ this.props.onReset();
+ }
+
+ getModels = (data = this.props.schema.models, destination = '/') => {
+ const models = Object.keys(data).reduce((acc, curr) => {
+ if (curr !== 'plugins') {
+
+ if (!data[curr].fields && _.isObject(data[curr])) {
+ return this.getModels(data[curr], `${destination}${curr}/`);
+ }
+
+ return acc.concat([{ name: curr, destination: `${destination}${curr}` }]);
+ }
+
+ return this.getModels(data[curr], `${destination}${curr}/`);
+ }, []);
+
+ return sortBy(
+ models.filter(obj => obj.name !== 'permission' && obj.name !== 'role'),
+ ['name'],
+ );
+ }
+
+ getPluginHeaderActions = () => (
+ [
+ {
+ label: 'content-manager.popUpWarning.button.cancel',
+ kind: 'secondary',
+ onClick: this.handleReset,
+ type: 'button',
+ },
+ {
+ kind: 'primary',
+ label: 'content-manager.containers.Edit.submit',
+ onClick: this.handleSubmit,
+ type: 'submit',
+ },
+ ]
+ );
+
+ getValue = (input) => {
+ const { schema: { generalSettings } } = this.props;
+ const value = get(generalSettings, input.name.split('.')[1], input.type === 'toggle' ? false : 10);
+
+ return input.type === 'toggle' ? value : value.toString();
+ }
+
+ handleClick = (destination) => {
+ const { location: { pathname } } = this.props;
+ this.props.history.push(`${pathname}${destination}`);
+ }
+
+ handleReset = (e) => {
+ e.preventDefault();
+ this.setState({ showWarningCancel: true });
+ }
+
+ handleSubmit = (e) => {
+ e.preventDefault();
+ this.setState({ showWarning: true });
+ }
+
+ toggle = () => this.setState(prevState => ({ showWarning: !prevState.showWarning }));
+
+ toggleWarningCancel = () => this.setState(prevState => ({ showWarningCancel: !prevState.showWarningCancel }));
+
+ render() {
+ const { showWarning, showWarningCancel } = this.state;
+ const { onChange, onReset, onSubmit } = this.props;
+
+ return (
+
+
+
{
+ onSubmit();
+ }}
+ />
+ {
+ onReset();
+ this.toggleWarningCancel();
+ }}
+ />
+
+
+
+
+
+
+ {this.getModels().map(model => )}
+
+
+
+
+ );
+ }
+}
+
+SettingsPage.defaultProps = {};
+
+SettingsPage.propTypes = {
+ history: PropTypes.object.isRequired,
+ location: PropTypes.object.isRequired,
+ onChange: PropTypes.func.isRequired,
+ onReset: PropTypes.func.isRequired,
+ onSubmit: PropTypes.func.isRequired,
+ schema: PropTypes.object.isRequired,
+ submitSuccess: PropTypes.bool.isRequired,
+};
+
+const mapDispatchToProps = (dispatch) => (
+ bindActionCreators(
+ {
+ onChange,
+ onReset,
+ onSubmit,
+ },
+ dispatch,
+ )
+);
+
+const mapStateToProps = createStructuredSelector({
+ schema: makeSelectModifiedSchema(),
+ submitSuccess: makeSelectSubmitSuccess(),
+});
+
+const withConnect = connect(mapStateToProps, mapDispatchToProps);
+const withReducer = injectReducer({ key: 'settingsPage', reducer });
+const withSaga = injectSaga({ key: 'settingsPage', saga });
+
+export default compose(
+ withReducer,
+ withSaga,
+ withConnect,
+)(SettingsPage);
diff --git a/packages/strapi-plugin-content-manager/admin/src/containers/SettingsPage/reducer.js b/packages/strapi-plugin-content-manager/admin/src/containers/SettingsPage/reducer.js
new file mode 100644
index 0000000000..676dcdc740
--- /dev/null
+++ b/packages/strapi-plugin-content-manager/admin/src/containers/SettingsPage/reducer.js
@@ -0,0 +1,17 @@
+/**
+ *
+ * SettingsPage reducer
+ */
+
+import { fromJS } from 'immutable';
+
+const initialState = fromJS({});
+
+function settingsPageReducer(state = initialState, action) {
+ switch (action.type) {
+ default:
+ return state;
+ }
+}
+
+export default settingsPageReducer;
\ No newline at end of file
diff --git a/packages/strapi-plugin-content-manager/admin/src/containers/SettingsPage/saga.js b/packages/strapi-plugin-content-manager/admin/src/containers/SettingsPage/saga.js
new file mode 100644
index 0000000000..64a1db2ad6
--- /dev/null
+++ b/packages/strapi-plugin-content-manager/admin/src/containers/SettingsPage/saga.js
@@ -0,0 +1,3 @@
+function* defaultSaga() {}
+
+export default defaultSaga;
\ No newline at end of file
diff --git a/packages/strapi-plugin-content-manager/admin/src/containers/SettingsPage/selectors.js b/packages/strapi-plugin-content-manager/admin/src/containers/SettingsPage/selectors.js
new file mode 100644
index 0000000000..8153dd3770
--- /dev/null
+++ b/packages/strapi-plugin-content-manager/admin/src/containers/SettingsPage/selectors.js
@@ -0,0 +1,24 @@
+/**
+ *
+ * SettingsPage selectors
+ */
+
+import { createSelector } from 'reselect';
+
+/**
+* Direct selector to the settingsPage state domain
+*/
+const selectSettingsPageDomain = () => state => state.get('settingsPage');
+
+
+/**
+ * Default selector used by EditPage
+ */
+
+const makeSelectSettingsPage = () => createSelector(
+ selectSettingsPageDomain(),
+ (substate) => substate.toJS()
+);
+
+
+export default makeSelectSettingsPage;
\ No newline at end of file
diff --git a/packages/strapi-plugin-content-manager/admin/src/containers/SettingsPage/styles.scss b/packages/strapi-plugin-content-manager/admin/src/containers/SettingsPage/styles.scss
new file mode 100644
index 0000000000..cc3db06f18
--- /dev/null
+++ b/packages/strapi-plugin-content-manager/admin/src/containers/SettingsPage/styles.scss
@@ -0,0 +1,38 @@
+.containerFluid {
+ padding: 18px 30px;
+ > div:first-child {
+ max-height: 33px;
+ }
+}
+
+.container {
+ padding-top: 18px;
+ > div:last-child {
+ > div {
+ padding-bottom: 0 !important;
+ }
+ }
+}
+
+.main_wrapper{
+ background: #ffffff;
+ padding: 22px 28px 0px;
+ border-radius: 2px;
+ box-shadow: 0 2px 4px #E3E9F3;
+}
+
+.ctmForm {
+ padding-top: 2.6rem;
+}
+
+.contentTypesWrapper {
+ padding-top: 9px;
+ margin-left: -28px;
+ margin-right: -28px;
+ > div:last-child {
+ > div {
+
+ border-bottom: none;
+ }
+ }
+}
\ No newline at end of file
diff --git a/packages/strapi-plugin-content-manager/admin/src/injectedComponents.js b/packages/strapi-plugin-content-manager/admin/src/injectedComponents.js
new file mode 100644
index 0000000000..109fa8b38c
--- /dev/null
+++ b/packages/strapi-plugin-content-manager/admin/src/injectedComponents.js
@@ -0,0 +1 @@
+export default [];
\ No newline at end of file
diff --git a/packages/strapi-plugin-content-manager/admin/src/translations/en.json b/packages/strapi-plugin-content-manager/admin/src/translations/en.json
index a44c540879..7f96cbbd48 100755
--- a/packages/strapi-plugin-content-manager/admin/src/translations/en.json
+++ b/packages/strapi-plugin-content-manager/admin/src/translations/en.json
@@ -14,16 +14,32 @@
"containers.List.pluginHeaderDescription.singular": "{label} entry found",
"components.LimitSelect.itemsPerPage": "Items per page",
"containers.List.errorFetchRecords": "Error",
+
+ "containers.SettingPage.addField": "Add new field",
+ "containers.SettingPage.attributes": "Attributes fields",
+ "containers.SettingPage.attributes.description": "Define the order of the attributes",
+
+ "containers.SettingPage.listSettings.title": "List — Settings",
+ "containers.SettingPage.listSettings.description": "Configure the options for this content type",
+ "containers.SettingPage.pluginHeaderDescription": "Configure the specific settings for this Content Type",
+ "containers.SettingsPage.pluginHeaderDescription": "Configure the default settings for all your Content types",
+ "containers.SettingsPage.Block.generalSettings.description": "Configure the default options for your Content Types",
+ "containers.SettingsPage.Block.generalSettings.title": "General",
+ "containers.SettingsPage.Block.contentType.title": "Content Types",
+ "containers.SettingsPage.Block.contentType.description": "Configure the specific settings",
"components.AddFilterCTA.add": "Filters",
"components.AddFilterCTA.hide": "Filters",
- "components.FilterOptions.button.apply": "Apply",
+
+ "components.DraggableAttr.edit": "Click to edit",
+
"components.FiltersPickWrapper.PluginHeader.actions.apply": "Apply",
"components.FiltersPickWrapper.PluginHeader.actions.clearAll": "Clear all",
"components.FiltersPickWrapper.PluginHeader.description": "Set the conditions to apply to filter the entries",
"components.FiltersPickWrapper.PluginHeader.title.filter": "Filters",
"components.FiltersPickWrapper.hide": "Hide",
-
+
+ "components.FilterOptions.button.apply": "Apply",
"components.FilterOptions.FILTER_TYPES.=": "is",
"components.FilterOptions.FILTER_TYPES._ne": "is not",
"components.FilterOptions.FILTER_TYPES._lt": "is lower than",
@@ -71,7 +87,19 @@
"error.validation.minSupMax": "Can't be superior",
"error.validation.json": "This is not a JSON",
+ "form.Input.label.inputDescription": "This value overrides the label displayed in the table's head",
+ "form.Input.label": "Label",
+ "form.Input.search": "Enable search",
+ "form.Input.search.field": "Enable search on this field",
+ "form.Input.filters": "Enable filters",
+ "form.Input.sort.field": "Enable sort on this field",
+ "form.Input.bulkActions": "Enable bulk actions",
+ "form.Input.pageEntries": "Entries per page",
+ "form.Input.pageEntries.inputDescription": "Note: You can override this value in the Content Type settings page.",
+ "form.Input.defaultSort": "Default sort attribute",
+
"notification.error.relationship.fetch": "An error occurred during relationship fetch.",
+ "notification.info.SettingPage.disableSort": "You need to have one attribute with the sorting allowed",
"success.record.delete": "Deleted",
"success.record.save": "Saved",
@@ -82,5 +110,7 @@
"popUpWarning.button.confirm": "Confirm",
"popUpWarning.title": "Please confirm",
"popUpWarning.bodyMessage.contentType.delete": "Are you sure you want to delete this entry?",
- "popUpWarning.bodyMessage.contentType.delete.all": "Are you sure you want to delete theses entries?"
+ "popUpWarning.bodyMessage.contentType.delete.all": "Are you sure you want to delete theses entries?",
+ "popUpWarning.warning.cancelAllSettings": "Are you sure you want to cancel your modifications?",
+ "popUpWarning.warning.updateAllSettings": "This will modify all your settings"
}
diff --git a/packages/strapi-plugin-content-manager/admin/src/translations/fr.json b/packages/strapi-plugin-content-manager/admin/src/translations/fr.json
index 6fe8272067..8546d06e92 100755
--- a/packages/strapi-plugin-content-manager/admin/src/translations/fr.json
+++ b/packages/strapi-plugin-content-manager/admin/src/translations/fr.json
@@ -4,7 +4,6 @@
"containers.Home.pluginHeaderTitle": "Type de contenu",
"containers.Home.pluginHeaderDescription": "Créer et modifier votre type de contenu",
"containers.Home.introduction": "Pour éditer du contenu, choisissez un type de données dans le menu de gauche.",
- "containers.Home.pluginHeaderDescription": "Un outil complet pour gérer facilement vos données.",
"containers.Edit.submit": "Valider",
"containers.Edit.editing": "Édition en cours...",
"containers.Edit.delete": "Supprimer",
@@ -16,25 +15,39 @@
"components.LimitSelect.itemsPerPage": "Éléments par page",
"containers.List.errorFetchRecords": "Erreur",
+ "containers.SettingPage.addField": "Ajouter un nouveau champs",
+ "containers.SettingPage.attributes": "Attributs",
+ "containers.SettingPage.attributes.description": "Organiser les attributs du modèle",
+ "containers.SettingPage.pluginHeaderDescription": "Configurez les paramètres de ce modèle",
+ "containers.SettingPage.listSettings.title": "Liste — Paramètres",
+ "containers.SettingPage.listSettings.description": "Configurez les options de ce modèle",
+
+ "containers.SettingsPage.pluginHeaderDescription": "Configurez les paramètres par défaut de vos modèles",
+ "containers.SettingsPage.Block.generalSettings.description": "Configurez les options par défault de vos modèles",
+ "containers.SettingsPage.Block.generalSettings.title": "Général",
+
"components.AddFilterCTA.add": "Filtres",
"components.AddFilterCTA.hide": "Filtres",
- "components.FilterOptions.button.apply": "Appliquer",
+
+ "components.DraggableAttr.edit": "Clicquez pour modifier",
+
"components.FiltersPickWrapper.PluginHeader.actions.apply": "Appliquer",
"components.FiltersPickWrapper.PluginHeader.actions.clearAll": "Tout supprimer",
"components.FiltersPickWrapper.PluginHeader.description": "Définissez les conditions des filtres à appliquer",
"components.FiltersPickWrapper.PluginHeader.title.filter": "Filtres",
"components.FiltersPickWrapper.hide": "Fermer",
-
+
"components.Search.placeholder": "Rechercher une entrée...",
-
+
"components.TableDelete.entries.plural": "{number} entrées sélectionnées",
"components.TableDelete.entries.singular": "{number} entrée sélectionnée",
"components.TableDelete.delete": "Tout supprimer",
-
+
"components.TableEmpty.withFilters": "Aucun {contentType} n'a été trouvé avec ces filtres...",
"components.TableEmpty.withoutFilter": "Aucun {contentType} n'a été trouvé...",
"components.TableEmpty.withSearch": "Aucun {contentType} n'a été trouvé avec cette recherche ({search})...",
-
+
+ "components.FilterOptions.button.apply": "Appliquer",
"components.FilterOptions.FILTER_TYPES.=": "est",
"components.FilterOptions.FILTER_TYPES._ne": "n'est pas",
"components.FilterOptions.FILTER_TYPES._lt": "inférieur à ",
@@ -71,7 +84,19 @@
"error.validation.minSupMax": "Ne peut pas ĂŞtre plus grand",
"error.validation.json": "Le format JSON n'est pas respecté",
+ "form.Input.label": "Label",
+ "form.Input.label.inputDescription": "Cette valeur modifie celle du champs de la table",
+ "form.Input.search": "Autoriser la search",
+ "form.Input.search.field": "Autoriser la search sur ce champs",
+ "form.Input.filters": "Autoriser les filtres",
+ "form.Input.sort.field": "Autoriser le tri sur ce champs",
+ "form.Input.bulkActions": "Autoriser les actions groupées",
+ "form.Input.pageEntries": "Nombre d'entrées par page",
+ "form.Input.pageEntries.inputDescription": "Note: Vous pouvez modifier ces valeurs par modèle",
+ "form.Input.defaultSort": "Attribut de tri par défault",
+
"notification.error.relationship.fetch": "Une erreur est survenue en récupérant les relations.",
+ "notification.info.SettingPage.disableSort": "Vous devez avoir au moins un attribut de tri par défaut",
"success.record.delete": "Supprimé",
"success.record.save": "Sauvegardé",
@@ -80,5 +105,7 @@
"popUpWarning.button.confirm": "Confirmer",
"popUpWarning.title": "Confirmation requise",
"popUpWarning.bodyMessage.contentType.delete": "Êtes-vous sûr de vouloir supprimer cette entrée ?",
- "popUpWarning.bodyMessage.contentType.delete.all": "Êtes-vous sûr de vouloir supprimer ces entrées ?"
+ "popUpWarning.bodyMessage.contentType.delete.all": "Êtes-vous sûr de vouloir supprimer ces entrées ?",
+ "popUpWarning.warning.cancelAllSettings": "Êtes-vous sûr de vouloir vos modifications?",
+ "popUpWarning.warning.updateAllSettings": "Cela modifiera tous vos précédents paramètres."
}
diff --git a/packages/strapi-plugin-content-manager/config/functions/bootstrap.js b/packages/strapi-plugin-content-manager/config/functions/bootstrap.js
new file mode 100644
index 0000000000..0f5f3fdf3c
--- /dev/null
+++ b/packages/strapi-plugin-content-manager/config/functions/bootstrap.js
@@ -0,0 +1,243 @@
+const _ = require('lodash');
+const pluralize = require('pluralize');
+
+module.exports = async cb => {
+ const pickData = (model) => _.pick(model, [
+ 'info',
+ 'connection',
+ 'collectionName',
+ 'attributes',
+ 'identity',
+ 'globalId',
+ 'globalName',
+ 'orm',
+ 'loadedModel',
+ 'primaryKey',
+ 'associations'
+ ]);
+
+ const models = _.mapValues(strapi.models, pickData);
+ delete models['core_store'];
+ const pluginsModel = Object.keys(strapi.plugins).reduce((acc, current) => {
+ acc[current] = {
+ models: _.mapValues(strapi.plugins[current].models, pickData),
+ };
+
+ return acc;
+ }, {});
+
+ // Init schema
+ const schema = {
+ generalSettings: {
+ search: true,
+ filters: true,
+ bulkActions: true,
+ pageEntries: 10,
+ },
+ models: {
+ plugins: {},
+ },
+ };
+
+ const buildSchema = (model, name, plugin = false) => {
+ // Model data
+ const schemaModel = Object.assign({
+ label: _.upperFirst(name),
+ labelPlural: _.upperFirst(pluralize(name)),
+ orm: model.orm || 'mongoose',
+ search: true,
+ filters: true,
+ bulkActions: true,
+ pageEntries: 10,
+ defaultSort: model.primaryKey,
+ sort: 'ASC',
+ }, model);
+
+ // Fields (non relation)
+ schemaModel.fields = _.mapValues(_.pickBy(model.attributes, attribute =>
+ !attribute.model && !attribute.collection
+ ), (value, attribute) => ({
+ label: _.upperFirst(attribute),
+ description: '',
+ type: value.type || 'string',
+ }));
+
+ // Select fields displayed in list view
+ // schemaModel.list = _.slice(_.keys(schemaModel.fields), 0, 4);
+ schemaModel.listDisplay = Object.keys(schemaModel.fields)
+ // Construct Array of attr ex { type: 'string', label: 'Foo', name: 'Foo', description: '' }
+ // NOTE: Do we allow sort on boolean?
+ .map(attr => {
+ const attrType = schemaModel.fields[attr].type;
+ const sortable = attrType !== 'json' && attrType !== 'array';
+
+ return Object.assign(schemaModel.fields[attr], { name: attr, sortable, searchable: sortable });
+ })
+ // Retrieve only the fourth first items
+ .slice(0, 4);
+
+ schemaModel.listDisplay.splice(0, 0, {
+ name: model.primaryKey || 'id',
+ label: 'Id',
+ type: 'string',
+ sortable: true,
+ searchable: true,
+ });
+
+ if (model.associations) {
+ // Model relations
+ schemaModel.relations = model.associations.reduce((acc, current) => {
+ const displayedAttribute = current.plugin ?
+ _.get(pluginsModel, [current.plugin, 'models', current.model || current.collection, 'info', 'mainField']) ||
+ _.findKey(_.get(pluginsModel, [current.plugin, 'models', current.model || current.collection, 'attributes']), { type : 'string'}) ||
+ 'id' :
+ _.get(models, [current.model || current.collection, 'info', 'mainField']) ||
+ _.findKey(_.get(models, [current.model || current.collection, 'attributes']), { type : 'string'}) ||
+ 'id';
+
+ acc[current.alias] = {
+ ...current,
+ description: '',
+ displayedAttribute,
+ };
+
+ return acc;
+ }, {});
+ }
+
+ if (plugin) {
+ return _.set(schema.models.plugins, `${plugin}.${name}`, schemaModel);
+ }
+
+ // Set the formatted model to the schema
+ schema.models[name] = schemaModel;
+ };
+
+ _.forEach(pluginsModel, (plugin, pluginName) => {
+ _.forEach(plugin.models, (model, name) => {
+ buildSchema(model, name, pluginName);
+ });
+ });
+
+ // Generate schema for models.
+ _.forEach(models, (model, name) => {
+ buildSchema(model, name);
+ });
+
+ const pluginStore = strapi.store({
+ environment: '',
+ type: 'plugin',
+ name: 'content-manager'
+ });
+
+ const getApis = (data) => Object.keys(data).reduce((acc, curr) => {
+ if (data[curr].fields) {
+ return acc.concat([curr]);
+ }
+
+ if (curr === 'plugins') {
+ Object.keys(data[curr]).map(plugin => {
+ Object.keys(data[curr][plugin]).map(api => {
+ acc = acc.concat([`${curr}.${plugin}.${api}`]);
+ });
+ });
+ }
+
+ return acc;
+ }, []);
+
+ const getApisKeys = (data, sameArray) => sameArray.map(apiPath => {
+ const fields = Object.keys(_.get(data.models, apiPath.concat(['fields'])));
+
+ return fields.map(field => `${apiPath.join('.')}.fields.${field}`);
+ });
+
+ try {
+ const prevSchema = await pluginStore.get({ key: 'schema' });
+
+ if (!prevSchema) {
+ pluginStore.set({ key: 'schema', value: schema });
+ return cb();
+ }
+
+ const splitted = str => str.split('.');
+ const prevSchemaApis = getApis(prevSchema.models);
+ const schemaApis = getApis(schema.models);
+ const apisToAdd = schemaApis.filter(api => prevSchemaApis.indexOf(api) === -1).map(splitted);
+ const apisToRemove = prevSchemaApis.filter(api => schemaApis.indexOf(api) === -1).map(splitted);
+ const sameApis = schemaApis.filter(api => prevSchemaApis.indexOf(api) !== -1).map(splitted);
+ const schemaSameApisKeys = _.flattenDeep(getApisKeys(schema, sameApis));
+ const prevSchemaSameApisKeys = _.flattenDeep(getApisKeys(prevSchema, sameApis));
+ const sameApisAttrToAdd = schemaSameApisKeys.filter(attr => prevSchemaSameApisKeys.indexOf(attr) === -1).map(splitted);
+ const sameApisAttrToRemove = prevSchemaSameApisKeys.filter(attr => schemaSameApisKeys.indexOf(attr) === -1).map(splitted);
+
+ // Remove api
+ apisToRemove.map(apiPath => {
+ _.unset(prevSchema.models, apiPath);
+ });
+
+ // Remove API attribute
+ sameApisAttrToRemove.map(attrPath => {
+ // Check default sort and change it if needed
+ _.unset(prevSchema.models, attrPath);
+ const apiPath = attrPath.length > 3 ? _.take(attrPath, 3) : _.take(attrPath, 1);
+ const listDisplayPath = apiPath.concat('listDisplay');
+ const prevListDisplay = _.get(prevSchema.models, listDisplayPath);
+ const defaultSortPath = apiPath.concat('defaultSort');
+ const currentAttr = attrPath.slice(-1);
+
+ const defaultSort = _.get(prevSchema.models, defaultSortPath);
+
+ if (_.includes(currentAttr, defaultSort)) {
+ _.set(prevSchema.models, defaultSortPath, _.get(schema.models, defaultSortPath));
+ }
+
+ // Update the displayed fields
+ const updatedListDisplay = prevListDisplay.filter(obj => obj.name !== currentAttr.join());
+
+ if (updatedListDisplay.length === 0) {
+ // Update it with the one from the generaeted schema
+ _.set(prevSchema.models, listDisplayPath, _.get(schema.models, listDisplayPath, []));
+ } else {
+ _.set(prevSchema.models, listDisplayPath, updatedListDisplay);
+ }
+ });
+
+ // Add API
+ apisToAdd.map(apiPath => {
+ const api = _.get(schema.models, apiPath);
+ const { search, filters, bulkActions, pageEntries } = _.get(prevSchema, 'generalSettings');
+
+ _.set(api, 'filters', filters);
+ _.set(api, 'search', search);
+ _.set(api, 'bulkActions', bulkActions);
+ _.set(api, 'pageEntries', pageEntries);
+ _.set(prevSchema.models, apiPath, api);
+ });
+
+ // Add attribute to existing API
+ sameApisAttrToAdd.map(attrPath => {
+ const attr = _.get(schema.models, attrPath);
+ _.set(prevSchema.models, attrPath, attr);
+ });
+
+
+ // Update other keys
+ sameApis.map(apiPath => {
+ const keysToUpdate = ['relations', 'loadedModel', 'associations', 'attributes'].map(key => apiPath.concat(key));
+
+ keysToUpdate.map(keyPath => {
+ const newValue = _.get(schema.models, keyPath);
+
+ _.set(prevSchema.models, keyPath, newValue);
+ });
+ });
+
+ await pluginStore.set({ key: 'schema', value: prevSchema });
+
+ } catch(err) {
+ console.log('error', err);
+ }
+
+ cb();
+};
\ No newline at end of file
diff --git a/packages/strapi-plugin-content-manager/config/layout.json b/packages/strapi-plugin-content-manager/config/layout.json
new file mode 100644
index 0000000000..9e26dfeeb6
--- /dev/null
+++ b/packages/strapi-plugin-content-manager/config/layout.json
@@ -0,0 +1 @@
+{}
\ No newline at end of file
diff --git a/packages/strapi-plugin-content-manager/config/routes.json b/packages/strapi-plugin-content-manager/config/routes.json
index 9b057fa5a5..5d63804036 100755
--- a/packages/strapi-plugin-content-manager/config/routes.json
+++ b/packages/strapi-plugin-content-manager/config/routes.json
@@ -32,6 +32,14 @@
"policies": ["routing"]
}
},
+ {
+ "method": "PUT",
+ "path": "/models",
+ "handler": "ContentManager.updateSettings",
+ "config": {
+ "policies": ["routing"]
+ }
+ },
{
"method": "GET",
"path": "/explorer/:model/:id",
diff --git a/packages/strapi-plugin-content-manager/controllers/ContentManager.js b/packages/strapi-plugin-content-manager/controllers/ContentManager.js
index 3bb5684dd7..1238ad7fbf 100755
--- a/packages/strapi-plugin-content-manager/controllers/ContentManager.js
+++ b/packages/strapi-plugin-content-manager/controllers/ContentManager.js
@@ -14,32 +14,15 @@ module.exports = {
},
models: async ctx => {
- const pickData = (model) => _.pick(model, [
- 'info',
- 'connection',
- 'collectionName',
- 'attributes',
- 'identity',
- 'globalId',
- 'globalName',
- 'orm',
- 'loadedModel',
- 'primaryKey',
- 'associations'
- ]);
-
- const models = _.mapValues(strapi.models, pickData);
- delete models['core_store'];
+ const pluginsStore = strapi.store({
+ environment: '',
+ type: 'plugin',
+ name: 'content-manager',
+ });
+ const models = await pluginsStore.get({ key: 'schema' });
ctx.body = {
models,
- plugins: Object.keys(strapi.plugins).reduce((acc, current) => {
- acc[current] = {
- models: _.mapValues(strapi.plugins[current].models, pickData)
- };
-
- return acc;
- }, {})
};
},
@@ -105,6 +88,18 @@ module.exports = {
}
},
+ updateSettings: async ctx => {
+ const { schema } = ctx.request.body;
+ const pluginStore = strapi.store({
+ environment: '',
+ type: 'plugin',
+ name: 'content-manager'
+ });
+ await pluginStore.set({ key: 'schema', value: schema });
+
+ return ctx.body = { ok: true };
+ },
+
delete: async ctx => {
ctx.body = await strapi.plugins['content-manager'].services['contentmanager'].delete(ctx.params, ctx.request.query);
},
diff --git a/packages/strapi-plugin-content-manager/package.json b/packages/strapi-plugin-content-manager/package.json
index 264d4c66bb..28bd99b5c4 100755
--- a/packages/strapi-plugin-content-manager/package.json
+++ b/packages/strapi-plugin-content-manager/package.json
@@ -22,7 +22,12 @@
"prepublishOnly": "IS_MONOREPO=true npm run build"
},
"devDependencies": {
- "react-select": "^1.0.0-rc.5",
+ "codemirror": "^5.39.0",
+ "draft-js": "^0.10.5",
+ "react-dnd": "^5.0.0",
+ "react-dnd-html5-backend": "^5.0.1",
+ "react-select": "^1.2.1",
+ "showdown": "^1.8.6",
"strapi-helper-plugin": "3.0.0-alpha.12.7.1"
},
"author": {
diff --git a/packages/strapi-plugin-content-manager/test/index.test.js b/packages/strapi-plugin-content-manager/test/index.test.js
index 0280307f05..74194bb832 100644
--- a/packages/strapi-plugin-content-manager/test/index.test.js
+++ b/packages/strapi-plugin-content-manager/test/index.test.js
@@ -1,4 +1,5 @@
// Helpers.
+const {login} = require('../../../test/helpers/auth');
const form = require('../../../test/helpers/generators');
const restart = require('../../../test/helpers/restart');
const rq = require('../../../test/helpers/request');
@@ -14,18 +15,11 @@ let data;
describe('App setup auth', () => {
test(
- 'Register admin user',
+ 'Login admin user',
async () => {
- const body = await rq({
- url: `/auth/local/register`,
- method: 'POST',
- body: {
- username: 'admin',
- email: 'admin@strapi.io',
- password: 'pcw123'
- },
- json: true
- });
+ await restart(rq);
+
+ const body = await login();
rq.defaults({
headers: {
@@ -881,8 +875,12 @@ describe('Test oneWay relation (reference - tag) with Content Manager', () => {
json: true
});
- if (Object.keys(referenceToGet.tag).length == 0) {
- referenceToGet.tag = null;
+ try {
+ if (Object.keys(referenceToGet.tag).length == 0) {
+ referenceToGet.tag = null;
+ }
+ } catch(err) {
+ // Silent
}
expect(referenceToGet.tag).toBe(null);
@@ -925,4 +923,14 @@ describe('Delete test APIs', () => {
});
}
);
+ test(
+ 'Delete reference API',
+ async () => {
+ await rq({
+ url: `/content-type-builder/models/reference`,
+ method: 'DELETE',
+ json: true
+ });
+ }
+ );
});
diff --git a/packages/strapi-plugin-content-type-builder/admin/src/injectedComponents.js b/packages/strapi-plugin-content-type-builder/admin/src/injectedComponents.js
new file mode 100644
index 0000000000..109fa8b38c
--- /dev/null
+++ b/packages/strapi-plugin-content-type-builder/admin/src/injectedComponents.js
@@ -0,0 +1 @@
+export default [];
\ No newline at end of file
diff --git a/packages/strapi-plugin-content-type-builder/controllers/ContentTypeBuilder.js b/packages/strapi-plugin-content-type-builder/controllers/ContentTypeBuilder.js
index ba08e31551..0e8e7ac4da 100755
--- a/packages/strapi-plugin-content-type-builder/controllers/ContentTypeBuilder.js
+++ b/packages/strapi-plugin-content-type-builder/controllers/ContentTypeBuilder.js
@@ -207,7 +207,7 @@ module.exports = {
return ctx.badRequest(null, [{ messages: [{ id: 'Connection doesn\'t exist' }] }]);
}
- if (connector === 'strapi-bookshelf') {
+ if (connector === 'strapi-hook-bookshelf') {
try {
const tableExists = await strapi.connections[connection].schema.hasTable(model);
diff --git a/packages/strapi-plugin-content-type-builder/services/ContentTypeBuilder.js b/packages/strapi-plugin-content-type-builder/services/ContentTypeBuilder.js
index eebcc5d686..4d0a13417a 100755
--- a/packages/strapi-plugin-content-type-builder/services/ContentTypeBuilder.js
+++ b/packages/strapi-plugin-content-type-builder/services/ContentTypeBuilder.js
@@ -117,7 +117,7 @@ module.exports = {
},
generateAPI: (name, description, connection, collectionName, attributes) => {
- const template = _.get(strapi.config.currentEnvironment, `database.connections.${connection}.connector`, 'strapi-mongoose').split('-')[1];
+ const template = _.get(strapi.config.currentEnvironment, `database.connections.${connection}.connector`, 'strapi-hook-mongoose').split('-')[2];
return new Promise((resolve, reject) => {
const scope = {
diff --git a/packages/strapi-plugin-email/admin/src/injectedComponents.js b/packages/strapi-plugin-email/admin/src/injectedComponents.js
new file mode 100644
index 0000000000..109fa8b38c
--- /dev/null
+++ b/packages/strapi-plugin-email/admin/src/injectedComponents.js
@@ -0,0 +1 @@
+export default [];
\ No newline at end of file
diff --git a/packages/strapi-plugin-graphql/package.json b/packages/strapi-plugin-graphql/package.json
index 17dd3cfbc8..bd3731a2a2 100644
--- a/packages/strapi-plugin-graphql/package.json
+++ b/packages/strapi-plugin-graphql/package.json
@@ -27,7 +27,8 @@
"graphql-depth-limit": "^1.1.0",
"graphql-playground-middleware-koa": "^1.6.1",
"graphql-tools": "^2.23.1",
- "graphql-type-json": "^0.2.0",
+ "graphql-type-json": "^0.2.1",
+ "graphql-type-datetime": "^0.2.2",
"pluralize": "^7.0.0",
"strapi-utils": "3.0.0-alpha.12.7.1"
},
diff --git a/packages/strapi-plugin-graphql/services/GraphQL.js b/packages/strapi-plugin-graphql/services/GraphQL.js
index f5ba06b29f..eb911fae2b 100644
--- a/packages/strapi-plugin-graphql/services/GraphQL.js
+++ b/packages/strapi-plugin-graphql/services/GraphQL.js
@@ -14,6 +14,7 @@ const pluralize = require('pluralize');
const graphql = require('graphql');
const { makeExecutableSchema } = require('graphql-tools');
const GraphQLJSON = require('graphql-type-json');
+const GraphQLDateTime = require('graphql-type-datetime');
const policyUtils = require('strapi-utils').policy;
module.exports = {
@@ -170,6 +171,12 @@ module.exports = {
case 'json':
type = 'JSON';
break;
+ case 'time':
+ case 'date':
+ case 'datetime':
+ case 'timestamp':
+ type = 'DateTime';
+ break;
case 'enumeration':
type = this.convertEnumType(definition, modelName, attributeName);
break;
@@ -453,8 +460,8 @@ module.exports = {
// Add timestamps attributes.
if (_.get(model, 'options.timestamps') === true) {
Object.assign(initialState, {
- createdAt: 'String!',
- updatedAt: 'String!'
+ createdAt: 'DateTime!',
+ updatedAt: 'DateTime!'
});
Object.assign(acc.resolver[globalId], {
@@ -780,10 +787,11 @@ module.exports = {
addCustomScalar: (resolvers) => {
Object.assign(resolvers, {
- JSON: GraphQLJSON
+ JSON: GraphQLJSON,
+ DateTime: GraphQLDateTime,
});
- return 'scalar JSON';
+ return 'scalar JSON \n scalar DateTime';
},
/**
diff --git a/packages/strapi-plugin-settings-manager/admin/src/injectedComponents.js b/packages/strapi-plugin-settings-manager/admin/src/injectedComponents.js
new file mode 100644
index 0000000000..109fa8b38c
--- /dev/null
+++ b/packages/strapi-plugin-settings-manager/admin/src/injectedComponents.js
@@ -0,0 +1 @@
+export default [];
\ No newline at end of file
diff --git a/packages/strapi-plugin-settings-manager/services/SettingsManager.js b/packages/strapi-plugin-settings-manager/services/SettingsManager.js
index 530b272d3b..7e104de4e9 100755
--- a/packages/strapi-plugin-settings-manager/services/SettingsManager.js
+++ b/packages/strapi-plugin-settings-manager/services/SettingsManager.js
@@ -650,9 +650,9 @@ module.exports = {
const redisClients = ['redis'];
let connector;
- if (_.indexOf(bookshelfClients, client) !== -1) connector = 'strapi-bookshelf';
- if (_.indexOf(mongooseClients, client) !== -1) connector = 'strapi-mongoose';
- if (_.indexOf(redisClients, client) !== -1) connector = 'strapi-redis';
+ if (_.indexOf(bookshelfClients, client) !== -1) connector = 'strapi-hook-bookshelf';
+ if (_.indexOf(mongooseClients, client) !== -1) connector = 'strapi-hook-mongoose';
+ if (_.indexOf(redisClients, client) !== -1) connector = 'strapi-hook-redis';
return connector;
},
@@ -911,7 +911,7 @@ module.exports = {
},
cleanDependency: (env, config) => {
- const availableConnectors = ['strapi-mongoose', 'strapi-bookshelf', 'strapi-redis'];
+ const availableConnectors = ['strapi-hook-mongoose', 'strapi-hook-bookshelf', 'strapi-hook-redis'];
let usedConnectors = [];
const errors = [];
diff --git a/packages/strapi-plugin-upload/admin/src/injectedComponents.js b/packages/strapi-plugin-upload/admin/src/injectedComponents.js
new file mode 100644
index 0000000000..109fa8b38c
--- /dev/null
+++ b/packages/strapi-plugin-upload/admin/src/injectedComponents.js
@@ -0,0 +1 @@
+export default [];
\ No newline at end of file
diff --git a/packages/strapi-plugin-upload/config/queries/bookshelf.js b/packages/strapi-plugin-upload/config/queries/bookshelf.js
index a216816473..f5efcaabd4 100644
--- a/packages/strapi-plugin-upload/config/queries/bookshelf.js
+++ b/packages/strapi-plugin-upload/config/queries/bookshelf.js
@@ -3,8 +3,16 @@ const _ = require('lodash');
module.exports = {
find: async function (params = {}, populate) {
const records = await this.query(function(qb) {
- _.forEach(params.where, (where, key) => {
- qb.where(key, where[0].symbol, where[0].value);
+ Object.keys(params.where).forEach((key) => {
+ const where = params.where[key];
+
+ if (Array.isArray(where.value)) {
+ for (const value in where.value) {
+ qb[value ? 'where' : 'orWhere'](key, where.symbol, where.value[value]);
+ }
+ } else {
+ qb.where(key, where.symbol, where.value);
+ }
});
if (params.sort) {
diff --git a/packages/strapi-plugin-upload/services/Upload.js b/packages/strapi-plugin-upload/services/Upload.js
index cef660041e..1b28422278 100644
--- a/packages/strapi-plugin-upload/services/Upload.js
+++ b/packages/strapi-plugin-upload/services/Upload.js
@@ -101,6 +101,8 @@ module.exports = {
},
remove: async (params, config) => {
+ params.id = (params._id || params.id);
+
const file = await strapi.plugins['upload'].services.upload.fetch(params);
// get upload provider settings to configure the provider to use
@@ -116,7 +118,6 @@ module.exports = {
// Use Content Manager business logic to handle relation.
if (strapi.plugins['content-manager']) {
params.model = 'file';
- params.id = (params._id || params.id);
await strapi.plugins['content-manager'].services['contentmanager'].delete(params, {source: 'upload'});
}
diff --git a/packages/strapi-plugin-users-permissions/admin/src/injectedComponents.js b/packages/strapi-plugin-users-permissions/admin/src/injectedComponents.js
new file mode 100644
index 0000000000..109fa8b38c
--- /dev/null
+++ b/packages/strapi-plugin-users-permissions/admin/src/injectedComponents.js
@@ -0,0 +1 @@
+export default [];
\ No newline at end of file
diff --git a/packages/strapi-plugin-users-permissions/admin/src/translations/it.json b/packages/strapi-plugin-users-permissions/admin/src/translations/it.json
new file mode 100644
index 0000000000..9e26dfeeb6
--- /dev/null
+++ b/packages/strapi-plugin-users-permissions/admin/src/translations/it.json
@@ -0,0 +1 @@
+{}
\ No newline at end of file
diff --git a/packages/strapi-plugin-users-permissions/admin/src/translations/pt.json b/packages/strapi-plugin-users-permissions/admin/src/translations/pt.json
new file mode 100644
index 0000000000..9e26dfeeb6
--- /dev/null
+++ b/packages/strapi-plugin-users-permissions/admin/src/translations/pt.json
@@ -0,0 +1 @@
+{}
\ No newline at end of file
diff --git a/packages/strapi-plugin-users-permissions/controllers/UsersPermissions.js b/packages/strapi-plugin-users-permissions/controllers/UsersPermissions.js
index 568e33155e..bc8bec8133 100644
--- a/packages/strapi-plugin-users-permissions/controllers/UsersPermissions.js
+++ b/packages/strapi-plugin-users-permissions/controllers/UsersPermissions.js
@@ -237,7 +237,7 @@ module.exports = {
type: 'plugin',
name: 'users-permissions',
key: 'grant'
- }).set({value: ctx.request.body});
+ }).set({value: ctx.request.body.providers});
ctx.send({ ok: true });
}
diff --git a/packages/strapi-plugin-users-permissions/services/Providers.js b/packages/strapi-plugin-users-permissions/services/Providers.js
index a7f32886a1..b2ea84b495 100644
--- a/packages/strapi-plugin-users-permissions/services/Providers.js
+++ b/packages/strapi-plugin-users-permissions/services/Providers.js
@@ -46,7 +46,9 @@ exports.connect = (provider, query) => {
try {
const users = await strapi.query('user', 'users-permissions').find({
- email: profile.email
+ where: {
+ email: profile.email
+ }
});
const advanced = await strapi.store({
diff --git a/packages/strapi-plugin-users-permissions/services/UsersPermissions.js b/packages/strapi-plugin-users-permissions/services/UsersPermissions.js
index 4e5fc46f2f..8064aa2aff 100644
--- a/packages/strapi-plugin-users-permissions/services/UsersPermissions.js
+++ b/packages/strapi-plugin-users-permissions/services/UsersPermissions.js
@@ -199,7 +199,11 @@ module.exports = {
},
updatePermissions: async function (cb) {
- const actions = strapi.plugins['users-permissions'].config.actions || [];
+ // fetch all the current permissions from the database, and format them into an array of actions.
+ const databasePermissions = await strapi.query('permission', 'users-permissions').find();
+ const actions = databasePermissions
+ .map(permission => `${permission.type}.${permission.controller}.${permission.action}`);
+
// Aggregate first level actions.
const appActions = Object.keys(strapi.api || {}).reduce((acc, api) => {
@@ -232,7 +236,7 @@ module.exports = {
// Merge array into one.
const currentActions = appActions.concat(pluginsActions);
// Count permissions available.
- const permissions = await strapi.query('permission', 'users-permissions').count();
+ const permissions = databasePermissions.length;
// Compare to know if actions have been added or removed from controllers.
if (!_.isEqual(actions, currentActions) || permissions < 1) {
diff --git a/packages/strapi-utils/lib/knex.js b/packages/strapi-utils/lib/knex.js
index 03b4c8affa..66d4621aa2 100755
--- a/packages/strapi-utils/lib/knex.js
+++ b/packages/strapi-utils/lib/knex.js
@@ -21,10 +21,10 @@ module.exports = scope => {
// First, make sure the application we have access to
// the migration generator.
try {
- require.resolve(path.resolve(scope.rootPath, 'node_modules', 'strapi-knex'));
+ require.resolve(path.resolve(scope.rootPath, 'node_modules', 'strapi-hook-knex'));
} catch (err) {
console.error('Impossible to call the Knex migration tool.');
- console.error('You can install it with `$ npm install strapi-knex --save`.');
+ console.error('You can install it with `$ npm install strapi-hook-knex --save`.');
process.exit(1);
}
diff --git a/packages/strapi-utils/lib/models.js b/packages/strapi-utils/lib/models.js
old mode 100755
new mode 100644
index 4f416be13a..0cfebd0248
--- a/packages/strapi-utils/lib/models.js
+++ b/packages/strapi-utils/lib/models.js
@@ -457,11 +457,28 @@ module.exports = {
_.forEach(params, (value, key) => {
let result;
let formattedValue;
-
- // Check if the value if a valid candidate to be converted to a number value
- formattedValue = isNumeric(value)
- ? _.toNumber(value)
- : value;
+ let modelAttributes = models[model]['attributes'];
+ let fieldType;
+ // Get the field type to later check if it's a string before number conversion
+ if (modelAttributes[key]) {
+ fieldType = modelAttributes[key]['type'];
+ } else {
+ // Remove the filter keyword at the end
+ let splitKey = key.split('_').slice(0,-1);
+ splitKey = splitKey.join('_');
+
+ if (modelAttributes[splitKey]) {
+ fieldType = modelAttributes[splitKey]['type'];
+ }
+ }
+ // Check if the value is a valid candidate to be converted to a number value
+ if (fieldType !== 'string') {
+ formattedValue = isNumeric(value)
+ ? _.toNumber(value)
+ : value;
+ } else {
+ formattedValue = value;
+ }
if (_.includes(['_start', '_limit'], key)) {
result = convertor(formattedValue, key);
diff --git a/packages/strapi-utils/lib/packageManager.js b/packages/strapi-utils/lib/packageManager.js
index 37f518e940..ee8e657420 100644
--- a/packages/strapi-utils/lib/packageManager.js
+++ b/packages/strapi-utils/lib/packageManager.js
@@ -43,8 +43,8 @@ module.exports = {
}
try {
- const yarnData = watcher('yarn global ls');
- isNPM = includes(yarnData, 'strapi');
+ const yarnData = watcher('yarn global list');
+ isNPM = !includes(yarnData, 'strapi');
} catch(err) {
isNPM = true;
}
diff --git a/packages/strapi/bin/strapi-new.js b/packages/strapi/bin/strapi-new.js
index c939bccb83..6c475c28f7 100755
--- a/packages/strapi/bin/strapi-new.js
+++ b/packages/strapi/bin/strapi-new.js
@@ -63,7 +63,10 @@ module.exports = function (name, cliArguments) {
username: cliArguments.dbusername,
password: cliArguments.dbpassword
},
- options: {}
+ options: {
+ authenticationDatabase: cliArguments.dbauth,
+ ssl: cliArguments.dbssl
+ }
};
}
diff --git a/packages/strapi/bin/strapi.js b/packages/strapi/bin/strapi.js
index 7d497e2517..453a7705eb 100755
--- a/packages/strapi/bin/strapi.js
+++ b/packages/strapi/bin/strapi.js
@@ -59,6 +59,8 @@ program
.option('--dbname ', 'Database name')
.option('--dbusername ', 'Database username')
.option('--dbpassword ', 'Database password')
+ .option('--dbssl ', 'Database SSL')
+ .option('--dbauth ', 'Authentication Database')
.description('create a new application')
.action(require('./strapi-new'));
diff --git a/packages/strapi/lib/Strapi.js b/packages/strapi/lib/Strapi.js
index d5b7c44b21..691ed09908 100755
--- a/packages/strapi/lib/Strapi.js
+++ b/packages/strapi/lib/Strapi.js
@@ -233,6 +233,8 @@ class Strapi extends EventEmitter {
const reload = function() {
if (state.shouldReload === false) {
+ // Reset the reloading state
+ reload.isReloading = false;
return;
}
diff --git a/packages/strapi/lib/core/configurations.js b/packages/strapi/lib/core/configurations.js
index ae39c6b89c..c011683707 100755
--- a/packages/strapi/lib/core/configurations.js
+++ b/packages/strapi/lib/core/configurations.js
@@ -281,7 +281,7 @@ module.exports.app = async function() {
// Enable hooks and dependencies related to the connections.
for (let name in this.config.connections) {
const connection = this.config.connections[name];
- const connector = connection.connector.replace('strapi-', '');
+ const connector = connection.connector.replace('strapi-hook-', '');
enableHookNestedDependencies.call(this, connector, flattenHooksConfig);
}
@@ -304,25 +304,16 @@ module.exports.app = async function() {
this.config.hook.settings = Object.keys(this.hook).reduce((acc, current) => {
// Try to find the settings in the current environment, then in the main configurations.
- const currentSettings = flattenHooksConfig[current] || this.config[current];
+ const currentSettings = merge(get(cloneDeep(this.hook[current]), ['defaults', current], {}), flattenHooksConfig[current] || this.config.currentEnvironment[current] || this.config[current]);
+ acc[current] = !isObject(currentSettings) ? {} : currentSettings;
- if (isString(currentSettings)) {
- acc[current] = currentSettings;
- } else {
- acc[current] = !isObject(currentSettings) ? {} : currentSettings;
-
- if (this.hook[current].isPlugin) {
- acc[current].enabled = true;
- }
-
- if (!acc[current].hasOwnProperty('enabled')) {
- this.log.warn(`(hook:${current}) wasn't loaded due to missing key \`enabled\` in the configuration`);
- }
-
- // Ensure that enabled key exist by forcing to false.
- defaults(acc[current], { enabled : false });
+ if (!acc[current].hasOwnProperty('enabled')) {
+ this.log.warn(`(hook:${current}) wasn't loaded due to missing key \`enabled\` in the configuration`);
}
+ // Ensure that enabled key exist by forcing to false.
+ defaults(acc[current], { enabled : false });
+
return acc;
}, {});
@@ -333,7 +324,7 @@ module.exports.app = async function() {
const enableHookNestedDependencies = function (name, flattenHooksConfig, force = false) {
if (!this.hook[name]) {
- this.log.warn(`(hook:${name}) \`strapi-${name}\` is missing in your dependencies. Please run \`npm install strapi-${name}\``);
+ this.log.warn(`(hook:${name}) \`strapi-hook-${name}\` is missing in your dependencies. Please run \`npm install strapi-hook-${name}\``);
}
// Couldn't find configurations for this hook.
@@ -347,7 +338,7 @@ const enableHookNestedDependencies = function (name, flattenHooksConfig, force =
const connector = get(this.config.connections, models[model].connection, {}).connector;
if (connector) {
- return connector.replace('strapi-', '') === name;
+ return connector.replace('strapi-hook-', '') === name;
}
return false;
@@ -363,7 +354,7 @@ const enableHookNestedDependencies = function (name, flattenHooksConfig, force =
// Enabled dependencies.
if (get(this.hook, `${name}.dependencies`, []).length > 0) {
this.hook[name].dependencies.forEach(dependency => {
- enableHookNestedDependencies.call(this, dependency.replace('strapi-', ''), flattenHooksConfig, true);
+ enableHookNestedDependencies.call(this, dependency.replace('strapi-hook-', ''), flattenHooksConfig, true);
});
}
}
diff --git a/packages/strapi/lib/core/hooks.js b/packages/strapi/lib/core/hooks.js
index e03c210761..5416b503ed 100755
--- a/packages/strapi/lib/core/hooks.js
+++ b/packages/strapi/lib/core/hooks.js
@@ -2,46 +2,34 @@
// Dependencies.
const path = require('path');
-const fs = require('fs');
const glob = require('glob');
const { parallel } = require('async');
-const { upperFirst, lowerFirst, get } = require('lodash');
+const { endsWith, get } = require('lodash');
module.exports = function() {
this.hook = {};
return Promise.all([
new Promise((resolve, reject) => {
- const cwd = '';
+ const cwd = this.config.appPath;
// Load configurations.
- glob('./node_modules/strapi-*', {
- ignore: [
- './node_modules/strapi-admin',
- './node_modules/strapi-utils',
- './node_modules/strapi-generate*',
- './node_modules/strapi-plugin-*',
- './node_modules/strapi-helper-*',
- './node_modules/strapi-middleware-*',
- './node_modules/strapi-upload-*',
- './node_modules/strapi-email-*',
- './node_modules/strapi-lint'
- ],
- cwd: this.config.appPath
+ glob('./node_modules/*(strapi-hook-*)/*/*(index|defaults).*(js|json)', {
+ cwd
}, (err, files) => {
if (err) {
return reject(err);
}
- mountHooks.call(this, files, cwd)(resolve, reject);
+ mountHooks.call(this, files, cwd, 'node_modules')(resolve, reject);
});
}),
new Promise((resolve, reject) => {
- const cwd = 'hooks';
+ const cwd = path.resolve(this.config.appPath, 'hooks');
// Load configurations.
- glob('./*', {
- cwd: path.resolve(this.config.appPath, 'hooks')
+ glob('./*/*(index|defaults).*(js|json)', {
+ cwd
}, (err, files) => {
if (err) {
return reject(err);
@@ -54,59 +42,55 @@ module.exports = function() {
const cwd = path.resolve(this.config.appPath, 'plugins');
// Load configurations.
- glob('./*/hooks/*', {
+ glob('./*/hooks/*/*(index|defaults).*(js|json)', {
cwd
}, (err, files) => {
if (err) {
return reject(err);
}
- mountHooks.call(this, files, cwd, true)(resolve, reject);
+ mountHooks.call(this, files, cwd, 'plugins')(resolve, reject);
});
})
]);
};
-const mountHooks = function (files, cwd, isPlugin) {
+const mountHooks = function (files, cwd, source) {
return (resolve, reject) =>
parallel(
files.map(p => cb => {
- const extractStr = p
- .split('/')
- .pop()
- .replace(/^strapi(-|\.)/, '')
- .split('-');
+ const folders = p.replace(/^.\/node_modules\/strapi-hook-/, './')
+ .split('/');
+ const name = source === 'plugins' ? folders[folders.length - 2] : folders[1];
- const name = lowerFirst(
- extractStr.length === 1
- ? extractStr[0]
- : extractStr.map(p => upperFirst(p)).join('')
- );
+ this.hook[name] = this.hook[name] || {
+ loaded: false
+ };
- fs.readFile(path.resolve(this.config.appPath, cwd, p, 'package.json'), (err, content) => {
+ let dependencies = [];
+ if (source === 'node_modules') {
try {
- const pkg = isPlugin ? {} : JSON.parse(content);
-
- this.hook[name] = {
- isPlugin,
- loaded: false,
- identity: name,
- dependencies: get(pkg, 'strapi.dependencies') || []
- };
-
- // Lazy loading.
- Object.defineProperty(this.hook[name], 'load', {
- configurable: false,
- enumerable: true,
- get: () => require(path.resolve(this.config.appPath, cwd, p))
- });
-
- cb();
- } catch (e) {
- cb(e);
+ dependencies = get(require(path.resolve(this.config.appPath, 'node_modules', `strapi-hook-${name}`, 'package.json')), 'strapi.dependencies', []);
+ } catch(err) {
+ // Silent
}
+ }
- });
+ if (endsWith(p, 'index.js') && !this.hook[name].load) {
+ // Lazy loading.
+ Object.defineProperty(this.hook[name], 'load', {
+ configurable: false,
+ enumerable: true,
+ get: () => require(path.resolve(cwd, p)),
+ dependencies
+ });
+
+ this.hook[name].dependencies = dependencies;
+ } else if (endsWith(p, 'defaults.json')) {
+ this.hook[name].defaults = require(path.resolve(cwd, p));
+ }
+
+ cb();
}),
(err) => {
if (err) {
diff --git a/packages/strapi/lib/hooks/index.js b/packages/strapi/lib/hooks/index.js
index ae3d3e1a81..7305f316fc 100755
--- a/packages/strapi/lib/hooks/index.js
+++ b/packages/strapi/lib/hooks/index.js
@@ -6,39 +6,35 @@ const { after, includes, indexOf, drop, dropRight, uniq, defaultsDeep, get, set,
module.exports = async function() {
// Method to initialize hooks and emit an event.
const initialize = (module, hook) => (resolve, reject) => {
- if (typeof module === 'function') {
- let timeout = true;
+ let timeout = true;
- setTimeout(() => {
- if (timeout) {
- reject(`(hook:${hook}) takes too long to load`);
- }
- }, this.config.hook.timeout || 1000);
+ setTimeout(() => {
+ if (timeout) {
+ reject(`(hook:${hook}) takes too long to load`);
+ }
+ }, this.config.hook.timeout || 1000);
- const loadedModule = module(this);
+ const loadedModule = module(this);
- loadedModule.initialize.call(module, err => {
- timeout = false;
+ loadedModule.initialize.call(module, err => {
+ timeout = false;
- if (err) {
- this.emit('hook:' + hook + ':error');
+ if (err) {
+ this.emit('hook:' + hook + ':error');
- return reject(err);
- }
+ return reject(err);
+ }
- this.hook[hook].loaded = true;
+ this.hook[hook].loaded = true;
- this.hook[hook] = merge(this.hook[hook], loadedModule);
+ this.hook[hook] = merge(this.hook[hook], loadedModule);
- this.emit('hook:' + hook + ':loaded');
- // Remove listeners.
- this.removeAllListeners('hook:' + hook + ':loaded');
+ this.emit('hook:' + hook + ':loaded');
+ // Remove listeners.
+ this.removeAllListeners('hook:' + hook + ':loaded');
- resolve();
- });
- } else {
resolve();
- }
+ });
};
await Promise.all(
@@ -83,7 +79,7 @@ module.exports = async function() {
}
// Initialize array.
- let previousDependencies = this.hook[hook].dependencies.map(x => x.replace('strapi-', '')) || [];
+ let previousDependencies = this.hook[hook].dependencies || [];
// Add BEFORE middlewares to load and remove the current one
// to avoid that it waits itself.
diff --git a/scripts/lint.js b/scripts/lint.js
index 2124a61dc8..58e4acb764 100644
--- a/scripts/lint.js
+++ b/scripts/lint.js
@@ -35,13 +35,14 @@ const files = glob
.sync('**/*.js', { ignore: '**/node_modules/**' })
.filter(f => changedFiles.has(f))
.filter(
- package =>
+ package =>
!package.includes('README.md') &&
!package.includes('strapi-middleware-views') &&
!package.includes('strapi-lint') &&
!package.includes('strapi-plugin-settings-manager') &&
!package.includes('scripts') &&
- !package.includes('test')
+ !package.includes('test') &&
+ !package.includes('jest.config.js')
)
.map(file => {
const directoryArray = file.split('/');
diff --git a/scripts/setup.js b/scripts/setup.js
index 908a20391d..3d58c6d9c4 100755
--- a/scripts/setup.js
+++ b/scripts/setup.js
@@ -99,17 +99,17 @@ shell.cd('../strapi-generate-new');
watcher('', 'npm install ../strapi-utils');
watcher('📦 Linking strapi-generate-new', 'npm link');
-shell.cd('../strapi-mongoose');
+shell.cd('../strapi-hook-mongoose');
watcher('', 'npm install ../strapi-utils');
-watcher('📦 Linking strapi-mongoose...', 'npm link');
+watcher('📦 Linking strapi-hook-mongoose...', 'npm link');
-shell.cd('../strapi-knex');
-watcher('📦 Linking strapi-knex...', 'npm link');
+shell.cd('../strapi-hook-knex');
+watcher('📦 Linking strapi-hook-knex...', 'npm link');
-shell.cd('../strapi-bookshelf');
+shell.cd('../strapi-hook-bookshelf');
watcher('', 'npm install ../strapi-utils');
-watcher('', 'npm install ../strapi-knex');
-watcher('📦 Linking strapi-bookshelf...', 'npm link');
+watcher('', 'npm install ../strapi-hook-knex');
+watcher('📦 Linking strapi-hook-bookshelf...', 'npm link');
shell.cd('../strapi');
watcher('', 'npm install ../strapi-generate ../strapi-generate-admin ../strapi-generate-api ../strapi-generate-new ../strapi-generate-plugin ../strapi-generate-policy ../strapi-generate-service ../strapi-utils');
diff --git a/test/helpers/auth.js b/test/helpers/auth.js
new file mode 100644
index 0000000000..8b8f333499
--- /dev/null
+++ b/test/helpers/auth.js
@@ -0,0 +1,26 @@
+const rq = require('./request');
+
+const auth = {
+ username: 'admin',
+ email: 'admin@strapi.io',
+ password: 'pcw123'
+};
+
+module.exports = {
+ auth,
+ login: () => {
+ return new Promise(async (resolve) => {
+ const body = await rq({
+ url: `/auth/local`,
+ method: 'POST',
+ body: {
+ identifier: auth.email,
+ password: auth.password
+ },
+ json: true
+ });
+
+ resolve(body);
+ });
+ }
+};
diff --git a/test/index.test.js b/test/index.test.js
index 932f771690..0ec226a7ea 100644
--- a/test/index.test.js
+++ b/test/index.test.js
@@ -1,3 +1,6 @@
+const {auth} = require('./helpers/auth');
+const rq = require('./helpers/request');
+
describe('Initialize', () => {
test(
'Avoid failure',
@@ -5,4 +8,16 @@ describe('Initialize', () => {
expect(true).toBeTruthy();
}
);
+
+ test(
+ 'Register admin user',
+ async () => {
+ await rq({
+ url: `/auth/local/register`,
+ method: 'POST',
+ body: auth,
+ json: true
+ });
+ }
+ );
});