mirror of
https://github.com/strapi/strapi.git
synced 2025-07-22 08:26:44 +00:00
Content manager edit layout and fields links list
This commit is contained in:
parent
c1ffbbfb57
commit
cbb53ac8d4
@ -20,7 +20,7 @@ import { bindActionCreators, compose } from 'redux';
|
||||
// Actions required for disabling and enabling the OverlayBlocker
|
||||
import {
|
||||
disableGlobalOverlayBlocker,
|
||||
enableGlobalOverlayBlocker
|
||||
enableGlobalOverlayBlocker,
|
||||
} from 'actions/overlayBlocker';
|
||||
import { pluginLoaded, updatePlugin } from 'containers/App/actions';
|
||||
import {
|
||||
@ -30,7 +30,7 @@ import {
|
||||
makeSelectIsAppLoading,
|
||||
makeSelectShowGlobalAppBlocker,
|
||||
selectHasUserPlugin,
|
||||
selectPlugins
|
||||
selectPlugins,
|
||||
} from 'containers/App/selectors';
|
||||
// Design
|
||||
import ComingSoonPage from 'containers/ComingSoonPage';
|
||||
@ -60,7 +60,7 @@ import styles from './styles.scss';
|
||||
|
||||
const PLUGINS_TO_BLOCK_PRODUCTION = [
|
||||
'content-type-builder',
|
||||
'settings-manager'
|
||||
'settings-manager',
|
||||
];
|
||||
|
||||
export class AdminPage extends React.Component {
|
||||
@ -71,7 +71,7 @@ export class AdminPage extends React.Component {
|
||||
disableGlobalOverlayBlocker: this.props.disableGlobalOverlayBlocker,
|
||||
enableGlobalOverlayBlocker: this.props.enableGlobalOverlayBlocker,
|
||||
plugins: this.props.plugins,
|
||||
updatePlugin: this.props.updatePlugin
|
||||
updatePlugin: this.props.updatePlugin,
|
||||
});
|
||||
|
||||
componentDidMount() {
|
||||
@ -84,7 +84,7 @@ export class AdminPage extends React.Component {
|
||||
const {
|
||||
adminPage: { uuid },
|
||||
location: { pathname },
|
||||
plugins
|
||||
plugins,
|
||||
} = this.props;
|
||||
|
||||
if (prevProps.location.pathname !== pathname) {
|
||||
@ -206,7 +206,7 @@ export class AdminPage extends React.Component {
|
||||
showLoading = () => {
|
||||
const {
|
||||
isAppLoading,
|
||||
adminPage: { isLoading }
|
||||
adminPage: { isLoading },
|
||||
} = this.props;
|
||||
|
||||
return (
|
||||
@ -219,7 +219,7 @@ export class AdminPage extends React.Component {
|
||||
retrievePlugins = () => {
|
||||
const {
|
||||
adminPage: { currentEnvironment },
|
||||
plugins
|
||||
plugins,
|
||||
} = this.props;
|
||||
|
||||
if (currentEnvironment === 'production') {
|
||||
@ -290,11 +290,11 @@ AdminPage.childContextTypes = {
|
||||
disableGlobalOverlayBlocker: PropTypes.func,
|
||||
enableGlobalOverlayBlocker: PropTypes.func,
|
||||
plugins: PropTypes.object,
|
||||
updatePlugin: PropTypes.func
|
||||
updatePlugin: PropTypes.func,
|
||||
};
|
||||
|
||||
AdminPage.contextTypes = {
|
||||
router: PropTypes.object.isRequired
|
||||
router: PropTypes.object.isRequired,
|
||||
};
|
||||
|
||||
AdminPage.defaultProps = {
|
||||
@ -302,7 +302,7 @@ AdminPage.defaultProps = {
|
||||
appPlugins: [],
|
||||
hasUserPlugin: true,
|
||||
isAppLoading: true,
|
||||
overlayBlockerData: {}
|
||||
overlayBlockerData: {},
|
||||
};
|
||||
|
||||
AdminPage.propTypes = {
|
||||
@ -320,7 +320,7 @@ AdminPage.propTypes = {
|
||||
pluginLoaded: PropTypes.func.isRequired,
|
||||
plugins: PropTypes.object.isRequired,
|
||||
showGlobalAppBlocker: PropTypes.bool.isRequired,
|
||||
updatePlugin: PropTypes.func.isRequired
|
||||
updatePlugin: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
@ -331,7 +331,7 @@ const mapStateToProps = createStructuredSelector({
|
||||
hasUserPlugin: selectHasUserPlugin(),
|
||||
isAppLoading: makeSelectIsAppLoading(),
|
||||
plugins: selectPlugins(),
|
||||
showGlobalAppBlocker: makeSelectShowGlobalAppBlocker()
|
||||
showGlobalAppBlocker: makeSelectShowGlobalAppBlocker(),
|
||||
});
|
||||
|
||||
function mapDispatchToProps(dispatch) {
|
||||
@ -341,7 +341,7 @@ function mapDispatchToProps(dispatch) {
|
||||
enableGlobalOverlayBlocker,
|
||||
getAdminData,
|
||||
pluginLoaded,
|
||||
updatePlugin
|
||||
updatePlugin,
|
||||
},
|
||||
dispatch
|
||||
);
|
||||
|
@ -23,12 +23,12 @@ import {
|
||||
makeSelectCurrentEnv,
|
||||
makeSelectPluginDeleteAction,
|
||||
makeSelectPlugins,
|
||||
makeSelectIsLoading
|
||||
makeSelectIsLoading,
|
||||
} from './selectors';
|
||||
import {
|
||||
getPlugins,
|
||||
onDeletePluginClick,
|
||||
onDeletePluginConfirm
|
||||
onDeletePluginConfirm,
|
||||
} from './actions';
|
||||
import reducer from './reducer';
|
||||
import saga from './saga';
|
||||
@ -37,7 +37,7 @@ import styles from './styles.scss';
|
||||
export class ListPluginsPage extends React.Component {
|
||||
// eslint-disable-line react/prefer-stateless-function
|
||||
getChildContext = () => ({
|
||||
currentEnvironment: this.props.currentEnvironment
|
||||
currentEnvironment: this.props.currentEnvironment,
|
||||
});
|
||||
|
||||
componentDidMount() {
|
||||
@ -64,10 +64,10 @@ export class ListPluginsPage extends React.Component {
|
||||
<div className={cn('container-fluid', styles.listPluginsPage)}>
|
||||
<PluginHeader
|
||||
title={{
|
||||
id: 'app.components.ListPluginsPage.title'
|
||||
id: 'app.components.ListPluginsPage.title',
|
||||
}}
|
||||
description={{
|
||||
id: 'app.components.ListPluginsPage.description'
|
||||
id: 'app.components.ListPluginsPage.description',
|
||||
}}
|
||||
actions={[]}
|
||||
/>
|
||||
@ -85,7 +85,7 @@ export class ListPluginsPage extends React.Component {
|
||||
}
|
||||
|
||||
ListPluginsPage.childContextTypes = {
|
||||
currentEnvironment: PropTypes.string.isRequired
|
||||
currentEnvironment: PropTypes.string.isRequired,
|
||||
};
|
||||
|
||||
ListPluginsPage.contextTypes = {};
|
||||
@ -98,14 +98,14 @@ ListPluginsPage.propTypes = {
|
||||
onDeletePluginClick: PropTypes.func.isRequired,
|
||||
onDeletePluginConfirm: PropTypes.func.isRequired,
|
||||
pluginActionSucceeded: PropTypes.bool.isRequired,
|
||||
plugins: PropTypes.object.isRequired
|
||||
plugins: PropTypes.object.isRequired,
|
||||
};
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
currentEnvironment: makeSelectCurrentEnv(),
|
||||
isLoading: makeSelectIsLoading(),
|
||||
pluginActionSucceeded: makeSelectPluginDeleteAction(),
|
||||
plugins: makeSelectPlugins()
|
||||
plugins: makeSelectPlugins(),
|
||||
});
|
||||
|
||||
function mapDispatchToProps(dispatch) {
|
||||
@ -113,15 +113,15 @@ function mapDispatchToProps(dispatch) {
|
||||
{
|
||||
getPlugins,
|
||||
onDeletePluginClick,
|
||||
onDeletePluginConfirm
|
||||
onDeletePluginConfirm,
|
||||
},
|
||||
dispatch
|
||||
dispatch,
|
||||
);
|
||||
}
|
||||
|
||||
const withConnect = connect(
|
||||
mapStateToProps,
|
||||
mapDispatchToProps
|
||||
mapDispatchToProps,
|
||||
);
|
||||
|
||||
/* Remove this line if the container doesn't have a route and
|
||||
@ -137,5 +137,5 @@ const withSaga = injectSaga({ key: 'listPluginsPage', saga });
|
||||
export default compose(
|
||||
withReducer,
|
||||
withSaga,
|
||||
withConnect
|
||||
withConnect,
|
||||
)(ListPluginsPage);
|
||||
|
@ -18,7 +18,7 @@ import { translationMessages } from './i18n';
|
||||
|
||||
const LoadableApp = Loadable({
|
||||
loader: () => import('containers/App'),
|
||||
loading: LoadingIndicatorPage
|
||||
loading: LoadingIndicatorPage,
|
||||
});
|
||||
|
||||
const tryRequireRoot = source => {
|
||||
@ -70,7 +70,7 @@ function Comp(props) {
|
||||
|
||||
if (window.Cypress) {
|
||||
window.__store__ = Object.assign(window.__store__ || {}, {
|
||||
[pluginId]: store
|
||||
[pluginId]: store,
|
||||
});
|
||||
}
|
||||
|
||||
@ -105,7 +105,7 @@ strapi.registerPlugin({
|
||||
name: pluginPkg.strapi.name,
|
||||
pluginRequirements,
|
||||
preventComponentRendering: false,
|
||||
translationMessages
|
||||
translationMessages,
|
||||
});
|
||||
|
||||
// Export store
|
||||
|
@ -0,0 +1 @@
|
||||
<svg width="13" height="11" xmlns="http://www.w3.org/2000/svg"><g fill="#4B515A" fill-rule="evenodd"><rect x="4" y="8" width="9" height="3" rx="1.5"/><rect y="4" width="9" height="3" rx="1.5"/><rect x="3" width="9" height="3" rx="1.5"/></g></svg>
|
After Width: | Height: | Size: 246 B |
@ -0,0 +1 @@
|
||||
<svg width="13" height="11" xmlns="http://www.w3.org/2000/svg"><g fill="#007EFF" fill-rule="evenodd"><rect x="4" y="8" width="9" height="3" rx="1.5"/><rect y="4" width="9" height="3" rx="1.5"/><rect x="3" width="9" height="3" rx="1.5"/></g></svg>
|
After Width: | Height: | Size: 246 B |
@ -0,0 +1,62 @@
|
||||
/**
|
||||
*
|
||||
* NavLink
|
||||
*
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { Link } from 'react-router-dom';
|
||||
import PropTypes from 'prop-types';
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
import { isObject } from 'lodash';
|
||||
import cn from 'classnames';
|
||||
|
||||
import styles from './styles.scss';
|
||||
|
||||
function NavLink(props) {
|
||||
let content = props.children;
|
||||
|
||||
if (typeof props.message === 'string') {
|
||||
content = props.message;
|
||||
}
|
||||
|
||||
if (isObject(props.message) && props.message.id) {
|
||||
content = <FormattedMessage id={props.message.id} />;
|
||||
}
|
||||
|
||||
let icon = <i className={`fa ${props.icon}`} />;
|
||||
|
||||
if (props.icon === 'layout') {
|
||||
icon = <i className={cn(styles.fa, styles.layout)} />;
|
||||
}
|
||||
|
||||
return (
|
||||
<Link to={props.url} className={cn(styles.navLink)}>
|
||||
{icon}
|
||||
{content}
|
||||
</Link>
|
||||
);
|
||||
}
|
||||
|
||||
NavLink.defaultProps = {
|
||||
children: '',
|
||||
icon: '',
|
||||
message: '',
|
||||
url: '',
|
||||
};
|
||||
|
||||
NavLink.propTypes = {
|
||||
children: PropTypes.node,
|
||||
icon: PropTypes.string,
|
||||
message: PropTypes.oneOfType([
|
||||
PropTypes.func,
|
||||
PropTypes.string,
|
||||
PropTypes.shape({
|
||||
id: PropTypes.string,
|
||||
params: PropTypes.object,
|
||||
}),
|
||||
]),
|
||||
url: PropTypes.string,
|
||||
};
|
||||
|
||||
export default NavLink;
|
@ -0,0 +1,28 @@
|
||||
a.navLink {
|
||||
text-decoration: none;
|
||||
width: 100%;
|
||||
display: block;
|
||||
span, i {
|
||||
color: #333740;
|
||||
}
|
||||
span {
|
||||
font-size: 13px;
|
||||
}
|
||||
i {
|
||||
margin-right: 10px;
|
||||
width: 13px;
|
||||
height: 11px;
|
||||
}
|
||||
.fa.layout {
|
||||
background-image: url('../../assets/icons/icon_layout.svg');
|
||||
display: inline-block;
|
||||
}
|
||||
&:hover {
|
||||
span, i {
|
||||
color: #007EFF;
|
||||
}
|
||||
.fa.layout {
|
||||
background-image: url('../../assets/icons/icon_layout_hover.svg');
|
||||
}
|
||||
}
|
||||
}
|
@ -2,23 +2,26 @@ import { map, omit } from 'lodash';
|
||||
import request from 'utils/request';
|
||||
|
||||
// This method is executed before the load of the plugin
|
||||
const bootstrap = (plugin) => new Promise((resolve, reject) => {
|
||||
request('/content-manager/models', { method: 'GET' })
|
||||
.then(models => {
|
||||
const menu = [{
|
||||
name: 'Content Types',
|
||||
links: map(omit(models.models.models, 'plugins'), (model, key) => ({
|
||||
label: model.labelPlural || model.label || key,
|
||||
destination: key,
|
||||
})),
|
||||
}];
|
||||
plugin.leftMenuSections = menu;
|
||||
resolve(plugin);
|
||||
})
|
||||
.catch(e => {
|
||||
strapi.notification.error('content-manager.error.model.fetch');
|
||||
reject(e);
|
||||
});
|
||||
});
|
||||
const bootstrap = plugin =>
|
||||
new Promise((resolve, reject) => {
|
||||
request('/content-manager/models', { method: 'GET' })
|
||||
.then(models => {
|
||||
const menu = [
|
||||
{
|
||||
name: 'Content Types',
|
||||
links: map(omit(models.models.models, 'plugins'), (model, key) => ({
|
||||
label: model.labelPlural || model.label || key,
|
||||
destination: key,
|
||||
})),
|
||||
},
|
||||
];
|
||||
plugin.leftMenuSections = menu;
|
||||
resolve(plugin);
|
||||
})
|
||||
.catch(e => {
|
||||
strapi.notification.error('content-manager.error.model.fetch');
|
||||
reject(e);
|
||||
});
|
||||
});
|
||||
|
||||
export default bootstrap;
|
||||
|
@ -7,7 +7,6 @@
|
||||
import React from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import { bindActionCreators, compose } from 'redux';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { createStructuredSelector } from 'reselect';
|
||||
import PropTypes from 'prop-types';
|
||||
import {
|
||||
@ -19,7 +18,7 @@ import {
|
||||
toNumber,
|
||||
toString,
|
||||
truncate,
|
||||
replace
|
||||
replace,
|
||||
} from 'lodash';
|
||||
import HTML5Backend from 'react-dnd-html5-backend';
|
||||
import { DragDropContext } from 'react-dnd';
|
||||
@ -32,7 +31,7 @@ import EmptyAttributesBlock from 'components/EmptyAttributesBlock';
|
||||
import LoadingIndicator from 'components/LoadingIndicator';
|
||||
import PluginHeader from 'components/PluginHeader';
|
||||
import PopUpWarning from 'components/PopUpWarning';
|
||||
import ListRow from 'components/ListRow';
|
||||
import NavLink from 'components/NavLink';
|
||||
// Plugin's components
|
||||
import CustomDragLayer from 'components/CustomDragLayer';
|
||||
import Edit from 'components/Edit';
|
||||
@ -59,13 +58,15 @@ import {
|
||||
resetProps,
|
||||
setFileRelations,
|
||||
setFormErrors,
|
||||
submit
|
||||
submit,
|
||||
} from './actions';
|
||||
import reducer from './reducer';
|
||||
import saga from './saga';
|
||||
import makeSelectEditPage from './selectors';
|
||||
import styles from './styles.scss';
|
||||
|
||||
const pluginId = 'content-manager';
|
||||
|
||||
export class EditPage extends React.Component {
|
||||
state = { showWarning: false, showWarningDelete: false };
|
||||
|
||||
@ -87,17 +88,17 @@ export class EditPage extends React.Component {
|
||||
includes(this.props.location.search, '?redirectUrl')
|
||||
) {
|
||||
const redirectUrl = this.props.location.search.split(
|
||||
'?redirectUrl='
|
||||
'?redirectUrl=',
|
||||
)[1];
|
||||
|
||||
this.props.history.push({
|
||||
pathname: redirectUrl.split('?')[0],
|
||||
search: redirectUrl.split('?')[1]
|
||||
search: redirectUrl.split('?')[1],
|
||||
});
|
||||
} else {
|
||||
this.props.history.push({
|
||||
pathname: replace(this.props.location.pathname, '/create', ''),
|
||||
search: `?source=${this.getSource()}`
|
||||
search: `?source=${this.getSource()}`,
|
||||
});
|
||||
}
|
||||
}
|
||||
@ -122,7 +123,7 @@ export class EditPage extends React.Component {
|
||||
getLayout = () =>
|
||||
bindLayout.call(
|
||||
this,
|
||||
get(this.props.schema, ['layout', this.getModelName()], {})
|
||||
get(this.props.schema, ['layout', this.getModelName()], {}),
|
||||
);
|
||||
|
||||
/**
|
||||
@ -135,9 +136,9 @@ export class EditPage extends React.Component {
|
||||
this.props.editPage.formValidations,
|
||||
[
|
||||
findIndex(this.props.editPage.formValidations, ['name', name]),
|
||||
'validations'
|
||||
'validations',
|
||||
],
|
||||
{}
|
||||
{},
|
||||
);
|
||||
|
||||
getDisplayedFields = () =>
|
||||
@ -153,7 +154,7 @@ export class EditPage extends React.Component {
|
||||
'models',
|
||||
'plugins',
|
||||
this.getSource(),
|
||||
this.getModelName()
|
||||
this.getModelName(),
|
||||
]);
|
||||
|
||||
/**
|
||||
@ -179,12 +180,12 @@ export class EditPage extends React.Component {
|
||||
* @return {Object}
|
||||
*/
|
||||
getSchema = () =>
|
||||
this.getSource() !== 'content-manager'
|
||||
this.getSource() !== pluginId
|
||||
? get(this.props.schema, [
|
||||
'models',
|
||||
'plugins',
|
||||
this.getSource(),
|
||||
this.getModelName()
|
||||
this.getModelName(),
|
||||
])
|
||||
: get(this.props.schema, ['models', this.getModelName()]);
|
||||
|
||||
@ -195,18 +196,18 @@ export class EditPage extends React.Component {
|
||||
const primaryKey = this.getModel().primaryKey;
|
||||
const {
|
||||
match: {
|
||||
params: { id }
|
||||
}
|
||||
params: { id },
|
||||
},
|
||||
} = this.props;
|
||||
const title = get(
|
||||
this.getSchema(),
|
||||
'editDisplay.displayedField',
|
||||
primaryKey
|
||||
primaryKey,
|
||||
);
|
||||
const valueToDisplay = get(
|
||||
this.props.editPage,
|
||||
['initialRecord', title],
|
||||
id
|
||||
id,
|
||||
);
|
||||
|
||||
return isEmpty(valueToDisplay)
|
||||
@ -220,6 +221,25 @@ export class EditPage extends React.Component {
|
||||
*/
|
||||
getSource = () => getQueryParameters(this.props.location.search, 'source');
|
||||
|
||||
/**
|
||||
* Get url base to create edit layout link
|
||||
* @type {String} url base
|
||||
*/
|
||||
getContentManagerBaseUrl = () => {
|
||||
let url = `/plugins/${pluginId}/ctm-configurations/edit-settings/`;
|
||||
if (this.getSource() === 'users-permissions') {
|
||||
url = `${url}plugins/${this.getSource()}/`;
|
||||
}
|
||||
|
||||
return url;
|
||||
};
|
||||
|
||||
/**
|
||||
* Access url base from injected component to create edit model link
|
||||
* @type {String} url base
|
||||
*/
|
||||
getContentTypeBuilderBaseUrl = () => '/plugins/content-type-builder/models/';
|
||||
|
||||
/**
|
||||
* Initialize component
|
||||
*/
|
||||
@ -229,7 +249,7 @@ export class EditPage extends React.Component {
|
||||
this.isCreating(),
|
||||
this.getSource(),
|
||||
this.getModelAttributes(),
|
||||
this.getDisplayedFields()
|
||||
this.getDisplayedFields(),
|
||||
);
|
||||
|
||||
if (!this.isCreating()) {
|
||||
@ -240,7 +260,7 @@ export class EditPage extends React.Component {
|
||||
|
||||
// Get all relations made with the upload plugin
|
||||
const fileRelations = Object.keys(
|
||||
get(this.getSchema(), 'relations', {})
|
||||
get(this.getSchema(), 'relations', {}),
|
||||
).reduce((acc, current) => {
|
||||
const association = get(this.getSchema(), ['relations', current], {});
|
||||
|
||||
@ -250,7 +270,7 @@ export class EditPage extends React.Component {
|
||||
) {
|
||||
const relation = {
|
||||
name: current,
|
||||
multiple: association.nature === 'manyToManyMorph'
|
||||
multiple: association.nature === 'manyToManyMorph',
|
||||
};
|
||||
|
||||
acc.push(relation);
|
||||
@ -265,7 +285,7 @@ export class EditPage extends React.Component {
|
||||
handleAddRelationItem = ({ key, value }) => {
|
||||
this.props.addRelationItem({
|
||||
key,
|
||||
value
|
||||
value,
|
||||
});
|
||||
};
|
||||
|
||||
@ -276,19 +296,19 @@ export class EditPage extends React.Component {
|
||||
return this.props.changeData({
|
||||
target: {
|
||||
name: `record.${target.name}`,
|
||||
value: defaultValue
|
||||
}
|
||||
value: defaultValue,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
const errorIndex = findIndex(this.props.editPage.formErrors, [
|
||||
'name',
|
||||
target.name
|
||||
target.name,
|
||||
]);
|
||||
const errors = inputValidations(
|
||||
target.value,
|
||||
this.getAttributeValidations(target.name),
|
||||
target.type
|
||||
target.type,
|
||||
);
|
||||
const formErrors = cloneDeep(this.props.editPage.formErrors);
|
||||
|
||||
@ -308,7 +328,7 @@ export class EditPage extends React.Component {
|
||||
// Check if date
|
||||
if (
|
||||
['float', 'integer', 'biginteger', 'decimal'].indexOf(
|
||||
get(this.getSchema(), ['fields', e.target.name, 'type'])
|
||||
get(this.getSchema(), ['fields', e.target.name, 'type']),
|
||||
) !== -1
|
||||
) {
|
||||
value = toNumber(e.target.value);
|
||||
@ -316,7 +336,7 @@ export class EditPage extends React.Component {
|
||||
|
||||
const target = {
|
||||
name: `record.${e.target.name}`,
|
||||
value
|
||||
value,
|
||||
};
|
||||
|
||||
this.props.changeData({ target });
|
||||
@ -336,14 +356,14 @@ export class EditPage extends React.Component {
|
||||
|
||||
handleGoBack = () => this.props.history.goBack();
|
||||
|
||||
handleRedirect = ({ model, id, source = 'content-manager' }) => {
|
||||
handleRedirect = ({ model, id, source = pluginId }) => {
|
||||
/* eslint-disable */
|
||||
switch (model) {
|
||||
case 'permission':
|
||||
case 'role':
|
||||
case 'file':
|
||||
// Exclude special models which are handled by plugins.
|
||||
if (source !== 'content-manager') {
|
||||
if (source !== pluginId) {
|
||||
break;
|
||||
}
|
||||
default:
|
||||
@ -355,8 +375,8 @@ export class EditPage extends React.Component {
|
||||
pathname,
|
||||
search: `?source=${source}&redirectURI=${generateRedirectURI({
|
||||
model,
|
||||
search: `?source=${source}`
|
||||
})}`
|
||||
search: `?source=${source}`,
|
||||
})}`,
|
||||
});
|
||||
}
|
||||
/* eslint-enable */
|
||||
@ -366,7 +386,7 @@ export class EditPage extends React.Component {
|
||||
e.preventDefault();
|
||||
const formErrors = checkFormValidity(
|
||||
this.generateFormFromRecord(),
|
||||
this.props.editPage.formValidations
|
||||
this.props.editPage.formValidations,
|
||||
);
|
||||
|
||||
if (isEmpty(formErrors)) {
|
||||
@ -376,24 +396,20 @@ export class EditPage extends React.Component {
|
||||
this.props.setFormErrors(formErrors);
|
||||
};
|
||||
|
||||
hasDisplayedRelations = () => {
|
||||
return this.getDisplayedRelations().length > 0;
|
||||
};
|
||||
|
||||
hasDisplayedDevSection = () => {
|
||||
return process.env.NODE_ENV === 'development';
|
||||
};
|
||||
|
||||
hasDisplayedRightSection = () => {
|
||||
return this.hasDisplayedRelations || this.hasDisplayedDevSection;
|
||||
};
|
||||
|
||||
hasDisplayedFields = () => {
|
||||
return get(this.getModel(), ['editDisplay', 'fields'], []).length > 0;
|
||||
};
|
||||
|
||||
isCreating = () => this.props.match.params.id === 'create';
|
||||
|
||||
/**
|
||||
* Check environment
|
||||
* @type {boolean} current env is dev
|
||||
*/
|
||||
isDevEnvironment = () => {
|
||||
return process.env.NODE_ENV === 'development';
|
||||
};
|
||||
|
||||
isRelationComponentNull = () =>
|
||||
Object.keys(get(this.getSchema(), 'relations', {})).filter(
|
||||
relation =>
|
||||
@ -401,7 +417,7 @@ export class EditPage extends React.Component {
|
||||
(!get(this.getSchema(), ['relations', relation, 'nature'], '')
|
||||
.toLowerCase()
|
||||
.includes('morph') ||
|
||||
!get(this.getSchema(), ['relations', relation, relation]))
|
||||
!get(this.getSchema(), ['relations', relation, relation])),
|
||||
).length === 0;
|
||||
|
||||
// NOTE: technical debt that needs to be redone
|
||||
@ -412,25 +428,43 @@ export class EditPage extends React.Component {
|
||||
return acc;
|
||||
}, {});
|
||||
|
||||
/**
|
||||
* Render the edit layout link
|
||||
* @type {NavLink}
|
||||
*/
|
||||
layoutLink = () => {
|
||||
// Retrieve URL
|
||||
const url = `${this.getContentManagerBaseUrl()}${this.getModelName()}`;
|
||||
// Link props to display
|
||||
const message = {
|
||||
message: {
|
||||
id: `${pluginId}.containers.Edit.Link.Layout`,
|
||||
},
|
||||
icon: 'layout',
|
||||
};
|
||||
|
||||
return <NavLink {...message} url={url} />;
|
||||
};
|
||||
|
||||
pluginHeaderActions = () => [
|
||||
{
|
||||
label: 'content-manager.containers.Edit.reset',
|
||||
label: `${pluginId}.containers.Edit.reset`,
|
||||
kind: 'secondary',
|
||||
onClick: this.toggle,
|
||||
type: 'button',
|
||||
disabled: this.showLoaders()
|
||||
disabled: this.showLoaders(),
|
||||
},
|
||||
{
|
||||
kind: 'primary',
|
||||
label: 'content-manager.containers.Edit.submit',
|
||||
label: `${pluginId}.containers.Edit.submit`,
|
||||
onClick: this.handleSubmit,
|
||||
type: 'submit',
|
||||
loader: this.props.editPage.showLoader,
|
||||
style: this.props.editPage.showLoader
|
||||
? { marginRight: '18px', flexGrow: 2 }
|
||||
: { flexGrow: 2 },
|
||||
disabled: this.showLoaders()
|
||||
}
|
||||
disabled: this.showLoaders(),
|
||||
},
|
||||
];
|
||||
|
||||
pluginHeaderSubActions = () => {
|
||||
@ -443,18 +477,67 @@ export class EditPage extends React.Component {
|
||||
kind: 'delete',
|
||||
onClick: this.toggleDelete,
|
||||
type: 'button',
|
||||
disabled: this.showLoaders()
|
||||
}
|
||||
disabled: this.showLoaders(),
|
||||
},
|
||||
];
|
||||
|
||||
return subActions;
|
||||
/* eslint-enable indent */
|
||||
};
|
||||
|
||||
/**
|
||||
* Retrieve external links from injected components
|
||||
* @type {Array} List of external links to display
|
||||
*/
|
||||
retrieveLinksContainerComponent = () => {
|
||||
// Should be retrieved from the global props (@soupette)
|
||||
const { plugins } = this.context;
|
||||
const appPlugins = plugins.toJS();
|
||||
const componentToInject = Object.keys(appPlugins).reduce((acc, current) => {
|
||||
// Retrieve injected compos from plugin
|
||||
// if compo can be injected in left.links area push the compo in the array
|
||||
const currentPlugin = appPlugins[current];
|
||||
const injectedComponents = get(currentPlugin, 'injectedComponents', []);
|
||||
|
||||
const compos = injectedComponents
|
||||
.filter(compo => {
|
||||
return (
|
||||
compo.plugin === `${pluginId}.editPage` &&
|
||||
compo.area === 'right.links'
|
||||
);
|
||||
})
|
||||
.map(compo => {
|
||||
const Component = compo.component;
|
||||
|
||||
return (
|
||||
<li key={compo.key}>
|
||||
<Component {...this} {...compo.props} />
|
||||
</li>
|
||||
);
|
||||
});
|
||||
|
||||
return [...acc, ...compos];
|
||||
}, []);
|
||||
|
||||
return componentToInject;
|
||||
};
|
||||
|
||||
shouldDisplayedRelations = () => {
|
||||
return this.getDisplayedRelations().length > 0;
|
||||
};
|
||||
|
||||
/**
|
||||
* Right section to display if needed
|
||||
* @type {boolean}
|
||||
*/
|
||||
shouldDisplayedRightSection = () => {
|
||||
return this.shouldDisplayedRelations() || this.isDevEnvironment();
|
||||
};
|
||||
|
||||
showLoaders = () => {
|
||||
const {
|
||||
editPage: { isLoading },
|
||||
schema: { layout }
|
||||
schema: { layout },
|
||||
} = this.props;
|
||||
|
||||
return (
|
||||
@ -468,72 +551,38 @@ export class EditPage extends React.Component {
|
||||
|
||||
toggleDelete = () =>
|
||||
this.setState(prevState => ({
|
||||
showWarningDelete: !prevState.showWarningDelete
|
||||
showWarningDelete: !prevState.showWarningDelete,
|
||||
}));
|
||||
|
||||
layoutLink = () => {
|
||||
const url = this.getContentManagerBaseUrl() + this.getModelName();
|
||||
/**
|
||||
* Render internal and external links
|
||||
* @type {Array} List of all links to display
|
||||
*/
|
||||
renderNavLinks = () => {
|
||||
// Add ctm link as list item to external links array to return the entire list
|
||||
let ctmLink = <li key={`${pluginId}.link`}>{this.layoutLink()}</li>;
|
||||
|
||||
return <Link to={url}>Configure the layout</Link>;
|
||||
};
|
||||
|
||||
getContentManagerBaseUrl = () => {
|
||||
if (this.getModelName() !== 'user') {
|
||||
return '/plugins/content-manager/ctm-configurations/edit-settings/';
|
||||
} else {
|
||||
return '/plugins/content-manager/ctm-configurations/edit-settings/plugins/users-permissions/';
|
||||
}
|
||||
};
|
||||
|
||||
getContentTypeBuilderBaseUrl = () => {
|
||||
return '/plugins/content-type-builder/models/';
|
||||
};
|
||||
|
||||
retrieveLinksContainerComponent = () => {
|
||||
// Should be retrieved from the global props (@soupette)
|
||||
const { plugins } = this.context;
|
||||
const appPlugins = plugins.toJS();
|
||||
const componentToInject = Object.keys(appPlugins).reduce((acc, current) => {
|
||||
// Retrieve injected compo from plugin
|
||||
// if compo can be injected in left.links area push the compo in the array
|
||||
const currentPlugin = appPlugins[current];
|
||||
const injectedComponents = get(currentPlugin, 'injectedComponents', []);
|
||||
|
||||
const compos = injectedComponents
|
||||
.filter(compo => {
|
||||
return (
|
||||
compo.plugin === 'content-manager.editPage' &&
|
||||
compo.area === 'left.links'
|
||||
);
|
||||
})
|
||||
.map(compo => {
|
||||
const Component = compo.component;
|
||||
|
||||
return <Component {...this} key={compo.key} />;
|
||||
});
|
||||
|
||||
return [...acc, ...compos];
|
||||
}, []);
|
||||
|
||||
return componentToInject;
|
||||
return [ctmLink, ...this.retrieveLinksContainerComponent()];
|
||||
};
|
||||
|
||||
renderEdit = () => {
|
||||
const {
|
||||
editPage,
|
||||
location: { search }
|
||||
location: { search },
|
||||
} = this.props;
|
||||
const source = getQueryParameters(search, 'source');
|
||||
const basePath = '/plugins/content-manager/ctm-configurations';
|
||||
const basePath = `/plugins/${pluginId}/ctm-configurations`;
|
||||
const pathname =
|
||||
source !== 'content-manager'
|
||||
source !== pluginId
|
||||
? `${basePath}/plugins/${source}/${this.getModelName()}`
|
||||
: `${basePath}/${this.getModelName()}`;
|
||||
|
||||
if (this.showLoaders()) {
|
||||
return (
|
||||
<div
|
||||
className={!this.hasDisplayedRelations() ? 'col-lg-12' : 'col-lg-9'}
|
||||
className={
|
||||
!this.shouldDisplayedRelations() ? 'col-lg-12' : 'col-lg-9'
|
||||
}
|
||||
>
|
||||
<div className={styles.main_wrapper}>
|
||||
<LoadingIndicator />
|
||||
@ -545,11 +594,13 @@ export class EditPage extends React.Component {
|
||||
if (!this.hasDisplayedFields()) {
|
||||
return (
|
||||
<div
|
||||
className={!this.hasDisplayedRelations() ? 'col-lg-12' : 'col-lg-9'}
|
||||
className={
|
||||
!this.shouldDisplayedRelations() ? 'col-lg-12' : 'col-lg-9'
|
||||
}
|
||||
>
|
||||
<EmptyAttributesBlock
|
||||
description="content-manager.components.EmptyAttributesBlock.description"
|
||||
label="content-manager.components.EmptyAttributesBlock.button"
|
||||
description={`${pluginId}.components.EmptyAttributesBlock.description`}
|
||||
label={`${pluginId}.components.EmptyAttributesBlock.button`}
|
||||
onClick={() => this.props.history.push(pathname)}
|
||||
/>
|
||||
</div>
|
||||
@ -558,7 +609,9 @@ export class EditPage extends React.Component {
|
||||
|
||||
return (
|
||||
<div
|
||||
className={!this.hasDisplayedRightSection() ? 'col-lg-12' : 'col-lg-9'}
|
||||
className={
|
||||
!this.shouldDisplayedRightSection() ? 'col-lg-12' : 'col-lg-9'
|
||||
}
|
||||
>
|
||||
<div className={styles.main_wrapper}>
|
||||
<Edit
|
||||
@ -599,11 +652,10 @@ export class EditPage extends React.Component {
|
||||
isOpen={showWarning}
|
||||
toggleModal={this.toggle}
|
||||
content={{
|
||||
title: 'content-manager.popUpWarning.title',
|
||||
message:
|
||||
'content-manager.popUpWarning.warning.cancelAllSettings',
|
||||
cancel: 'content-manager.popUpWarning.button.cancel',
|
||||
confirm: 'content-manager.popUpWarning.button.confirm'
|
||||
title: `${pluginId}.popUpWarning.title`,
|
||||
message: `${pluginId}.popUpWarning.warning.cancelAllSettings`,
|
||||
cancel: `${pluginId}.popUpWarning.button.cancel`,
|
||||
confirm: `${pluginId}.popUpWarning.button.confirm`,
|
||||
}}
|
||||
popUpWarningType="danger"
|
||||
onConfirm={this.handleConfirm}
|
||||
@ -612,20 +664,19 @@ export class EditPage extends React.Component {
|
||||
isOpen={showWarningDelete}
|
||||
toggleModal={this.toggleDelete}
|
||||
content={{
|
||||
title: 'content-manager.popUpWarning.title',
|
||||
message:
|
||||
'content-manager.popUpWarning.bodyMessage.contentType.delete',
|
||||
cancel: 'content-manager.popUpWarning.button.cancel',
|
||||
confirm: 'content-manager.popUpWarning.button.confirm'
|
||||
title: `${pluginId}.popUpWarning.title`,
|
||||
message: `${pluginId}.popUpWarning.bodyMessage.contentType.delete`,
|
||||
cancel: `${pluginId}.popUpWarning.button.cancel`,
|
||||
confirm: `${pluginId}.popUpWarning.button.confirm`,
|
||||
}}
|
||||
popUpWarningType="danger"
|
||||
onConfirm={this.handleConfirm}
|
||||
/>
|
||||
<div className="row">
|
||||
{this.renderEdit()}
|
||||
{this.hasDisplayedRightSection() && (
|
||||
{this.shouldDisplayedRightSection() && (
|
||||
<div className={cn('col-lg-3')}>
|
||||
{this.hasDisplayedRelations() && (
|
||||
{this.shouldDisplayedRelations() && (
|
||||
<div className={styles.sub_wrapper}>
|
||||
<EditRelations
|
||||
changeData={this.props.changeData}
|
||||
@ -644,12 +695,9 @@ export class EditPage extends React.Component {
|
||||
</div>
|
||||
)}
|
||||
|
||||
{this.hasDisplayedDevSection() && (
|
||||
{this.isDevEnvironment() && (
|
||||
<div className={styles.links_wrapper}>
|
||||
<ul>
|
||||
<li>{this.layoutLink()}</li>
|
||||
<li>{this.retrieveLinksContainerComponent()}</li>
|
||||
</ul>
|
||||
<ul>{this.renderNavLinks()}</ul>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
@ -663,11 +711,11 @@ export class EditPage extends React.Component {
|
||||
}
|
||||
|
||||
EditPage.contextTypes = {
|
||||
plugins: PropTypes.object
|
||||
plugins: PropTypes.object,
|
||||
};
|
||||
|
||||
EditPage.defaultProps = {
|
||||
schema: {}
|
||||
schema: {},
|
||||
};
|
||||
|
||||
EditPage.propTypes = {
|
||||
@ -688,7 +736,7 @@ EditPage.propTypes = {
|
||||
schema: PropTypes.object,
|
||||
setFileRelations: PropTypes.func.isRequired,
|
||||
setFormErrors: PropTypes.func.isRequired,
|
||||
submit: PropTypes.func.isRequired
|
||||
submit: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
function mapDispatchToProps(dispatch) {
|
||||
@ -706,20 +754,20 @@ function mapDispatchToProps(dispatch) {
|
||||
resetProps,
|
||||
setFileRelations,
|
||||
setFormErrors,
|
||||
submit
|
||||
submit,
|
||||
},
|
||||
dispatch
|
||||
dispatch,
|
||||
);
|
||||
}
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
editPage: makeSelectEditPage(),
|
||||
schema: makeSelectSchema()
|
||||
schema: makeSelectSchema(),
|
||||
});
|
||||
|
||||
const withConnect = connect(
|
||||
mapStateToProps,
|
||||
mapDispatchToProps
|
||||
mapDispatchToProps,
|
||||
);
|
||||
|
||||
const withReducer = injectReducer({ key: 'editPage', reducer });
|
||||
@ -728,5 +776,5 @@ const withSaga = injectSaga({ key: 'editPage', saga });
|
||||
export default compose(
|
||||
withReducer,
|
||||
withSaga,
|
||||
withConnect
|
||||
withConnect,
|
||||
)(DragDropContext(HTML5Backend)(EditPage));
|
||||
|
@ -19,7 +19,7 @@
|
||||
|
||||
.sub_wrapper {
|
||||
padding: 0 20px 1px;
|
||||
margin-bottom: 40px;
|
||||
margin-bottom: 28px;
|
||||
}
|
||||
|
||||
.links_wrapper {
|
||||
@ -33,13 +33,5 @@
|
||||
&:first-of-type {
|
||||
border-color: transparent;
|
||||
}
|
||||
a {
|
||||
text-decoration: none;
|
||||
width: 100%;
|
||||
display: block;
|
||||
p {
|
||||
font-size: 13px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -35,6 +35,8 @@
|
||||
"containers.Edit.returnList": "Return to list",
|
||||
"containers.Edit.seeDetails": "Details",
|
||||
"containers.Edit.submit": "Save",
|
||||
"containers.Edit.Link.Layout": "Configure the layout",
|
||||
"containers.Edit.Link.Fields": "Edit the fields",
|
||||
"containers.Home.introduction": "To edit your entries go to the specific link in the left menu. This plugin doesn't have a proper way to edit settings and it's still under active development.",
|
||||
"containers.Home.pluginHeaderDescription": "Manage your entries through a powerful and beautiful interface.",
|
||||
"containers.Home.pluginHeaderTitle": "Content Manager",
|
||||
@ -119,4 +121,4 @@
|
||||
"popUpWarning.warning.updateAllSettings": "This will modify all your settings",
|
||||
"success.record.delete": "Deleted",
|
||||
"success.record.save": "Saved"
|
||||
}
|
||||
}
|
||||
|
@ -35,6 +35,8 @@
|
||||
"containers.Edit.returnList": "Retourner à la liste",
|
||||
"containers.Edit.seeDetails": "Détails",
|
||||
"containers.Edit.submit": "Valider",
|
||||
"containers.Edit.Link.Layout": "Paramétrer la vue",
|
||||
"containers.Edit.Link.Fields": "Éditer le modèle",
|
||||
"containers.Home.introduction": "Pour éditer du contenu, choisissez un type de données dans le menu de gauche.",
|
||||
"containers.Home.pluginHeaderDescription": "Créer et modifier votre type de contenu",
|
||||
"containers.Home.pluginHeaderTitle": "Type de contenu",
|
||||
@ -120,4 +122,3 @@
|
||||
"success.record.delete": "Supprimé",
|
||||
"success.record.save": "Sauvegardé"
|
||||
}
|
||||
|
||||
|
@ -5,30 +5,35 @@ const {
|
||||
getApisKeys,
|
||||
getApisUploadRelations,
|
||||
getEditDisplayAvailableFieldsPath,
|
||||
getEditDisplayFieldsPath
|
||||
getEditDisplayFieldsPath,
|
||||
} = require('./utils/getters');
|
||||
const splitted = str => str.split('.');
|
||||
const pickData = (model) => _.pick(model, [
|
||||
'info',
|
||||
'connection',
|
||||
'collectionName',
|
||||
'attributes',
|
||||
'identity',
|
||||
'globalId',
|
||||
'globalName',
|
||||
'orm',
|
||||
'options',
|
||||
'loadedModel',
|
||||
'primaryKey',
|
||||
'associations'
|
||||
]);
|
||||
const pickData = model =>
|
||||
_.pick(model, [
|
||||
'info',
|
||||
'connection',
|
||||
'collectionName',
|
||||
'attributes',
|
||||
'identity',
|
||||
'globalId',
|
||||
'globalName',
|
||||
'orm',
|
||||
'options',
|
||||
'loadedModel',
|
||||
'primaryKey',
|
||||
'associations',
|
||||
]);
|
||||
|
||||
module.exports = async cb => {
|
||||
// Retrieve all layout files from the plugins config folder
|
||||
const pluginsLayout = Object.keys(strapi.plugins).reduce((acc, current) => {
|
||||
const models = _.get(strapi.plugins, [current, 'config', 'layout'], {});
|
||||
Object.keys(models).forEach(model => {
|
||||
const layout = _.get(strapi.plugins, [current, 'config', 'layout', model], {});
|
||||
const layout = _.get(
|
||||
strapi.plugins,
|
||||
[current, 'config', 'layout', model],
|
||||
{}
|
||||
);
|
||||
acc[model] = layout;
|
||||
});
|
||||
|
||||
@ -69,48 +74,59 @@ module.exports = async cb => {
|
||||
models: {
|
||||
plugins: {},
|
||||
},
|
||||
layout: {}
|
||||
layout: {},
|
||||
};
|
||||
|
||||
// Populate the schema object
|
||||
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',
|
||||
options: model.options,
|
||||
editDisplay: {
|
||||
availableFields: {},
|
||||
displayedField: model.primaryKey,
|
||||
fields: [],
|
||||
relations: [],
|
||||
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',
|
||||
options: model.options,
|
||||
editDisplay: {
|
||||
availableFields: {},
|
||||
displayedField: model.primaryKey,
|
||||
fields: [],
|
||||
relations: [],
|
||||
},
|
||||
},
|
||||
}, model);
|
||||
model
|
||||
);
|
||||
const fieldsToRemove = [];
|
||||
// Fields (non relation)
|
||||
const fields = _.mapValues(_.pickBy(model.attributes, attribute =>
|
||||
!attribute.model && !attribute.collection
|
||||
), (value, attribute) => {
|
||||
const fieldClassName = _.get(tempLayout, [name, 'attributes', attribute, 'className'], '');
|
||||
const fields = _.mapValues(
|
||||
_.pickBy(
|
||||
model.attributes,
|
||||
attribute => !attribute.model && !attribute.collection
|
||||
),
|
||||
(value, attribute) => {
|
||||
const fieldClassName = _.get(
|
||||
tempLayout,
|
||||
[name, 'attributes', attribute, 'className'],
|
||||
''
|
||||
);
|
||||
|
||||
if (fieldClassName === 'd-none') {
|
||||
fieldsToRemove.push(attribute);
|
||||
if (fieldClassName === 'd-none') {
|
||||
fieldsToRemove.push(attribute);
|
||||
}
|
||||
|
||||
return {
|
||||
label: _.upperFirst(attribute),
|
||||
description: '',
|
||||
type: value.type || 'string',
|
||||
disabled: false,
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
label: _.upperFirst(attribute),
|
||||
description: '',
|
||||
type: value.type || 'string',
|
||||
disabled: false,
|
||||
};
|
||||
});
|
||||
);
|
||||
|
||||
// Don't display fields that are hidden by default like the resetPasswordToken for the model user
|
||||
fieldsToRemove.forEach(field => {
|
||||
@ -128,7 +144,11 @@ module.exports = async cb => {
|
||||
const attrType = schemaModel.fields[attr].type;
|
||||
const sortable = attrType !== 'json' && attrType !== 'array';
|
||||
|
||||
return Object.assign(schemaModel.fields[attr], { name: attr, sortable, searchable: sortable });
|
||||
return Object.assign(schemaModel.fields[attr], {
|
||||
name: attr,
|
||||
sortable,
|
||||
searchable: sortable,
|
||||
});
|
||||
})
|
||||
// Retrieve only the fourth first items
|
||||
.slice(0, 4);
|
||||
@ -143,13 +163,24 @@ module.exports = async cb => {
|
||||
|
||||
// This object will be used to customise the label and description and so on of an input.
|
||||
// TODO: maybe add the customBootstrapClass in it;
|
||||
schemaModel.editDisplay.availableFields = Object.keys(schemaModel.fields).reduce((acc, current) => {
|
||||
schemaModel.editDisplay.availableFields = Object.keys(
|
||||
schemaModel.fields
|
||||
).reduce((acc, current) => {
|
||||
acc[current] = Object.assign(
|
||||
_.pick(_.get(schemaModel, ['fields', current], {}), ['label', 'type', 'description', 'name']),
|
||||
_.pick(_.get(schemaModel, ['fields', current], {}), [
|
||||
'label',
|
||||
'type',
|
||||
'description',
|
||||
'name',
|
||||
]),
|
||||
{
|
||||
editable: ['updatedAt', 'createdAt', 'updated_at', 'created_at'].indexOf(current) === -1,
|
||||
editable:
|
||||
['updatedAt', 'createdAt', 'updated_at', 'created_at'].indexOf(
|
||||
current
|
||||
) === -1,
|
||||
placeholder: '',
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
return acc;
|
||||
}, {});
|
||||
@ -158,13 +189,37 @@ module.exports = async cb => {
|
||||
// Model relations
|
||||
schemaModel.relations = model.associations.reduce((acc, current) => {
|
||||
const label = _.upperFirst(current.alias);
|
||||
const displayedAttribute = current.plugin ? // Value to modified to custom what's displayed in the react-select
|
||||
_.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';
|
||||
const displayedAttribute = current.plugin // Value to modified to custom what's displayed in the react-select
|
||||
? _.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,
|
||||
@ -175,37 +230,55 @@ module.exports = async cb => {
|
||||
|
||||
return acc;
|
||||
}, {});
|
||||
const relationsArray = Object.keys(schemaModel.relations).filter(relation => {
|
||||
const isUploadRelation = _.get(schemaModel, ['relations', relation, 'plugin'], '') === 'upload';
|
||||
const isMorphSide = _.get(schemaModel, ['relations', relation, 'nature'], '').toLowerCase().includes('morp') && _.get(schemaModel, ['relations', relation, relation]) !== undefined;
|
||||
const relationsArray = Object.keys(schemaModel.relations).filter(
|
||||
relation => {
|
||||
const isUploadRelation =
|
||||
_.get(schemaModel, ['relations', relation, 'plugin'], '') ===
|
||||
'upload';
|
||||
const isMorphSide =
|
||||
_.get(schemaModel, ['relations', relation, 'nature'], '')
|
||||
.toLowerCase()
|
||||
.includes('morp') &&
|
||||
_.get(schemaModel, ['relations', relation, relation]) !== undefined;
|
||||
|
||||
return !isUploadRelation && !isMorphSide;
|
||||
});
|
||||
|
||||
const uploadRelations = Object.keys(schemaModel.relations).reduce((acc, current) => {
|
||||
if (_.get(schemaModel, ['relations', current, 'plugin']) === 'upload') {
|
||||
const model = _.get(schemaModel, ['relations', current]);
|
||||
|
||||
acc[current] = {
|
||||
description: '',
|
||||
editable: true,
|
||||
label: _.upperFirst(current),
|
||||
multiple: _.has(model, 'collection'),
|
||||
name: current,
|
||||
placeholder: '',
|
||||
type: 'file',
|
||||
disabled: false,
|
||||
};
|
||||
return !isUploadRelation && !isMorphSide;
|
||||
}
|
||||
);
|
||||
|
||||
return acc;
|
||||
}, {});
|
||||
const uploadRelations = Object.keys(schemaModel.relations).reduce(
|
||||
(acc, current) => {
|
||||
if (
|
||||
_.get(schemaModel, ['relations', current, 'plugin']) === 'upload'
|
||||
) {
|
||||
const model = _.get(schemaModel, ['relations', current]);
|
||||
|
||||
schemaModel.editDisplay.availableFields = _.merge(schemaModel.editDisplay.availableFields, uploadRelations);
|
||||
acc[current] = {
|
||||
description: '',
|
||||
editable: true,
|
||||
label: _.upperFirst(current),
|
||||
multiple: _.has(model, 'collection'),
|
||||
name: current,
|
||||
placeholder: '',
|
||||
type: 'file',
|
||||
disabled: false,
|
||||
};
|
||||
}
|
||||
|
||||
return acc;
|
||||
},
|
||||
{}
|
||||
);
|
||||
|
||||
schemaModel.editDisplay.availableFields = _.merge(
|
||||
schemaModel.editDisplay.availableFields,
|
||||
uploadRelations
|
||||
);
|
||||
schemaModel.editDisplay.relations = relationsArray;
|
||||
}
|
||||
|
||||
schemaModel.editDisplay.fields = Object.keys(schemaModel.editDisplay.availableFields);
|
||||
schemaModel.editDisplay.fields = Object.keys(
|
||||
schemaModel.editDisplay.availableFields
|
||||
);
|
||||
|
||||
if (plugin) {
|
||||
return _.set(schema.models.plugins, `${plugin}.${name}`, schemaModel);
|
||||
@ -230,7 +303,7 @@ module.exports = async cb => {
|
||||
const pluginStore = strapi.store({
|
||||
environment: '',
|
||||
type: 'plugin',
|
||||
name: 'content-manager'
|
||||
name: 'content-manager',
|
||||
});
|
||||
|
||||
try {
|
||||
@ -263,24 +336,42 @@ module.exports = async cb => {
|
||||
const schemaApis = getApis(schema.models);
|
||||
|
||||
// Array of apis to add
|
||||
const apisToAdd = schemaApis.filter(api => prevSchemaApis.indexOf(api) === -1).map(splitted);
|
||||
const apisToAdd = schemaApis
|
||||
.filter(api => prevSchemaApis.indexOf(api) === -1)
|
||||
.map(splitted);
|
||||
// Array of apis to remove
|
||||
const apisToRemove = prevSchemaApis.filter(api => schemaApis.indexOf(api) === -1).map(splitted);
|
||||
const apisToRemove = prevSchemaApis
|
||||
.filter(api => schemaApis.indexOf(api) === -1)
|
||||
.map(splitted);
|
||||
|
||||
// Retrieve the same apis by name
|
||||
const sameApis = schemaApis.filter(api => prevSchemaApis.indexOf(api) !== -1).map(splitted);
|
||||
const sameApis = schemaApis
|
||||
.filter(api => prevSchemaApis.indexOf(api) !== -1)
|
||||
.map(splitted);
|
||||
// Retrieve all the field's path of the current unchanged api name
|
||||
const schemaSameApisKeys = _.flattenDeep(getApisKeys(schema, sameApis));
|
||||
// Retrieve all the field's path of the previous unchanged api name
|
||||
const prevSchemaSameApisKeys = _.flattenDeep(getApisKeys(prevSchema, sameApis));
|
||||
const prevSchemaSameApisKeys = _.flattenDeep(
|
||||
getApisKeys(prevSchema, sameApis)
|
||||
);
|
||||
// Determine for the same api if we need to add some fields
|
||||
const sameApisAttrToAdd = schemaSameApisKeys.filter(attr => prevSchemaSameApisKeys.indexOf(attr) === -1).map(splitted);
|
||||
const sameApisAttrToAdd = schemaSameApisKeys
|
||||
.filter(attr => prevSchemaSameApisKeys.indexOf(attr) === -1)
|
||||
.map(splitted);
|
||||
// Special case for the relations
|
||||
const prevSchemaSameApisUploadRelations = _.flattenDeep(getApisUploadRelations(prevSchema, sameApis));
|
||||
const schemaSameApisUploadRelations = _.flattenDeep(getApisUploadRelations(schema, sameApis));
|
||||
const sameApisUploadRelationsToAdd = schemaSameApisUploadRelations.filter(attr => prevSchemaSameApisUploadRelations.indexOf(attr) === -1).map(splitted);
|
||||
const prevSchemaSameApisUploadRelations = _.flattenDeep(
|
||||
getApisUploadRelations(prevSchema, sameApis)
|
||||
);
|
||||
const schemaSameApisUploadRelations = _.flattenDeep(
|
||||
getApisUploadRelations(schema, sameApis)
|
||||
);
|
||||
const sameApisUploadRelationsToAdd = schemaSameApisUploadRelations
|
||||
.filter(attr => prevSchemaSameApisUploadRelations.indexOf(attr) === -1)
|
||||
.map(splitted);
|
||||
// Determine the fields to remove for the unchanged api name
|
||||
const sameApisAttrToRemove = prevSchemaSameApisKeys.filter(attr => schemaSameApisKeys.indexOf(attr) === -1).map(splitted);
|
||||
const sameApisAttrToRemove = prevSchemaSameApisKeys
|
||||
.filter(attr => schemaSameApisKeys.indexOf(attr) === -1)
|
||||
.map(splitted);
|
||||
|
||||
// Remove api
|
||||
apisToRemove.map(apiPath => {
|
||||
@ -295,7 +386,8 @@ module.exports = async cb => {
|
||||
// Check default sort and change it if needed
|
||||
_.unset(prevSchema.models, attrPath);
|
||||
// Retrieve the api path in the schema Object
|
||||
const apiPath = attrPath.length > 3 ? _.take(attrPath, 3) : _.take(attrPath, 1);
|
||||
const apiPath =
|
||||
attrPath.length > 3 ? _.take(attrPath, 3) : _.take(attrPath, 1);
|
||||
// Retrieve the listDisplay path in the schema Object
|
||||
const listDisplayPath = apiPath.concat('listDisplay');
|
||||
const prevListDisplay = _.get(prevSchema.models, listDisplayPath);
|
||||
@ -306,24 +398,36 @@ module.exports = async cb => {
|
||||
// If the user has deleted the default sort attribute in the content type builder
|
||||
// Replace it by new generated one from the current schema
|
||||
if (_.includes(currentAttr, defaultSort)) {
|
||||
_.set(prevSchema.models, defaultSortPath, _.get(schema.models, defaultSortPath));
|
||||
_.set(
|
||||
prevSchema.models,
|
||||
defaultSortPath,
|
||||
_.get(schema.models, defaultSortPath)
|
||||
);
|
||||
}
|
||||
|
||||
// Update the displayed fields
|
||||
const updatedListDisplay = prevListDisplay.filter(obj => obj.name !== currentAttr.join());
|
||||
const updatedListDisplay = prevListDisplay.filter(
|
||||
obj => obj.name !== currentAttr.join()
|
||||
);
|
||||
|
||||
// Retrieve the model's displayed fields for the `EditPage`
|
||||
const fieldsPath = getEditDisplayFieldsPath(attrPath);
|
||||
// Retrieve the previous settings
|
||||
const prevEditDisplayFields = _.get(prevSchema.models, fieldsPath);
|
||||
// Update the fields
|
||||
const updatedEditDisplayFields = prevEditDisplayFields.filter(field => field !== currentAttr.join());
|
||||
const updatedEditDisplayFields = prevEditDisplayFields.filter(
|
||||
field => field !== currentAttr.join()
|
||||
);
|
||||
// Set the new layout
|
||||
_.set(prevSchema.models, fieldsPath, updatedEditDisplayFields);
|
||||
|
||||
if (updatedListDisplay.length === 0) {
|
||||
// Update it with the one from the generated schema
|
||||
_.set(prevSchema.models, listDisplayPath, _.get(schema.models, listDisplayPath, []));
|
||||
_.set(
|
||||
prevSchema.models,
|
||||
listDisplayPath,
|
||||
_.get(schema.models, listDisplayPath, [])
|
||||
);
|
||||
} else {
|
||||
_.set(prevSchema.models, listDisplayPath, updatedListDisplay);
|
||||
}
|
||||
@ -333,7 +437,10 @@ module.exports = async cb => {
|
||||
// Here we just need to add the data from the current schema Object
|
||||
apisToAdd.map(apiPath => {
|
||||
const api = _.get(schema.models, apiPath);
|
||||
const { search, filters, bulkActions, pageEntries, options } = _.get(prevSchema, 'generalSettings');
|
||||
const { search, filters, bulkActions, pageEntries, options } = _.get(
|
||||
prevSchema,
|
||||
'generalSettings'
|
||||
);
|
||||
|
||||
_.set(api, 'options', options);
|
||||
_.set(api, 'filters', filters);
|
||||
@ -364,7 +471,13 @@ module.exports = async cb => {
|
||||
sameApis.forEach(apiPath => {
|
||||
// This doesn't keep the prevSettings for the relations, the user will have to reset it.
|
||||
// We might have to improve this if we want the order of the relations to be kept
|
||||
['relations', 'loadedModel', 'associations', 'attributes', ['editDisplay', 'relations']]
|
||||
[
|
||||
'relations',
|
||||
'loadedModel',
|
||||
'associations',
|
||||
'attributes',
|
||||
['editDisplay', 'relations'],
|
||||
]
|
||||
.map(key => apiPath.concat(key))
|
||||
.forEach(keyPath => {
|
||||
const newValue = _.get(schema.models, keyPath);
|
||||
@ -378,22 +491,29 @@ module.exports = async cb => {
|
||||
const attr = _.get(schema.models, attrPath);
|
||||
_.set(prevSchema.models, attrPath, attr);
|
||||
|
||||
const fieldsPath = [..._.take(attrPath, attrPath.length -2), 'fields'];
|
||||
const fieldsPath = [..._.take(attrPath, attrPath.length - 2), 'fields'];
|
||||
const currentFields = _.get(prevSchema.models, fieldsPath, []);
|
||||
currentFields.push(attr.name);
|
||||
_.set(prevSchema.models, fieldsPath, currentFields);
|
||||
});
|
||||
|
||||
schemaApis.map((model) => {
|
||||
schemaApis.map(model => {
|
||||
const isPlugin = model.includes('plugins.');
|
||||
_.set(prevSchema.models[model], 'info', _.get(!isPlugin ? strapi.models[model] : strapi[model], 'info'));
|
||||
_.set(prevSchema.models[model], 'options', _.get(!isPlugin ? strapi.models[model] : strapi[model], 'options'));
|
||||
_.set(
|
||||
prevSchema.models[model],
|
||||
'info',
|
||||
_.get(!isPlugin ? strapi.models[model] : strapi[model], 'info')
|
||||
);
|
||||
_.set(
|
||||
prevSchema.models[model],
|
||||
'options',
|
||||
_.get(!isPlugin ? strapi.models[model] : strapi[model], 'options')
|
||||
);
|
||||
});
|
||||
|
||||
await pluginStore.set({ key: 'schema', value: prevSchema });
|
||||
|
||||
} catch(err) {
|
||||
console.log('error', err);
|
||||
} catch (err) {
|
||||
console.log('error', err); // eslint-disable-line no-console
|
||||
}
|
||||
|
||||
cb();
|
||||
|
@ -0,0 +1,30 @@
|
||||
/**
|
||||
*
|
||||
* EditViewLink
|
||||
*
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import NavLink from 'components/NavLink';
|
||||
|
||||
// Create link from content-type-builder to content-manager
|
||||
function EditViewLink(props) {
|
||||
// Retrieve URL from props
|
||||
let url = `${props.getContentTypeBuilderBaseUrl()}${props.getModelName()}`;
|
||||
|
||||
// Add users-permissions to URL for permission, role and user content types
|
||||
if (props.getSource() === 'users-permissions') {
|
||||
url = `${url}&source=${props.getSource()}`;
|
||||
}
|
||||
|
||||
return <NavLink {...props} url={url} />;
|
||||
}
|
||||
|
||||
EditViewLink.propTypes = {
|
||||
getContentTypeBuilderBaseUrl: PropTypes.func.isRequired,
|
||||
getModelName: PropTypes.func.isRequired,
|
||||
getSource: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
export default EditViewLink;
|
@ -1,25 +0,0 @@
|
||||
/**
|
||||
*
|
||||
* ContentManagerEditViewLink
|
||||
*
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { Link } from 'react-router-dom';
|
||||
|
||||
function ContentManagerEditViewLink(props) {
|
||||
let url = props.getContentTypeBuilderBaseUrl() + props.getModelName();
|
||||
if (
|
||||
props.getModelName() === 'user' ||
|
||||
props.getModelName() === 'role' ||
|
||||
props.getModelName() === 'permission'
|
||||
) {
|
||||
url = url + '&source=users-permissions';
|
||||
}
|
||||
|
||||
return <Link to={url}>Edit the fields</Link>;
|
||||
}
|
||||
|
||||
ContentManagerEditViewLink.propTypes = {};
|
||||
|
||||
export default ContentManagerEditViewLink;
|
@ -1,10 +1,16 @@
|
||||
import Link from 'components/ContentManagerEditViewLink';
|
||||
import Link from './InjectedComponents/ContentManager/EditViewLink';
|
||||
|
||||
export default [
|
||||
{
|
||||
plugin: 'content-manager.editPage',
|
||||
area: 'left.links',
|
||||
area: 'right.links',
|
||||
component: Link,
|
||||
key: 'content-type-builder.link'
|
||||
}
|
||||
key: 'content-type-builder.link',
|
||||
props: {
|
||||
message: {
|
||||
id: 'content-manager.containers.Edit.Link.Fields',
|
||||
},
|
||||
icon: 'fa-cog',
|
||||
},
|
||||
},
|
||||
];
|
||||
|
Loading…
x
Reference in New Issue
Block a user