mirror of
https://github.com/silverstripe/silverstripe-framework
synced 2024-10-22 14:05:37 +02:00
Add ESLint support
See https://github.com/silverstripe/silverstripe-framework/pull/5108
This commit is contained in:
parent
6aa22c38ed
commit
34d40bed5f
@ -17,6 +17,10 @@ trim_trailing_whitespace = false
|
|||||||
indent_size = 2
|
indent_size = 2
|
||||||
indent_style = space
|
indent_style = space
|
||||||
|
|
||||||
|
[*.js]
|
||||||
|
indent_size = 2
|
||||||
|
indent_style = space
|
||||||
|
|
||||||
# Don't perform any clean-up on thirdparty files
|
# Don't perform any clean-up on thirdparty files
|
||||||
|
|
||||||
[thirdparty/**]
|
[thirdparty/**]
|
||||||
|
37
.eslintignore
Normal file
37
.eslintignore
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
# Ignore dist files
|
||||||
|
javascript/dist/
|
||||||
|
|
||||||
|
# Ignore legacy files
|
||||||
|
javascript/src/AssetUploadField.js
|
||||||
|
javascript/src/ConfirmedPasswordField.js
|
||||||
|
javascript/src/DateField.js
|
||||||
|
javascript/src/GridField.js
|
||||||
|
javascript/src/HtmlEditorField.js
|
||||||
|
javascript/src/InlineFormAction.js
|
||||||
|
javascript/src/PermissionCheckboxSetField.js
|
||||||
|
javascript/src/SelectionGroup.js
|
||||||
|
javascript/src/TabSet.js
|
||||||
|
javascript/src/TinyMCE_SSPlugin.js
|
||||||
|
javascript/src/ToggleCompositeField.js
|
||||||
|
javascript/src/ToggleField.js
|
||||||
|
javascript/src/TreeDropdownField.js
|
||||||
|
javascript/src/UploadField.js
|
||||||
|
javascript/src/UploadField_downloadtemplate.js
|
||||||
|
javascript/src/UploadField_select.js
|
||||||
|
javascript/src/UploadField_uploadtemplate.js
|
||||||
|
javascript/src/i18n.js
|
||||||
|
javascript/src/i18nx.js
|
||||||
|
javascript/src/jQuery.js
|
||||||
|
admin/javascript/src/LeftAndMain.js
|
||||||
|
admin/javascript/src/LeftAndMain.*.js
|
||||||
|
admin/javascript/src/CMSSecurity.js
|
||||||
|
admin/javascript/src/MemberDatetimeOptionsetField.js
|
||||||
|
admin/javascript/src/MemberImportForm.js
|
||||||
|
admin/javascript/src/ModelAdmin.js
|
||||||
|
admin/javascript/src/SecurityAdmin.js
|
||||||
|
admin/javascript/src/leaktools.js
|
||||||
|
admin/javascript/src/sspath.js
|
||||||
|
admin/javascript/src/ssui.core.js
|
||||||
|
|
||||||
|
# Ignore tests
|
||||||
|
admin/javascript/**/tests/
|
@ -10,6 +10,7 @@ import SchemaReducer from 'state/schema/reducer';
|
|||||||
import RecordsReducer from 'state/records/reducer';
|
import RecordsReducer from 'state/records/reducer';
|
||||||
|
|
||||||
// Sections
|
// Sections
|
||||||
|
// eslint-disable-next-line no-unused-vars
|
||||||
import CampaignAdmin from 'sections/campaign-admin/index';
|
import CampaignAdmin from 'sections/campaign-admin/index';
|
||||||
|
|
||||||
function appBoot() {
|
function appBoot() {
|
||||||
@ -28,6 +29,8 @@ function appBoot() {
|
|||||||
configActions.setConfig(window.ss.config)(window.store.dispatch);
|
configActions.setConfig(window.ss.config)(window.store.dispatch);
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: This should be using `window.onload` but isn't because Entwine hooks are being used to set up the <Provider>.
|
// TODO: This should be using `window.onload` but isn't because
|
||||||
// `window.onload` happens AFTER these Entwine hooks which means the store is undefined when the <Provider> is constructed.
|
// Entwine hooks are being used to set up the <Provider>.
|
||||||
$('body').entwine({ onadd: function () { appBoot(); } });
|
// `window.onload` happens AFTER these Entwine hooks which means
|
||||||
|
// the store is undefined when the <Provider> is constructed.
|
||||||
|
$('body').entwine({ onadd: () => { appBoot(); } });
|
||||||
|
@ -23,7 +23,7 @@ class FormActionComponent extends SilverStripeComponent {
|
|||||||
* @returns string
|
* @returns string
|
||||||
*/
|
*/
|
||||||
getButtonClasses() {
|
getButtonClasses() {
|
||||||
var buttonClasses = 'btn';
|
let buttonClasses = 'btn';
|
||||||
|
|
||||||
// Add 'type' class
|
// Add 'type' class
|
||||||
buttonClasses += ` btn-${this.props.style}`;
|
buttonClasses += ` btn-${this.props.style}`;
|
||||||
@ -91,12 +91,12 @@ FormActionComponent.propTypes = {
|
|||||||
loading: React.PropTypes.bool,
|
loading: React.PropTypes.bool,
|
||||||
icon: React.PropTypes.string,
|
icon: React.PropTypes.string,
|
||||||
disabled: React.PropTypes.bool,
|
disabled: React.PropTypes.bool,
|
||||||
style: React.PropTypes.string
|
style: React.PropTypes.string,
|
||||||
};
|
};
|
||||||
|
|
||||||
FormActionComponent.defaultProps = {
|
FormActionComponent.defaultProps = {
|
||||||
type: 'button',
|
type: 'button',
|
||||||
style: 'secondary'
|
style: 'secondary',
|
||||||
};
|
};
|
||||||
|
|
||||||
export default FormActionComponent;
|
export default FormActionComponent;
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import { bindActionCreators } from 'redux';
|
import { bindActionCreators } from 'redux';
|
||||||
import $ from 'jQuery';
|
|
||||||
import * as schemaActions from 'state/schema/actions';
|
import * as schemaActions from 'state/schema/actions';
|
||||||
import SilverStripeComponent from 'silverstripe-component';
|
import SilverStripeComponent from 'silverstripe-component';
|
||||||
import FormComponent from 'components/form/index';
|
import FormComponent from 'components/form/index';
|
||||||
@ -14,15 +13,15 @@ import es6promise from 'es6-promise';
|
|||||||
es6promise.polyfill();
|
es6promise.polyfill();
|
||||||
|
|
||||||
// Using this to map field types to components until we implement dependency injection.
|
// Using this to map field types to components until we implement dependency injection.
|
||||||
var fakeInjector = {
|
const fakeInjector = {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Components registered with the fake DI container.
|
* Components registered with the fake DI container.
|
||||||
*/
|
*/
|
||||||
components: {
|
components: {
|
||||||
'TextField': TextField,
|
TextField,
|
||||||
'GridField': GridField,
|
GridField,
|
||||||
'HiddenField': HiddenField
|
HiddenField,
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -33,7 +32,7 @@ var fakeInjector = {
|
|||||||
*
|
*
|
||||||
* @return object|null
|
* @return object|null
|
||||||
*/
|
*/
|
||||||
getComponentByName: function (componentName) {
|
getComponentByName(componentName) {
|
||||||
return this.components[componentName];
|
return this.components[componentName];
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -45,7 +44,7 @@ var fakeInjector = {
|
|||||||
*
|
*
|
||||||
* @return object|null
|
* @return object|null
|
||||||
*/
|
*/
|
||||||
getComponentByDataType: function (dataType) {
|
getComponentByDataType(dataType) {
|
||||||
switch (dataType) {
|
switch (dataType) {
|
||||||
case 'String':
|
case 'String':
|
||||||
return this.components.TextField;
|
return this.components.TextField;
|
||||||
@ -86,8 +85,8 @@ var fakeInjector = {
|
|||||||
default:
|
default:
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
}
|
};
|
||||||
|
|
||||||
export class FormBuilderComponent extends SilverStripeComponent {
|
export class FormBuilderComponent extends SilverStripeComponent {
|
||||||
|
|
||||||
@ -110,7 +109,7 @@ export class FormBuilderComponent extends SilverStripeComponent {
|
|||||||
* @return object - Promise from the AJAX request.
|
* @return object - Promise from the AJAX request.
|
||||||
*/
|
*/
|
||||||
fetch(schema = true, state = false) {
|
fetch(schema = true, state = false) {
|
||||||
var headerValues = [];
|
const headerValues = [];
|
||||||
|
|
||||||
if (this.isFetching === true) {
|
if (this.isFetching === true) {
|
||||||
return this.formSchemaPromise;
|
return this.formSchemaPromise;
|
||||||
@ -126,11 +125,9 @@ export class FormBuilderComponent extends SilverStripeComponent {
|
|||||||
|
|
||||||
this.formSchemaPromise = fetch(this.props.schemaUrl, {
|
this.formSchemaPromise = fetch(this.props.schemaUrl, {
|
||||||
headers: { 'X-FormSchema-Request': headerValues.join() },
|
headers: { 'X-FormSchema-Request': headerValues.join() },
|
||||||
credentials: 'same-origin'
|
credentials: 'same-origin',
|
||||||
})
|
|
||||||
.then(response => {
|
|
||||||
return response.json();
|
|
||||||
})
|
})
|
||||||
|
.then(response => response.json())
|
||||||
.then(json => {
|
.then(json => {
|
||||||
this.isFetching = false;
|
this.isFetching = false;
|
||||||
this.props.actions.setSchema(json);
|
this.props.actions.setSchema(json);
|
||||||
@ -152,7 +149,6 @@ export class FormBuilderComponent extends SilverStripeComponent {
|
|||||||
*/
|
*/
|
||||||
mapFieldsToComponents(fields) {
|
mapFieldsToComponents(fields) {
|
||||||
return fields.map((field, i) => {
|
return fields.map((field, i) => {
|
||||||
|
|
||||||
const Component = field.component !== null
|
const Component = field.component !== null
|
||||||
? fakeInjector.getComponentByName(field.component)
|
? fakeInjector.getComponentByName(field.component)
|
||||||
: fakeInjector.getComponentByDataType(field.type);
|
: fakeInjector.getComponentByDataType(field.type);
|
||||||
@ -162,17 +158,18 @@ export class FormBuilderComponent extends SilverStripeComponent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Props which every form field receives.
|
// Props which every form field receives.
|
||||||
let props = {
|
const props = {
|
||||||
attributes: field.attributes,
|
attributes: field.attributes,
|
||||||
data: field.data,
|
data: field.data,
|
||||||
description: field.description,
|
description: field.description,
|
||||||
extraClass: field.extraClass,
|
extraClass: field.extraClass,
|
||||||
fields: field.children,
|
fields: field.children,
|
||||||
id: field.id,
|
id: field.id,
|
||||||
name: field.name
|
name: field.name,
|
||||||
};
|
};
|
||||||
|
|
||||||
// Structural fields (like TabSets) are not posted back to the server and don't receive some props.
|
// Structural fields (like TabSets) are not posted back to
|
||||||
|
// the server and don't receive some props.
|
||||||
if (field.type !== 'Structural') {
|
if (field.type !== 'Structural') {
|
||||||
props.rightTitle = field.rightTitle;
|
props.rightTitle = field.rightTitle;
|
||||||
props.leftTitle = field.leftTitle;
|
props.leftTitle = field.leftTitle;
|
||||||
@ -186,7 +183,7 @@ export class FormBuilderComponent extends SilverStripeComponent {
|
|||||||
props.source = field.source;
|
props.source = field.source;
|
||||||
}
|
}
|
||||||
|
|
||||||
return <Component key={i} {...props} />
|
return <Component key={i} {...props} />;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -204,29 +201,29 @@ export class FormBuilderComponent extends SilverStripeComponent {
|
|||||||
attributes: schema.schema.attributes,
|
attributes: schema.schema.attributes,
|
||||||
data: schema.schema.data,
|
data: schema.schema.data,
|
||||||
fields: schema.schema.fields,
|
fields: schema.schema.fields,
|
||||||
mapFieldsToComponents: this.mapFieldsToComponents
|
mapFieldsToComponents: this.mapFieldsToComponents,
|
||||||
};
|
};
|
||||||
|
|
||||||
return <FormComponent {...formProps} />
|
return <FormComponent {...formProps} />;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
FormBuilderComponent.propTypes = {
|
FormBuilderComponent.propTypes = {
|
||||||
actions: React.PropTypes.object.isRequired,
|
actions: React.PropTypes.object.isRequired,
|
||||||
schemaUrl: React.PropTypes.string.isRequired,
|
schemaUrl: React.PropTypes.string.isRequired,
|
||||||
schemas: React.PropTypes.object.isRequired
|
schemas: React.PropTypes.object.isRequired,
|
||||||
};
|
};
|
||||||
|
|
||||||
function mapStateToProps(state) {
|
function mapStateToProps(state) {
|
||||||
return {
|
return {
|
||||||
schemas: state.schemas
|
schemas: state.schemas,
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function mapDispatchToProps(dispatch) {
|
function mapDispatchToProps(dispatch) {
|
||||||
return {
|
return {
|
||||||
actions: bindActionCreators(schemaActions, dispatch)
|
actions: bindActionCreators(schemaActions, dispatch),
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export default connect(mapStateToProps, mapDispatchToProps)(FormBuilderComponent);
|
export default connect(mapStateToProps, mapDispatchToProps)(FormBuilderComponent);
|
||||||
|
@ -11,9 +11,9 @@ class FormComponent extends SilverStripeComponent {
|
|||||||
* @return array|null
|
* @return array|null
|
||||||
*/
|
*/
|
||||||
getFormActionComponents() {
|
getFormActionComponents() {
|
||||||
return this.props.actions.map((action) => {
|
return this.props.actions.map((action) =>
|
||||||
return <FormActionComponent {...action} />;
|
<FormActionComponent {...action} />
|
||||||
});
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
@ -22,16 +22,22 @@ class FormComponent extends SilverStripeComponent {
|
|||||||
const actions = this.getFormActionComponents();
|
const actions = this.getFormActionComponents();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<form id={attr.id} className={attr.className} encType={attr.enctype} method={attr.method} action={attr.action}>
|
<form
|
||||||
|
id={attr.id}
|
||||||
|
className={attr.className}
|
||||||
|
encType={attr.enctype}
|
||||||
|
method={attr.method}
|
||||||
|
action={attr.action}
|
||||||
|
>
|
||||||
{fields &&
|
{fields &&
|
||||||
<fieldset className='form-group'>
|
<fieldset className="form-group">
|
||||||
{fields}
|
{fields}
|
||||||
</fieldset>
|
</fieldset>
|
||||||
}
|
}
|
||||||
|
|
||||||
{actions &&
|
{actions &&
|
||||||
<div className='actions-fix-btm'>
|
<div className="actions-fix-btm">
|
||||||
<div className='btn-group' role='group'>
|
<div className="btn-group" role="group">
|
||||||
{actions}
|
{actions}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -46,14 +52,14 @@ FormComponent.propTypes = {
|
|||||||
actions: React.PropTypes.array,
|
actions: React.PropTypes.array,
|
||||||
attributes: React.PropTypes.shape({
|
attributes: React.PropTypes.shape({
|
||||||
action: React.PropTypes.string.isRequired,
|
action: React.PropTypes.string.isRequired,
|
||||||
'class': React.PropTypes.string.isRequired,
|
class: React.PropTypes.string.isRequired,
|
||||||
enctype: React.PropTypes.string.isRequired,
|
enctype: React.PropTypes.string.isRequired,
|
||||||
id: React.PropTypes.string.isRequired,
|
id: React.PropTypes.string.isRequired,
|
||||||
method: React.PropTypes.string.isRequired
|
method: React.PropTypes.string.isRequired,
|
||||||
}),
|
}),
|
||||||
data: React.PropTypes.array,
|
data: React.PropTypes.array,
|
||||||
fields: React.PropTypes.array.isRequired,
|
fields: React.PropTypes.array.isRequired,
|
||||||
mapFieldsToComponents: React.PropTypes.func.isRequired
|
mapFieldsToComponents: React.PropTypes.func.isRequired,
|
||||||
};
|
};
|
||||||
|
|
||||||
export default FormComponent;
|
export default FormComponent;
|
||||||
|
@ -4,7 +4,6 @@ import SilverStripeComponent from 'silverstripe-component';
|
|||||||
class GridFieldActionComponent extends SilverStripeComponent {
|
class GridFieldActionComponent extends SilverStripeComponent {
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
|
|
||||||
this.handleClick = this.handleClick.bind(this);
|
this.handleClick = this.handleClick.bind(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -12,17 +11,18 @@ class GridFieldActionComponent extends SilverStripeComponent {
|
|||||||
return (
|
return (
|
||||||
<button
|
<button
|
||||||
className={`grid-field-action-component font-icon-${this.props.icon}`}
|
className={`grid-field-action-component font-icon-${this.props.icon}`}
|
||||||
onClick={this.handleClick} />
|
onClick={this.handleClick}
|
||||||
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
handleClick(event) {
|
handleClick(event) {
|
||||||
this.props.handleClick(event);
|
this.props.handleClick(event, this.props.record.ID);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
GridFieldActionComponent.PropTypes = {
|
GridFieldActionComponent.PropTypes = {
|
||||||
handleClick: React.PropTypes.func.isRequired
|
handleClick: React.PropTypes.func.isRequired,
|
||||||
}
|
};
|
||||||
|
|
||||||
export default GridFieldActionComponent;
|
export default GridFieldActionComponent;
|
||||||
|
@ -5,14 +5,14 @@ class GridFieldCellComponent extends SilverStripeComponent {
|
|||||||
|
|
||||||
render() {
|
render() {
|
||||||
return (
|
return (
|
||||||
<div className='grid-field-cell-component'>{this.props.children}</div>
|
<div className="grid-field-cell-component">{this.props.children}</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
GridFieldCellComponent.PropTypes = {
|
GridFieldCellComponent.PropTypes = {
|
||||||
width: React.PropTypes.number
|
width: React.PropTypes.number,
|
||||||
}
|
};
|
||||||
|
|
||||||
export default GridFieldCellComponent;
|
export default GridFieldCellComponent;
|
||||||
|
@ -5,14 +5,14 @@ class GridFieldHeaderCellComponent extends SilverStripeComponent {
|
|||||||
|
|
||||||
render() {
|
render() {
|
||||||
return (
|
return (
|
||||||
<div className='grid-field-header-cell-component'>{this.props.children}</div>
|
<div className="grid-field-header-cell-component">{this.props.children}</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
GridFieldHeaderCellComponent.PropTypes = {
|
GridFieldHeaderCellComponent.PropTypes = {
|
||||||
width: React.PropTypes.number
|
width: React.PropTypes.number,
|
||||||
}
|
};
|
||||||
|
|
||||||
export default GridFieldHeaderCellComponent;
|
export default GridFieldHeaderCellComponent;
|
||||||
|
@ -14,7 +14,8 @@ import * as actions from 'state/records/actions';
|
|||||||
* The component acts as a container for a grid field,
|
* The component acts as a container for a grid field,
|
||||||
* with smarts around data retrieval from external sources.
|
* with smarts around data retrieval from external sources.
|
||||||
*
|
*
|
||||||
* @todo Convert to higher order component which hooks up form schema data to an API backend as a grid data source
|
* @todo Convert to higher order component which hooks up form
|
||||||
|
* schema data to an API backend as a grid data source
|
||||||
* @todo Replace "dumb" inner components with third party library (e.g. https://griddlegriddle.github.io)
|
* @todo Replace "dumb" inner components with third party library (e.g. https://griddlegriddle.github.io)
|
||||||
*/
|
*/
|
||||||
class GridField extends SilverStripeComponent {
|
class GridField extends SilverStripeComponent {
|
||||||
@ -29,49 +30,59 @@ class GridField extends SilverStripeComponent {
|
|||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
super.componentDidMount();
|
super.componentDidMount();
|
||||||
|
|
||||||
let data = this.props.data;
|
const data = this.props.data;
|
||||||
|
|
||||||
this.props.actions.fetchRecords(data.recordType, data.collectionReadEndpoint.method, data.collectionReadEndpoint.url);
|
this.props.actions.fetchRecords(
|
||||||
|
data.recordType,
|
||||||
|
data.collectionReadEndpoint.method,
|
||||||
|
data.collectionReadEndpoint.url
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const records = this.props.records;
|
const records = this.props.records;
|
||||||
if(!records) {
|
if (!records) {
|
||||||
return <div></div>;
|
return <div></div>;
|
||||||
}
|
}
|
||||||
|
|
||||||
const columns = this.props.data.columns;
|
const columns = this.props.data.columns;
|
||||||
|
|
||||||
// Placeholder to align the headers correctly with the content
|
// Placeholder to align the headers correctly with the content
|
||||||
const actionPlaceholder = <GridFieldCell key={'actionPlaceholder'} />;
|
const actionPlaceholder = <span key={'actionPlaceholder'} />;
|
||||||
const headerCells = columns.map((column, i) => <GridFieldHeaderCell key={i} >{column.name}</GridFieldHeaderCell>);
|
const headerCells = columns.map((column, i) =>
|
||||||
|
<GridFieldHeaderCell key={i}>{column.name}</GridFieldHeaderCell>
|
||||||
|
);
|
||||||
const header = <GridFieldHeader>{headerCells.concat(actionPlaceholder)}</GridFieldHeader>;
|
const header = <GridFieldHeader>{headerCells.concat(actionPlaceholder)}</GridFieldHeader>;
|
||||||
|
|
||||||
const rows = records.map((record, i) => {
|
const rows = records.map((record, i) => {
|
||||||
var cells = columns.map((column, i) => {
|
const cells = columns.map((column, j) => {
|
||||||
// Get value by dot notation
|
// Get value by dot notation
|
||||||
var val = column.field.split('.').reduce((a, b) => a[b], record)
|
const val = column.field.split('.').reduce((a, b) => a[b], record);
|
||||||
return <GridFieldCell key={i}>{val}</GridFieldCell>
|
return <GridFieldCell key={j} width={column.width}>{val}</GridFieldCell>;
|
||||||
});
|
});
|
||||||
|
|
||||||
var rowActions = <GridFieldCell key={i + '-actions'}>
|
const rowActions = (
|
||||||
|
<GridFieldCell key={`${i}-actions`}>
|
||||||
<GridFieldAction
|
<GridFieldAction
|
||||||
icon={'cog'}
|
icon={'cog'}
|
||||||
handleClick={this.editRecord.bind(this, record.ID)}
|
handleClick={this.editRecord}
|
||||||
key={"action-" + i + "-edit"}
|
key={`action-${i}-edit`}
|
||||||
/>
|
record={record}
|
||||||
|
/>,
|
||||||
<GridFieldAction
|
<GridFieldAction
|
||||||
icon={'cancel'}
|
icon={'cancel'}
|
||||||
handleClick={this.deleteRecord.bind(this, record.ID)}
|
handleClick={this.deleteRecord}
|
||||||
key={"action-" + i + "-delete"}
|
key={`action-${i}-delete`}
|
||||||
/>
|
record={record}
|
||||||
</GridFieldCell>;
|
/>,
|
||||||
|
</GridFieldCell>
|
||||||
|
);
|
||||||
|
|
||||||
return <GridFieldRow key={i}>{cells.concat(rowActions)}</GridFieldRow>;
|
return <GridFieldRow key={i}>{cells.concat(rowActions)}</GridFieldRow>;
|
||||||
});
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<GridFieldTable header={header} rows={rows}></GridFieldTable>
|
<GridFieldTable header={header} rows={rows} />
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -79,7 +90,7 @@ class GridField extends SilverStripeComponent {
|
|||||||
* @param number int
|
* @param number int
|
||||||
* @param event
|
* @param event
|
||||||
*/
|
*/
|
||||||
deleteRecord(id, event) {
|
deleteRecord(event, id) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
this.props.actions.deleteRecord(
|
this.props.actions.deleteRecord(
|
||||||
this.props.data.recordType,
|
this.props.data.recordType,
|
||||||
@ -89,7 +100,7 @@ class GridField extends SilverStripeComponent {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
editRecord(id, event) {
|
editRecord(event) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
// TODO
|
// TODO
|
||||||
}
|
}
|
||||||
@ -100,21 +111,21 @@ GridField.propTypes = {
|
|||||||
data: React.PropTypes.shape({
|
data: React.PropTypes.shape({
|
||||||
recordType: React.PropTypes.string.isRequired,
|
recordType: React.PropTypes.string.isRequired,
|
||||||
headerColumns: React.PropTypes.array,
|
headerColumns: React.PropTypes.array,
|
||||||
collectionReadEndpoint: React.PropTypes.object
|
collectionReadEndpoint: React.PropTypes.object,
|
||||||
})
|
}),
|
||||||
};
|
};
|
||||||
|
|
||||||
function mapStateToProps(state, ownProps) {
|
function mapStateToProps(state, ownProps) {
|
||||||
let recordType = ownProps.data ? ownProps.data.recordType : null;
|
const recordType = ownProps.data ? ownProps.data.recordType : null;
|
||||||
return {
|
return {
|
||||||
records: (state.records && recordType) ? state.records[recordType] : []
|
records: (state.records && recordType) ? state.records[recordType] : [],
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function mapDispatchToProps(dispatch) {
|
function mapDispatchToProps(dispatch) {
|
||||||
return {
|
return {
|
||||||
actions: bindActionCreators(actions, dispatch)
|
actions: bindActionCreators(actions, dispatch),
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export default connect(mapStateToProps, mapDispatchToProps)(GridField);
|
export default connect(mapStateToProps, mapDispatchToProps)(GridField);
|
||||||
|
@ -5,7 +5,7 @@ class GridFieldRowComponent extends SilverStripeComponent {
|
|||||||
|
|
||||||
render() {
|
render() {
|
||||||
return (
|
return (
|
||||||
<li className='grid-field-row-component [ list-group-item ]'>{this.props.children}</li>
|
<li className="grid-field-row-component [ list-group-item ]">{this.props.children}</li>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -5,7 +5,7 @@ class GridFieldTableComponent extends SilverStripeComponent {
|
|||||||
|
|
||||||
render() {
|
render() {
|
||||||
return (
|
return (
|
||||||
<ul className='grid-field-table-component [ list-group ]'>
|
<ul className="grid-field-table-component [ list-group ]">
|
||||||
{this.generateHeader()}
|
{this.generateHeader()}
|
||||||
{this.generateRows()}
|
{this.generateRows()}
|
||||||
</ul>
|
</ul>
|
||||||
@ -57,7 +57,7 @@ class GridFieldTableComponent extends SilverStripeComponent {
|
|||||||
GridFieldTableComponent.propTypes = {
|
GridFieldTableComponent.propTypes = {
|
||||||
data: React.PropTypes.object,
|
data: React.PropTypes.object,
|
||||||
header: React.PropTypes.object,
|
header: React.PropTypes.object,
|
||||||
rows: React.PropTypes.array
|
rows: React.PropTypes.array,
|
||||||
};
|
};
|
||||||
|
|
||||||
export default GridFieldTableComponent;
|
export default GridFieldTableComponent;
|
||||||
|
@ -11,7 +11,7 @@ class HiddenFieldComponent extends SilverStripeComponent {
|
|||||||
|
|
||||||
render() {
|
render() {
|
||||||
return (
|
return (
|
||||||
<div className='field hidden'>
|
<div className="field hidden">
|
||||||
<input {...this.getInputProps()} />
|
<input {...this.getInputProps()} />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
@ -24,11 +24,11 @@ class HiddenFieldComponent extends SilverStripeComponent {
|
|||||||
name: this.props.name,
|
name: this.props.name,
|
||||||
onChange: this.props.onChange,
|
onChange: this.props.onChange,
|
||||||
type: 'hidden',
|
type: 'hidden',
|
||||||
value: this.props.value
|
value: this.props.value,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
handleChange(event) {
|
handleChange() {
|
||||||
if (typeof this.props.onChange === 'undefined') {
|
if (typeof this.props.onChange === 'undefined') {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -42,7 +42,7 @@ HiddenFieldComponent.propTypes = {
|
|||||||
extraClass: React.PropTypes.string,
|
extraClass: React.PropTypes.string,
|
||||||
name: React.PropTypes.string.isRequired,
|
name: React.PropTypes.string.isRequired,
|
||||||
onChange: React.PropTypes.func,
|
onChange: React.PropTypes.func,
|
||||||
value: React.PropTypes.string
|
value: React.PropTypes.string,
|
||||||
};
|
};
|
||||||
|
|
||||||
export default HiddenFieldComponent;
|
export default HiddenFieldComponent;
|
||||||
|
@ -20,16 +20,18 @@ class NorthHeaderBreadcrumbsComponent extends SilverStripeComponent {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
var breadcrumbs = this.props.crumbs.map((crumb, index, crumbs) => {
|
const breadcrumbs = this.props.crumbs.map((crumb, index, crumbs) => {
|
||||||
|
let component;
|
||||||
// If its the last item in the array
|
// If its the last item in the array
|
||||||
if (index === crumbs.length - 1) {
|
if (index === crumbs.length - 1) {
|
||||||
return <span key={index} className="crumb last">{crumb.text}</span>;
|
component = <span key={index} className="crumb last">{crumb.text}</span>;
|
||||||
} else {
|
} else {
|
||||||
return [
|
component = [
|
||||||
<a key={index} className="cms-panel-link crumb" href={crumb.href}>{crumb.text}</a>,
|
<a key={index} className="cms-panel-link crumb" href={crumb.href}>{crumb.text}</a>,
|
||||||
<span className="sep">/</span>
|
<span className="sep">/</span>,
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
return component;
|
||||||
});
|
});
|
||||||
|
|
||||||
return breadcrumbs;
|
return breadcrumbs;
|
||||||
|
@ -7,7 +7,7 @@ class NorthHeaderComponent extends SilverStripeComponent {
|
|||||||
render() {
|
render() {
|
||||||
return (
|
return (
|
||||||
<div className="north-header-component">
|
<div className="north-header-component">
|
||||||
<NorthHeaderBreadcrumbsComponent crumbs={this.getBreadcrumbs()}/>
|
<NorthHeaderBreadcrumbsComponent crumbs={this.getBreadcrumbs()} />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -16,13 +16,13 @@ class NorthHeaderComponent extends SilverStripeComponent {
|
|||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
text: 'Campaigns',
|
text: 'Campaigns',
|
||||||
href: 'admin/campaigns'
|
href: 'admin/campaigns',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
text: 'March release',
|
text: 'March release',
|
||||||
href: 'admin/campaigns/show/1'
|
href: 'admin/campaigns/show/1',
|
||||||
}
|
},
|
||||||
]
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -11,13 +11,13 @@ class TextFieldComponent extends SilverStripeComponent {
|
|||||||
|
|
||||||
render() {
|
render() {
|
||||||
return (
|
return (
|
||||||
<div className='field text'>
|
<div className="field text">
|
||||||
{this.props.label &&
|
{this.props.label &&
|
||||||
<label className='left' htmlFor={'gallery_' + this.props.name}>
|
<label className="left" htmlFor={`gallery_${this.props.name}`}>
|
||||||
{this.props.label}
|
{this.props.label}
|
||||||
</label>
|
</label>
|
||||||
}
|
}
|
||||||
<div className='middleColumn'>
|
<div className="middleColumn">
|
||||||
<input {...this.getInputProps()} />
|
<input {...this.getInputProps()} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -31,11 +31,11 @@ class TextFieldComponent extends SilverStripeComponent {
|
|||||||
name: this.props.name,
|
name: this.props.name,
|
||||||
onChange: this.props.onChange,
|
onChange: this.props.onChange,
|
||||||
type: 'text',
|
type: 'text',
|
||||||
value: this.props.value
|
value: this.props.value,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
handleChange(event) {
|
handleChange() {
|
||||||
if (typeof this.props.onChange === 'undefined') {
|
if (typeof this.props.onChange === 'undefined') {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -49,7 +49,7 @@ TextFieldComponent.propTypes = {
|
|||||||
extraClass: React.PropTypes.string,
|
extraClass: React.PropTypes.string,
|
||||||
name: React.PropTypes.string.isRequired,
|
name: React.PropTypes.string.isRequired,
|
||||||
onChange: React.PropTypes.func,
|
onChange: React.PropTypes.func,
|
||||||
value: React.PropTypes.string
|
value: React.PropTypes.string,
|
||||||
};
|
};
|
||||||
|
|
||||||
export default TextFieldComponent;
|
export default TextFieldComponent;
|
||||||
|
@ -23,15 +23,13 @@ class Config {
|
|||||||
* @return array
|
* @return array
|
||||||
*/
|
*/
|
||||||
static getTopLevelRoutes() {
|
static getTopLevelRoutes() {
|
||||||
var topLevelRoutes = [];
|
const topLevelRoutes = [];
|
||||||
|
|
||||||
Object.keys(window.ss.config.sections).forEach((key) => {
|
Object.keys(window.ss.config.sections).forEach((key) => {
|
||||||
const route = window.ss.config.sections[key].route;
|
const route = window.ss.config.sections[key].route;
|
||||||
const isTopLevelRoute = route.indexOf('/') === -1;
|
const isTopLevelRoute = route.indexOf('/') === -1;
|
||||||
const isUnique = topLevelRoutes.indexOf(route) === -1;
|
const isUnique = topLevelRoutes.indexOf(route) === -1;
|
||||||
|
|
||||||
//console.log(this.getSection(key).route);
|
|
||||||
|
|
||||||
if (isTopLevelRoute && isUnique) {
|
if (isTopLevelRoute && isUnique) {
|
||||||
topLevelRoutes.push(route);
|
topLevelRoutes.push(route);
|
||||||
}
|
}
|
||||||
|
@ -1,15 +1,17 @@
|
|||||||
|
/* globals jest */
|
||||||
|
|
||||||
function jQuery() {
|
function jQuery() {
|
||||||
return {
|
return {
|
||||||
// Add jQuery methods such as 'find', 'change', 'trigger' as needed.
|
// Add jQuery methods such as 'find', 'change', 'trigger' as needed.
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
var mockAjaxFn = jest.genMockFunction();
|
const mockAjaxFn = jest.genMockFunction();
|
||||||
|
|
||||||
mockAjaxFn.mockReturnValue({
|
mockAjaxFn.mockReturnValue({
|
||||||
done: jest.genMockFunction(),
|
done: jest.genMockFunction(),
|
||||||
fail: jest.genMockFunction(),
|
fail: jest.genMockFunction(),
|
||||||
always: jest.genMockFunction()
|
always: jest.genMockFunction(),
|
||||||
});
|
});
|
||||||
|
|
||||||
jQuery.ajax = mockAjaxFn;
|
jQuery.ajax = mockAjaxFn;
|
||||||
|
@ -1,15 +1,5 @@
|
|||||||
import { Component } from 'react';
|
import { Component } from 'react';
|
||||||
|
|
||||||
|
// eslint-disable-next-line react/prefer-stateless-function
|
||||||
export default class SilverStripeComponent extends Component {
|
export default class SilverStripeComponent extends Component {
|
||||||
constructor(props) {
|
}
|
||||||
super(props);
|
|
||||||
}
|
|
||||||
|
|
||||||
componentDidMount() {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
componentWillUnmount() {
|
|
||||||
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
@ -2,10 +2,11 @@
|
|||||||
* The register of Redux reducers.
|
* The register of Redux reducers.
|
||||||
* @private
|
* @private
|
||||||
*/
|
*/
|
||||||
var register = {};
|
const register = {};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The central register of Redux reducers for the CMS. All registered reducers are combined when the application boots.
|
* The central register of Redux reducers for the CMS.
|
||||||
|
* All registered reducers are combined when the application boots.
|
||||||
*/
|
*/
|
||||||
class ReducerRegister {
|
class ReducerRegister {
|
||||||
|
|
||||||
@ -43,8 +44,6 @@ class ReducerRegister {
|
|||||||
return register[key];
|
return register[key];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Removes a reducer from the register.
|
* Removes a reducer from the register.
|
||||||
*
|
*
|
||||||
@ -59,6 +58,6 @@ class ReducerRegister {
|
|||||||
// Create an instance to export. The same instance is exported to
|
// Create an instance to export. The same instance is exported to
|
||||||
// each script which imports the reducerRegister. This means the
|
// each script which imports the reducerRegister. This means the
|
||||||
// same register is available throughout the application.
|
// same register is available throughout the application.
|
||||||
let reducerRegister = new ReducerRegister();
|
const reducerRegister = new ReducerRegister();
|
||||||
|
|
||||||
export default reducerRegister;
|
export default reducerRegister;
|
||||||
|
@ -23,14 +23,15 @@ class CampaignAdminContainer extends SilverStripeComponent {
|
|||||||
<FormAction
|
<FormAction
|
||||||
label={i18n._t('Campaigns.ADDCAMPAIGN')}
|
label={i18n._t('Campaigns.ADDCAMPAIGN')}
|
||||||
icon={'plus-circled'}
|
icon={'plus-circled'}
|
||||||
handleClick={this.addCampaign} />
|
handleClick={this.addCampaign}
|
||||||
|
/>
|
||||||
<FormBuilder schemaUrl={schemaUrl} />
|
<FormBuilder schemaUrl={schemaUrl} />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
addCampaign() {
|
addCampaign() {
|
||||||
//Add campaign
|
// Add campaign
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@ -39,17 +40,17 @@ CampaignAdminContainer.propTypes = {
|
|||||||
config: React.PropTypes.shape({
|
config: React.PropTypes.shape({
|
||||||
forms: React.PropTypes.shape({
|
forms: React.PropTypes.shape({
|
||||||
editForm: React.PropTypes.shape({
|
editForm: React.PropTypes.shape({
|
||||||
schemaUrl: React.PropTypes.string
|
schemaUrl: React.PropTypes.string,
|
||||||
})
|
|
||||||
})
|
|
||||||
}),
|
}),
|
||||||
sectionConfigKey: React.PropTypes.string.isRequired
|
}),
|
||||||
|
}),
|
||||||
|
sectionConfigKey: React.PropTypes.string.isRequired,
|
||||||
};
|
};
|
||||||
|
|
||||||
function mapStateToProps(state, ownProps) {
|
function mapStateToProps(state, ownProps) {
|
||||||
return {
|
return {
|
||||||
config: state.config.sections[ownProps.sectionConfigKey]
|
config: state.config.sections[ownProps.sectionConfigKey],
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export default connect(mapStateToProps)(CampaignAdminContainer);
|
export default connect(mapStateToProps)(CampaignAdminContainer);
|
||||||
|
@ -1,24 +1,22 @@
|
|||||||
import reducerRegister from 'reducer-register';
|
|
||||||
import $ from 'jQuery';
|
import $ from 'jQuery';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import ReactDOM from 'react-dom';
|
import ReactDOM from 'react-dom';
|
||||||
import { Provider } from 'react-redux';
|
import { Provider } from 'react-redux';
|
||||||
import CampaignAdmin from './controller';
|
import CampaignAdmin from './controller';
|
||||||
|
|
||||||
$.entwine('ss', function ($) {
|
// eslint-disable-next-line no-shadow
|
||||||
|
$.entwine('ss', ($) => {
|
||||||
$('.cms-content.CampaignAdmin').entwine({
|
$('.cms-content.CampaignAdmin').entwine({
|
||||||
onadd: function () {
|
onadd() {
|
||||||
ReactDOM.render(
|
ReactDOM.render(
|
||||||
<Provider store={window.store}>
|
<Provider store={window.store}>
|
||||||
<CampaignAdmin sectionConfigKey='CampaignAdmin' />
|
<CampaignAdmin sectionConfigKey="CampaignAdmin" />
|
||||||
</Provider>
|
</Provider>
|
||||||
, this[0]);
|
, this[0]);
|
||||||
},
|
},
|
||||||
|
|
||||||
onremove: function () {
|
onremove() {
|
||||||
ReactDOM.unmountComponentAtNode(this[0]);
|
ReactDOM.unmountComponentAtNode(this[0]);
|
||||||
}
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
|
@ -6,13 +6,17 @@ es6promise.polyfill();
|
|||||||
* @see https://github.com/github/fetch#handling-http-error-statuses
|
* @see https://github.com/github/fetch#handling-http-error-statuses
|
||||||
*/
|
*/
|
||||||
function checkStatus(response) {
|
function checkStatus(response) {
|
||||||
|
let ret;
|
||||||
|
let error;
|
||||||
if (response.status >= 200 && response.status < 300) {
|
if (response.status >= 200 && response.status < 300) {
|
||||||
return response
|
ret = response;
|
||||||
} else {
|
} else {
|
||||||
var error = new Error(response.statusText)
|
error = new Error(response.statusText);
|
||||||
error.response = response
|
error.response = response;
|
||||||
throw error
|
throw error;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
class SilverStripeBackend {
|
class SilverStripeBackend {
|
||||||
@ -73,6 +77,6 @@ class SilverStripeBackend {
|
|||||||
|
|
||||||
// Exported as a singleton so we can implement things like
|
// Exported as a singleton so we can implement things like
|
||||||
// global caching and request batching at some stage.
|
// global caching and request batching at some stage.
|
||||||
let backend = new SilverStripeBackend();
|
const backend = new SilverStripeBackend();
|
||||||
|
|
||||||
export default backend;
|
export default backend;
|
||||||
|
@ -2,16 +2,41 @@
|
|||||||
* @file Base component which all SilverStripe ReactJS components should extend from.
|
* @file Base component which all SilverStripe ReactJS components should extend from.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React, { PropTypes, Component } from 'react';
|
import React, { Component } from 'react';
|
||||||
import $ from '../../../javascript/src/jQuery';
|
import $ from '../../../javascript/src/jQuery';
|
||||||
|
|
||||||
class SilverStripeComponent extends Component {
|
class SilverStripeComponent extends Component {
|
||||||
|
|
||||||
/**
|
constructor(props) {
|
||||||
* @func componentDidMount
|
super(props);
|
||||||
* @desc Bind event listeners which are triggered by legacy-land JavaScript.
|
|
||||||
* This lets us update the component when something happens in the outside world.
|
// Setup component routing.
|
||||||
*/
|
if (typeof this.props.route !== 'undefined') {
|
||||||
|
// The component's render method gets switched based on the current path.
|
||||||
|
// If the current path matches the component's route, the component is displayed.
|
||||||
|
// Otherwise the component's render method returns null, resulting in
|
||||||
|
// the component not rendering.
|
||||||
|
this._render = this.render;
|
||||||
|
|
||||||
|
this.render = () => {
|
||||||
|
let component = null;
|
||||||
|
|
||||||
|
if (this.isComponentRoute()) {
|
||||||
|
component = this._render();
|
||||||
|
}
|
||||||
|
|
||||||
|
return component;
|
||||||
|
};
|
||||||
|
|
||||||
|
window.ss.router(this.props.route, (ctx, next) => {
|
||||||
|
this.handleEnterRoute(ctx, next);
|
||||||
|
});
|
||||||
|
window.ss.router.exit(this.props.route, (ctx, next) => {
|
||||||
|
this.handleExitRoute(ctx, next);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
if (typeof this.props.cmsEvents === 'undefined') {
|
if (typeof this.props.cmsEvents === 'undefined') {
|
||||||
return;
|
return;
|
||||||
@ -21,26 +46,53 @@ class SilverStripeComponent extends Component {
|
|||||||
// there's no guarantee these props will be the same or even present.
|
// there's no guarantee these props will be the same or even present.
|
||||||
this.cmsEvents = this.props.cmsEvents;
|
this.cmsEvents = this.props.cmsEvents;
|
||||||
|
|
||||||
for (let cmsEvent in this.cmsEvents) {
|
// Bind event listeners which are triggered by legacy-land JavaScript.
|
||||||
|
// This lets us update the component when something happens in the outside world.
|
||||||
|
for (const cmsEvent in this.cmsEvents) {
|
||||||
|
if ({}.hasOwnProperty.call(this.cmsEvents, cmsEvent)) {
|
||||||
$(document).on(cmsEvent, this.cmsEvents[cmsEvent].bind(this));
|
$(document).on(cmsEvent, this.cmsEvents[cmsEvent].bind(this));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @func componentWillUnmount
|
|
||||||
* @desc Unbind the event listeners we added in componentDidMount.
|
|
||||||
*/
|
|
||||||
componentWillUnmount() {
|
componentWillUnmount() {
|
||||||
for (let cmsEvent in this.cmsEvents) {
|
// Unbind the event listeners we added in componentDidMount.
|
||||||
|
for (const cmsEvent in this.cmsEvents) {
|
||||||
|
if ({}.hasOwnProperty.call(this.cmsEvents, cmsEvent)) {
|
||||||
$(document).off(cmsEvent);
|
$(document).off(cmsEvent);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
handleEnterRoute(ctx, next) {
|
||||||
|
next();
|
||||||
|
}
|
||||||
|
|
||||||
|
handleExitRoute(ctx, next) {
|
||||||
|
next();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @func emitCmsEvent
|
* Checks if the component should be rended on the current path.
|
||||||
|
*
|
||||||
|
* @param object [params] - If a params object is passed in it's
|
||||||
|
* mutated by page.js to contains route parans like ':id'.
|
||||||
|
*/
|
||||||
|
isComponentRoute(params = {}) {
|
||||||
|
if (typeof this.props.route === 'undefined') {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
const route = new window.ss.router.Route(this.props.route);
|
||||||
|
|
||||||
|
return route.match(window.ss.router.current, params);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Notifies legacy-land something has changed within our component.
|
||||||
|
*
|
||||||
* @param string componentEvent - Namespace component event e.g. 'my-component.title-changed'.
|
* @param string componentEvent - Namespace component event e.g. 'my-component.title-changed'.
|
||||||
* @param object|string|array|number [data] - Some data to pass with the event.
|
* @param object|string|array|number [data] - Some data to pass with the event.
|
||||||
* @desc Notifies legacy-land something has changed within our component.
|
|
||||||
*/
|
*/
|
||||||
emitCmsEvent(componentEvent, data) {
|
emitCmsEvent(componentEvent, data) {
|
||||||
$(document).trigger(componentEvent, data);
|
$(document).trigger(componentEvent, data);
|
||||||
@ -49,7 +101,8 @@ class SilverStripeComponent extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
SilverStripeComponent.propTypes = {
|
SilverStripeComponent.propTypes = {
|
||||||
'cmsEvents': React.PropTypes.object
|
cmsEvents: React.PropTypes.object,
|
||||||
|
route: React.PropTypes.string,
|
||||||
};
|
};
|
||||||
|
|
||||||
export default SilverStripeComponent;
|
export default SilverStripeComponent;
|
||||||
|
@ -1,3 +1,3 @@
|
|||||||
export default {
|
export default {
|
||||||
SET_CONFIG: 'SET_CONFIG'
|
SET_CONFIG: 'SET_CONFIG',
|
||||||
}
|
};
|
||||||
|
@ -6,10 +6,9 @@ import ACTION_TYPES from './action-types';
|
|||||||
* @param object config
|
* @param object config
|
||||||
*/
|
*/
|
||||||
export function setConfig(config) {
|
export function setConfig(config) {
|
||||||
return (dispatch, getState) => {
|
return (dispatch) =>
|
||||||
return dispatch({
|
dispatch({
|
||||||
type: ACTION_TYPES.SET_CONFIG,
|
type: ACTION_TYPES.SET_CONFIG,
|
||||||
payload: { config }
|
payload: { config },
|
||||||
});
|
});
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -2,7 +2,6 @@ import deepFreeze from 'deep-freeze';
|
|||||||
import ACTION_TYPES from './action-types';
|
import ACTION_TYPES from './action-types';
|
||||||
|
|
||||||
function configReducer(state = {}, action) {
|
function configReducer(state = {}, action) {
|
||||||
|
|
||||||
switch (action.type) {
|
switch (action.type) {
|
||||||
|
|
||||||
case ACTION_TYPES.SET_CONFIG:
|
case ACTION_TYPES.SET_CONFIG:
|
||||||
@ -12,7 +11,6 @@ function configReducer(state = {}, action) {
|
|||||||
return state;
|
return state;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default configReducer;
|
export default configReducer;
|
||||||
|
@ -7,5 +7,5 @@ export default {
|
|||||||
FETCH_RECORDS_SUCCESS: 'FETCH_RECORDS_SUCCESS',
|
FETCH_RECORDS_SUCCESS: 'FETCH_RECORDS_SUCCESS',
|
||||||
DELETE_RECORD_REQUEST: 'DELETE_RECORD_REQUEST',
|
DELETE_RECORD_REQUEST: 'DELETE_RECORD_REQUEST',
|
||||||
DELETE_RECORD_FAILURE: 'DELETE_RECORD_FAILURE',
|
DELETE_RECORD_FAILURE: 'DELETE_RECORD_FAILURE',
|
||||||
DELETE_RECORD_SUCCESS: 'DELETE_RECORD_SUCCESS'
|
DELETE_RECORD_SUCCESS: 'DELETE_RECORD_SUCCESS',
|
||||||
};
|
};
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
import ACTION_TYPES from './action-types';
|
import ACTION_TYPES from './action-types';
|
||||||
import fetch from 'isomorphic-fetch';
|
|
||||||
import backend from 'silverstripe-backend.js';
|
import backend from 'silverstripe-backend.js';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -14,8 +13,8 @@ import backend from 'silverstripe-backend.js';
|
|||||||
* @return string
|
* @return string
|
||||||
*/
|
*/
|
||||||
function populate(str, params) {
|
function populate(str, params) {
|
||||||
let names = ['id'];
|
const names = ['id'];
|
||||||
return names.reduce((str, name) => str.replace(`:${name}`, params[name]), str);
|
return names.reduce((acc, name) => acc.replace(`:${name}`, params[name]), str);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -26,19 +25,27 @@ function populate(str, params) {
|
|||||||
* @param string url API endpoint
|
* @param string url API endpoint
|
||||||
*/
|
*/
|
||||||
export function fetchRecords(recordType, method, url) {
|
export function fetchRecords(recordType, method, url) {
|
||||||
let payload = {recordType: recordType};
|
const payload = { recordType };
|
||||||
url = populate(url, payload);
|
return (dispatch) => {
|
||||||
return (dispatch, getState) => {
|
dispatch({
|
||||||
dispatch ({type: ACTION_TYPES.FETCH_RECORDS_REQUEST, payload: payload});
|
type: ACTION_TYPES.FETCH_RECORDS_REQUEST,
|
||||||
return backend[method.toLowerCase()](url)
|
payload,
|
||||||
|
});
|
||||||
|
return backend[method.toLowerCase()](populate(url, payload))
|
||||||
.then(response => response.json())
|
.then(response => response.json())
|
||||||
.then(json => {
|
.then(json => {
|
||||||
dispatch({type: ACTION_TYPES.FETCH_RECORDS_SUCCESS, payload: {recordType: recordType, data: json}})
|
dispatch({
|
||||||
|
type: ACTION_TYPES.FETCH_RECORDS_SUCCESS,
|
||||||
|
payload: { recordType, data: json },
|
||||||
|
});
|
||||||
})
|
})
|
||||||
.catch((err) => {
|
.catch((err) => {
|
||||||
dispatch({type: ACTION_TYPES.FETCH_RECORDS_FAILURE, payload: {error: err, recordType: recordType}})
|
dispatch({
|
||||||
|
type: ACTION_TYPES.FETCH_RECORDS_FAILURE,
|
||||||
|
payload: { error: err, recordType },
|
||||||
});
|
});
|
||||||
}
|
});
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -50,16 +57,24 @@ export function fetchRecords(recordType, method, url) {
|
|||||||
* @param string url API endpoint
|
* @param string url API endpoint
|
||||||
*/
|
*/
|
||||||
export function deleteRecord(recordType, id, method, url) {
|
export function deleteRecord(recordType, id, method, url) {
|
||||||
let payload = {recordType: recordType, id: id};
|
const payload = { recordType, id };
|
||||||
url = populate(url, payload);
|
return (dispatch) => {
|
||||||
return (dispatch, getState) => {
|
dispatch({
|
||||||
dispatch ({type: ACTION_TYPES.DELETE_RECORD_REQUEST, payload: payload});
|
type: ACTION_TYPES.DELETE_RECORD_REQUEST,
|
||||||
return backend[method.toLowerCase()](url)
|
payload,
|
||||||
.then(json => {
|
});
|
||||||
dispatch({type: ACTION_TYPES.DELETE_RECORD_SUCCESS, payload: {recordType: recordType, id: id}})
|
return backend[method.toLowerCase()](populate(url, payload))
|
||||||
|
.then(() => {
|
||||||
|
dispatch({
|
||||||
|
type: ACTION_TYPES.DELETE_RECORD_SUCCESS,
|
||||||
|
payload: { recordType, id },
|
||||||
|
});
|
||||||
})
|
})
|
||||||
.catch((err) => {
|
.catch((err) => {
|
||||||
dispatch({type: ACTION_TYPES.DELETE_RECORD_FAILURE, payload: {error: err, recordType: recordType, id: id}})
|
dispatch({
|
||||||
|
type: ACTION_TYPES.DELETE_RECORD_FAILURE,
|
||||||
|
payload: { error: err, recordType, id },
|
||||||
});
|
});
|
||||||
}
|
});
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
@ -34,9 +34,9 @@ function recordsReducer(state = initialState, action) {
|
|||||||
case ACTION_TYPES.FETCH_RECORDS_SUCCESS:
|
case ACTION_TYPES.FETCH_RECORDS_SUCCESS:
|
||||||
recordType = action.payload.recordType;
|
recordType = action.payload.recordType;
|
||||||
// TODO Automatic pluralisation from recordType
|
// TODO Automatic pluralisation from recordType
|
||||||
records = action.payload.data._embedded[recordType + 's'];
|
records = action.payload.data._embedded[`${recordType}s`];
|
||||||
return deepFreeze(Object.assign({}, state, {
|
return deepFreeze(Object.assign({}, state, {
|
||||||
[recordType]: records
|
[recordType]: records,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
case ACTION_TYPES.DELETE_RECORD_REQUEST:
|
case ACTION_TYPES.DELETE_RECORD_REQUEST:
|
||||||
@ -48,16 +48,15 @@ function recordsReducer(state = initialState, action) {
|
|||||||
case ACTION_TYPES.DELETE_RECORD_SUCCESS:
|
case ACTION_TYPES.DELETE_RECORD_SUCCESS:
|
||||||
recordType = action.payload.recordType;
|
recordType = action.payload.recordType;
|
||||||
records = state[recordType]
|
records = state[recordType]
|
||||||
.filter(record => record.ID != action.payload.id)
|
.filter(record => record.ID !== action.payload.id);
|
||||||
|
|
||||||
return deepFreeze(Object.assign({}, state, {
|
return deepFreeze(Object.assign({}, state, {
|
||||||
[recordType]: records
|
[recordType]: records,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
default:
|
default:
|
||||||
return state;
|
return state;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default recordsReducer;
|
export default recordsReducer;
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
const ACTION_TYPES = {
|
const ACTION_TYPES = {
|
||||||
SET_SCHEMA: 'SET_SCHEMA'
|
SET_SCHEMA: 'SET_SCHEMA',
|
||||||
};
|
};
|
||||||
|
|
||||||
export default ACTION_TYPES;
|
export default ACTION_TYPES;
|
||||||
|
@ -6,10 +6,9 @@ import ACTION_TYPES from './action-types';
|
|||||||
* @param string schema - JSON schema for the layout.
|
* @param string schema - JSON schema for the layout.
|
||||||
*/
|
*/
|
||||||
export function setSchema(schema) {
|
export function setSchema(schema) {
|
||||||
return (dispatch, getState) => {
|
return (dispatch) =>
|
||||||
return dispatch ({
|
dispatch({
|
||||||
type: ACTION_TYPES.SET_SCHEMA,
|
type: ACTION_TYPES.SET_SCHEMA,
|
||||||
payload: schema
|
payload: schema,
|
||||||
});
|
});
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -4,15 +4,14 @@ import ACTION_TYPES from './action-types';
|
|||||||
const initialState = deepFreeze({});
|
const initialState = deepFreeze({});
|
||||||
|
|
||||||
export default function schemaReducer(state = initialState, action = null) {
|
export default function schemaReducer(state = initialState, action = null) {
|
||||||
|
|
||||||
switch (action.type) {
|
switch (action.type) {
|
||||||
|
|
||||||
case ACTION_TYPES.SET_SCHEMA:
|
case ACTION_TYPES.SET_SCHEMA: {
|
||||||
const id = action.payload.schema.schema_url;
|
const id = action.payload.schema.schema_url;
|
||||||
return deepFreeze(Object.assign({}, state, {[id]: action.payload}));
|
return deepFreeze(Object.assign({}, state, { [id]: action.payload }));
|
||||||
|
}
|
||||||
|
|
||||||
default:
|
default:
|
||||||
return state;
|
return state;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
71
gulpfile.js
71
gulpfile.js
@ -1,29 +1,31 @@
|
|||||||
var packageJson = require('./package.json'),
|
const packageJson = require('./package.json');
|
||||||
autoprefixer = require('autoprefixer'),
|
const autoprefixer = require('autoprefixer');
|
||||||
babelify = require('babelify'),
|
const babelify = require('babelify'); // eslint-disable-line no-unused-vars
|
||||||
browserify = require('browserify'),
|
const browserify = require('browserify');
|
||||||
eventStream = require('event-stream'),
|
const eventStream = require('event-stream');
|
||||||
glob = require('glob'),
|
const glob = require('glob');
|
||||||
gulp = require('gulp'),
|
const gulp = require('gulp');
|
||||||
babel = require('gulp-babel'),
|
const babel = require('gulp-babel');
|
||||||
diff = require('gulp-diff'),
|
const diff = require('gulp-diff');
|
||||||
gulpif = require('gulp-if'),
|
const gulpif = require('gulp-if');
|
||||||
notify = require('gulp-notify'),
|
const notify = require('gulp-notify');
|
||||||
postcss = require('gulp-postcss'),
|
const postcss = require('gulp-postcss');
|
||||||
sass = require('gulp-sass'),
|
const sass = require('gulp-sass');
|
||||||
sourcemaps = require('gulp-sourcemaps'),
|
const sourcemaps = require('gulp-sourcemaps');
|
||||||
uglify = require('gulp-uglify'),
|
const uglify = require('gulp-uglify');
|
||||||
gulpUtil = require('gulp-util'),
|
const gulpUtil = require('gulp-util');
|
||||||
path = require('path'),
|
const path = require('path');
|
||||||
source = require('vinyl-source-stream'),
|
const source = require('vinyl-source-stream');
|
||||||
buffer = require('vinyl-buffer'),
|
const buffer = require('vinyl-buffer');
|
||||||
semver = require('semver'),
|
const semver = require('semver');
|
||||||
sprity = require('sprity'),
|
const sprity = require('sprity');
|
||||||
watchify = require('watchify');
|
const watchify = require('watchify');
|
||||||
|
|
||||||
var isDev = typeof process.env.npm_config_development !== 'undefined';
|
const isDev = typeof process.env.npm_config_development !== 'undefined';
|
||||||
|
|
||||||
var PATHS = {
|
process.env.NODE_ENV = isDev ? 'development' : 'production';
|
||||||
|
|
||||||
|
const PATHS = {
|
||||||
MODULES: './node_modules',
|
MODULES: './node_modules',
|
||||||
ADMIN: './admin',
|
ADMIN: './admin',
|
||||||
ADMIN_IMAGES: './admin/images',
|
ADMIN_IMAGES: './admin/images',
|
||||||
@ -35,25 +37,25 @@ var PATHS = {
|
|||||||
FRAMEWORK_THIRDPARTY: './thirdparty',
|
FRAMEWORK_THIRDPARTY: './thirdparty',
|
||||||
FRAMEWORK_DEV_INSTALL: './dev/install',
|
FRAMEWORK_DEV_INSTALL: './dev/install',
|
||||||
FRAMEWORK_JAVASCRIPT_SRC: './javascript/src',
|
FRAMEWORK_JAVASCRIPT_SRC: './javascript/src',
|
||||||
FRAMEWORK_JAVASCRIPT_DIST: './javascript/dist'
|
FRAMEWORK_JAVASCRIPT_DIST: './javascript/dist',
|
||||||
};
|
};
|
||||||
|
|
||||||
// Folders which contain both scss and css folders to be compiled
|
// Folders which contain both scss and css folders to be compiled
|
||||||
var rootCompileFolders = [PATHS.FRAMEWORK, PATHS.ADMIN, PATHS.FRAMEWORK_DEV_INSTALL]
|
const rootCompileFolders = [PATHS.FRAMEWORK, PATHS.ADMIN, PATHS.FRAMEWORK_DEV_INSTALL];
|
||||||
|
|
||||||
var browserifyOptions = {
|
const browserifyOptions = {
|
||||||
debug: true,
|
debug: true,
|
||||||
paths: [PATHS.ADMIN_JAVASCRIPT_SRC, PATHS.FRAMEWORK_JAVASCRIPT_SRC]
|
paths: [PATHS.ADMIN_JAVASCRIPT_SRC, PATHS.FRAMEWORK_JAVASCRIPT_SRC],
|
||||||
};
|
};
|
||||||
|
|
||||||
var babelifyOptions = {
|
const babelifyOptions = {
|
||||||
presets: ['es2015', 'react'],
|
presets: ['es2015', 'react'],
|
||||||
ignore: /(node_modules|thirdparty)/,
|
ignore: /(node_modules|thirdparty)/,
|
||||||
comments: false
|
comments: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
// Used for autoprefixing css properties (same as Bootstrap Aplha.2 defaults)
|
// Used for autoprefixing css properties (same as Bootstrap Aplha.2 defaults)
|
||||||
var supportedBrowsers = [
|
const supportedBrowsers = [
|
||||||
'Chrome >= 35',
|
'Chrome >= 35',
|
||||||
'Firefox >= 31',
|
'Firefox >= 31',
|
||||||
'Edge >= 12',
|
'Edge >= 12',
|
||||||
@ -62,11 +64,11 @@ var supportedBrowsers = [
|
|||||||
'Safari >= 8',
|
'Safari >= 8',
|
||||||
'Android 2.3',
|
'Android 2.3',
|
||||||
'Android >= 4',
|
'Android >= 4',
|
||||||
'Opera >= 12'
|
'Opera >= 12',
|
||||||
];
|
];
|
||||||
|
|
||||||
var blueimpFileUploadConfig = {
|
const blueimpFileUploadConfig = {
|
||||||
src: PATHS.MODULES + '/blueimp-file-upload',
|
src: `${PATHS.MODULES}/blueimp-file-upload`,
|
||||||
dest: PATHS.FRAMEWORK_THIRDPARTY + '/jquery-fileupload',
|
dest: PATHS.FRAMEWORK_THIRDPARTY + '/jquery-fileupload',
|
||||||
files: [
|
files: [
|
||||||
'/cors/jquery.postmessage-transport.js',
|
'/cors/jquery.postmessage-transport.js',
|
||||||
@ -219,7 +221,6 @@ gulp.task('bundle-lib', function bundleLib() {
|
|||||||
.require(PATHS.FRAMEWORK_JAVASCRIPT_SRC + '/router.js', { expose: 'router' })
|
.require(PATHS.FRAMEWORK_JAVASCRIPT_SRC + '/router.js', { expose: 'router' })
|
||||||
.require(PATHS.ADMIN_JAVASCRIPT_SRC + '/silverstripe-component', { expose: 'silverstripe-component' })
|
.require(PATHS.ADMIN_JAVASCRIPT_SRC + '/silverstripe-component', { expose: 'silverstripe-component' })
|
||||||
.bundle()
|
.bundle()
|
||||||
.on('update', bundleLib)
|
|
||||||
.on('error', notify.onError({ message: bundleFileName + ': <%= error.message %>' }))
|
.on('error', notify.onError({ message: bundleFileName + ': <%= error.message %>' }))
|
||||||
.pipe(source(bundleFileName))
|
.pipe(source(bundleFileName))
|
||||||
.pipe(buffer())
|
.pipe(buffer())
|
||||||
|
@ -12,15 +12,16 @@ function show(pageShow) {
|
|||||||
// Normalise `path` so that pattern matching is more robust.
|
// Normalise `path` so that pattern matching is more robust.
|
||||||
// For example if your route is '/pages' it should match when `path` is
|
// For example if your route is '/pages' it should match when `path` is
|
||||||
// 'http://foo.com/admin/pages', '/pages', and 'pages'.
|
// 'http://foo.com/admin/pages', '/pages', and 'pages'.
|
||||||
var el = document.createElement('a');
|
const el = document.createElement('a');
|
||||||
|
let pathWithSearch;
|
||||||
el.href = path;
|
el.href = path;
|
||||||
path = el.pathname;
|
pathWithSearch = el.pathname;
|
||||||
if(el.search) {
|
if (el.search) {
|
||||||
path += el.search;
|
pathWithSearch += el.search;
|
||||||
}
|
}
|
||||||
|
|
||||||
return pageShow(path, state, dispatch, push);
|
return pageShow(pathWithSearch, state, dispatch, push);
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
page.show = show(page.show);
|
page.show = show(page.show);
|
||||||
|
@ -17,7 +17,8 @@
|
|||||||
"sprites": "gulp sprites",
|
"sprites": "gulp sprites",
|
||||||
"test": "NODE_PATH=\"./javascript/src:./admin/javascript/src\" jest",
|
"test": "NODE_PATH=\"./javascript/src:./admin/javascript/src\" jest",
|
||||||
"coverage": "NODE_PATH=\"./javascript/src:./admin/javascript/src\" jest --coverage",
|
"coverage": "NODE_PATH=\"./javascript/src:./admin/javascript/src\" jest --coverage",
|
||||||
"thirdparty": "gulp thirdparty"
|
"thirdparty": "gulp thirdparty",
|
||||||
|
"lint": "eslint javascript/src & eslint admin/javascript/src"
|
||||||
},
|
},
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
@ -60,6 +61,9 @@
|
|||||||
"babel-preset-react": "^6.5.0",
|
"babel-preset-react": "^6.5.0",
|
||||||
"babelify": "^7.2.0",
|
"babelify": "^7.2.0",
|
||||||
"browserify": "^13.0.0",
|
"browserify": "^13.0.0",
|
||||||
|
"eslint": "^2.5.3",
|
||||||
|
"eslint-config-airbnb": "^6.2.0",
|
||||||
|
"eslint-plugin-react": "^4.2.3",
|
||||||
"event-stream": "^3.3.2",
|
"event-stream": "^3.3.2",
|
||||||
"glob": "^6.0.4",
|
"glob": "^6.0.4",
|
||||||
"gulp": "^3.9.0",
|
"gulp": "^3.9.0",
|
||||||
|
Loading…
Reference in New Issue
Block a user