diff --git a/.editorconfig b/.editorconfig
index a133be8b2..66e2e1452 100644
--- a/.editorconfig
+++ b/.editorconfig
@@ -17,6 +17,10 @@ trim_trailing_whitespace = false
indent_size = 2
indent_style = space
+[*.js]
+indent_size = 2
+indent_style = space
+
# Don't perform any clean-up on thirdparty files
[thirdparty/**]
diff --git a/.eslintignore b/.eslintignore
new file mode 100644
index 000000000..366bc945f
--- /dev/null
+++ b/.eslintignore
@@ -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/
diff --git a/.eslintrc b/.eslintrc
new file mode 100644
index 000000000..b0c0c8bda
--- /dev/null
+++ b/.eslintrc
@@ -0,0 +1,3 @@
+{
+ "extends": "airbnb"
+}
diff --git a/admin/javascript/src/LeftAndMain.FieldDescriptionToggle.js b/admin/javascript/src/LeftAndMain.FieldDescriptionToggle.js
index 6a446e6c6..5a7f66097 100644
--- a/admin/javascript/src/LeftAndMain.FieldDescriptionToggle.js
+++ b/admin/javascript/src/LeftAndMain.FieldDescriptionToggle.js
@@ -6,38 +6,38 @@ import $ from 'jQuery';
$.entwine('ss', function ($) {
- $('.cms-description-toggle').entwine({
- onadd: function () {
- var shown = false, // Current state of the description.
- fieldId = this.prop('id').substr(0, this.prop('id').indexOf('_Holder')),
- $trigger = this.find('.cms-description-trigger'), // Click target for toggling the description.
- $description = this.find('.description');
+ $('.cms-description-toggle').entwine({
+ onadd: function () {
+ var shown = false, // Current state of the description.
+ fieldId = this.prop('id').substr(0, this.prop('id').indexOf('_Holder')),
+ $trigger = this.find('.cms-description-trigger'), // Click target for toggling the description.
+ $description = this.find('.description');
- // Prevent multiple events being added.
- if (this.hasClass('description-toggle-enabled')) {
- return;
- }
+ // Prevent multiple events being added.
+ if (this.hasClass('description-toggle-enabled')) {
+ return;
+ }
- // If a custom trigger han't been supplied use a sensible default.
- if ($trigger.length === 0) {
- $trigger = this
- .find('.middleColumn')
- .first() // Get the first middleColumn so we don't add multiple triggers on composite field types.
- .after(' ')
- .next();
- }
+ // If a custom trigger han't been supplied use a sensible default.
+ if ($trigger.length === 0) {
+ $trigger = this
+ .find('.middleColumn')
+ .first() // Get the first middleColumn so we don't add multiple triggers on composite field types.
+ .after(' ')
+ .next();
+ }
- this.addClass('description-toggle-enabled');
+ this.addClass('description-toggle-enabled');
- // Toggle next description when button is clicked.
- $trigger.on('click', function() {
- $description[shown ? 'hide' : 'show']();
- shown = !shown;
- });
+ // Toggle next description when button is clicked.
+ $trigger.on('click', function() {
+ $description[shown ? 'hide' : 'show']();
+ shown = !shown;
+ });
- // Hide next description by default.
- $description.hide();
- }
- });
+ // Hide next description by default.
+ $description.hide();
+ }
+ });
});
diff --git a/admin/javascript/src/LeftAndMain.Menu.js b/admin/javascript/src/LeftAndMain.Menu.js
index 205088383..401463167 100644
--- a/admin/javascript/src/LeftAndMain.Menu.js
+++ b/admin/javascript/src/LeftAndMain.Menu.js
@@ -15,11 +15,11 @@ $.entwine('ss', function($){
*
*
@@ -38,7 +38,7 @@ $.entwine('ss', function($){
$(this).addClass('collapse');
}
});
- } else { //collapse
+ } else { //collapse
$(this).children('ul').each(function() {
$(this).addClass('collapsed-flyout');
$(this).hasClass('collapse');
@@ -59,7 +59,7 @@ $.entwine('ss', function($){
//hide all the flyout-indicator
$('.cms-menu-list').find('.child-flyout-indicator').hide();
- } else { //collapse
+ } else { //collapse
//hide the flyout only if it is not the current section
$('.collapsed-flyout').find('li').each(function() {
//if (!$(this).hasClass('current'))
@@ -110,11 +110,11 @@ $.entwine('ss', function($){
* @func getEvaluatedCollapsedState
* @return {boolean} - Returns true if the menu should be collapsed, false if expanded.
* @desc Evaluate whether the menu should be collapsed.
- * The basic rule is "If the SiteTree (middle column) is present, collapse the menu, otherwise expand the menu".
- * This reason behind this is to give the content area more real estate when the SiteTree is present.
- * The user may wish to override this automatic behaviour and have the menu expanded or collapsed at all times.
- * So unlike manually toggling the menu, the automatic behaviour never updates the menu's cookie value.
- * Here we use the manually set state and the automatic behaviour to evaluate what the collapsed state should be.
+ * The basic rule is "If the SiteTree (middle column) is present, collapse the menu, otherwise expand the menu".
+ * This reason behind this is to give the content area more real estate when the SiteTree is present.
+ * The user may wish to override this automatic behaviour and have the menu expanded or collapsed at all times.
+ * So unlike manually toggling the menu, the automatic behaviour never updates the menu's cookie value.
+ * Here we use the manually set state and the automatic behaviour to evaluate what the collapsed state should be.
*/
getEvaluatedCollapsedState: function () {
var shouldCollapse,
@@ -269,7 +269,7 @@ $.entwine('ss', function($){
$('.collapsed-flyout').show();
fly.addClass('opened');
fly.children('ul').find('li').fadeIn('fast');
- } else { //collapse
+ } else { //collapse
if(li) {
li.remove();
}
diff --git a/admin/javascript/src/LeftAndMain.Preview.js b/admin/javascript/src/LeftAndMain.Preview.js
index c22b24d1f..d2381b817 100644
--- a/admin/javascript/src/LeftAndMain.Preview.js
+++ b/admin/javascript/src/LeftAndMain.Preview.js
@@ -501,17 +501,17 @@ $.entwine('ss.preview', function($){
_loadCurrentPage: function() {
if (!this.getIsPreviewEnabled()) return;
- var doc,
- containerEl = $('.cms-container');
- try {
- doc = this.find('iframe')[0].contentDocument;
- } catch (e) {
- // iframe can't be accessed - might be secure?
- console.warn('Unable to access iframe, possible https mis-match');
- }
- if (!doc) {
- return;
- }
+ var doc,
+ containerEl = $('.cms-container');
+ try {
+ doc = this.find('iframe')[0].contentDocument;
+ } catch (e) {
+ // iframe can't be accessed - might be secure?
+ console.warn('Unable to access iframe, possible https mis-match');
+ }
+ if (!doc) {
+ return;
+ }
// Load this page in the admin interface if appropriate
var id = $(doc).find('meta[name=x-page-id]').attr('content');
@@ -529,21 +529,21 @@ $.entwine('ss.preview', function($){
* Prepare the iframe content for preview.
*/
_adjustIframeForPreview: function() {
- var iframe = this.find('iframe')[0],
- doc;
- if(!iframe){
- return;
- }
+ var iframe = this.find('iframe')[0],
+ doc;
+ if(!iframe){
+ return;
+ }
- try {
- doc = iframe.contentDocument;
- } catch (e) {
- // iframe can't be accessed - might be secure?
- console.warn('Unable to access iframe, possible https mis-match');
- }
- if(!doc) {
- return;
- }
+ try {
+ doc = iframe.contentDocument;
+ } catch (e) {
+ // iframe can't be accessed - might be secure?
+ console.warn('Unable to access iframe, possible https mis-match');
+ }
+ if(!doc) {
+ return;
+ }
// Open external links in new window to avoid "escaping" the internal page context in the preview
// iframe, which is important to stay in for the CMS logic.
diff --git a/admin/javascript/src/boot/index.js b/admin/javascript/src/boot/index.js
index 9e258836e..863bdb58a 100644
--- a/admin/javascript/src/boot/index.js
+++ b/admin/javascript/src/boot/index.js
@@ -10,24 +10,27 @@ import SchemaReducer from 'state/schema/reducer';
import RecordsReducer from 'state/records/reducer';
// Sections
+// eslint-disable-next-line no-unused-vars
import CampaignAdmin from 'sections/campaign-admin/index';
function appBoot() {
- reducerRegister.add('config', ConfigReducer);
- reducerRegister.add('schemas', SchemaReducer);
- reducerRegister.add('records', RecordsReducer);
+ reducerRegister.add('config', ConfigReducer);
+ reducerRegister.add('schemas', SchemaReducer);
+ reducerRegister.add('records', RecordsReducer);
- const initialState = {};
- const rootReducer = combineReducers(reducerRegister.getAll());
- const createStoreWithMiddleware = applyMiddleware(thunkMiddleware, createLogger())(createStore);
+ const initialState = {};
+ const rootReducer = combineReducers(reducerRegister.getAll());
+ const createStoreWithMiddleware = applyMiddleware(thunkMiddleware, createLogger())(createStore);
- // TODO: The store needs to be passed into route callbacks on the route context.
- window.store = createStoreWithMiddleware(rootReducer, initialState);
+ // TODO: The store needs to be passed into route callbacks on the route context.
+ window.store = createStoreWithMiddleware(rootReducer, initialState);
- // Set the initial config state.
- configActions.setConfig(window.ss.config)(window.store.dispatch);
+ // Set the initial config state.
+ 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 .
-// `window.onload` happens AFTER these Entwine hooks which means the store is undefined when the is constructed.
-$('body').entwine({ onadd: function () { appBoot(); } });
+// TODO: This should be using `window.onload` but isn't because
+// Entwine hooks are being used to set up the .
+// `window.onload` happens AFTER these Entwine hooks which means
+// the store is undefined when the is constructed.
+$('body').entwine({ onadd: () => { appBoot(); } });
diff --git a/admin/javascript/src/components/form-action/index.js b/admin/javascript/src/components/form-action/index.js
index c0ade7191..7e3927efe 100644
--- a/admin/javascript/src/components/form-action/index.js
+++ b/admin/javascript/src/components/form-action/index.js
@@ -2,101 +2,101 @@ import React from 'react';
import SilverStripeComponent from 'silverstripe-component';
class FormActionComponent extends SilverStripeComponent {
- constructor(props) {
- super(props);
+ constructor(props) {
+ super(props);
- this.handleClick = this.handleClick.bind(this);
+ this.handleClick = this.handleClick.bind(this);
+ }
+
+ render() {
+ return (
+
+ {this.getLoadingIcon()}
+ {this.props.label}
+
+ );
+ }
+
+ /**
+ * Returns the necessary button classes based on the given props
+ *
+ * @returns string
+ */
+ getButtonClasses() {
+ let buttonClasses = 'btn';
+
+ // Add 'type' class
+ buttonClasses += ` btn-${this.props.style}`;
+
+ // If there is no text
+ if (typeof this.props.label === 'undefined') {
+ buttonClasses += ' no-text';
}
- render() {
- return (
-
- {this.getLoadingIcon()}
- {this.props.label}
-
- );
+ // Add icon class
+ if (typeof this.props.icon !== 'undefined') {
+ buttonClasses += ` font-icon-${this.props.icon}`;
}
- /**
- * Returns the necessary button classes based on the given props
- *
- * @returns string
- */
- getButtonClasses() {
- var buttonClasses = 'btn';
-
- // Add 'type' class
- buttonClasses += ` btn-${this.props.style}`;
-
- // If there is no text
- if (typeof this.props.label === 'undefined') {
- buttonClasses += ' no-text';
- }
-
- // Add icon class
- if (typeof this.props.icon !== 'undefined') {
- buttonClasses += ` font-icon-${this.props.icon}`;
- }
-
- // Add loading class
- if (this.props.loading === true) {
- buttonClasses += ' btn--loading';
- }
-
- // Add disabled class
- if (this.props.disabled === true) {
- buttonClasses += ' disabled';
- }
-
- return buttonClasses;
+ // Add loading class
+ if (this.props.loading === true) {
+ buttonClasses += ' btn--loading';
}
- /**
- * Returns markup for the loading icon
- *
- * @returns object|null
- */
- getLoadingIcon() {
- if (this.props.loading) {
- return (
-
-
-
-
-
-
-
- );
- }
-
- return null;
+ // Add disabled class
+ if (this.props.disabled === true) {
+ buttonClasses += ' disabled';
}
- /**
- * Event handler triggered when a user clicks the button.
- *
- * @param object event
- * @returns null
- */
- handleClick(event) {
- this.props.handleClick(event);
+ return buttonClasses;
+ }
+
+ /**
+ * Returns markup for the loading icon
+ *
+ * @returns object|null
+ */
+ getLoadingIcon() {
+ if (this.props.loading) {
+ return (
+
+
+
+
+
+
+
+ );
}
+ return null;
+ }
+
+ /**
+ * Event handler triggered when a user clicks the button.
+ *
+ * @param object event
+ * @returns null
+ */
+ handleClick(event) {
+ this.props.handleClick(event);
+ }
+
}
FormActionComponent.propTypes = {
- handleClick: React.PropTypes.func.isRequired,
- label: React.PropTypes.string,
- type: React.PropTypes.string,
- loading: React.PropTypes.bool,
- icon: React.PropTypes.string,
- disabled: React.PropTypes.bool,
- style: React.PropTypes.string
+ handleClick: React.PropTypes.func.isRequired,
+ label: React.PropTypes.string,
+ type: React.PropTypes.string,
+ loading: React.PropTypes.bool,
+ icon: React.PropTypes.string,
+ disabled: React.PropTypes.bool,
+ style: React.PropTypes.string,
};
FormActionComponent.defaultProps = {
- type: 'button',
- style: 'secondary'
+ type: 'button',
+ style: 'secondary',
};
export default FormActionComponent;
diff --git a/admin/javascript/src/components/form-builder/index.js b/admin/javascript/src/components/form-builder/index.js
index a9790b866..a250f7d68 100644
--- a/admin/javascript/src/components/form-builder/index.js
+++ b/admin/javascript/src/components/form-builder/index.js
@@ -1,7 +1,6 @@
import React from 'react';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
-import $ from 'jQuery';
import * as schemaActions from 'state/schema/actions';
import SilverStripeComponent from 'silverstripe-component';
import FormComponent from 'components/form/index';
@@ -14,219 +13,217 @@ import es6promise from 'es6-promise';
es6promise.polyfill();
// 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: {
- 'TextField': TextField,
- 'GridField': GridField,
- 'HiddenField': HiddenField
- },
+ /**
+ * Components registered with the fake DI container.
+ */
+ components: {
+ TextField,
+ GridField,
+ HiddenField,
+ },
- /**
- * Gets the component matching the passed component name.
- * Used when a component type is provided bt the form schema.
- *
- * @param string componentName - The name of the component to get from the injector.
- *
- * @return object|null
- */
- getComponentByName: function (componentName) {
- return this.components[componentName];
- },
+ /**
+ * Gets the component matching the passed component name.
+ * Used when a component type is provided bt the form schema.
+ *
+ * @param string componentName - The name of the component to get from the injector.
+ *
+ * @return object|null
+ */
+ getComponentByName(componentName) {
+ return this.components[componentName];
+ },
- /**
- * Default data type to component mappings.
- * Used as a fallback when no component type is provided in the form schema.
- *
- * @param string dataType - The data type provided by the form schema.
- *
- * @return object|null
- */
- getComponentByDataType: function (dataType) {
- switch (dataType) {
- case 'String':
- return this.components.TextField;
- case 'Hidden':
- return this.components.HiddenField;
- case 'Text':
- // Textarea field (not implemented)
- return null;
- case 'HTML':
- // HTML editor field (not implemented)
- return null;
- case 'Integer':
- // Numeric field (not implemented)
- return null;
- case 'Decimal':
- // Numeric field (not implemented)
- return null;
- case 'MultiSelect':
- // Radio field (not implemented)
- return null;
- case 'SingleSelect':
- // Dropdown field (not implemented)
- return null;
- case 'Date':
- // DateTime field (not implemented)
- return null;
- case 'DateTime':
- // DateTime field (not implemented)
- return null;
- case 'Time':
- // DateTime field (not implemented)
- return null;
- case 'Boolean':
- // Checkbox field (not implemented)
- return null;
- case 'Custom':
- return this.components.GridField;
- default:
- return null;
- }
+ /**
+ * Default data type to component mappings.
+ * Used as a fallback when no component type is provided in the form schema.
+ *
+ * @param string dataType - The data type provided by the form schema.
+ *
+ * @return object|null
+ */
+ getComponentByDataType(dataType) {
+ switch (dataType) {
+ case 'String':
+ return this.components.TextField;
+ case 'Hidden':
+ return this.components.HiddenField;
+ case 'Text':
+ // Textarea field (not implemented)
+ return null;
+ case 'HTML':
+ // HTML editor field (not implemented)
+ return null;
+ case 'Integer':
+ // Numeric field (not implemented)
+ return null;
+ case 'Decimal':
+ // Numeric field (not implemented)
+ return null;
+ case 'MultiSelect':
+ // Radio field (not implemented)
+ return null;
+ case 'SingleSelect':
+ // Dropdown field (not implemented)
+ return null;
+ case 'Date':
+ // DateTime field (not implemented)
+ return null;
+ case 'DateTime':
+ // DateTime field (not implemented)
+ return null;
+ case 'Time':
+ // DateTime field (not implemented)
+ return null;
+ case 'Boolean':
+ // Checkbox field (not implemented)
+ return null;
+ case 'Custom':
+ return this.components.GridField;
+ default:
+ return null;
}
-}
+ },
+};
export class FormBuilderComponent extends SilverStripeComponent {
- constructor(props) {
- super(props);
+ constructor(props) {
+ super(props);
- this.formSchemaPromise = null;
+ this.formSchemaPromise = null;
+ this.isFetching = false;
+
+ this.fetch();
+ }
+
+ /**
+ * Fetches data used to generate a form. This can be form schema and or form state data.
+ * When the response comes back the data is saved to state.
+ *
+ * @param boolean schema - If form schema data should be returned in the response.
+ * @param boolean state - If form state data should be returned in the response.
+ *
+ * @return object - Promise from the AJAX request.
+ */
+ fetch(schema = true, state = false) {
+ const headerValues = [];
+
+ if (this.isFetching === true) {
+ return this.formSchemaPromise;
+ }
+
+ if (schema === true) {
+ headerValues.push('schema');
+ }
+
+ if (state === true) {
+ headerValues.push('state');
+ }
+
+ this.formSchemaPromise = fetch(this.props.schemaUrl, {
+ headers: { 'X-FormSchema-Request': headerValues.join() },
+ credentials: 'same-origin',
+ })
+ .then(response => response.json())
+ .then(json => {
this.isFetching = false;
+ this.props.actions.setSchema(json);
+ });
- this.fetch();
+ this.isFetching = true;
+
+ return this.formSchemaPromise;
+ }
+
+ /**
+ * Maps a list of schema fields to their React Component.
+ * Only top level form fields are handled here, composite fields (TabSets etc),
+ * are responsible for mapping and rendering their children.
+ *
+ * @param array fields
+ *
+ * @return array
+ */
+ mapFieldsToComponents(fields) {
+ return fields.map((field, i) => {
+ const Component = field.component !== null
+ ? fakeInjector.getComponentByName(field.component)
+ : fakeInjector.getComponentByDataType(field.type);
+
+ if (Component === null) {
+ return null;
+ }
+
+ // Props which every form field receives.
+ const props = {
+ attributes: field.attributes,
+ data: field.data,
+ description: field.description,
+ extraClass: field.extraClass,
+ fields: field.children,
+ id: field.id,
+ name: field.name,
+ };
+
+ // Structural fields (like TabSets) are not posted back to
+ // the server and don't receive some props.
+ if (field.type !== 'Structural') {
+ props.rightTitle = field.rightTitle;
+ props.leftTitle = field.leftTitle;
+ props.readOnly = field.readOnly;
+ props.disabled = field.disabled;
+ props.customValidationMessage = field.customValidationMessage;
+ }
+
+ // Dropdown and Radio fields need some options...
+ if (field.type === 'MultiSelect' || field.type === 'SingleSelect') {
+ props.source = field.source;
+ }
+
+ return ;
+ });
+ }
+
+ render() {
+ const schema = this.props.schemas[this.props.schemaUrl];
+
+ // If the response from fetching the initial data
+ // hasn't come back yet, don't render anything.
+ if (!schema) {
+ return null;
}
- /**
- * Fetches data used to generate a form. This can be form schema and or form state data.
- * When the response comes back the data is saved to state.
- *
- * @param boolean schema - If form schema data should be returned in the response.
- * @param boolean state - If form state data should be returned in the response.
- *
- * @return object - Promise from the AJAX request.
- */
- fetch(schema = true, state = false) {
- var headerValues = [];
+ const formProps = {
+ actions: schema.schema.actions,
+ attributes: schema.schema.attributes,
+ data: schema.schema.data,
+ fields: schema.schema.fields,
+ mapFieldsToComponents: this.mapFieldsToComponents,
+ };
- if (this.isFetching === true) {
- return this.formSchemaPromise;
- }
-
- if (schema === true) {
- headerValues.push('schema');
- }
-
- if (state === true) {
- headerValues.push('state');
- }
-
- this.formSchemaPromise = fetch(this.props.schemaUrl, {
- headers: { 'X-FormSchema-Request': headerValues.join() },
- credentials: 'same-origin'
- })
- .then(response => {
- return response.json();
- })
- .then(json => {
- this.isFetching = false;
- this.props.actions.setSchema(json);
- });
-
- this.isFetching = true;
-
- return this.formSchemaPromise;
- }
-
- /**
- * Maps a list of schema fields to their React Component.
- * Only top level form fields are handled here, composite fields (TabSets etc),
- * are responsible for mapping and rendering their children.
- *
- * @param array fields
- *
- * @return array
- */
- mapFieldsToComponents(fields) {
- return fields.map((field, i) => {
-
- const Component = field.component !== null
- ? fakeInjector.getComponentByName(field.component)
- : fakeInjector.getComponentByDataType(field.type);
-
- if (Component === null) {
- return null;
- }
-
- // Props which every form field receives.
- let props = {
- attributes: field.attributes,
- data: field.data,
- description: field.description,
- extraClass: field.extraClass,
- fields: field.children,
- id: field.id,
- name: field.name
- };
-
- // Structural fields (like TabSets) are not posted back to the server and don't receive some props.
- if (field.type !== 'Structural') {
- props.rightTitle = field.rightTitle;
- props.leftTitle = field.leftTitle;
- props.readOnly = field.readOnly;
- props.disabled = field.disabled;
- props.customValidationMessage = field.customValidationMessage;
- }
-
- // Dropdown and Radio fields need some options...
- if (field.type === 'MultiSelect' || field.type === 'SingleSelect') {
- props.source = field.source;
- }
-
- return
- });
- }
-
- render() {
- const schema = this.props.schemas[this.props.schemaUrl];
-
- // If the response from fetching the initial data
- // hasn't come back yet, don't render anything.
- if (!schema) {
- return null;
- }
-
- const formProps = {
- actions: schema.schema.actions,
- attributes: schema.schema.attributes,
- data: schema.schema.data,
- fields: schema.schema.fields,
- mapFieldsToComponents: this.mapFieldsToComponents
- };
-
- return
- }
+ return ;
+ }
}
FormBuilderComponent.propTypes = {
- actions: React.PropTypes.object.isRequired,
- schemaUrl: React.PropTypes.string.isRequired,
- schemas: React.PropTypes.object.isRequired
+ actions: React.PropTypes.object.isRequired,
+ schemaUrl: React.PropTypes.string.isRequired,
+ schemas: React.PropTypes.object.isRequired,
};
function mapStateToProps(state) {
- return {
- schemas: state.schemas
- }
+ return {
+ schemas: state.schemas,
+ };
}
function mapDispatchToProps(dispatch) {
- return {
- actions: bindActionCreators(schemaActions, dispatch)
- }
+ return {
+ actions: bindActionCreators(schemaActions, dispatch),
+ };
}
export default connect(mapStateToProps, mapDispatchToProps)(FormBuilderComponent);
diff --git a/admin/javascript/src/components/form-builder/tests/form-builder-test.js b/admin/javascript/src/components/form-builder/tests/form-builder-test.js
index 977b7d7c5..d5702b462 100644
--- a/admin/javascript/src/components/form-builder/tests/form-builder-test.js
+++ b/admin/javascript/src/components/form-builder/tests/form-builder-test.js
@@ -5,22 +5,22 @@ import { FormBuilderComponent } from '../';
describe('FormBuilderComponent', () => {
- describe('getFormSchema()', () => {
+ describe('getFormSchema()', () => {
- var formBuilder;
+ var formBuilder;
- beforeEach(() => {
- const props = {
- store: {
- getState: () => {}
- },
- actions: {},
- schemaUrl: 'admin/assets/schema/1',
- schema: { forms: [{ schema: { id: '1', schema_url: 'admin/assets/schema/1' } }] }
- };
+ beforeEach(() => {
+ const props = {
+ store: {
+ getState: () => {}
+ },
+ actions: {},
+ schemaUrl: 'admin/assets/schema/1',
+ schema: { forms: [{ schema: { id: '1', schema_url: 'admin/assets/schema/1' } }] }
+ };
- formBuilder = new FormBuilderComponent(props);
- });
+ formBuilder = new FormBuilderComponent(props);
});
+ });
});
diff --git a/admin/javascript/src/components/form/index.js b/admin/javascript/src/components/form/index.js
index 47f3d4893..6ee80e5d9 100644
--- a/admin/javascript/src/components/form/index.js
+++ b/admin/javascript/src/components/form/index.js
@@ -4,56 +4,62 @@ import FormActionComponent from 'components/form-action/index';
class FormComponent extends SilverStripeComponent {
- /**
- * Gets the components responsible for perfoming actions on the form.
- * For example form submission.
- *
- * @return array|null
- */
- getFormActionComponents() {
- return this.props.actions.map((action) => {
- return ;
- });
- }
+ /**
+ * Gets the components responsible for perfoming actions on the form.
+ * For example form submission.
+ *
+ * @return array|null
+ */
+ getFormActionComponents() {
+ return this.props.actions.map((action) =>
+
+ );
+ }
- render() {
- const attr = this.props.attributes;
- const fields = this.props.mapFieldsToComponents(this.props.fields);
- const actions = this.getFormActionComponents();
+ render() {
+ const attr = this.props.attributes;
+ const fields = this.props.mapFieldsToComponents(this.props.fields);
+ const actions = this.getFormActionComponents();
- return (
-
- );
- }
+ {actions &&
+
+ }
+
+ );
+ }
}
FormComponent.propTypes = {
- actions: React.PropTypes.array,
- attributes: React.PropTypes.shape({
- action: React.PropTypes.string.isRequired,
- 'class': React.PropTypes.string.isRequired,
- enctype: React.PropTypes.string.isRequired,
- id: React.PropTypes.string.isRequired,
- method: React.PropTypes.string.isRequired
- }),
- data: React.PropTypes.array,
- fields: React.PropTypes.array.isRequired,
- mapFieldsToComponents: React.PropTypes.func.isRequired
+ actions: React.PropTypes.array,
+ attributes: React.PropTypes.shape({
+ action: React.PropTypes.string.isRequired,
+ class: React.PropTypes.string.isRequired,
+ enctype: React.PropTypes.string.isRequired,
+ id: React.PropTypes.string.isRequired,
+ method: React.PropTypes.string.isRequired,
+ }),
+ data: React.PropTypes.array,
+ fields: React.PropTypes.array.isRequired,
+ mapFieldsToComponents: React.PropTypes.func.isRequired,
};
export default FormComponent;
diff --git a/admin/javascript/src/components/grid-field/action.js b/admin/javascript/src/components/grid-field/action.js
index a5c6be5f5..185cbd12b 100644
--- a/admin/javascript/src/components/grid-field/action.js
+++ b/admin/javascript/src/components/grid-field/action.js
@@ -2,27 +2,27 @@ import React from 'react';
import SilverStripeComponent from 'silverstripe-component';
class GridFieldActionComponent extends SilverStripeComponent {
- constructor(props) {
- super(props);
-
- this.handleClick = this.handleClick.bind(this);
- }
+ constructor(props) {
+ super(props);
+ this.handleClick = this.handleClick.bind(this);
+ }
- render() {
- return (
-
- );
- }
+ render() {
+ return (
+
+ );
+ }
- handleClick(event) {
- this.props.handleClick(event);
- }
+ handleClick(event) {
+ this.props.handleClick(event, this.props.record.ID);
+ }
}
GridFieldActionComponent.PropTypes = {
- handleClick: React.PropTypes.func.isRequired
-}
+ handleClick: React.PropTypes.func.isRequired,
+};
export default GridFieldActionComponent;
diff --git a/admin/javascript/src/components/grid-field/cell.js b/admin/javascript/src/components/grid-field/cell.js
index b8a205a18..528f073c9 100644
--- a/admin/javascript/src/components/grid-field/cell.js
+++ b/admin/javascript/src/components/grid-field/cell.js
@@ -3,16 +3,16 @@ import SilverStripeComponent from 'silverstripe-component';
class GridFieldCellComponent extends SilverStripeComponent {
- render() {
- return (
- {this.props.children}
- );
- }
+ render() {
+ return (
+ {this.props.children}
+ );
+ }
}
GridFieldCellComponent.PropTypes = {
- width: React.PropTypes.number
-}
+ width: React.PropTypes.number,
+};
export default GridFieldCellComponent;
diff --git a/admin/javascript/src/components/grid-field/header-cell.js b/admin/javascript/src/components/grid-field/header-cell.js
index 9b67d4055..4add622cc 100644
--- a/admin/javascript/src/components/grid-field/header-cell.js
+++ b/admin/javascript/src/components/grid-field/header-cell.js
@@ -3,16 +3,16 @@ import SilverStripeComponent from 'silverstripe-component';
class GridFieldHeaderCellComponent extends SilverStripeComponent {
- render() {
- return (
- {this.props.children}
- );
- }
+ render() {
+ return (
+ {this.props.children}
+ );
+ }
}
GridFieldHeaderCellComponent.PropTypes = {
- width: React.PropTypes.number
-}
+ width: React.PropTypes.number,
+};
export default GridFieldHeaderCellComponent;
diff --git a/admin/javascript/src/components/grid-field/header.js b/admin/javascript/src/components/grid-field/header.js
index dcf26a8fb..2d2c99ad3 100644
--- a/admin/javascript/src/components/grid-field/header.js
+++ b/admin/javascript/src/components/grid-field/header.js
@@ -4,11 +4,11 @@ import GridFieldRowComponent from './row';
class GridFieldHeaderComponent extends SilverStripeComponent {
- render() {
- return (
- {this.props.children}
- );
- }
+ render() {
+ return (
+ {this.props.children}
+ );
+ }
}
diff --git a/admin/javascript/src/components/grid-field/index.js b/admin/javascript/src/components/grid-field/index.js
index b2e34d8c5..90b391946 100644
--- a/admin/javascript/src/components/grid-field/index.js
+++ b/admin/javascript/src/components/grid-field/index.js
@@ -14,107 +14,118 @@ import * as actions from 'state/records/actions';
* The component acts as a container for a grid field,
* 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)
*/
class GridField extends SilverStripeComponent {
- constructor(props) {
- super(props);
+ constructor(props) {
+ super(props);
- this.deleteRecord = this.deleteRecord.bind(this);
- this.editRecord = this.editRecord.bind(this);
+ this.deleteRecord = this.deleteRecord.bind(this);
+ this.editRecord = this.editRecord.bind(this);
+ }
+
+ componentDidMount() {
+ super.componentDidMount();
+
+ const data = this.props.data;
+
+ this.props.actions.fetchRecords(
+ data.recordType,
+ data.collectionReadEndpoint.method,
+ data.collectionReadEndpoint.url
+ );
+ }
+
+ render() {
+ const records = this.props.records;
+ if (!records) {
+ return
;
}
- componentDidMount() {
- super.componentDidMount();
+ const columns = this.props.data.columns;
- let data = this.props.data;
+ // Placeholder to align the headers correctly with the content
+ const actionPlaceholder = ;
+ const headerCells = columns.map((column, i) =>
+ {column.name}
+ );
+ const header = {headerCells.concat(actionPlaceholder)} ;
- this.props.actions.fetchRecords(data.recordType, data.collectionReadEndpoint.method, data.collectionReadEndpoint.url);
- }
+ const rows = records.map((record, i) => {
+ const cells = columns.map((column, j) => {
+ // Get value by dot notation
+ const val = column.field.split('.').reduce((a, b) => a[b], record);
+ return {val} ;
+ });
- render() {
- const records = this.props.records;
- if(!records) {
- return
;
- }
+ const rowActions = (
+
+ ,
+ ,
+
+ );
- const columns = this.props.data.columns;
+ return {cells.concat(rowActions)} ;
+ });
- // Placeholder to align the headers correctly with the content
- const actionPlaceholder = ;
- const headerCells = columns.map((column, i) => {column.name} );
- const header = {headerCells.concat(actionPlaceholder)} ;
+ return (
+
+ );
+ }
- const rows = records.map((record, i) => {
- var cells = columns.map((column, i) => {
- // Get value by dot notation
- var val = column.field.split('.').reduce((a, b) => a[b], record)
- return {val}
- });
+ /**
+ * @param number int
+ * @param event
+ */
+ deleteRecord(event, id) {
+ event.preventDefault();
+ this.props.actions.deleteRecord(
+ this.props.data.recordType,
+ id,
+ this.props.data.itemDeleteEndpoint.method,
+ this.props.data.itemDeleteEndpoint.url
+ );
+ }
- var rowActions =
-
-
- ;
-
- return {cells.concat(rowActions)} ;
- });
-
- return (
-
- );
- }
-
- /**
- * @param number int
- * @param event
- */
- deleteRecord(id, event) {
- event.preventDefault();
- this.props.actions.deleteRecord(
- this.props.data.recordType,
- id,
- this.props.data.itemDeleteEndpoint.method,
- this.props.data.itemDeleteEndpoint.url
- );
- }
-
- editRecord(id, event) {
- event.preventDefault();
- // TODO
- }
+ editRecord(event) {
+ event.preventDefault();
+ // TODO
+ }
}
GridField.propTypes = {
- data: React.PropTypes.shape({
- recordType: React.PropTypes.string.isRequired,
- headerColumns: React.PropTypes.array,
- collectionReadEndpoint: React.PropTypes.object
- })
+ data: React.PropTypes.shape({
+ recordType: React.PropTypes.string.isRequired,
+ headerColumns: React.PropTypes.array,
+ collectionReadEndpoint: React.PropTypes.object,
+ }),
};
function mapStateToProps(state, ownProps) {
- let recordType = ownProps.data ? ownProps.data.recordType : null;
- return {
- records: (state.records && recordType) ? state.records[recordType] : []
- }
+ const recordType = ownProps.data ? ownProps.data.recordType : null;
+ return {
+ records: (state.records && recordType) ? state.records[recordType] : [],
+ };
}
function mapDispatchToProps(dispatch) {
- return {
- actions: bindActionCreators(actions, dispatch)
- }
+ return {
+ actions: bindActionCreators(actions, dispatch),
+ };
}
export default connect(mapStateToProps, mapDispatchToProps)(GridField);
diff --git a/admin/javascript/src/components/grid-field/row.js b/admin/javascript/src/components/grid-field/row.js
index a2c945553..21bc8084e 100644
--- a/admin/javascript/src/components/grid-field/row.js
+++ b/admin/javascript/src/components/grid-field/row.js
@@ -3,11 +3,11 @@ import SilverStripeComponent from 'silverstripe-component';
class GridFieldRowComponent extends SilverStripeComponent {
- render() {
- return (
- {this.props.children}
- );
- }
+ render() {
+ return (
+ {this.props.children}
+ );
+ }
}
diff --git a/admin/javascript/src/components/grid-field/table.js b/admin/javascript/src/components/grid-field/table.js
index 479acdeaa..56dc90b8c 100644
--- a/admin/javascript/src/components/grid-field/table.js
+++ b/admin/javascript/src/components/grid-field/table.js
@@ -3,61 +3,61 @@ import SilverStripeComponent from 'silverstripe-component';
class GridFieldTableComponent extends SilverStripeComponent {
- render() {
- return (
-
- {this.generateHeader()}
- {this.generateRows()}
-
- );
+ render() {
+ return (
+
+ {this.generateHeader()}
+ {this.generateRows()}
+
+ );
+ }
+
+ /**
+ * Generates the header component.
+ *
+ * Uses the header component passed via the `header` prop if it exists.
+ * Otherwise generates a header from the `data` prop.
+ *
+ * @return object|null
+ */
+ generateHeader() {
+ if (typeof this.props.header !== 'undefined') {
+ return this.props.header;
}
- /**
- * Generates the header component.
- *
- * Uses the header component passed via the `header` prop if it exists.
- * Otherwise generates a header from the `data` prop.
- *
- * @return object|null
- */
- generateHeader() {
- if (typeof this.props.header !== 'undefined') {
- return this.props.header;
- }
-
- if (typeof this.props.data !== 'undefined') {
- // TODO: Generate the header.
- }
-
- return null;
+ if (typeof this.props.data !== 'undefined') {
+ // TODO: Generate the header.
}
- /**
- * Generates the table rows.
- *
- * Uses the components passed via the `rows` props if it exists.
- * Otherwise generates rows from the `data` prop.
- *
- * @return object|null
- */
- generateRows() {
- if (typeof this.props.rows !== 'undefined') {
- return this.props.rows;
- }
+ return null;
+ }
- if (typeof this.props.data !== 'undefined') {
- // TODO: Generate the rows.
- }
-
- return null;
+ /**
+ * Generates the table rows.
+ *
+ * Uses the components passed via the `rows` props if it exists.
+ * Otherwise generates rows from the `data` prop.
+ *
+ * @return object|null
+ */
+ generateRows() {
+ if (typeof this.props.rows !== 'undefined') {
+ return this.props.rows;
}
+ if (typeof this.props.data !== 'undefined') {
+ // TODO: Generate the rows.
+ }
+
+ return null;
+ }
+
}
GridFieldTableComponent.propTypes = {
- data: React.PropTypes.object,
- header: React.PropTypes.object,
- rows: React.PropTypes.array
+ data: React.PropTypes.object,
+ header: React.PropTypes.object,
+ rows: React.PropTypes.array,
};
export default GridFieldTableComponent;
diff --git a/admin/javascript/src/components/grid-field/tests/grid-field-test.js b/admin/javascript/src/components/grid-field/tests/grid-field-test.js
index b5bbc9d2a..de1bde79d 100644
--- a/admin/javascript/src/components/grid-field/tests/grid-field-test.js
+++ b/admin/javascript/src/components/grid-field/tests/grid-field-test.js
@@ -2,66 +2,66 @@ jest.dontMock('../index');
jest.dontMock('../table');
const React = require('react'),
- ReactTestUtils = require('react-addons-test-utils'),
- GridFieldTableComponent = require('../table.js').default;
+ ReactTestUtils = require('react-addons-test-utils'),
+ GridFieldTableComponent = require('../table.js').default;
describe('GridFieldTableComponent', () => {
- var props;
+ var props;
- beforeEach(function () {
- props = {
- }
+ beforeEach(function () {
+ props = {
+ }
+ });
+
+ describe('generateHeader()', function () {
+ var gridfield;
+
+ it('should return props.header if it is set', function () {
+ props.header =
;
+
+ gridfield = ReactTestUtils.renderIntoDocument(
+
+ );
+
+ expect(gridfield.generateHeader().props.className).toBe('header');
});
- describe('generateHeader()', function () {
- var gridfield;
+ it('should generate and return a header from props.data if it is set', function () {
- it('should return props.header if it is set', function () {
- props.header =
;
-
- gridfield = ReactTestUtils.renderIntoDocument(
-
- );
-
- expect(gridfield.generateHeader().props.className).toBe('header');
- });
-
- it('should generate and return a header from props.data if it is set', function () {
-
- });
-
- it('should return null if props.header and props.data are both not set', function () {
- gridfield = ReactTestUtils.renderIntoDocument(
-
- );
-
- expect(gridfield.generateHeader()).toBe(null);
- });
});
- describe('generateRows()', function () {
- var gridfield;
+ it('should return null if props.header and props.data are both not set', function () {
+ gridfield = ReactTestUtils.renderIntoDocument(
+
+ );
- it('should return props.rows if it is set', function () {
- props.rows = ['row1'];
-
- gridfield = ReactTestUtils.renderIntoDocument(
-
- );
-
- expect(gridfield.generateRows()[0]).toBe('row1');
- });
-
- it('should generate and return rows from props.data if it is set', function () {
-
- });
-
- it('should return null if props.rows and props.data are both not set', function () {
- gridfield = ReactTestUtils.renderIntoDocument(
-
- );
-
- expect(gridfield.generateRows()).toBe(null);
- });
+ expect(gridfield.generateHeader()).toBe(null);
});
+ });
+
+ describe('generateRows()', function () {
+ var gridfield;
+
+ it('should return props.rows if it is set', function () {
+ props.rows = ['row1'];
+
+ gridfield = ReactTestUtils.renderIntoDocument(
+
+ );
+
+ expect(gridfield.generateRows()[0]).toBe('row1');
+ });
+
+ it('should generate and return rows from props.data if it is set', function () {
+
+ });
+
+ it('should return null if props.rows and props.data are both not set', function () {
+ gridfield = ReactTestUtils.renderIntoDocument(
+
+ );
+
+ expect(gridfield.generateRows()).toBe(null);
+ });
+ });
});
diff --git a/admin/javascript/src/components/hidden-field/index.js b/admin/javascript/src/components/hidden-field/index.js
index 0b36ba916..25657ea9c 100644
--- a/admin/javascript/src/components/hidden-field/index.js
+++ b/admin/javascript/src/components/hidden-field/index.js
@@ -3,46 +3,46 @@ import SilverStripeComponent from 'silverstripe-component';
class HiddenFieldComponent extends SilverStripeComponent {
- constructor(props) {
- super(props);
+ constructor(props) {
+ super(props);
- this.handleChange = this.handleChange.bind(this);
+ this.handleChange = this.handleChange.bind(this);
+ }
+
+ render() {
+ return (
+
+
+
+ );
+ }
+
+ getInputProps() {
+ return {
+ className: ['hidden', this.props.extraClass].join(' '),
+ id: this.props.name,
+ name: this.props.name,
+ onChange: this.props.onChange,
+ type: 'hidden',
+ value: this.props.value,
+ };
+ }
+
+ handleChange() {
+ if (typeof this.props.onChange === 'undefined') {
+ return;
}
- render() {
- return (
-
-
-
- );
- }
-
- getInputProps() {
- return {
- className: ['hidden', this.props.extraClass].join(' '),
- id: this.props.name,
- name: this.props.name,
- onChange: this.props.onChange,
- type: 'hidden',
- value: this.props.value
- };
- }
-
- handleChange(event) {
- if (typeof this.props.onChange === 'undefined') {
- return;
- }
-
- this.props.onChange();
- }
+ this.props.onChange();
+ }
}
HiddenFieldComponent.propTypes = {
- label: React.PropTypes.string,
- extraClass: React.PropTypes.string,
- name: React.PropTypes.string.isRequired,
- onChange: React.PropTypes.func,
- value: React.PropTypes.string
+ label: React.PropTypes.string,
+ extraClass: React.PropTypes.string,
+ name: React.PropTypes.string.isRequired,
+ onChange: React.PropTypes.func,
+ value: React.PropTypes.string,
};
export default HiddenFieldComponent;
diff --git a/admin/javascript/src/components/north-header-breadcrumbs/index.js b/admin/javascript/src/components/north-header-breadcrumbs/index.js
index 1bacb6c6e..47f5f9b14 100644
--- a/admin/javascript/src/components/north-header-breadcrumbs/index.js
+++ b/admin/javascript/src/components/north-header-breadcrumbs/index.js
@@ -3,37 +3,39 @@ import SilverStripeComponent from 'silverstripe-component';
class NorthHeaderBreadcrumbsComponent extends SilverStripeComponent {
- render() {
- return (
-
-
-
- {this.getBreadcrumbs()}
-
-
-
- );
+ render() {
+ return (
+
+
+
+ {this.getBreadcrumbs()}
+
+
+
+ );
+ }
+
+ getBreadcrumbs() {
+ if (typeof this.props.crumbs === 'undefined') {
+ return null;
}
- getBreadcrumbs() {
- if (typeof this.props.crumbs === 'undefined') {
- return null;
- }
+ const breadcrumbs = this.props.crumbs.map((crumb, index, crumbs) => {
+ let component;
+ // If its the last item in the array
+ if (index === crumbs.length - 1) {
+ component = {crumb.text} ;
+ } else {
+ component = [
+ {crumb.text} ,
+ / ,
+ ];
+ }
+ return component;
+ });
- var breadcrumbs = this.props.crumbs.map((crumb, index, crumbs) => {
- // If its the last item in the array
- if (index === crumbs.length - 1) {
- return {crumb.text} ;
- } else {
- return [
- {crumb.text} ,
- /
- ];
- }
- });
-
- return breadcrumbs;
- }
+ return breadcrumbs;
+ }
}
diff --git a/admin/javascript/src/components/north-header-breadcrumbs/tests/north-header-breadcrumbs-test.js b/admin/javascript/src/components/north-header-breadcrumbs/tests/north-header-breadcrumbs-test.js
index 0e7a9591c..88c7fb729 100644
--- a/admin/javascript/src/components/north-header-breadcrumbs/tests/north-header-breadcrumbs-test.js
+++ b/admin/javascript/src/components/north-header-breadcrumbs/tests/north-header-breadcrumbs-test.js
@@ -1,40 +1,40 @@
jest.dontMock('../index');
const React = require('react'),
- ReactTestUtils = require('react-addons-test-utils'),
- NorthHeaderBreadcrumbsComponent = require('../index').default;
+ ReactTestUtils = require('react-addons-test-utils'),
+ NorthHeaderBreadcrumbsComponent = require('../index').default;
describe('NorthHeaderBreadcrumbsComponent', () => {
- var props;
+ var props;
- beforeEach(() => {
- props = {};
+ beforeEach(() => {
+ props = {};
+ });
+
+ describe('getBreadcrumbs()', () => {
+ var northHeaderBreadcrumbs;
+
+ it('should convert the props.crumbs array into jsx to be rendered', () => {
+ props.crumbs = [
+ { text: 'breadcrumb1', href: 'href1'},
+ { text: 'breadcrumb2', href: 'href2'}
+ ];
+
+ northHeaderBreadcrumbs = ReactTestUtils.renderIntoDocument(
+
+ );
+
+ expect(northHeaderBreadcrumbs.getBreadcrumbs()[0][0].props.children).toBe('breadcrumb1');
+ expect(northHeaderBreadcrumbs.getBreadcrumbs()[0][1].props.children).toBe('/');
+ expect(northHeaderBreadcrumbs.getBreadcrumbs()[1].props.children).toBe('breadcrumb2');
});
- describe('getBreadcrumbs()', () => {
- var northHeaderBreadcrumbs;
+ it('should return null if props.crumbs is not set', () => {
+ northHeaderBreadcrumbs = ReactTestUtils.renderIntoDocument(
+
+ );
- it('should convert the props.crumbs array into jsx to be rendered', () => {
- props.crumbs = [
- { text: 'breadcrumb1', href: 'href1'},
- { text: 'breadcrumb2', href: 'href2'}
- ];
-
- northHeaderBreadcrumbs = ReactTestUtils.renderIntoDocument(
-
- );
-
- expect(northHeaderBreadcrumbs.getBreadcrumbs()[0][0].props.children).toBe('breadcrumb1');
- expect(northHeaderBreadcrumbs.getBreadcrumbs()[0][1].props.children).toBe('/');
- expect(northHeaderBreadcrumbs.getBreadcrumbs()[1].props.children).toBe('breadcrumb2');
- });
-
- it('should return null if props.crumbs is not set', () => {
- northHeaderBreadcrumbs = ReactTestUtils.renderIntoDocument(
-
- );
-
- expect(northHeaderBreadcrumbs.getBreadcrumbs()).toBe(null);
- });
+ expect(northHeaderBreadcrumbs.getBreadcrumbs()).toBe(null);
});
+ });
});
diff --git a/admin/javascript/src/components/north-header/index.js b/admin/javascript/src/components/north-header/index.js
index 45473fda0..13583b68b 100644
--- a/admin/javascript/src/components/north-header/index.js
+++ b/admin/javascript/src/components/north-header/index.js
@@ -4,26 +4,26 @@ import SilverStripeComponent from 'silverstripe-component';
class NorthHeaderComponent extends SilverStripeComponent {
- render() {
- return (
-
-
-
- );
- }
+ render() {
+ return (
+
+
+
+ );
+ }
- getBreadcrumbs() {
- return [
- {
- text: 'Campaigns',
- href: 'admin/campaigns'
- },
- {
- text: 'March release',
- href: 'admin/campaigns/show/1'
- }
- ]
- }
+ getBreadcrumbs() {
+ return [
+ {
+ text: 'Campaigns',
+ href: 'admin/campaigns',
+ },
+ {
+ text: 'March release',
+ href: 'admin/campaigns/show/1',
+ },
+ ];
+ }
}
diff --git a/admin/javascript/src/components/text-field/index.js b/admin/javascript/src/components/text-field/index.js
index 166c8925f..491335292 100644
--- a/admin/javascript/src/components/text-field/index.js
+++ b/admin/javascript/src/components/text-field/index.js
@@ -3,53 +3,53 @@ import SilverStripeComponent from 'silverstripe-component';
class TextFieldComponent extends SilverStripeComponent {
- constructor(props) {
- super(props);
+ constructor(props) {
+ super(props);
- this.handleChange = this.handleChange.bind(this);
- }
+ this.handleChange = this.handleChange.bind(this);
+ }
- render() {
- return (
-
- {this.props.label &&
-
- {this.props.label}
-
- }
-
-
-
-
- );
- }
-
- getInputProps() {
- return {
- className: ['text', this.props.extraClass].join(' '),
- id: `gallery_${this.props.name}`,
- name: this.props.name,
- onChange: this.props.onChange,
- type: 'text',
- value: this.props.value
- };
- }
-
- handleChange(event) {
- if (typeof this.props.onChange === 'undefined') {
- return;
+ render() {
+ return (
+
+ {this.props.label &&
+
+ {this.props.label}
+
}
+
+
+
+
+ );
+ }
- this.props.onChange();
+ getInputProps() {
+ return {
+ className: ['text', this.props.extraClass].join(' '),
+ id: `gallery_${this.props.name}`,
+ name: this.props.name,
+ onChange: this.props.onChange,
+ type: 'text',
+ value: this.props.value,
+ };
+ }
+
+ handleChange() {
+ if (typeof this.props.onChange === 'undefined') {
+ return;
}
+
+ this.props.onChange();
+ }
}
TextFieldComponent.propTypes = {
- label: React.PropTypes.string,
- extraClass: React.PropTypes.string,
- name: React.PropTypes.string.isRequired,
- onChange: React.PropTypes.func,
- value: React.PropTypes.string
+ label: React.PropTypes.string,
+ extraClass: React.PropTypes.string,
+ name: React.PropTypes.string.isRequired,
+ onChange: React.PropTypes.func,
+ value: React.PropTypes.string,
};
export default TextFieldComponent;
diff --git a/admin/javascript/src/components/text-field/tests/text-field-component-test.js b/admin/javascript/src/components/text-field/tests/text-field-component-test.js
index 28adee1cb..f51efbe62 100644
--- a/admin/javascript/src/components/text-field/tests/text-field-component-test.js
+++ b/admin/javascript/src/components/text-field/tests/text-field-component-test.js
@@ -8,30 +8,30 @@ import TextFieldComponent from '../';
describe('TextFieldComponent', function() {
- var props;
+ var props;
+
+ beforeEach(function () {
+ props = {
+ label: '',
+ name: '',
+ value: '',
+ onChange: jest.genMockFunction()
+ };
+ });
+
+ describe('handleChange()', function () {
+ var textField;
beforeEach(function () {
- props = {
- label: '',
- name: '',
- value: '',
- onChange: jest.genMockFunction()
- };
+ textField = ReactTestUtils.renderIntoDocument(
+
+ );
});
- describe('handleChange()', function () {
- var textField;
+ it('should call the onChange function on props', function () {
+ textField.handleChange();
- beforeEach(function () {
- textField = ReactTestUtils.renderIntoDocument(
-
- );
- });
-
- it('should call the onChange function on props', function () {
- textField.handleChange();
-
- expect(textField.props.onChange.mock.calls.length).toBe(1);
- });
+ expect(textField.props.onChange.mock.calls.length).toBe(1);
});
+ });
});
diff --git a/admin/javascript/src/config.js b/admin/javascript/src/config.js
index ff71b1754..252faf172 100644
--- a/admin/javascript/src/config.js
+++ b/admin/javascript/src/config.js
@@ -6,39 +6,37 @@
*/
class Config {
- /**
- * Gets the the config for a specific section.
- *
- * @param string key - The section config key.
- *
- * @return object|undefined
- */
- static getSection(key) {
- return window.ss.config.sections[key];
- }
+ /**
+ * Gets the the config for a specific section.
+ *
+ * @param string key - The section config key.
+ *
+ * @return object|undefined
+ */
+ static getSection(key) {
+ return window.ss.config.sections[key];
+ }
- /**
- * Gets a de-duped list of routes for top level controllers. E.g. 'assets', 'pages', etc.
- *
- * @return array
- */
- static getTopLevelRoutes() {
- var topLevelRoutes = [];
+ /**
+ * Gets a de-duped list of routes for top level controllers. E.g. 'assets', 'pages', etc.
+ *
+ * @return array
+ */
+ static getTopLevelRoutes() {
+ const topLevelRoutes = [];
- Object.keys(window.ss.config.sections).forEach((key) => {
- const route = window.ss.config.sections[key].route;
- const isTopLevelRoute = route.indexOf('/') === -1;
- const isUnique = topLevelRoutes.indexOf(route) === -1;
+ Object.keys(window.ss.config.sections).forEach((key) => {
+ const route = window.ss.config.sections[key].route;
+ const isTopLevelRoute = route.indexOf('/') === -1;
+ const isUnique = topLevelRoutes.indexOf(route) === -1;
- //console.log(this.getSection(key).route);
+ if (isTopLevelRoute && isUnique) {
+ topLevelRoutes.push(route);
+ }
+ });
- if (isTopLevelRoute && isUnique) {
- topLevelRoutes.push(route);
- }
- });
-
- return topLevelRoutes;
- }
+ return topLevelRoutes;
+ }
}
diff --git a/admin/javascript/src/mocks/jQuery.js b/admin/javascript/src/mocks/jQuery.js
index b19a7680d..6a80e2cac 100644
--- a/admin/javascript/src/mocks/jQuery.js
+++ b/admin/javascript/src/mocks/jQuery.js
@@ -1,15 +1,17 @@
+/* globals jest */
+
function jQuery() {
- return {
- // Add jQuery methods such as 'find', 'change', 'trigger' as needed.
- };
+ return {
+ // Add jQuery methods such as 'find', 'change', 'trigger' as needed.
+ };
}
-var mockAjaxFn = jest.genMockFunction();
+const mockAjaxFn = jest.genMockFunction();
mockAjaxFn.mockReturnValue({
- done: jest.genMockFunction(),
- fail: jest.genMockFunction(),
- always: jest.genMockFunction()
+ done: jest.genMockFunction(),
+ fail: jest.genMockFunction(),
+ always: jest.genMockFunction(),
});
jQuery.ajax = mockAjaxFn;
diff --git a/admin/javascript/src/mocks/silverstripe-component.js b/admin/javascript/src/mocks/silverstripe-component.js
index 8a4dcdcc3..512a6c4ee 100644
--- a/admin/javascript/src/mocks/silverstripe-component.js
+++ b/admin/javascript/src/mocks/silverstripe-component.js
@@ -1,15 +1,5 @@
import { Component } from 'react';
+// eslint-disable-next-line react/prefer-stateless-function
export default class SilverStripeComponent extends Component {
- constructor(props) {
- super(props);
- }
-
- componentDidMount() {
-
- }
-
- componentWillUnmount() {
-
- }
-};
+}
diff --git a/admin/javascript/src/reducer-register.js b/admin/javascript/src/reducer-register.js
index 5b0ac5e5d..61932b28a 100644
--- a/admin/javascript/src/reducer-register.js
+++ b/admin/javascript/src/reducer-register.js
@@ -2,63 +2,62 @@
* The register of Redux reducers.
* @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 {
- /**
- * Adds a reducer to the register.
- *
- * @param string key - The key to register the reducer against.
- * @param object reducer - Redux reducer.
- */
- add(key, reducer) {
- if (typeof register[key] !== 'undefined') {
- throw new Error(`Reducer already exists at '${key}'`);
- }
-
- register[key] = reducer;
+ /**
+ * Adds a reducer to the register.
+ *
+ * @param string key - The key to register the reducer against.
+ * @param object reducer - Redux reducer.
+ */
+ add(key, reducer) {
+ if (typeof register[key] !== 'undefined') {
+ throw new Error(`Reducer already exists at '${key}'`);
}
- /**
- * Gets all reducers from the register.
- *
- * @return object
- */
- getAll() {
- return register;
- }
+ register[key] = reducer;
+ }
- /**
- * Gets a reducer from the register.
- *
- * @param string [key] - The key the reducer is registered against.
- *
- * @return object|undefined
- */
- getByKey(key) {
- return register[key];
- }
+ /**
+ * Gets all reducers from the register.
+ *
+ * @return object
+ */
+ getAll() {
+ return register;
+ }
+ /**
+ * Gets a reducer from the register.
+ *
+ * @param string [key] - The key the reducer is registered against.
+ *
+ * @return object|undefined
+ */
+ getByKey(key) {
+ return register[key];
+ }
-
- /**
- * Removes a reducer from the register.
- *
- * @param string key - The key the reducer is registered against.
- */
- remove(key) {
- delete register[key];
- }
+ /**
+ * Removes a reducer from the register.
+ *
+ * @param string key - The key the reducer is registered against.
+ */
+ remove(key) {
+ delete register[key];
+ }
}
// Create an instance to export. The same instance is exported to
// each script which imports the reducerRegister. This means the
// same register is available throughout the application.
-let reducerRegister = new ReducerRegister();
+const reducerRegister = new ReducerRegister();
export default reducerRegister;
diff --git a/admin/javascript/src/sections/campaign-admin/controller.js b/admin/javascript/src/sections/campaign-admin/controller.js
index ea88fe6ca..130cb121e 100644
--- a/admin/javascript/src/sections/campaign-admin/controller.js
+++ b/admin/javascript/src/sections/campaign-admin/controller.js
@@ -8,48 +8,49 @@ import FormBuilder from 'components/form-builder/index';
class CampaignAdminContainer extends SilverStripeComponent {
- constructor(props) {
- super(props);
+ constructor(props) {
+ super(props);
- this.addCampaign = this.addCampaign.bind(this);
- }
+ this.addCampaign = this.addCampaign.bind(this);
+ }
- render() {
- const schemaUrl = this.props.config.forms.editForm.schemaUrl;
+ render() {
+ const schemaUrl = this.props.config.forms.editForm.schemaUrl;
- return (
-
-
-
-
-
- );
- }
+ return (
+
+
+
+
+
+ );
+ }
- addCampaign() {
- //Add campaign
- }
+ addCampaign() {
+ // Add campaign
+ }
}
CampaignAdminContainer.propTypes = {
- config: React.PropTypes.shape({
- forms: React.PropTypes.shape({
- editForm: React.PropTypes.shape({
- schemaUrl: React.PropTypes.string
- })
- })
+ config: React.PropTypes.shape({
+ forms: React.PropTypes.shape({
+ editForm: React.PropTypes.shape({
+ schemaUrl: React.PropTypes.string,
+ }),
}),
- sectionConfigKey: React.PropTypes.string.isRequired
+ }),
+ sectionConfigKey: React.PropTypes.string.isRequired,
};
function mapStateToProps(state, ownProps) {
- return {
- config: state.config.sections[ownProps.sectionConfigKey]
- }
+ return {
+ config: state.config.sections[ownProps.sectionConfigKey],
+ };
}
export default connect(mapStateToProps)(CampaignAdminContainer);
diff --git a/admin/javascript/src/sections/campaign-admin/index.js b/admin/javascript/src/sections/campaign-admin/index.js
index 69c15f30d..69b94be8e 100644
--- a/admin/javascript/src/sections/campaign-admin/index.js
+++ b/admin/javascript/src/sections/campaign-admin/index.js
@@ -1,24 +1,22 @@
-import reducerRegister from 'reducer-register';
import $ from 'jQuery';
import React from 'react';
import ReactDOM from 'react-dom';
import { Provider } from 'react-redux';
import CampaignAdmin from './controller';
-$.entwine('ss', function ($) {
-
- $('.cms-content.CampaignAdmin').entwine({
- onadd: function () {
- ReactDOM.render(
-
-
-
- , this[0]);
- },
-
- onremove: function () {
- ReactDOM.unmountComponentAtNode(this[0]);
- }
- });
+// eslint-disable-next-line no-shadow
+$.entwine('ss', ($) => {
+ $('.cms-content.CampaignAdmin').entwine({
+ onadd() {
+ ReactDOM.render(
+
+
+
+ , this[0]);
+ },
+ onremove() {
+ ReactDOM.unmountComponentAtNode(this[0]);
+ },
+ });
});
diff --git a/admin/javascript/src/silverstripe-backend.js b/admin/javascript/src/silverstripe-backend.js
index 685bd84af..cc69640ab 100644
--- a/admin/javascript/src/silverstripe-backend.js
+++ b/admin/javascript/src/silverstripe-backend.js
@@ -6,73 +6,77 @@ es6promise.polyfill();
* @see https://github.com/github/fetch#handling-http-error-statuses
*/
function checkStatus(response) {
+ let ret;
+ let error;
if (response.status >= 200 && response.status < 300) {
- return response
+ ret = response;
} else {
- var error = new Error(response.statusText)
- error.response = response
- throw error
+ error = new Error(response.statusText);
+ error.response = response;
+ throw error;
}
+
+ return ret;
}
class SilverStripeBackend {
- constructor() {
- // Allow mocking
- this.fetch = fetch;
- }
+ constructor() {
+ // Allow mocking
+ this.fetch = fetch;
+ }
- /**
- * Makes a network request using the GET HTTP verb.
- *
- * @param string url - Endpoint URL.
- * @return object - Promise
- */
- get(url) {
- return this.fetch(url, { method: 'get', credentials: 'same-origin' })
- .then(checkStatus);
- }
+ /**
+ * Makes a network request using the GET HTTP verb.
+ *
+ * @param string url - Endpoint URL.
+ * @return object - Promise
+ */
+ get(url) {
+ return this.fetch(url, { method: 'get', credentials: 'same-origin' })
+ .then(checkStatus);
+ }
- /**
- * Makes a network request using the POST HTTP verb.
- *
- * @param string url - Endpoint URL.
- * @param object data - Data to send with the request.
- * @return object - Promise
- */
- post(url, data) {
- return this.fetch(url, { method: 'post', credentials: 'same-origin', body: data })
- .then(checkStatus);
- }
+ /**
+ * Makes a network request using the POST HTTP verb.
+ *
+ * @param string url - Endpoint URL.
+ * @param object data - Data to send with the request.
+ * @return object - Promise
+ */
+ post(url, data) {
+ return this.fetch(url, { method: 'post', credentials: 'same-origin', body: data })
+ .then(checkStatus);
+ }
- /**
- * Makes a newtwork request using the PUT HTTP verb.
- *
- * @param string url - Endpoint URL.
- * @param object data - Data to send with the request.
- * @return object - Promise
- */
- put(url, data) {
- return this.fetch(url, { method: 'put', credentials: 'same-origin', body: data })
- .then(checkStatus);
- }
+ /**
+ * Makes a newtwork request using the PUT HTTP verb.
+ *
+ * @param string url - Endpoint URL.
+ * @param object data - Data to send with the request.
+ * @return object - Promise
+ */
+ put(url, data) {
+ return this.fetch(url, { method: 'put', credentials: 'same-origin', body: data })
+ .then(checkStatus);
+ }
- /**
- * Makes a newtwork request using the DELETE HTTP verb.
- *
- * @param string url - Endpoint URL.
- * @param object data - Data to send with the request.
- * @return object - Promise
- */
- delete(url, data) {
- return this.fetch(url, { method: 'delete', credentials: 'same-origin', body: data })
- .then(checkStatus);
- }
+ /**
+ * Makes a newtwork request using the DELETE HTTP verb.
+ *
+ * @param string url - Endpoint URL.
+ * @param object data - Data to send with the request.
+ * @return object - Promise
+ */
+ delete(url, data) {
+ return this.fetch(url, { method: 'delete', credentials: 'same-origin', body: data })
+ .then(checkStatus);
+ }
}
// Exported as a singleton so we can implement things like
// global caching and request batching at some stage.
-let backend = new SilverStripeBackend();
+const backend = new SilverStripeBackend();
export default backend;
diff --git a/admin/javascript/src/silverstripe-component.js b/admin/javascript/src/silverstripe-component.js
index adf27c028..bbb1a0716 100644
--- a/admin/javascript/src/silverstripe-component.js
+++ b/admin/javascript/src/silverstripe-component.js
@@ -2,54 +2,107 @@
* @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';
class SilverStripeComponent extends Component {
- /**
- * @func componentDidMount
- * @desc Bind event listeners which are triggered by legacy-land JavaScript.
- * This lets us update the component when something happens in the outside world.
- */
- componentDidMount() {
- if (typeof this.props.cmsEvents === 'undefined') {
- return;
- }
+ constructor(props) {
+ super(props);
- // Save some props for later. When we come to unbind these listeners
- // there's no guarantee these props will be the same or even present.
- this.cmsEvents = this.props.cmsEvents;
+ // 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;
- for (let cmsEvent in this.cmsEvents) {
- $(document).on(cmsEvent, this.cmsEvents[cmsEvent].bind(this));
- }
- }
+ this.render = () => {
+ let component = null;
- /**
- * @func componentWillUnmount
- * @desc Unbind the event listeners we added in componentDidMount.
- */
- componentWillUnmount() {
- for (let cmsEvent in this.cmsEvents) {
- $(document).off(cmsEvent);
- }
- }
+ if (this.isComponentRoute()) {
+ component = this._render();
+ }
- /**
- * @func emitCmsEvent
- * @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.
- * @desc Notifies legacy-land something has changed within our component.
- */
- emitCmsEvent(componentEvent, data) {
- $(document).trigger(componentEvent, data);
- }
+ 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() {
+ if (typeof this.props.cmsEvents === 'undefined') {
+ return;
+ }
+
+ // Save some props for later. When we come to unbind these listeners
+ // there's no guarantee these props will be the same or even present.
+ this.cmsEvents = this.props.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));
+ }
+ }
+ }
+
+ componentWillUnmount() {
+ // Unbind the event listeners we added in componentDidMount.
+ for (const cmsEvent in this.cmsEvents) {
+ if ({}.hasOwnProperty.call(this.cmsEvents, cmsEvent)) {
+ $(document).off(cmsEvent);
+ }
+ }
+ }
+
+ handleEnterRoute(ctx, next) {
+ next();
+ }
+
+ handleExitRoute(ctx, next) {
+ next();
+ }
+
+ /**
+ * 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 object|string|array|number [data] - Some data to pass with the event.
+ */
+ emitCmsEvent(componentEvent, data) {
+ $(document).trigger(componentEvent, data);
+ }
}
SilverStripeComponent.propTypes = {
- 'cmsEvents': React.PropTypes.object
+ cmsEvents: React.PropTypes.object,
+ route: React.PropTypes.string,
};
export default SilverStripeComponent;
diff --git a/admin/javascript/src/sspath.js b/admin/javascript/src/sspath.js
index 5edeb8e9c..09bb273c5 100644
--- a/admin/javascript/src/sspath.js
+++ b/admin/javascript/src/sspath.js
@@ -35,24 +35,24 @@ var $window = $( window ),
// URL as well as some other commonly used sub-parts. When used with RegExp.exec()
// or String.match, it parses the URL into a results array that looks like this:
//
- // [0]: http://jblas:password@mycompany.com:8080/mail/inbox?msg=1234&type=unread#msg-content
- // [1]: http://jblas:password@mycompany.com:8080/mail/inbox?msg=1234&type=unread
- // [2]: http://jblas:password@mycompany.com:8080/mail/inbox
- // [3]: http://jblas:password@mycompany.com:8080
- // [4]: http:
- // [5]: //
- // [6]: jblas:password@mycompany.com:8080
- // [7]: jblas:password
- // [8]: jblas
- // [9]: password
- // [10]: mycompany.com:8080
- // [11]: mycompany.com
- // [12]: 8080
- // [13]: /mail/inbox
- // [14]: /mail/
- // [15]: inbox
- // [16]: ?msg=1234&type=unread
- // [17]: #msg-content
+ // [0]: http://jblas:password@mycompany.com:8080/mail/inbox?msg=1234&type=unread#msg-content
+ // [1]: http://jblas:password@mycompany.com:8080/mail/inbox?msg=1234&type=unread
+ // [2]: http://jblas:password@mycompany.com:8080/mail/inbox
+ // [3]: http://jblas:password@mycompany.com:8080
+ // [4]: http:
+ // [5]: //
+ // [6]: jblas:password@mycompany.com:8080
+ // [7]: jblas:password
+ // [8]: jblas
+ // [9]: password
+ // [10]: mycompany.com:8080
+ // [11]: mycompany.com
+ // [12]: 8080
+ // [13]: /mail/inbox
+ // [14]: /mail/
+ // [15]: inbox
+ // [16]: ?msg=1234&type=unread
+ // [17]: #msg-content
//
urlParseRE: /^(((([^:\/#\?]+:)?(?:(\/\/)((?:(([^:@\/#\?]+)(?:\:([^:@\/#\?]+))?)@)?(([^:\/#\?\]\[]+|\[[^\/\]@#?]+\])(?:\:([0-9]+))?))?)?)?((\/?(?:[^\/\?#]+\/+)*)([^\?#]*)))?(\?[^#]+)?)(#.*)?/,
@@ -72,23 +72,23 @@ var $window = $( window ),
// like all other browsers do, so we normalize everything so its consistent
// no matter what browser we're running on.
return {
- href: matches[ 0 ] || "",
+ href: matches[ 0 ] || "",
hrefNoHash: matches[ 1 ] || "",
hrefNoSearch: matches[ 2 ] || "",
- domain: matches[ 3 ] || "",
- protocol: matches[ 4 ] || "",
+ domain: matches[ 3 ] || "",
+ protocol: matches[ 4 ] || "",
doubleSlash: matches[ 5 ] || "",
- authority: matches[ 6 ] || "",
- username: matches[ 8 ] || "",
- password: matches[ 9 ] || "",
- host: matches[ 10 ] || "",
- hostname: matches[ 11 ] || "",
- port: matches[ 12 ] || "",
- pathname: matches[ 13 ] || "",
- directory: matches[ 14 ] || "",
- filename: matches[ 15 ] || "",
- search: matches[ 16 ] || "",
- hash: matches[ 17 ] || ""
+ authority: matches[ 6 ] || "",
+ username: matches[ 8 ] || "",
+ password: matches[ 9 ] || "",
+ host: matches[ 10 ] || "",
+ hostname: matches[ 11 ] || "",
+ port: matches[ 12 ] || "",
+ pathname: matches[ 13 ] || "",
+ directory: matches[ 14 ] || "",
+ filename: matches[ 15 ] || "",
+ search: matches[ 16 ] || "",
+ hash: matches[ 17 ] || ""
};
},
diff --git a/admin/javascript/src/state/config/action-types.js b/admin/javascript/src/state/config/action-types.js
index 9494962d0..3b14a70ba 100644
--- a/admin/javascript/src/state/config/action-types.js
+++ b/admin/javascript/src/state/config/action-types.js
@@ -1,3 +1,3 @@
export default {
- SET_CONFIG: 'SET_CONFIG'
-}
+ SET_CONFIG: 'SET_CONFIG',
+};
diff --git a/admin/javascript/src/state/config/actions.js b/admin/javascript/src/state/config/actions.js
index a6f69a974..d26139ee4 100644
--- a/admin/javascript/src/state/config/actions.js
+++ b/admin/javascript/src/state/config/actions.js
@@ -6,10 +6,9 @@ import ACTION_TYPES from './action-types';
* @param object config
*/
export function setConfig(config) {
- return (dispatch, getState) => {
- return dispatch({
- type: ACTION_TYPES.SET_CONFIG,
- payload: { config }
- });
- }
+ return (dispatch) =>
+ dispatch({
+ type: ACTION_TYPES.SET_CONFIG,
+ payload: { config },
+ });
}
diff --git a/admin/javascript/src/state/config/reducer.js b/admin/javascript/src/state/config/reducer.js
index 88309c9da..ef5c623a9 100644
--- a/admin/javascript/src/state/config/reducer.js
+++ b/admin/javascript/src/state/config/reducer.js
@@ -2,17 +2,15 @@ import deepFreeze from 'deep-freeze';
import ACTION_TYPES from './action-types';
function configReducer(state = {}, action) {
+ switch (action.type) {
- switch (action.type) {
+ case ACTION_TYPES.SET_CONFIG:
+ return deepFreeze(Object.assign({}, state, action.payload.config));
- case ACTION_TYPES.SET_CONFIG:
- return deepFreeze(Object.assign({}, state, action.payload.config));
-
- default:
- return state;
-
- }
+ default:
+ return state;
+ }
}
export default configReducer;
diff --git a/admin/javascript/src/state/records/action-types.js b/admin/javascript/src/state/records/action-types.js
index 7f83118ac..186cae2f2 100644
--- a/admin/javascript/src/state/records/action-types.js
+++ b/admin/javascript/src/state/records/action-types.js
@@ -1,11 +1,11 @@
export default {
- CREATE_RECORD: 'CREATE_RECORD',
- UPDATE_RECORD: 'UPDATE_RECORD',
- DELETE_RECORD: 'DELETE_RECORD',
- FETCH_RECORDS_REQUEST: 'FETCH_RECORDS_REQUEST',
- FETCH_RECORDS_FAILURE: 'FETCH_RECORDS_FAILURE',
- FETCH_RECORDS_SUCCESS: 'FETCH_RECORDS_SUCCESS',
- DELETE_RECORD_REQUEST: 'DELETE_RECORD_REQUEST',
- DELETE_RECORD_FAILURE: 'DELETE_RECORD_FAILURE',
- DELETE_RECORD_SUCCESS: 'DELETE_RECORD_SUCCESS'
+ CREATE_RECORD: 'CREATE_RECORD',
+ UPDATE_RECORD: 'UPDATE_RECORD',
+ DELETE_RECORD: 'DELETE_RECORD',
+ FETCH_RECORDS_REQUEST: 'FETCH_RECORDS_REQUEST',
+ FETCH_RECORDS_FAILURE: 'FETCH_RECORDS_FAILURE',
+ FETCH_RECORDS_SUCCESS: 'FETCH_RECORDS_SUCCESS',
+ DELETE_RECORD_REQUEST: 'DELETE_RECORD_REQUEST',
+ DELETE_RECORD_FAILURE: 'DELETE_RECORD_FAILURE',
+ DELETE_RECORD_SUCCESS: 'DELETE_RECORD_SUCCESS',
};
diff --git a/admin/javascript/src/state/records/actions.js b/admin/javascript/src/state/records/actions.js
index ae793b892..44de8aa15 100644
--- a/admin/javascript/src/state/records/actions.js
+++ b/admin/javascript/src/state/records/actions.js
@@ -1,5 +1,4 @@
import ACTION_TYPES from './action-types';
-import fetch from 'isomorphic-fetch';
import backend from 'silverstripe-backend.js';
/**
@@ -14,8 +13,8 @@ import backend from 'silverstripe-backend.js';
* @return string
*/
function populate(str, params) {
- let names = ['id'];
- return names.reduce((str, name) => str.replace(`:${name}`, params[name]), str);
+ const names = ['id'];
+ return names.reduce((acc, name) => acc.replace(`:${name}`, params[name]), str);
}
/**
@@ -26,19 +25,27 @@ function populate(str, params) {
* @param string url API endpoint
*/
export function fetchRecords(recordType, method, url) {
- let payload = {recordType: recordType};
- url = populate(url, payload);
- return (dispatch, getState) => {
- dispatch ({type: ACTION_TYPES.FETCH_RECORDS_REQUEST, payload: payload});
- return backend[method.toLowerCase()](url)
- .then(response => response.json())
- .then(json => {
- dispatch({type: ACTION_TYPES.FETCH_RECORDS_SUCCESS, payload: {recordType: recordType, data: json}})
- })
- .catch((err) => {
- dispatch({type: ACTION_TYPES.FETCH_RECORDS_FAILURE, payload: {error: err, recordType: recordType}})
- });
- }
+ const payload = { recordType };
+ return (dispatch) => {
+ dispatch({
+ type: ACTION_TYPES.FETCH_RECORDS_REQUEST,
+ payload,
+ });
+ return backend[method.toLowerCase()](populate(url, payload))
+ .then(response => response.json())
+ .then(json => {
+ dispatch({
+ type: ACTION_TYPES.FETCH_RECORDS_SUCCESS,
+ payload: { recordType, data: json },
+ });
+ })
+ .catch((err) => {
+ 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
*/
export function deleteRecord(recordType, id, method, url) {
- let payload = {recordType: recordType, id: id};
- url = populate(url, payload);
- return (dispatch, getState) => {
- dispatch ({type: ACTION_TYPES.DELETE_RECORD_REQUEST, payload: payload});
- return backend[method.toLowerCase()](url)
- .then(json => {
- dispatch({type: ACTION_TYPES.DELETE_RECORD_SUCCESS, payload: {recordType: recordType, id: id}})
- })
- .catch((err) => {
- dispatch({type: ACTION_TYPES.DELETE_RECORD_FAILURE, payload: {error: err, recordType: recordType, id: id}})
- });
- }
+ const payload = { recordType, id };
+ return (dispatch) => {
+ dispatch({
+ type: ACTION_TYPES.DELETE_RECORD_REQUEST,
+ payload,
+ });
+ return backend[method.toLowerCase()](populate(url, payload))
+ .then(() => {
+ dispatch({
+ type: ACTION_TYPES.DELETE_RECORD_SUCCESS,
+ payload: { recordType, id },
+ });
+ })
+ .catch((err) => {
+ dispatch({
+ type: ACTION_TYPES.DELETE_RECORD_FAILURE,
+ payload: { error: err, recordType, id },
+ });
+ });
+ };
}
diff --git a/admin/javascript/src/state/records/reducer.js b/admin/javascript/src/state/records/reducer.js
index a6aa540e7..7babf062b 100644
--- a/admin/javascript/src/state/records/reducer.js
+++ b/admin/javascript/src/state/records/reducer.js
@@ -5,59 +5,58 @@ const initialState = {
};
function recordsReducer(state = initialState, action) {
- let records;
- let recordType;
+ let records;
+ let recordType;
- switch (action.type) {
+ switch (action.type) {
- case ACTION_TYPES.CREATE_RECORD:
- return deepFreeze(Object.assign({}, state, {
+ case ACTION_TYPES.CREATE_RECORD:
+ return deepFreeze(Object.assign({}, state, {
- }));
+ }));
- case ACTION_TYPES.UPDATE_RECORD:
- return deepFreeze(Object.assign({}, state, {
+ case ACTION_TYPES.UPDATE_RECORD:
+ return deepFreeze(Object.assign({}, state, {
- }));
+ }));
- case ACTION_TYPES.DELETE_RECORD:
- return deepFreeze(Object.assign({}, state, {
+ case ACTION_TYPES.DELETE_RECORD:
+ return deepFreeze(Object.assign({}, state, {
- }));
+ }));
- case ACTION_TYPES.FETCH_RECORDS_REQUEST:
- return state;
+ case ACTION_TYPES.FETCH_RECORDS_REQUEST:
+ return state;
- case ACTION_TYPES.FETCH_RECORDS_FAILURE:
- return state;
+ case ACTION_TYPES.FETCH_RECORDS_FAILURE:
+ return state;
- case ACTION_TYPES.FETCH_RECORDS_SUCCESS:
- recordType = action.payload.recordType;
- // TODO Automatic pluralisation from recordType
- records = action.payload.data._embedded[recordType + 's'];
- return deepFreeze(Object.assign({}, state, {
- [recordType]: records
- }));
+ case ACTION_TYPES.FETCH_RECORDS_SUCCESS:
+ recordType = action.payload.recordType;
+ // TODO Automatic pluralisation from recordType
+ records = action.payload.data._embedded[`${recordType}s`];
+ return deepFreeze(Object.assign({}, state, {
+ [recordType]: records,
+ }));
- case ACTION_TYPES.DELETE_RECORD_REQUEST:
- return state;
+ case ACTION_TYPES.DELETE_RECORD_REQUEST:
+ return state;
- case ACTION_TYPES.DELETE_RECORD_FAILURE:
- return state;
+ case ACTION_TYPES.DELETE_RECORD_FAILURE:
+ return state;
- case ACTION_TYPES.DELETE_RECORD_SUCCESS:
- recordType = action.payload.recordType;
- records = state[recordType]
- .filter(record => record.ID != action.payload.id)
+ case ACTION_TYPES.DELETE_RECORD_SUCCESS:
+ recordType = action.payload.recordType;
+ records = state[recordType]
+ .filter(record => record.ID !== action.payload.id);
- return deepFreeze(Object.assign({}, state, {
- [recordType]: records
- }));
-
- default:
- return state;
- }
+ return deepFreeze(Object.assign({}, state, {
+ [recordType]: records,
+ }));
+ default:
+ return state;
+ }
}
export default recordsReducer;
diff --git a/admin/javascript/src/state/records/tests/reducer-test.js b/admin/javascript/src/state/records/tests/reducer-test.js
index 870d5f122..10d92d26e 100644
--- a/admin/javascript/src/state/records/tests/reducer-test.js
+++ b/admin/javascript/src/state/records/tests/reducer-test.js
@@ -3,34 +3,34 @@ jest.dontMock('../reducer');
jest.dontMock('../action-types');
var recordsReducer = require('../reducer').default,
- ACTION_TYPES = require('../action-types').default;
+ ACTION_TYPES = require('../action-types').default;
describe('recordsReducer', () => {
- describe('DELETE_RECORD_SUCCESS', () => {
- const initialState = {
- TypeA: [
- {ID: 1},
- {ID: 2}
- ],
- TypeB: [
- {ID: 1},
- {ID: 2}
- ]
- };
+ describe('DELETE_RECORD_SUCCESS', () => {
+ const initialState = {
+ TypeA: [
+ {ID: 1},
+ {ID: 2}
+ ],
+ TypeB: [
+ {ID: 1},
+ {ID: 2}
+ ]
+ };
- it('removes records from the declared type', () => {
- const nextState = recordsReducer(initialState, {
- type: ACTION_TYPES.DELETE_RECORD_SUCCESS,
- payload: { recordType: 'TypeA', id: 2 }
- });
+ it('removes records from the declared type', () => {
+ const nextState = recordsReducer(initialState, {
+ type: ACTION_TYPES.DELETE_RECORD_SUCCESS,
+ payload: { recordType: 'TypeA', id: 2 }
+ });
- expect(nextState.TypeA.length).toBe(1);
- expect(nextState.TypeA[0].ID).toBe(1);
- expect(nextState.TypeB.length).toBe(2);
- expect(nextState.TypeB[0].ID).toBe(1);
- expect(nextState.TypeB[1].ID).toBe(2);
- })
- });
+ expect(nextState.TypeA.length).toBe(1);
+ expect(nextState.TypeA[0].ID).toBe(1);
+ expect(nextState.TypeB.length).toBe(2);
+ expect(nextState.TypeB[0].ID).toBe(1);
+ expect(nextState.TypeB[1].ID).toBe(2);
+ })
+ });
});
diff --git a/admin/javascript/src/state/schema/action-types.js b/admin/javascript/src/state/schema/action-types.js
index da4e46080..a27790f0d 100644
--- a/admin/javascript/src/state/schema/action-types.js
+++ b/admin/javascript/src/state/schema/action-types.js
@@ -1,5 +1,5 @@
const ACTION_TYPES = {
- SET_SCHEMA: 'SET_SCHEMA'
+ SET_SCHEMA: 'SET_SCHEMA',
};
export default ACTION_TYPES;
diff --git a/admin/javascript/src/state/schema/actions.js b/admin/javascript/src/state/schema/actions.js
index 3f8cc2058..cc6ac4671 100644
--- a/admin/javascript/src/state/schema/actions.js
+++ b/admin/javascript/src/state/schema/actions.js
@@ -6,10 +6,9 @@ import ACTION_TYPES from './action-types';
* @param string schema - JSON schema for the layout.
*/
export function setSchema(schema) {
- return (dispatch, getState) => {
- return dispatch ({
- type: ACTION_TYPES.SET_SCHEMA,
- payload: schema
- });
- }
+ return (dispatch) =>
+ dispatch({
+ type: ACTION_TYPES.SET_SCHEMA,
+ payload: schema,
+ });
}
diff --git a/admin/javascript/src/state/schema/reducer.js b/admin/javascript/src/state/schema/reducer.js
index 29e25faa0..23af05f8b 100644
--- a/admin/javascript/src/state/schema/reducer.js
+++ b/admin/javascript/src/state/schema/reducer.js
@@ -4,15 +4,14 @@ import ACTION_TYPES from './action-types';
const initialState = deepFreeze({});
export default function schemaReducer(state = initialState, action = null) {
+ switch (action.type) {
- switch (action.type) {
-
- case ACTION_TYPES.SET_SCHEMA:
- const id = action.payload.schema.schema_url;
- return deepFreeze(Object.assign({}, state, {[id]: action.payload}));
-
- default:
- return state;
+ case ACTION_TYPES.SET_SCHEMA: {
+ const id = action.payload.schema.schema_url;
+ return deepFreeze(Object.assign({}, state, { [id]: action.payload }));
}
+ default:
+ return state;
+ }
}
diff --git a/admin/javascript/src/state/schema/tests/reducer-test.js b/admin/javascript/src/state/schema/tests/reducer-test.js
index 50c61b9da..9210cdd19 100644
--- a/admin/javascript/src/state/schema/tests/reducer-test.js
+++ b/admin/javascript/src/state/schema/tests/reducer-test.js
@@ -7,18 +7,18 @@ import ACTION_TYPES from '../action-types';
describe('schemaReducer', () => {
- describe('SET_SCHEMA', () => {
+ describe('SET_SCHEMA', () => {
- it('should create a new form', () => {
- const initialState = { };
- const serverResponse = { id: 'TestForm', schema_url: 'URL' };
+ it('should create a new form', () => {
+ const initialState = { };
+ const serverResponse = { id: 'TestForm', schema_url: 'URL' };
- const nextState = schemaReducer(initialState, {
- type: ACTION_TYPES.SET_SCHEMA,
- payload: { schema: serverResponse }
- });
+ const nextState = schemaReducer(initialState, {
+ type: ACTION_TYPES.SET_SCHEMA,
+ payload: { schema: serverResponse }
+ });
- expect(nextState.URL.schema.id).toBe('TestForm');
- });
+ expect(nextState.URL.schema.id).toBe('TestForm');
});
+ });
});
diff --git a/admin/javascript/src/tests/reducer-register-test.js b/admin/javascript/src/tests/reducer-register-test.js
index 1935c2b37..49d377029 100644
--- a/admin/javascript/src/tests/reducer-register-test.js
+++ b/admin/javascript/src/tests/reducer-register-test.js
@@ -4,41 +4,41 @@ var reducerRegister = require('../reducer-register').default;
describe('ReducerRegister', () => {
- var reducer = () => null;
+ var reducer = () => null;
- it('should add a reducer to the register', () => {
- expect(reducerRegister.getAll().test).toBe(undefined);
+ it('should add a reducer to the register', () => {
+ expect(reducerRegister.getAll().test).toBe(undefined);
- reducerRegister.add('test', reducer);
- expect(reducerRegister.getAll().test).toBe(reducer);
+ reducerRegister.add('test', reducer);
+ expect(reducerRegister.getAll().test).toBe(reducer);
- reducerRegister.remove('test');
- });
+ reducerRegister.remove('test');
+ });
- it('should remove a reducer from the register', () => {
- reducerRegister.add('test', reducer);
- expect(reducerRegister.getAll().test).toBe(reducer);
+ it('should remove a reducer from the register', () => {
+ reducerRegister.add('test', reducer);
+ expect(reducerRegister.getAll().test).toBe(reducer);
- reducerRegister.remove('test');
- expect(reducerRegister.getAll().test).toBe(undefined);
- });
+ reducerRegister.remove('test');
+ expect(reducerRegister.getAll().test).toBe(undefined);
+ });
- it('should get all reducers from the register', () => {
- reducerRegister.add('test1', reducer);
- reducerRegister.add('test2', reducer);
+ it('should get all reducers from the register', () => {
+ reducerRegister.add('test1', reducer);
+ reducerRegister.add('test2', reducer);
- expect(reducerRegister.getAll().test1).toBe(reducer);
- expect(reducerRegister.getAll().test2).toBe(reducer);
+ expect(reducerRegister.getAll().test1).toBe(reducer);
+ expect(reducerRegister.getAll().test2).toBe(reducer);
- reducerRegister.remove('test1');
- reducerRegister.remove('test2');
- });
+ reducerRegister.remove('test1');
+ reducerRegister.remove('test2');
+ });
- it('should get a single reducer from the register', () => {
- reducerRegister.add('test', reducer);
- expect(reducerRegister.getByKey('test')).toBe(reducer);
+ it('should get a single reducer from the register', () => {
+ reducerRegister.add('test', reducer);
+ expect(reducerRegister.getByKey('test')).toBe(reducer);
- reducerRegister.remove('test');
- });
+ reducerRegister.remove('test');
+ });
});
diff --git a/admin/javascript/src/tests/silverstripe-backend-test.js b/admin/javascript/src/tests/silverstripe-backend-test.js
index c3edb3b05..bab15624f 100644
--- a/admin/javascript/src/tests/silverstripe-backend-test.js
+++ b/admin/javascript/src/tests/silverstripe-backend-test.js
@@ -5,98 +5,98 @@ import fetch from 'isomorphic-fetch';
import backend from '../silverstripe-backend';
var getFetchMock = function(data) {
- let mock = jest.genMockFunction();
- let promise = new Promise((resolve, reject) => {
- process.nextTick(() => resolve(data));
- });
- mock.mockReturnValue(promise);
+ let mock = jest.genMockFunction();
+ let promise = new Promise((resolve, reject) => {
+ process.nextTick(() => resolve(data));
+ });
+ mock.mockReturnValue(promise);
- return mock;
+ return mock;
};
describe('SilverStripeBackend', () => {
- beforeAll(() => {
- let fetchMock = getFetchMock();
- backend.fetch = fetchMock;
+ beforeAll(() => {
+ let fetchMock = getFetchMock();
+ backend.fetch = fetchMock;
+ });
+
+ describe('get()', () => {
+
+ it('should return a promise', () => {
+ var promise = backend.get('http://example.com');
+ expect(typeof promise).toBe('object');
});
- describe('get()', () => {
-
- it('should return a promise', () => {
- var promise = backend.get('http://example.com');
- expect(typeof promise).toBe('object');
- });
-
- it('should send a GET request to an endpoint', () => {
- backend.get('http://example.com');
- expect(backend.fetch).toBeCalledWith(
- 'http://example.com',
- {method: 'get', credentials: 'same-origin'}
- );
- });
-
+ it('should send a GET request to an endpoint', () => {
+ backend.get('http://example.com');
+ expect(backend.fetch).toBeCalledWith(
+ 'http://example.com',
+ {method: 'get', credentials: 'same-origin'}
+ );
});
- describe('post()', () => {
+ });
- it('should return a promise', () => {
- var promise = backend.get('http://example.com/item');
- expect(typeof promise).toBe('object');
- });
-
- it('should send a POST request to an endpoint', () => {
- const postData = { id: 1 };
-
- backend.post('http://example.com', postData);
-
- expect(backend.fetch).toBeCalledWith(
- 'http://example.com',
- {method: 'post', body: postData, credentials: 'same-origin'}
- );
- });
+ describe('post()', () => {
+ it('should return a promise', () => {
+ var promise = backend.get('http://example.com/item');
+ expect(typeof promise).toBe('object');
});
- describe('put()', () => {
+ it('should send a POST request to an endpoint', () => {
+ const postData = { id: 1 };
- it('should return a promise', () => {
- var promise = backend.get('http://example.com/item');
- expect(typeof promise).toBe('object');
- });
-
- it('should send a PUT request to an endpoint', () => {
- const putData = { id: 1 };
-
- backend.put('http://example.com', putData);
-
- expect(backend.fetch).toBeCalledWith(
- 'http://example.com',
- {method: 'put', body: putData, credentials: 'same-origin'}
- );
- });
+ backend.post('http://example.com', postData);
+ expect(backend.fetch).toBeCalledWith(
+ 'http://example.com',
+ {method: 'post', body: postData, credentials: 'same-origin'}
+ );
});
- describe('delete()', () => {
+ });
- it('should return a promise', () => {
- var promise = backend.get('http://example.com/item');
-
- expect(typeof promise).toBe('object');
- });
-
- it('should send a DELETE request to an endpoint', () => {
- const deleteData = { id: 1 };
-
- backend.delete('http://example.com', deleteData);
-
- expect(backend.fetch).toBeCalledWith(
- 'http://example.com',
- {method: 'delete', body: deleteData, credentials: 'same-origin'}
- );
- });
+ describe('put()', () => {
+ it('should return a promise', () => {
+ var promise = backend.get('http://example.com/item');
+ expect(typeof promise).toBe('object');
});
+ it('should send a PUT request to an endpoint', () => {
+ const putData = { id: 1 };
+
+ backend.put('http://example.com', putData);
+
+ expect(backend.fetch).toBeCalledWith(
+ 'http://example.com',
+ {method: 'put', body: putData, credentials: 'same-origin'}
+ );
+ });
+
+ });
+
+ describe('delete()', () => {
+
+ it('should return a promise', () => {
+ var promise = backend.get('http://example.com/item');
+
+ expect(typeof promise).toBe('object');
+ });
+
+ it('should send a DELETE request to an endpoint', () => {
+ const deleteData = { id: 1 };
+
+ backend.delete('http://example.com', deleteData);
+
+ expect(backend.fetch).toBeCalledWith(
+ 'http://example.com',
+ {method: 'delete', body: deleteData, credentials: 'same-origin'}
+ );
+ });
+
+ });
+
});
diff --git a/gulpfile.js b/gulpfile.js
index 6f336e787..50883d270 100644
--- a/gulpfile.js
+++ b/gulpfile.js
@@ -1,110 +1,112 @@
-var packageJson = require('./package.json'),
- autoprefixer = require('autoprefixer'),
- babelify = require('babelify'),
- browserify = require('browserify'),
- eventStream = require('event-stream'),
- glob = require('glob'),
- gulp = require('gulp'),
- babel = require('gulp-babel'),
- diff = require('gulp-diff'),
- gulpif = require('gulp-if'),
- notify = require('gulp-notify'),
- postcss = require('gulp-postcss'),
- sass = require('gulp-sass'),
- sourcemaps = require('gulp-sourcemaps'),
- uglify = require('gulp-uglify'),
- gulpUtil = require('gulp-util'),
- path = require('path'),
- source = require('vinyl-source-stream'),
- buffer = require('vinyl-buffer'),
- semver = require('semver'),
- sprity = require('sprity'),
- watchify = require('watchify');
+const packageJson = require('./package.json');
+const autoprefixer = require('autoprefixer');
+const babelify = require('babelify'); // eslint-disable-line no-unused-vars
+const browserify = require('browserify');
+const eventStream = require('event-stream');
+const glob = require('glob');
+const gulp = require('gulp');
+const babel = require('gulp-babel');
+const diff = require('gulp-diff');
+const gulpif = require('gulp-if');
+const notify = require('gulp-notify');
+const postcss = require('gulp-postcss');
+const sass = require('gulp-sass');
+const sourcemaps = require('gulp-sourcemaps');
+const uglify = require('gulp-uglify');
+const gulpUtil = require('gulp-util');
+const path = require('path');
+const source = require('vinyl-source-stream');
+const buffer = require('vinyl-buffer');
+const semver = require('semver');
+const sprity = require('sprity');
+const watchify = require('watchify');
-var isDev = typeof process.env.npm_config_development !== 'undefined';
+const isDev = typeof process.env.npm_config_development !== 'undefined';
-var PATHS = {
- MODULES: './node_modules',
- ADMIN: './admin',
- ADMIN_IMAGES: './admin/images',
- ADMIN_SCSS: './admin/scss',
- ADMIN_THIRDPARTY: './admin/thirdparty',
- ADMIN_JAVASCRIPT_SRC: './admin/javascript/src',
- ADMIN_JAVASCRIPT_DIST: './admin/javascript/dist',
- FRAMEWORK: '.',
- FRAMEWORK_THIRDPARTY: './thirdparty',
- FRAMEWORK_DEV_INSTALL: './dev/install',
- FRAMEWORK_JAVASCRIPT_SRC: './javascript/src',
- FRAMEWORK_JAVASCRIPT_DIST: './javascript/dist'
+process.env.NODE_ENV = isDev ? 'development' : 'production';
+
+const PATHS = {
+ MODULES: './node_modules',
+ ADMIN: './admin',
+ ADMIN_IMAGES: './admin/images',
+ ADMIN_SCSS: './admin/scss',
+ ADMIN_THIRDPARTY: './admin/thirdparty',
+ ADMIN_JAVASCRIPT_SRC: './admin/javascript/src',
+ ADMIN_JAVASCRIPT_DIST: './admin/javascript/dist',
+ FRAMEWORK: '.',
+ FRAMEWORK_THIRDPARTY: './thirdparty',
+ FRAMEWORK_DEV_INSTALL: './dev/install',
+ FRAMEWORK_JAVASCRIPT_SRC: './javascript/src',
+ FRAMEWORK_JAVASCRIPT_DIST: './javascript/dist',
};
// 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 = {
- debug: true,
- paths: [PATHS.ADMIN_JAVASCRIPT_SRC, PATHS.FRAMEWORK_JAVASCRIPT_SRC]
+const browserifyOptions = {
+ debug: true,
+ paths: [PATHS.ADMIN_JAVASCRIPT_SRC, PATHS.FRAMEWORK_JAVASCRIPT_SRC],
};
-var babelifyOptions = {
- presets: ['es2015', 'react'],
- ignore: /(node_modules|thirdparty)/,
- comments: false
+const babelifyOptions = {
+ presets: ['es2015', 'react'],
+ ignore: /(node_modules|thirdparty)/,
+ comments: false,
};
// Used for autoprefixing css properties (same as Bootstrap Aplha.2 defaults)
-var supportedBrowsers = [
- 'Chrome >= 35',
- 'Firefox >= 31',
- 'Edge >= 12',
- 'Explorer >= 9',
- 'iOS >= 8',
- 'Safari >= 8',
- 'Android 2.3',
- 'Android >= 4',
- 'Opera >= 12'
+const supportedBrowsers = [
+ 'Chrome >= 35',
+ 'Firefox >= 31',
+ 'Edge >= 12',
+ 'Explorer >= 9',
+ 'iOS >= 8',
+ 'Safari >= 8',
+ 'Android 2.3',
+ 'Android >= 4',
+ 'Opera >= 12',
];
-var blueimpFileUploadConfig = {
- src: PATHS.MODULES + '/blueimp-file-upload',
- dest: PATHS.FRAMEWORK_THIRDPARTY + '/jquery-fileupload',
- files: [
- '/cors/jquery.postmessage-transport.js',
- '/cors/jquery.xdr-transport.js',
- '/jquery.fileupload-ui.js',
- '/jquery.fileupload.js',
- '/jquery.iframe-transport.js'
- ]
+const blueimpFileUploadConfig = {
+ src: `${PATHS.MODULES}/blueimp-file-upload`,
+ dest: PATHS.FRAMEWORK_THIRDPARTY + '/jquery-fileupload',
+ files: [
+ '/cors/jquery.postmessage-transport.js',
+ '/cors/jquery.xdr-transport.js',
+ '/jquery.fileupload-ui.js',
+ '/jquery.fileupload.js',
+ '/jquery.iframe-transport.js'
+ ]
};
var blueimpLoadImageConfig = {
- src: PATHS.MODULES + '/blueimp-load-image',
- dest: PATHS.FRAMEWORK_THIRDPARTY + '/javascript-loadimage',
- files: ['/load-image.js']
+ src: PATHS.MODULES + '/blueimp-load-image',
+ dest: PATHS.FRAMEWORK_THIRDPARTY + '/javascript-loadimage',
+ files: ['/load-image.js']
};
var blueimpTmplConfig = {
- src: PATHS.MODULES + '/blueimp-tmpl',
- dest: PATHS.FRAMEWORK_THIRDPARTY + '/javascript-templates',
- files: ['/tmpl.js']
+ src: PATHS.MODULES + '/blueimp-tmpl',
+ dest: PATHS.FRAMEWORK_THIRDPARTY + '/javascript-templates',
+ files: ['/tmpl.js']
};
var jquerySizesConfig = {
- src: PATHS.MODULES + '/jquery-sizes',
- dest: PATHS.ADMIN_THIRDPARTY + '/jsizes',
- files: ['/lib/jquery.sizes.js']
+ src: PATHS.MODULES + '/jquery-sizes',
+ dest: PATHS.ADMIN_THIRDPARTY + '/jsizes',
+ files: ['/lib/jquery.sizes.js']
};
var tinymceConfig = {
- src: PATHS.MODULES + '/tinymce',
- dest: PATHS.FRAMEWORK_THIRDPARTY + '/tinymce',
- files: [
- '/tinymce.min.js', // Exclude unminified file to keep repository size down
- '/jquery.tinymce.min.js',
- '/themes/**',
- '/skins/**',
- '/plugins/**'
- ]
+ src: PATHS.MODULES + '/tinymce',
+ dest: PATHS.FRAMEWORK_THIRDPARTY + '/tinymce',
+ files: [
+ '/tinymce.min.js', // Exclude unminified file to keep repository size down
+ '/jquery.tinymce.min.js',
+ '/themes/**',
+ '/skins/**',
+ '/plugins/**'
+ ]
};
/**
@@ -116,12 +118,12 @@ var tinymceConfig = {
* @param array libConfig.files - The list of files to copy from the source to the destination directory
*/
function copyFiles(libConfig) {
- libConfig.files.forEach(function (file) {
- var dir = path.parse(file).dir;
+ libConfig.files.forEach(function (file) {
+ var dir = path.parse(file).dir;
- gulp.src(libConfig.src + file)
- .pipe(gulp.dest(libConfig.dest + dir));
- });
+ gulp.src(libConfig.src + file)
+ .pipe(gulp.dest(libConfig.dest + dir));
+ });
}
/**
@@ -133,17 +135,17 @@ function copyFiles(libConfig) {
* @param array libConfig.files - The list of files to copy from the source to the destination directory
*/
function diffFiles(libConfig) {
- libConfig.files.forEach(function (file) {
- var dir = path.parse(file).dir;
+ libConfig.files.forEach(function (file) {
+ var dir = path.parse(file).dir;
- gulp.src(libConfig.src + file)
- .pipe(diff(libConfig.dest + dir))
- .pipe(diff.reporter({ fail: true, quiet: true }))
- .on('error', function (error) {
- console.error(new Error('Sanity check failed. \'' + libConfig.dest + file + '\' has been modified.'));
- process.exit(1);
- });
- });
+ gulp.src(libConfig.src + file)
+ .pipe(diff(libConfig.dest + dir))
+ .pipe(diff.reporter({ fail: true, quiet: true }))
+ .on('error', function (error) {
+ console.error(new Error('Sanity check failed. \'' + libConfig.dest + file + '\' has been modified.'));
+ process.exit(1);
+ });
+ });
}
/**
@@ -154,31 +156,31 @@ function diffFiles(libConfig) {
* @return object
*/
function transformToUmd(files, dest) {
- return eventStream.merge(files.map(function (file) {
- return gulp.src(file)
- .pipe(babel({
- presets: ['es2015'],
- moduleId: 'ss.' + path.parse(file).name,
- plugins: ['transform-es2015-modules-umd'],
- comments: false
- }))
- .on('error', notify.onError({
- message: 'Error: <%= error.message %>',
- }))
- .pipe(gulp.dest(dest));
- }));
+ return eventStream.merge(files.map(function (file) {
+ return gulp.src(file)
+ .pipe(babel({
+ presets: ['es2015'],
+ moduleId: 'ss.' + path.parse(file).name,
+ plugins: ['transform-es2015-modules-umd'],
+ comments: false
+ }))
+ .on('error', notify.onError({
+ message: 'Error: <%= error.message %>',
+ }))
+ .pipe(gulp.dest(dest));
+ }));
}
// Make sure the version of Node being used is valid.
if (!semver.satisfies(process.versions.node, packageJson.engines.node)) {
- console.error('Invalid Node.js version. You need to be using ' + packageJson.engines.node + '. If you want to manage multiple Node.js versions try https://github.com/creationix/nvm');
- process.exit(1);
+ console.error('Invalid Node.js version. You need to be using ' + packageJson.engines.node + '. If you want to manage multiple Node.js versions try https://github.com/creationix/nvm');
+ process.exit(1);
}
if (isDev) {
- browserifyOptions.cache = {};
- browserifyOptions.packageCache = {};
- browserifyOptions.plugin = [watchify];
+ browserifyOptions.cache = {};
+ browserifyOptions.packageCache = {};
+ browserifyOptions.plugin = [watchify];
}
gulp.task('build', ['umd', 'bundle']);
@@ -186,162 +188,161 @@ gulp.task('build', ['umd', 'bundle']);
gulp.task('bundle', ['bundle-lib', 'bundle-legacy', 'bundle-framework']);
gulp.task('bundle-lib', function bundleLib() {
- var bundleFileName = 'bundle-lib.js';
+ var bundleFileName = 'bundle-lib.js';
- return browserify(Object.assign({}, browserifyOptions, { entries: PATHS.ADMIN_JAVASCRIPT_SRC + '/bundles/lib.js' }))
- .on('update', bundleLib)
- .on('log', function (msg) { gulpUtil.log('Finished', 'bundled ' + bundleFileName + ' ' + msg) })
- .transform('babelify', babelifyOptions)
- .require('deep-freeze', { expose: 'deep-freeze' })
- .require('react', { expose: 'react' })
- .require('react-addons-css-transition-group', { expose: 'react-addons-css-transition-group' })
- .require('react-addons-test-utils', { expose: 'react-addons-test-utils' })
- .require('react-dom', { expose: 'react-dom' })
- .require('react-redux', { expose: 'react-redux' })
- .require('redux', { expose: 'redux' })
- .require('redux-thunk', { expose: 'redux-thunk' })
- .require(PATHS.ADMIN_JAVASCRIPT_SRC + '/components/form/index', { expose: 'components/form/index' })
- .require(PATHS.ADMIN_JAVASCRIPT_SRC + '/components/form-action/index', { expose: 'components/form-action/index' })
- .require(PATHS.ADMIN_JAVASCRIPT_SRC + '/components/form-builder/index', { expose: 'components/form-builder/index' })
- .require(PATHS.ADMIN_JAVASCRIPT_SRC + '/components/grid-field/index', { expose: 'components/grid-field/index' })
- .require(PATHS.ADMIN_JAVASCRIPT_SRC + '/components/grid-field/cell', { expose: 'components/grid-field/cell/index' })
- .require(PATHS.ADMIN_JAVASCRIPT_SRC + '/components/grid-field/header', { expose: 'components/grid-field/header' })
- .require(PATHS.ADMIN_JAVASCRIPT_SRC + '/components/grid-field/header-cell', { expose: 'components/grid-field/header-cell' })
- .require(PATHS.ADMIN_JAVASCRIPT_SRC + '/components/grid-field/row', { expose: 'components/grid-field/row' })
- .require(PATHS.ADMIN_JAVASCRIPT_SRC + '/components/grid-field/table', { expose: 'components/grid-field/table' })
- .require(PATHS.ADMIN_JAVASCRIPT_SRC + '/components/hidden-field/index', { expose: 'components/hidden-field/index' })
- .require(PATHS.ADMIN_JAVASCRIPT_SRC + '/components/text-field/index', { expose: 'components/text-field/index' })
- .require(PATHS.ADMIN_JAVASCRIPT_SRC + '/components/north-header/index', { expose: 'components/north-header/index' })
- .require(PATHS.ADMIN_JAVASCRIPT_SRC + '/components/north-header-breadcrumbs/index', { expose: 'components/north-header-breadcrumbs/index' })
- .require(PATHS.FRAMEWORK_JAVASCRIPT_SRC + '/i18n.js', { expose: 'i18n' })
- .require(PATHS.FRAMEWORK_JAVASCRIPT_SRC + '/jQuery.js', { expose: 'jQuery' })
- .require(PATHS.ADMIN_JAVASCRIPT_SRC + '/reducer-register.js', { expose: 'reducer-register' })
- .require(PATHS.FRAMEWORK_JAVASCRIPT_SRC + '/router.js', { expose: 'router' })
- .require(PATHS.ADMIN_JAVASCRIPT_SRC + '/silverstripe-component', { expose: 'silverstripe-component' })
- .bundle()
- .on('update', bundleLib)
- .on('error', notify.onError({ message: bundleFileName + ': <%= error.message %>' }))
- .pipe(source(bundleFileName))
- .pipe(buffer())
- .pipe(sourcemaps.init({ loadMaps: true }))
- .pipe(gulpif(!isDev, uglify()))
- .pipe(sourcemaps.write('./'))
- .pipe(gulp.dest(PATHS.ADMIN_JAVASCRIPT_DIST));
+ return browserify(Object.assign({}, browserifyOptions, { entries: PATHS.ADMIN_JAVASCRIPT_SRC + '/bundles/lib.js' }))
+ .on('update', bundleLib)
+ .on('log', function (msg) { gulpUtil.log('Finished', 'bundled ' + bundleFileName + ' ' + msg) })
+ .transform('babelify', babelifyOptions)
+ .require('deep-freeze', { expose: 'deep-freeze' })
+ .require('react', { expose: 'react' })
+ .require('react-addons-css-transition-group', { expose: 'react-addons-css-transition-group' })
+ .require('react-addons-test-utils', { expose: 'react-addons-test-utils' })
+ .require('react-dom', { expose: 'react-dom' })
+ .require('react-redux', { expose: 'react-redux' })
+ .require('redux', { expose: 'redux' })
+ .require('redux-thunk', { expose: 'redux-thunk' })
+ .require(PATHS.ADMIN_JAVASCRIPT_SRC + '/components/form/index', { expose: 'components/form/index' })
+ .require(PATHS.ADMIN_JAVASCRIPT_SRC + '/components/form-action/index', { expose: 'components/form-action/index' })
+ .require(PATHS.ADMIN_JAVASCRIPT_SRC + '/components/form-builder/index', { expose: 'components/form-builder/index' })
+ .require(PATHS.ADMIN_JAVASCRIPT_SRC + '/components/grid-field/index', { expose: 'components/grid-field/index' })
+ .require(PATHS.ADMIN_JAVASCRIPT_SRC + '/components/grid-field/cell', { expose: 'components/grid-field/cell/index' })
+ .require(PATHS.ADMIN_JAVASCRIPT_SRC + '/components/grid-field/header', { expose: 'components/grid-field/header' })
+ .require(PATHS.ADMIN_JAVASCRIPT_SRC + '/components/grid-field/header-cell', { expose: 'components/grid-field/header-cell' })
+ .require(PATHS.ADMIN_JAVASCRIPT_SRC + '/components/grid-field/row', { expose: 'components/grid-field/row' })
+ .require(PATHS.ADMIN_JAVASCRIPT_SRC + '/components/grid-field/table', { expose: 'components/grid-field/table' })
+ .require(PATHS.ADMIN_JAVASCRIPT_SRC + '/components/hidden-field/index', { expose: 'components/hidden-field/index' })
+ .require(PATHS.ADMIN_JAVASCRIPT_SRC + '/components/text-field/index', { expose: 'components/text-field/index' })
+ .require(PATHS.ADMIN_JAVASCRIPT_SRC + '/components/north-header/index', { expose: 'components/north-header/index' })
+ .require(PATHS.ADMIN_JAVASCRIPT_SRC + '/components/north-header-breadcrumbs/index', { expose: 'components/north-header-breadcrumbs/index' })
+ .require(PATHS.FRAMEWORK_JAVASCRIPT_SRC + '/i18n.js', { expose: 'i18n' })
+ .require(PATHS.FRAMEWORK_JAVASCRIPT_SRC + '/jQuery.js', { expose: 'jQuery' })
+ .require(PATHS.ADMIN_JAVASCRIPT_SRC + '/reducer-register.js', { expose: 'reducer-register' })
+ .require(PATHS.FRAMEWORK_JAVASCRIPT_SRC + '/router.js', { expose: 'router' })
+ .require(PATHS.ADMIN_JAVASCRIPT_SRC + '/silverstripe-component', { expose: 'silverstripe-component' })
+ .bundle()
+ .on('error', notify.onError({ message: bundleFileName + ': <%= error.message %>' }))
+ .pipe(source(bundleFileName))
+ .pipe(buffer())
+ .pipe(sourcemaps.init({ loadMaps: true }))
+ .pipe(gulpif(!isDev, uglify()))
+ .pipe(sourcemaps.write('./'))
+ .pipe(gulp.dest(PATHS.ADMIN_JAVASCRIPT_DIST));
});
gulp.task('bundle-legacy', function bundleLeftAndMain() {
- var bundleFileName = 'bundle-legacy.js';
+ var bundleFileName = 'bundle-legacy.js';
- return browserify(Object.assign({}, browserifyOptions, { entries: PATHS.ADMIN_JAVASCRIPT_SRC + '/bundles/legacy.js' }))
- .on('update', bundleLeftAndMain)
- .on('log', function (msg) { gulpUtil.log('Finished', 'bundled ' + bundleFileName + ' ' + msg) })
- .transform('babelify', babelifyOptions)
- .external('jQuery')
- .external('i18n')
- .external('router')
- .bundle()
- .on('update', bundleLeftAndMain)
- .on('error', notify.onError({ message: bundleFileName + ': <%= error.message %>' }))
- .pipe(source(bundleFileName))
- .pipe(buffer())
- .pipe(sourcemaps.init({ loadMaps: true }))
- .pipe(gulpif(!isDev, uglify()))
- .pipe(sourcemaps.write('./'))
- .pipe(gulp.dest(PATHS.ADMIN_JAVASCRIPT_DIST));
+ return browserify(Object.assign({}, browserifyOptions, { entries: PATHS.ADMIN_JAVASCRIPT_SRC + '/bundles/legacy.js' }))
+ .on('update', bundleLeftAndMain)
+ .on('log', function (msg) { gulpUtil.log('Finished', 'bundled ' + bundleFileName + ' ' + msg) })
+ .transform('babelify', babelifyOptions)
+ .external('jQuery')
+ .external('i18n')
+ .external('router')
+ .bundle()
+ .on('update', bundleLeftAndMain)
+ .on('error', notify.onError({ message: bundleFileName + ': <%= error.message %>' }))
+ .pipe(source(bundleFileName))
+ .pipe(buffer())
+ .pipe(sourcemaps.init({ loadMaps: true }))
+ .pipe(gulpif(!isDev, uglify()))
+ .pipe(sourcemaps.write('./'))
+ .pipe(gulp.dest(PATHS.ADMIN_JAVASCRIPT_DIST));
});
gulp.task('bundle-framework', function bundleBoot() {
- var bundleFileName = 'bundle-framework.js';
+ var bundleFileName = 'bundle-framework.js';
- return browserify(Object.assign({}, browserifyOptions, { entries: PATHS.ADMIN_JAVASCRIPT_SRC + '/boot/index.js' }))
- .on('update', bundleBoot)
- .on('log', function (msg) { gulpUtil.log('Finished', 'bundled ' + bundleFileName + ' ' + msg) })
- .transform('babelify', babelifyOptions)
- .external('components/action-button/index')
- .external('components/north-header/index')
- .external('components/form-builder/index')
- .external('deep-freeze')
- .external('components/grid-field/index')
- .external('i18n')
- .external('jQuery')
- .external('page.js')
- .external('react-addons-test-utils')
- .external('react-dom')
- .external('react-redux')
- .external('react')
- .external('reducer-register')
- .external('redux-thunk')
- .external('redux')
- .external('silverstripe-component')
- .bundle()
- .on('update', bundleBoot)
- .on('error', notify.onError({ message: bundleFileName + ': <%= error.message %>' }))
- .pipe(source(bundleFileName))
- .pipe(buffer())
- .pipe(sourcemaps.init({ loadMaps: true }))
- .pipe(gulpif(!isDev, uglify()))
- .pipe(sourcemaps.write('./'))
- .pipe(gulp.dest(PATHS.ADMIN_JAVASCRIPT_DIST));
+ return browserify(Object.assign({}, browserifyOptions, { entries: PATHS.ADMIN_JAVASCRIPT_SRC + '/boot/index.js' }))
+ .on('update', bundleBoot)
+ .on('log', function (msg) { gulpUtil.log('Finished', 'bundled ' + bundleFileName + ' ' + msg) })
+ .transform('babelify', babelifyOptions)
+ .external('components/action-button/index')
+ .external('components/north-header/index')
+ .external('components/form-builder/index')
+ .external('deep-freeze')
+ .external('components/grid-field/index')
+ .external('i18n')
+ .external('jQuery')
+ .external('page.js')
+ .external('react-addons-test-utils')
+ .external('react-dom')
+ .external('react-redux')
+ .external('react')
+ .external('reducer-register')
+ .external('redux-thunk')
+ .external('redux')
+ .external('silverstripe-component')
+ .bundle()
+ .on('update', bundleBoot)
+ .on('error', notify.onError({ message: bundleFileName + ': <%= error.message %>' }))
+ .pipe(source(bundleFileName))
+ .pipe(buffer())
+ .pipe(sourcemaps.init({ loadMaps: true }))
+ .pipe(gulpif(!isDev, uglify()))
+ .pipe(sourcemaps.write('./'))
+ .pipe(gulp.dest(PATHS.ADMIN_JAVASCRIPT_DIST));
});
gulp.task('sanity', function () {
- diffFiles(blueimpFileUploadConfig);
- diffFiles(blueimpLoadImageConfig);
- diffFiles(blueimpTmplConfig);
- diffFiles(jquerySizesConfig);
- diffFiles(tinymceConfig);
+ diffFiles(blueimpFileUploadConfig);
+ diffFiles(blueimpLoadImageConfig);
+ diffFiles(blueimpTmplConfig);
+ diffFiles(jquerySizesConfig);
+ diffFiles(tinymceConfig);
});
gulp.task('thirdparty', function () {
- copyFiles(blueimpFileUploadConfig);
- copyFiles(blueimpLoadImageConfig);
- copyFiles(blueimpTmplConfig);
- copyFiles(jquerySizesConfig);
- copyFiles(tinymceConfig);
+ copyFiles(blueimpFileUploadConfig);
+ copyFiles(blueimpLoadImageConfig);
+ copyFiles(blueimpTmplConfig);
+ copyFiles(jquerySizesConfig);
+ copyFiles(tinymceConfig);
});
gulp.task('umd', ['umd-admin', 'umd-framework'], function () {
- if (isDev) {
- gulp.watch(PATHS.ADMIN_JAVASCRIPT_SRC + '/*.js', ['umd-admin']);
- gulp.watch(PATHS.FRAMEWORK_JAVASCRIPT_SRC + '/*.js', ['umd-framework']);
- }
+ if (isDev) {
+ gulp.watch(PATHS.ADMIN_JAVASCRIPT_SRC + '/*.js', ['umd-admin']);
+ gulp.watch(PATHS.FRAMEWORK_JAVASCRIPT_SRC + '/*.js', ['umd-framework']);
+ }
});
gulp.task('umd-admin', function () {
- var files = glob.sync(PATHS.ADMIN_JAVASCRIPT_SRC + '/*.js', { ignore: PATHS.ADMIN_JAVASCRIPT_SRC + '/LeftAndMain.!(Ping).js' });
+ var files = glob.sync(PATHS.ADMIN_JAVASCRIPT_SRC + '/*.js', { ignore: PATHS.ADMIN_JAVASCRIPT_SRC + '/LeftAndMain.!(Ping).js' });
- return transformToUmd(files, PATHS.ADMIN_JAVASCRIPT_DIST);
+ return transformToUmd(files, PATHS.ADMIN_JAVASCRIPT_DIST);
});
gulp.task('umd-framework', function () {
- return transformToUmd(glob.sync(PATHS.FRAMEWORK_JAVASCRIPT_SRC + '/*.js'), PATHS.FRAMEWORK_JAVASCRIPT_DIST);
+ return transformToUmd(glob.sync(PATHS.FRAMEWORK_JAVASCRIPT_SRC + '/*.js'), PATHS.FRAMEWORK_JAVASCRIPT_DIST);
});
/*
* Takes individual images and compiles them together into sprites
*/
gulp.task('sprites', function () {
- return sprity.src({
- src: PATHS.ADMIN_IMAGES + '/sprites/src/**/*.{png,jpg}',
- cssPath: '../images/sprites/dist',
- style: './_spritey.scss',
- processor: 'sass',
- split: true,
- margin: 0
- })
- .pipe(gulpif('*.png', gulp.dest(PATHS.ADMIN_IMAGES + '/sprites/dist'), gulp.dest(PATHS.ADMIN_SCSS)))
+ return sprity.src({
+ src: PATHS.ADMIN_IMAGES + '/sprites/src/**/*.{png,jpg}',
+ cssPath: '../images/sprites/dist',
+ style: './_spritey.scss',
+ processor: 'sass',
+ split: true,
+ margin: 0
+ })
+ .pipe(gulpif('*.png', gulp.dest(PATHS.ADMIN_IMAGES + '/sprites/dist'), gulp.dest(PATHS.ADMIN_SCSS)))
});
gulp.task('css', ['compile:css'], function () {
- if (isDev) {
- rootCompileFolders.forEach(function (folder) {
- gulp.watch(folder + '/scss/**/*.scss', ['compile:css']);
- });
+ if (isDev) {
+ rootCompileFolders.forEach(function (folder) {
+ gulp.watch(folder + '/scss/**/*.scss', ['compile:css']);
+ });
- // Watch the .scss files in react components
- gulp.watch('./admin/javascript/src/**/*.scss', ['compile:css']);
- }
+ // Watch the .scss files in react components
+ gulp.watch('./admin/javascript/src/**/*.scss', ['compile:css']);
+ }
})
/*
@@ -349,20 +350,20 @@ gulp.task('css', ['compile:css'], function () {
* Watches for changes if --development flag is given
*/
gulp.task('compile:css', function () {
- var outputStyle = isDev ? 'expanded' : 'compressed';
+ var outputStyle = isDev ? 'expanded' : 'compressed';
- var tasks = rootCompileFolders.map(function(folder) {
- return gulp.src(folder + '/scss/**/*.scss')
- .pipe(sourcemaps.init())
- .pipe(sass({ outputStyle: outputStyle })
- .on('error', notify.onError({
- message: 'Error: <%= error.message %>'
- }))
- )
- .pipe(postcss([autoprefixer({ browsers: supportedBrowsers })]))
- .pipe(sourcemaps.write())
- .pipe(gulp.dest(folder + '/css'))
- });
+ var tasks = rootCompileFolders.map(function(folder) {
+ return gulp.src(folder + '/scss/**/*.scss')
+ .pipe(sourcemaps.init())
+ .pipe(sass({ outputStyle: outputStyle })
+ .on('error', notify.onError({
+ message: 'Error: <%= error.message %>'
+ }))
+ )
+ .pipe(postcss([autoprefixer({ browsers: supportedBrowsers })]))
+ .pipe(sourcemaps.write())
+ .pipe(gulp.dest(folder + '/css'))
+ });
- return tasks;
+ return tasks;
});
diff --git a/javascript/src/HtmlEditorField.js b/javascript/src/HtmlEditorField.js
index 87b3a73ca..9a8197463 100644
--- a/javascript/src/HtmlEditorField.js
+++ b/javascript/src/HtmlEditorField.js
@@ -27,7 +27,7 @@ ss.editorWrappers.tinyMCE = (function() {
* Initialise the editor
*
* @param {String} ID of parent textarea domID
- */
+ */
init: function(ID) {
editorID = ID;
@@ -45,7 +45,7 @@ ss.editorWrappers.tinyMCE = (function() {
* Get TinyMCE Editor instance
*
* @returns Editor
- */
+ */
getInstance: function() {
return tinymce.EditorManager.get(editorID);
},
@@ -68,7 +68,7 @@ ss.editorWrappers.tinyMCE = (function() {
* Get config for this data
*
* @returns array
- */
+ */
getConfig: function() {
var selector = "#" + editorID,
config = $(selector).data('config'),
@@ -581,8 +581,8 @@ $.entwine('ss', function($) {
break;
case 'file':
- var fileid = this.find('.ss-uploadfield .ss-uploadfield-item').attr('data-fileid');
- href = fileid ? '[file_link,id=' + fileid + ']' : '';
+ var fileid = this.find('.ss-uploadfield .ss-uploadfield-item').attr('data-fileid');
+ href = fileid ? '[file_link,id=' + fileid + ']' : '';
break;
case 'email':
diff --git a/javascript/src/UploadField.js b/javascript/src/UploadField.js
index 391bae389..b721b6f22 100644
--- a/javascript/src/UploadField.js
+++ b/javascript/src/UploadField.js
@@ -85,7 +85,7 @@ $.widget('blueimpUIX.fileupload', $.blueimpUI.fileupload, {
e.preventDefault(); // Avoid a form submit
return false;
});
- } else { //regular file upload
+ } else { //regular file upload
return $.blueimpUI.fileupload.prototype._onSend.call(that, e, data);
}
}
diff --git a/javascript/src/router.js b/javascript/src/router.js
index 2a56f2a80..41559cd5c 100644
--- a/javascript/src/router.js
+++ b/javascript/src/router.js
@@ -8,19 +8,20 @@ import page from 'page.js';
* Wrapper for `page.show()` with SilverStripe specific behaviour.
*/
function show(pageShow) {
- return (path, state, dispatch, push) => {
- // Normalise `path` so that pattern matching is more robust.
- // For example if your route is '/pages' it should match when `path` is
- // 'http://foo.com/admin/pages', '/pages', and 'pages'.
- var el = document.createElement('a');
- el.href = path;
- path = el.pathname;
- if(el.search) {
- path += el.search;
- }
-
- return pageShow(path, state, dispatch, push);
+ return (path, state, dispatch, push) => {
+ // Normalise `path` so that pattern matching is more robust.
+ // For example if your route is '/pages' it should match when `path` is
+ // 'http://foo.com/admin/pages', '/pages', and 'pages'.
+ const el = document.createElement('a');
+ let pathWithSearch;
+ el.href = path;
+ pathWithSearch = el.pathname;
+ if (el.search) {
+ pathWithSearch += el.search;
}
+
+ return pageShow(pathWithSearch, state, dispatch, push);
+ };
}
page.show = show(page.show);
diff --git a/package.json b/package.json
index 38ca1c7cc..436305ede 100644
--- a/package.json
+++ b/package.json
@@ -17,7 +17,8 @@
"sprites": "gulp sprites",
"test": "NODE_PATH=\"./javascript/src:./admin/javascript/src\" jest",
"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": {
"type": "git",
@@ -60,6 +61,9 @@
"babel-preset-react": "^6.5.0",
"babelify": "^7.2.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",
"glob": "^6.0.4",
"gulp": "^3.9.0",