mirror of
https://github.com/silverstripe/silverstripe-framework
synced 2024-06-17 02:01:46 +02:00
Add ESLint support
See https://github.com/silverstripe/silverstripe-framework/pull/5108
This commit is contained in:
parent
6aa22c38ed
commit
34d40bed5f
|
@ -17,6 +17,10 @@ trim_trailing_whitespace = false
|
||||||
indent_size = 2
|
indent_size = 2
|
||||||
indent_style = space
|
indent_style = space
|
||||||
|
|
||||||
|
[*.js]
|
||||||
|
indent_size = 2
|
||||||
|
indent_style = space
|
||||||
|
|
||||||
# Don't perform any clean-up on thirdparty files
|
# Don't perform any clean-up on thirdparty files
|
||||||
|
|
||||||
[thirdparty/**]
|
[thirdparty/**]
|
||||||
|
|
37
.eslintignore
Normal file
37
.eslintignore
Normal file
|
@ -0,0 +1,37 @@
|
||||||
|
# Ignore dist files
|
||||||
|
javascript/dist/
|
||||||
|
|
||||||
|
# Ignore legacy files
|
||||||
|
javascript/src/AssetUploadField.js
|
||||||
|
javascript/src/ConfirmedPasswordField.js
|
||||||
|
javascript/src/DateField.js
|
||||||
|
javascript/src/GridField.js
|
||||||
|
javascript/src/HtmlEditorField.js
|
||||||
|
javascript/src/InlineFormAction.js
|
||||||
|
javascript/src/PermissionCheckboxSetField.js
|
||||||
|
javascript/src/SelectionGroup.js
|
||||||
|
javascript/src/TabSet.js
|
||||||
|
javascript/src/TinyMCE_SSPlugin.js
|
||||||
|
javascript/src/ToggleCompositeField.js
|
||||||
|
javascript/src/ToggleField.js
|
||||||
|
javascript/src/TreeDropdownField.js
|
||||||
|
javascript/src/UploadField.js
|
||||||
|
javascript/src/UploadField_downloadtemplate.js
|
||||||
|
javascript/src/UploadField_select.js
|
||||||
|
javascript/src/UploadField_uploadtemplate.js
|
||||||
|
javascript/src/i18n.js
|
||||||
|
javascript/src/i18nx.js
|
||||||
|
javascript/src/jQuery.js
|
||||||
|
admin/javascript/src/LeftAndMain.js
|
||||||
|
admin/javascript/src/LeftAndMain.*.js
|
||||||
|
admin/javascript/src/CMSSecurity.js
|
||||||
|
admin/javascript/src/MemberDatetimeOptionsetField.js
|
||||||
|
admin/javascript/src/MemberImportForm.js
|
||||||
|
admin/javascript/src/ModelAdmin.js
|
||||||
|
admin/javascript/src/SecurityAdmin.js
|
||||||
|
admin/javascript/src/leaktools.js
|
||||||
|
admin/javascript/src/sspath.js
|
||||||
|
admin/javascript/src/ssui.core.js
|
||||||
|
|
||||||
|
# Ignore tests
|
||||||
|
admin/javascript/**/tests/
|
|
@ -6,38 +6,38 @@ import $ from 'jQuery';
|
||||||
|
|
||||||
$.entwine('ss', function ($) {
|
$.entwine('ss', function ($) {
|
||||||
|
|
||||||
$('.cms-description-toggle').entwine({
|
$('.cms-description-toggle').entwine({
|
||||||
onadd: function () {
|
onadd: function () {
|
||||||
var shown = false, // Current state of the description.
|
var shown = false, // Current state of the description.
|
||||||
fieldId = this.prop('id').substr(0, this.prop('id').indexOf('_Holder')),
|
fieldId = this.prop('id').substr(0, this.prop('id').indexOf('_Holder')),
|
||||||
$trigger = this.find('.cms-description-trigger'), // Click target for toggling the description.
|
$trigger = this.find('.cms-description-trigger'), // Click target for toggling the description.
|
||||||
$description = this.find('.description');
|
$description = this.find('.description');
|
||||||
|
|
||||||
// Prevent multiple events being added.
|
// Prevent multiple events being added.
|
||||||
if (this.hasClass('description-toggle-enabled')) {
|
if (this.hasClass('description-toggle-enabled')) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// If a custom trigger han't been supplied use a sensible default.
|
// If a custom trigger han't been supplied use a sensible default.
|
||||||
if ($trigger.length === 0) {
|
if ($trigger.length === 0) {
|
||||||
$trigger = this
|
$trigger = this
|
||||||
.find('.middleColumn')
|
.find('.middleColumn')
|
||||||
.first() // Get the first middleColumn so we don't add multiple triggers on composite field types.
|
.first() // Get the first middleColumn so we don't add multiple triggers on composite field types.
|
||||||
.after('<label class="right" for="' + fieldId + '"><a class="cms-description-trigger" href="javascript:void(0)"><span class="btn-icon-information"></span></a></label>')
|
.after('<label class="right" for="' + fieldId + '"><a class="cms-description-trigger" href="javascript:void(0)"><span class="btn-icon-information"></span></a></label>')
|
||||||
.next();
|
.next();
|
||||||
}
|
}
|
||||||
|
|
||||||
this.addClass('description-toggle-enabled');
|
this.addClass('description-toggle-enabled');
|
||||||
|
|
||||||
// Toggle next description when button is clicked.
|
// Toggle next description when button is clicked.
|
||||||
$trigger.on('click', function() {
|
$trigger.on('click', function() {
|
||||||
$description[shown ? 'hide' : 'show']();
|
$description[shown ? 'hide' : 'show']();
|
||||||
shown = !shown;
|
shown = !shown;
|
||||||
});
|
});
|
||||||
|
|
||||||
// Hide next description by default.
|
// Hide next description by default.
|
||||||
$description.hide();
|
$description.hide();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
|
@ -15,11 +15,11 @@ $.entwine('ss', function($){
|
||||||
* <ul class="cms-menu-list">
|
* <ul class="cms-menu-list">
|
||||||
* <li><a href="#">Item 1</a></li>
|
* <li><a href="#">Item 1</a></li>
|
||||||
* <li class="current opened">
|
* <li class="current opened">
|
||||||
* <a href="#">Item 2</a>
|
* <a href="#">Item 2</a>
|
||||||
* <ul>
|
* <ul>
|
||||||
* <li class="current opened"><a href="#">Item 2.1</a></li>
|
* <li class="current opened"><a href="#">Item 2.1</a></li>
|
||||||
* <li><a href="#">Item 2.2</a></li>
|
* <li><a href="#">Item 2.2</a></li>
|
||||||
* </ul>
|
* </ul>
|
||||||
* </li>
|
* </li>
|
||||||
* </ul>
|
* </ul>
|
||||||
*
|
*
|
||||||
|
@ -38,7 +38,7 @@ $.entwine('ss', function($){
|
||||||
$(this).addClass('collapse');
|
$(this).addClass('collapse');
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
} else { //collapse
|
} else { //collapse
|
||||||
$(this).children('ul').each(function() {
|
$(this).children('ul').each(function() {
|
||||||
$(this).addClass('collapsed-flyout');
|
$(this).addClass('collapsed-flyout');
|
||||||
$(this).hasClass('collapse');
|
$(this).hasClass('collapse');
|
||||||
|
@ -59,7 +59,7 @@ $.entwine('ss', function($){
|
||||||
|
|
||||||
//hide all the flyout-indicator
|
//hide all the flyout-indicator
|
||||||
$('.cms-menu-list').find('.child-flyout-indicator').hide();
|
$('.cms-menu-list').find('.child-flyout-indicator').hide();
|
||||||
} else { //collapse
|
} else { //collapse
|
||||||
//hide the flyout only if it is not the current section
|
//hide the flyout only if it is not the current section
|
||||||
$('.collapsed-flyout').find('li').each(function() {
|
$('.collapsed-flyout').find('li').each(function() {
|
||||||
//if (!$(this).hasClass('current'))
|
//if (!$(this).hasClass('current'))
|
||||||
|
@ -110,11 +110,11 @@ $.entwine('ss', function($){
|
||||||
* @func getEvaluatedCollapsedState
|
* @func getEvaluatedCollapsedState
|
||||||
* @return {boolean} - Returns true if the menu should be collapsed, false if expanded.
|
* @return {boolean} - Returns true if the menu should be collapsed, false if expanded.
|
||||||
* @desc Evaluate whether the menu should be collapsed.
|
* @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".
|
* 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.
|
* 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.
|
* 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.
|
* 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.
|
* Here we use the manually set state and the automatic behaviour to evaluate what the collapsed state should be.
|
||||||
*/
|
*/
|
||||||
getEvaluatedCollapsedState: function () {
|
getEvaluatedCollapsedState: function () {
|
||||||
var shouldCollapse,
|
var shouldCollapse,
|
||||||
|
@ -269,7 +269,7 @@ $.entwine('ss', function($){
|
||||||
$('.collapsed-flyout').show();
|
$('.collapsed-flyout').show();
|
||||||
fly.addClass('opened');
|
fly.addClass('opened');
|
||||||
fly.children('ul').find('li').fadeIn('fast');
|
fly.children('ul').find('li').fadeIn('fast');
|
||||||
} else { //collapse
|
} else { //collapse
|
||||||
if(li) {
|
if(li) {
|
||||||
li.remove();
|
li.remove();
|
||||||
}
|
}
|
||||||
|
|
|
@ -501,17 +501,17 @@ $.entwine('ss.preview', function($){
|
||||||
_loadCurrentPage: function() {
|
_loadCurrentPage: function() {
|
||||||
if (!this.getIsPreviewEnabled()) return;
|
if (!this.getIsPreviewEnabled()) return;
|
||||||
|
|
||||||
var doc,
|
var doc,
|
||||||
containerEl = $('.cms-container');
|
containerEl = $('.cms-container');
|
||||||
try {
|
try {
|
||||||
doc = this.find('iframe')[0].contentDocument;
|
doc = this.find('iframe')[0].contentDocument;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
// iframe can't be accessed - might be secure?
|
// iframe can't be accessed - might be secure?
|
||||||
console.warn('Unable to access iframe, possible https mis-match');
|
console.warn('Unable to access iframe, possible https mis-match');
|
||||||
}
|
}
|
||||||
if (!doc) {
|
if (!doc) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Load this page in the admin interface if appropriate
|
// Load this page in the admin interface if appropriate
|
||||||
var id = $(doc).find('meta[name=x-page-id]').attr('content');
|
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.
|
* Prepare the iframe content for preview.
|
||||||
*/
|
*/
|
||||||
_adjustIframeForPreview: function() {
|
_adjustIframeForPreview: function() {
|
||||||
var iframe = this.find('iframe')[0],
|
var iframe = this.find('iframe')[0],
|
||||||
doc;
|
doc;
|
||||||
if(!iframe){
|
if(!iframe){
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
doc = iframe.contentDocument;
|
doc = iframe.contentDocument;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
// iframe can't be accessed - might be secure?
|
// iframe can't be accessed - might be secure?
|
||||||
console.warn('Unable to access iframe, possible https mis-match');
|
console.warn('Unable to access iframe, possible https mis-match');
|
||||||
}
|
}
|
||||||
if(!doc) {
|
if(!doc) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Open external links in new window to avoid "escaping" the internal page context in the preview
|
// 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.
|
// iframe, which is important to stay in for the CMS logic.
|
||||||
|
|
|
@ -10,24 +10,27 @@ import SchemaReducer from 'state/schema/reducer';
|
||||||
import RecordsReducer from 'state/records/reducer';
|
import RecordsReducer from 'state/records/reducer';
|
||||||
|
|
||||||
// Sections
|
// Sections
|
||||||
|
// eslint-disable-next-line no-unused-vars
|
||||||
import CampaignAdmin from 'sections/campaign-admin/index';
|
import CampaignAdmin from 'sections/campaign-admin/index';
|
||||||
|
|
||||||
function appBoot() {
|
function appBoot() {
|
||||||
reducerRegister.add('config', ConfigReducer);
|
reducerRegister.add('config', ConfigReducer);
|
||||||
reducerRegister.add('schemas', SchemaReducer);
|
reducerRegister.add('schemas', SchemaReducer);
|
||||||
reducerRegister.add('records', RecordsReducer);
|
reducerRegister.add('records', RecordsReducer);
|
||||||
|
|
||||||
const initialState = {};
|
const initialState = {};
|
||||||
const rootReducer = combineReducers(reducerRegister.getAll());
|
const rootReducer = combineReducers(reducerRegister.getAll());
|
||||||
const createStoreWithMiddleware = applyMiddleware(thunkMiddleware, createLogger())(createStore);
|
const createStoreWithMiddleware = applyMiddleware(thunkMiddleware, createLogger())(createStore);
|
||||||
|
|
||||||
// TODO: The store needs to be passed into route callbacks on the route context.
|
// TODO: The store needs to be passed into route callbacks on the route context.
|
||||||
window.store = createStoreWithMiddleware(rootReducer, initialState);
|
window.store = createStoreWithMiddleware(rootReducer, initialState);
|
||||||
|
|
||||||
// Set the initial config state.
|
// Set the initial config state.
|
||||||
configActions.setConfig(window.ss.config)(window.store.dispatch);
|
configActions.setConfig(window.ss.config)(window.store.dispatch);
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: This should be using `window.onload` but isn't because Entwine hooks are being used to set up the <Provider>.
|
// TODO: This should be using `window.onload` but isn't because
|
||||||
// `window.onload` happens AFTER these Entwine hooks which means the store is undefined when the <Provider> is constructed.
|
// Entwine hooks are being used to set up the <Provider>.
|
||||||
$('body').entwine({ onadd: function () { appBoot(); } });
|
// `window.onload` happens AFTER these Entwine hooks which means
|
||||||
|
// the store is undefined when the <Provider> is constructed.
|
||||||
|
$('body').entwine({ onadd: () => { appBoot(); } });
|
||||||
|
|
|
@ -2,101 +2,101 @@ import React from 'react';
|
||||||
import SilverStripeComponent from 'silverstripe-component';
|
import SilverStripeComponent from 'silverstripe-component';
|
||||||
|
|
||||||
class FormActionComponent extends SilverStripeComponent {
|
class FormActionComponent extends SilverStripeComponent {
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
|
|
||||||
this.handleClick = this.handleClick.bind(this);
|
this.handleClick = this.handleClick.bind(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
return (
|
||||||
|
<button type={this.props.type} className={this.getButtonClasses()} onClick={this.handleClick}>
|
||||||
|
{this.getLoadingIcon()}
|
||||||
|
{this.props.label}
|
||||||
|
</button>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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() {
|
// Add icon class
|
||||||
return (
|
if (typeof this.props.icon !== 'undefined') {
|
||||||
<button type={this.props.type} className={this.getButtonClasses()} onClick={this.handleClick}>
|
buttonClasses += ` font-icon-${this.props.icon}`;
|
||||||
{this.getLoadingIcon()}
|
|
||||||
{this.props.label}
|
|
||||||
</button>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
// Add loading class
|
||||||
* Returns the necessary button classes based on the given props
|
if (this.props.loading === true) {
|
||||||
*
|
buttonClasses += ' btn--loading';
|
||||||
* @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 disabled class
|
||||||
* Returns markup for the loading icon
|
if (this.props.disabled === true) {
|
||||||
*
|
buttonClasses += ' disabled';
|
||||||
* @returns object|null
|
|
||||||
*/
|
|
||||||
getLoadingIcon() {
|
|
||||||
if (this.props.loading) {
|
|
||||||
return (
|
|
||||||
<div className="btn__loading-icon" >
|
|
||||||
<svg viewBox="0 0 44 12">
|
|
||||||
<circle cx="6" cy="6" r="6" />
|
|
||||||
<circle cx="22" cy="6" r="6" />
|
|
||||||
<circle cx="38" cy="6" r="6" />
|
|
||||||
</svg>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
return buttonClasses;
|
||||||
* Event handler triggered when a user clicks the button.
|
}
|
||||||
*
|
|
||||||
* @param object event
|
/**
|
||||||
* @returns null
|
* Returns markup for the loading icon
|
||||||
*/
|
*
|
||||||
handleClick(event) {
|
* @returns object|null
|
||||||
this.props.handleClick(event);
|
*/
|
||||||
|
getLoadingIcon() {
|
||||||
|
if (this.props.loading) {
|
||||||
|
return (
|
||||||
|
<div className="btn__loading-icon" >
|
||||||
|
<svg viewBox="0 0 44 12">
|
||||||
|
<circle cx="6" cy="6" r="6" />
|
||||||
|
<circle cx="22" cy="6" r="6" />
|
||||||
|
<circle cx="38" cy="6" r="6" />
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Event handler triggered when a user clicks the button.
|
||||||
|
*
|
||||||
|
* @param object event
|
||||||
|
* @returns null
|
||||||
|
*/
|
||||||
|
handleClick(event) {
|
||||||
|
this.props.handleClick(event);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
FormActionComponent.propTypes = {
|
FormActionComponent.propTypes = {
|
||||||
handleClick: React.PropTypes.func.isRequired,
|
handleClick: React.PropTypes.func.isRequired,
|
||||||
label: React.PropTypes.string,
|
label: React.PropTypes.string,
|
||||||
type: React.PropTypes.string,
|
type: React.PropTypes.string,
|
||||||
loading: React.PropTypes.bool,
|
loading: React.PropTypes.bool,
|
||||||
icon: React.PropTypes.string,
|
icon: React.PropTypes.string,
|
||||||
disabled: React.PropTypes.bool,
|
disabled: React.PropTypes.bool,
|
||||||
style: React.PropTypes.string
|
style: React.PropTypes.string,
|
||||||
};
|
};
|
||||||
|
|
||||||
FormActionComponent.defaultProps = {
|
FormActionComponent.defaultProps = {
|
||||||
type: 'button',
|
type: 'button',
|
||||||
style: 'secondary'
|
style: 'secondary',
|
||||||
};
|
};
|
||||||
|
|
||||||
export default FormActionComponent;
|
export default FormActionComponent;
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import { bindActionCreators } from 'redux';
|
import { bindActionCreators } from 'redux';
|
||||||
import $ from 'jQuery';
|
|
||||||
import * as schemaActions from 'state/schema/actions';
|
import * as schemaActions from 'state/schema/actions';
|
||||||
import SilverStripeComponent from 'silverstripe-component';
|
import SilverStripeComponent from 'silverstripe-component';
|
||||||
import FormComponent from 'components/form/index';
|
import FormComponent from 'components/form/index';
|
||||||
|
@ -14,219 +13,217 @@ import es6promise from 'es6-promise';
|
||||||
es6promise.polyfill();
|
es6promise.polyfill();
|
||||||
|
|
||||||
// Using this to map field types to components until we implement dependency injection.
|
// Using this to map field types to components until we implement dependency injection.
|
||||||
var fakeInjector = {
|
const fakeInjector = {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Components registered with the fake DI container.
|
* Components registered with the fake DI container.
|
||||||
*/
|
*/
|
||||||
components: {
|
components: {
|
||||||
'TextField': TextField,
|
TextField,
|
||||||
'GridField': GridField,
|
GridField,
|
||||||
'HiddenField': HiddenField
|
HiddenField,
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets the component matching the passed component name.
|
* Gets the component matching the passed component name.
|
||||||
* Used when a component type is provided bt the form schema.
|
* Used when a component type is provided bt the form schema.
|
||||||
*
|
*
|
||||||
* @param string componentName - The name of the component to get from the injector.
|
* @param string componentName - The name of the component to get from the injector.
|
||||||
*
|
*
|
||||||
* @return object|null
|
* @return object|null
|
||||||
*/
|
*/
|
||||||
getComponentByName: function (componentName) {
|
getComponentByName(componentName) {
|
||||||
return this.components[componentName];
|
return this.components[componentName];
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Default data type to component mappings.
|
* Default data type to component mappings.
|
||||||
* Used as a fallback when no component type is provided in the form schema.
|
* 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.
|
* @param string dataType - The data type provided by the form schema.
|
||||||
*
|
*
|
||||||
* @return object|null
|
* @return object|null
|
||||||
*/
|
*/
|
||||||
getComponentByDataType: function (dataType) {
|
getComponentByDataType(dataType) {
|
||||||
switch (dataType) {
|
switch (dataType) {
|
||||||
case 'String':
|
case 'String':
|
||||||
return this.components.TextField;
|
return this.components.TextField;
|
||||||
case 'Hidden':
|
case 'Hidden':
|
||||||
return this.components.HiddenField;
|
return this.components.HiddenField;
|
||||||
case 'Text':
|
case 'Text':
|
||||||
// Textarea field (not implemented)
|
// Textarea field (not implemented)
|
||||||
return null;
|
return null;
|
||||||
case 'HTML':
|
case 'HTML':
|
||||||
// HTML editor field (not implemented)
|
// HTML editor field (not implemented)
|
||||||
return null;
|
return null;
|
||||||
case 'Integer':
|
case 'Integer':
|
||||||
// Numeric field (not implemented)
|
// Numeric field (not implemented)
|
||||||
return null;
|
return null;
|
||||||
case 'Decimal':
|
case 'Decimal':
|
||||||
// Numeric field (not implemented)
|
// Numeric field (not implemented)
|
||||||
return null;
|
return null;
|
||||||
case 'MultiSelect':
|
case 'MultiSelect':
|
||||||
// Radio field (not implemented)
|
// Radio field (not implemented)
|
||||||
return null;
|
return null;
|
||||||
case 'SingleSelect':
|
case 'SingleSelect':
|
||||||
// Dropdown field (not implemented)
|
// Dropdown field (not implemented)
|
||||||
return null;
|
return null;
|
||||||
case 'Date':
|
case 'Date':
|
||||||
// DateTime field (not implemented)
|
// DateTime field (not implemented)
|
||||||
return null;
|
return null;
|
||||||
case 'DateTime':
|
case 'DateTime':
|
||||||
// DateTime field (not implemented)
|
// DateTime field (not implemented)
|
||||||
return null;
|
return null;
|
||||||
case 'Time':
|
case 'Time':
|
||||||
// DateTime field (not implemented)
|
// DateTime field (not implemented)
|
||||||
return null;
|
return null;
|
||||||
case 'Boolean':
|
case 'Boolean':
|
||||||
// Checkbox field (not implemented)
|
// Checkbox field (not implemented)
|
||||||
return null;
|
return null;
|
||||||
case 'Custom':
|
case 'Custom':
|
||||||
return this.components.GridField;
|
return this.components.GridField;
|
||||||
default:
|
default:
|
||||||
return null;
|
return null;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
|
};
|
||||||
|
|
||||||
export class FormBuilderComponent extends SilverStripeComponent {
|
export class FormBuilderComponent extends SilverStripeComponent {
|
||||||
|
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(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.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 <Component key={i} {...props} />;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
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 = {
|
||||||
* Fetches data used to generate a form. This can be form schema and or form state data.
|
actions: schema.schema.actions,
|
||||||
* When the response comes back the data is saved to state.
|
attributes: schema.schema.attributes,
|
||||||
*
|
data: schema.schema.data,
|
||||||
* @param boolean schema - If form schema data should be returned in the response.
|
fields: schema.schema.fields,
|
||||||
* @param boolean state - If form state data should be returned in the response.
|
mapFieldsToComponents: this.mapFieldsToComponents,
|
||||||
*
|
};
|
||||||
* @return object - Promise from the AJAX request.
|
|
||||||
*/
|
|
||||||
fetch(schema = true, state = false) {
|
|
||||||
var headerValues = [];
|
|
||||||
|
|
||||||
if (this.isFetching === true) {
|
return <FormComponent {...formProps} />;
|
||||||
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 <Component key={i} {...props} />
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
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 <FormComponent {...formProps} />
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
FormBuilderComponent.propTypes = {
|
FormBuilderComponent.propTypes = {
|
||||||
actions: React.PropTypes.object.isRequired,
|
actions: React.PropTypes.object.isRequired,
|
||||||
schemaUrl: React.PropTypes.string.isRequired,
|
schemaUrl: React.PropTypes.string.isRequired,
|
||||||
schemas: React.PropTypes.object.isRequired
|
schemas: React.PropTypes.object.isRequired,
|
||||||
};
|
};
|
||||||
|
|
||||||
function mapStateToProps(state) {
|
function mapStateToProps(state) {
|
||||||
return {
|
return {
|
||||||
schemas: state.schemas
|
schemas: state.schemas,
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function mapDispatchToProps(dispatch) {
|
function mapDispatchToProps(dispatch) {
|
||||||
return {
|
return {
|
||||||
actions: bindActionCreators(schemaActions, dispatch)
|
actions: bindActionCreators(schemaActions, dispatch),
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export default connect(mapStateToProps, mapDispatchToProps)(FormBuilderComponent);
|
export default connect(mapStateToProps, mapDispatchToProps)(FormBuilderComponent);
|
||||||
|
|
|
@ -5,22 +5,22 @@ import { FormBuilderComponent } from '../';
|
||||||
|
|
||||||
describe('FormBuilderComponent', () => {
|
describe('FormBuilderComponent', () => {
|
||||||
|
|
||||||
describe('getFormSchema()', () => {
|
describe('getFormSchema()', () => {
|
||||||
|
|
||||||
var formBuilder;
|
var formBuilder;
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
const props = {
|
const props = {
|
||||||
store: {
|
store: {
|
||||||
getState: () => {}
|
getState: () => {}
|
||||||
},
|
},
|
||||||
actions: {},
|
actions: {},
|
||||||
schemaUrl: 'admin/assets/schema/1',
|
schemaUrl: 'admin/assets/schema/1',
|
||||||
schema: { forms: [{ schema: { id: '1', schema_url: 'admin/assets/schema/1' } }] }
|
schema: { forms: [{ schema: { id: '1', schema_url: 'admin/assets/schema/1' } }] }
|
||||||
};
|
};
|
||||||
|
|
||||||
formBuilder = new FormBuilderComponent(props);
|
formBuilder = new FormBuilderComponent(props);
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
|
@ -4,56 +4,62 @@ import FormActionComponent from 'components/form-action/index';
|
||||||
|
|
||||||
class FormComponent extends SilverStripeComponent {
|
class FormComponent extends SilverStripeComponent {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets the components responsible for perfoming actions on the form.
|
* Gets the components responsible for perfoming actions on the form.
|
||||||
* For example form submission.
|
* For example form submission.
|
||||||
*
|
*
|
||||||
* @return array|null
|
* @return array|null
|
||||||
*/
|
*/
|
||||||
getFormActionComponents() {
|
getFormActionComponents() {
|
||||||
return this.props.actions.map((action) => {
|
return this.props.actions.map((action) =>
|
||||||
return <FormActionComponent {...action} />;
|
<FormActionComponent {...action} />
|
||||||
});
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const attr = this.props.attributes;
|
const attr = this.props.attributes;
|
||||||
const fields = this.props.mapFieldsToComponents(this.props.fields);
|
const fields = this.props.mapFieldsToComponents(this.props.fields);
|
||||||
const actions = this.getFormActionComponents();
|
const actions = this.getFormActionComponents();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<form id={attr.id} className={attr.className} encType={attr.enctype} method={attr.method} action={attr.action}>
|
<form
|
||||||
{fields &&
|
id={attr.id}
|
||||||
<fieldset className='form-group'>
|
className={attr.className}
|
||||||
{fields}
|
encType={attr.enctype}
|
||||||
</fieldset>
|
method={attr.method}
|
||||||
}
|
action={attr.action}
|
||||||
|
>
|
||||||
|
{fields &&
|
||||||
|
<fieldset className="form-group">
|
||||||
|
{fields}
|
||||||
|
</fieldset>
|
||||||
|
}
|
||||||
|
|
||||||
{actions &&
|
{actions &&
|
||||||
<div className='actions-fix-btm'>
|
<div className="actions-fix-btm">
|
||||||
<div className='btn-group' role='group'>
|
<div className="btn-group" role="group">
|
||||||
{actions}
|
{actions}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
</form>
|
</form>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
FormComponent.propTypes = {
|
FormComponent.propTypes = {
|
||||||
actions: React.PropTypes.array,
|
actions: React.PropTypes.array,
|
||||||
attributes: React.PropTypes.shape({
|
attributes: React.PropTypes.shape({
|
||||||
action: React.PropTypes.string.isRequired,
|
action: React.PropTypes.string.isRequired,
|
||||||
'class': React.PropTypes.string.isRequired,
|
class: React.PropTypes.string.isRequired,
|
||||||
enctype: React.PropTypes.string.isRequired,
|
enctype: React.PropTypes.string.isRequired,
|
||||||
id: React.PropTypes.string.isRequired,
|
id: React.PropTypes.string.isRequired,
|
||||||
method: React.PropTypes.string.isRequired
|
method: React.PropTypes.string.isRequired,
|
||||||
}),
|
}),
|
||||||
data: React.PropTypes.array,
|
data: React.PropTypes.array,
|
||||||
fields: React.PropTypes.array.isRequired,
|
fields: React.PropTypes.array.isRequired,
|
||||||
mapFieldsToComponents: React.PropTypes.func.isRequired
|
mapFieldsToComponents: React.PropTypes.func.isRequired,
|
||||||
};
|
};
|
||||||
|
|
||||||
export default FormComponent;
|
export default FormComponent;
|
||||||
|
|
|
@ -2,27 +2,27 @@ import React from 'react';
|
||||||
import SilverStripeComponent from 'silverstripe-component';
|
import SilverStripeComponent from 'silverstripe-component';
|
||||||
|
|
||||||
class GridFieldActionComponent extends SilverStripeComponent {
|
class GridFieldActionComponent extends SilverStripeComponent {
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
|
this.handleClick = this.handleClick.bind(this);
|
||||||
this.handleClick = this.handleClick.bind(this);
|
}
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
return (
|
return (
|
||||||
<button
|
<button
|
||||||
className={`grid-field-action-component font-icon-${this.props.icon}`}
|
className={`grid-field-action-component font-icon-${this.props.icon}`}
|
||||||
onClick={this.handleClick} />
|
onClick={this.handleClick}
|
||||||
);
|
/>
|
||||||
}
|
);
|
||||||
|
}
|
||||||
|
|
||||||
handleClick(event) {
|
handleClick(event) {
|
||||||
this.props.handleClick(event);
|
this.props.handleClick(event, this.props.record.ID);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
GridFieldActionComponent.PropTypes = {
|
GridFieldActionComponent.PropTypes = {
|
||||||
handleClick: React.PropTypes.func.isRequired
|
handleClick: React.PropTypes.func.isRequired,
|
||||||
}
|
};
|
||||||
|
|
||||||
export default GridFieldActionComponent;
|
export default GridFieldActionComponent;
|
||||||
|
|
|
@ -3,16 +3,16 @@ import SilverStripeComponent from 'silverstripe-component';
|
||||||
|
|
||||||
class GridFieldCellComponent extends SilverStripeComponent {
|
class GridFieldCellComponent extends SilverStripeComponent {
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
return (
|
return (
|
||||||
<div className='grid-field-cell-component'>{this.props.children}</div>
|
<div className="grid-field-cell-component">{this.props.children}</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
GridFieldCellComponent.PropTypes = {
|
GridFieldCellComponent.PropTypes = {
|
||||||
width: React.PropTypes.number
|
width: React.PropTypes.number,
|
||||||
}
|
};
|
||||||
|
|
||||||
export default GridFieldCellComponent;
|
export default GridFieldCellComponent;
|
||||||
|
|
|
@ -3,16 +3,16 @@ import SilverStripeComponent from 'silverstripe-component';
|
||||||
|
|
||||||
class GridFieldHeaderCellComponent extends SilverStripeComponent {
|
class GridFieldHeaderCellComponent extends SilverStripeComponent {
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
return (
|
return (
|
||||||
<div className='grid-field-header-cell-component'>{this.props.children}</div>
|
<div className="grid-field-header-cell-component">{this.props.children}</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
GridFieldHeaderCellComponent.PropTypes = {
|
GridFieldHeaderCellComponent.PropTypes = {
|
||||||
width: React.PropTypes.number
|
width: React.PropTypes.number,
|
||||||
}
|
};
|
||||||
|
|
||||||
export default GridFieldHeaderCellComponent;
|
export default GridFieldHeaderCellComponent;
|
||||||
|
|
|
@ -4,11 +4,11 @@ import GridFieldRowComponent from './row';
|
||||||
|
|
||||||
class GridFieldHeaderComponent extends SilverStripeComponent {
|
class GridFieldHeaderComponent extends SilverStripeComponent {
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
return (
|
return (
|
||||||
<GridFieldRowComponent>{this.props.children}</GridFieldRowComponent>
|
<GridFieldRowComponent>{this.props.children}</GridFieldRowComponent>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -14,107 +14,118 @@ import * as actions from 'state/records/actions';
|
||||||
* The component acts as a container for a grid field,
|
* The component acts as a container for a grid field,
|
||||||
* with smarts around data retrieval from external sources.
|
* with smarts around data retrieval from external sources.
|
||||||
*
|
*
|
||||||
* @todo Convert to higher order component which hooks up form schema data to an API backend as a grid data source
|
* @todo Convert to higher order component which hooks up form
|
||||||
|
* schema data to an API backend as a grid data source
|
||||||
* @todo Replace "dumb" inner components with third party library (e.g. https://griddlegriddle.github.io)
|
* @todo Replace "dumb" inner components with third party library (e.g. https://griddlegriddle.github.io)
|
||||||
*/
|
*/
|
||||||
class GridField extends SilverStripeComponent {
|
class GridField extends SilverStripeComponent {
|
||||||
|
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
|
|
||||||
this.deleteRecord = this.deleteRecord.bind(this);
|
this.deleteRecord = this.deleteRecord.bind(this);
|
||||||
this.editRecord = this.editRecord.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 <div></div>;
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount() {
|
const columns = this.props.data.columns;
|
||||||
super.componentDidMount();
|
|
||||||
|
|
||||||
let data = this.props.data;
|
// Placeholder to align the headers correctly with the content
|
||||||
|
const actionPlaceholder = <span key={'actionPlaceholder'} />;
|
||||||
|
const headerCells = columns.map((column, i) =>
|
||||||
|
<GridFieldHeaderCell key={i}>{column.name}</GridFieldHeaderCell>
|
||||||
|
);
|
||||||
|
const header = <GridFieldHeader>{headerCells.concat(actionPlaceholder)}</GridFieldHeader>;
|
||||||
|
|
||||||
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 <GridFieldCell key={j} width={column.width}>{val}</GridFieldCell>;
|
||||||
|
});
|
||||||
|
|
||||||
render() {
|
const rowActions = (
|
||||||
const records = this.props.records;
|
<GridFieldCell key={`${i}-actions`}>
|
||||||
if(!records) {
|
<GridFieldAction
|
||||||
return <div></div>;
|
icon={'cog'}
|
||||||
}
|
handleClick={this.editRecord}
|
||||||
|
key={`action-${i}-edit`}
|
||||||
|
record={record}
|
||||||
|
/>,
|
||||||
|
<GridFieldAction
|
||||||
|
icon={'cancel'}
|
||||||
|
handleClick={this.deleteRecord}
|
||||||
|
key={`action-${i}-delete`}
|
||||||
|
record={record}
|
||||||
|
/>,
|
||||||
|
</GridFieldCell>
|
||||||
|
);
|
||||||
|
|
||||||
const columns = this.props.data.columns;
|
return <GridFieldRow key={i}>{cells.concat(rowActions)}</GridFieldRow>;
|
||||||
|
});
|
||||||
|
|
||||||
// Placeholder to align the headers correctly with the content
|
return (
|
||||||
const actionPlaceholder = <GridFieldCell key={'actionPlaceholder'} />;
|
<GridFieldTable header={header} rows={rows} />
|
||||||
const headerCells = columns.map((column, i) => <GridFieldHeaderCell key={i} >{column.name}</GridFieldHeaderCell>);
|
);
|
||||||
const header = <GridFieldHeader>{headerCells.concat(actionPlaceholder)}</GridFieldHeader>;
|
}
|
||||||
|
|
||||||
const rows = records.map((record, i) => {
|
/**
|
||||||
var cells = columns.map((column, i) => {
|
* @param number int
|
||||||
// Get value by dot notation
|
* @param event
|
||||||
var val = column.field.split('.').reduce((a, b) => a[b], record)
|
*/
|
||||||
return <GridFieldCell key={i}>{val}</GridFieldCell>
|
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 = <GridFieldCell key={i + '-actions'}>
|
editRecord(event) {
|
||||||
<GridFieldAction
|
event.preventDefault();
|
||||||
icon={'cog'}
|
// TODO
|
||||||
handleClick={this.editRecord.bind(this, record.ID)}
|
}
|
||||||
key={"action-" + i + "-edit"}
|
|
||||||
/>
|
|
||||||
<GridFieldAction
|
|
||||||
icon={'cancel'}
|
|
||||||
handleClick={this.deleteRecord.bind(this, record.ID)}
|
|
||||||
key={"action-" + i + "-delete"}
|
|
||||||
/>
|
|
||||||
</GridFieldCell>;
|
|
||||||
|
|
||||||
return <GridFieldRow key={i}>{cells.concat(rowActions)}</GridFieldRow>;
|
|
||||||
});
|
|
||||||
|
|
||||||
return (
|
|
||||||
<GridFieldTable header={header} rows={rows}></GridFieldTable>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @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
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
GridField.propTypes = {
|
GridField.propTypes = {
|
||||||
data: React.PropTypes.shape({
|
data: React.PropTypes.shape({
|
||||||
recordType: React.PropTypes.string.isRequired,
|
recordType: React.PropTypes.string.isRequired,
|
||||||
headerColumns: React.PropTypes.array,
|
headerColumns: React.PropTypes.array,
|
||||||
collectionReadEndpoint: React.PropTypes.object
|
collectionReadEndpoint: React.PropTypes.object,
|
||||||
})
|
}),
|
||||||
};
|
};
|
||||||
|
|
||||||
function mapStateToProps(state, ownProps) {
|
function mapStateToProps(state, ownProps) {
|
||||||
let recordType = ownProps.data ? ownProps.data.recordType : null;
|
const recordType = ownProps.data ? ownProps.data.recordType : null;
|
||||||
return {
|
return {
|
||||||
records: (state.records && recordType) ? state.records[recordType] : []
|
records: (state.records && recordType) ? state.records[recordType] : [],
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function mapDispatchToProps(dispatch) {
|
function mapDispatchToProps(dispatch) {
|
||||||
return {
|
return {
|
||||||
actions: bindActionCreators(actions, dispatch)
|
actions: bindActionCreators(actions, dispatch),
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export default connect(mapStateToProps, mapDispatchToProps)(GridField);
|
export default connect(mapStateToProps, mapDispatchToProps)(GridField);
|
||||||
|
|
|
@ -3,11 +3,11 @@ import SilverStripeComponent from 'silverstripe-component';
|
||||||
|
|
||||||
class GridFieldRowComponent extends SilverStripeComponent {
|
class GridFieldRowComponent extends SilverStripeComponent {
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
return (
|
return (
|
||||||
<li className='grid-field-row-component [ list-group-item ]'>{this.props.children}</li>
|
<li className="grid-field-row-component [ list-group-item ]">{this.props.children}</li>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -3,61 +3,61 @@ import SilverStripeComponent from 'silverstripe-component';
|
||||||
|
|
||||||
class GridFieldTableComponent extends SilverStripeComponent {
|
class GridFieldTableComponent extends SilverStripeComponent {
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
return (
|
return (
|
||||||
<ul className='grid-field-table-component [ list-group ]'>
|
<ul className="grid-field-table-component [ list-group ]">
|
||||||
{this.generateHeader()}
|
{this.generateHeader()}
|
||||||
{this.generateRows()}
|
{this.generateRows()}
|
||||||
</ul>
|
</ul>
|
||||||
);
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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') {
|
||||||
* Generates the header component.
|
// TODO: Generate the header.
|
||||||
*
|
|
||||||
* 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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
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.
|
* Generates the table rows.
|
||||||
}
|
*
|
||||||
|
* Uses the components passed via the `rows` props if it exists.
|
||||||
return null;
|
* 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 = {
|
GridFieldTableComponent.propTypes = {
|
||||||
data: React.PropTypes.object,
|
data: React.PropTypes.object,
|
||||||
header: React.PropTypes.object,
|
header: React.PropTypes.object,
|
||||||
rows: React.PropTypes.array
|
rows: React.PropTypes.array,
|
||||||
};
|
};
|
||||||
|
|
||||||
export default GridFieldTableComponent;
|
export default GridFieldTableComponent;
|
||||||
|
|
|
@ -2,66 +2,66 @@ jest.dontMock('../index');
|
||||||
jest.dontMock('../table');
|
jest.dontMock('../table');
|
||||||
|
|
||||||
const React = require('react'),
|
const React = require('react'),
|
||||||
ReactTestUtils = require('react-addons-test-utils'),
|
ReactTestUtils = require('react-addons-test-utils'),
|
||||||
GridFieldTableComponent = require('../table.js').default;
|
GridFieldTableComponent = require('../table.js').default;
|
||||||
|
|
||||||
describe('GridFieldTableComponent', () => {
|
describe('GridFieldTableComponent', () => {
|
||||||
var props;
|
var props;
|
||||||
|
|
||||||
beforeEach(function () {
|
beforeEach(function () {
|
||||||
props = {
|
props = {
|
||||||
}
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('generateHeader()', function () {
|
||||||
|
var gridfield;
|
||||||
|
|
||||||
|
it('should return props.header if it is set', function () {
|
||||||
|
props.header = <div className='header'></div>;
|
||||||
|
|
||||||
|
gridfield = ReactTestUtils.renderIntoDocument(
|
||||||
|
<GridFieldTableComponent {...props} />
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(gridfield.generateHeader().props.className).toBe('header');
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('generateHeader()', function () {
|
it('should generate and return a header from props.data if it is set', function () {
|
||||||
var gridfield;
|
|
||||||
|
|
||||||
it('should return props.header if it is set', function () {
|
|
||||||
props.header = <div className='header'></div>;
|
|
||||||
|
|
||||||
gridfield = ReactTestUtils.renderIntoDocument(
|
|
||||||
<GridFieldTableComponent {...props} />
|
|
||||||
);
|
|
||||||
|
|
||||||
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(
|
|
||||||
<GridFieldTableComponent {...props} />
|
|
||||||
);
|
|
||||||
|
|
||||||
expect(gridfield.generateHeader()).toBe(null);
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('generateRows()', function () {
|
it('should return null if props.header and props.data are both not set', function () {
|
||||||
var gridfield;
|
gridfield = ReactTestUtils.renderIntoDocument(
|
||||||
|
<GridFieldTableComponent {...props} />
|
||||||
|
);
|
||||||
|
|
||||||
it('should return props.rows if it is set', function () {
|
expect(gridfield.generateHeader()).toBe(null);
|
||||||
props.rows = ['row1'];
|
|
||||||
|
|
||||||
gridfield = ReactTestUtils.renderIntoDocument(
|
|
||||||
<GridFieldTableComponent {...props} />
|
|
||||||
);
|
|
||||||
|
|
||||||
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(
|
|
||||||
<GridFieldTableComponent {...props} />
|
|
||||||
);
|
|
||||||
|
|
||||||
expect(gridfield.generateRows()).toBe(null);
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('generateRows()', function () {
|
||||||
|
var gridfield;
|
||||||
|
|
||||||
|
it('should return props.rows if it is set', function () {
|
||||||
|
props.rows = ['row1'];
|
||||||
|
|
||||||
|
gridfield = ReactTestUtils.renderIntoDocument(
|
||||||
|
<GridFieldTableComponent {...props} />
|
||||||
|
);
|
||||||
|
|
||||||
|
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(
|
||||||
|
<GridFieldTableComponent {...props} />
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(gridfield.generateRows()).toBe(null);
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -3,46 +3,46 @@ import SilverStripeComponent from 'silverstripe-component';
|
||||||
|
|
||||||
class HiddenFieldComponent extends SilverStripeComponent {
|
class HiddenFieldComponent extends SilverStripeComponent {
|
||||||
|
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
|
|
||||||
this.handleChange = this.handleChange.bind(this);
|
this.handleChange = this.handleChange.bind(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
return (
|
||||||
|
<div className="field hidden">
|
||||||
|
<input {...this.getInputProps()} />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
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() {
|
this.props.onChange();
|
||||||
return (
|
}
|
||||||
<div className='field hidden'>
|
|
||||||
<input {...this.getInputProps()} />
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
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();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
HiddenFieldComponent.propTypes = {
|
HiddenFieldComponent.propTypes = {
|
||||||
label: React.PropTypes.string,
|
label: React.PropTypes.string,
|
||||||
extraClass: React.PropTypes.string,
|
extraClass: React.PropTypes.string,
|
||||||
name: React.PropTypes.string.isRequired,
|
name: React.PropTypes.string.isRequired,
|
||||||
onChange: React.PropTypes.func,
|
onChange: React.PropTypes.func,
|
||||||
value: React.PropTypes.string
|
value: React.PropTypes.string,
|
||||||
};
|
};
|
||||||
|
|
||||||
export default HiddenFieldComponent;
|
export default HiddenFieldComponent;
|
||||||
|
|
|
@ -3,37 +3,39 @@ import SilverStripeComponent from 'silverstripe-component';
|
||||||
|
|
||||||
class NorthHeaderBreadcrumbsComponent extends SilverStripeComponent {
|
class NorthHeaderBreadcrumbsComponent extends SilverStripeComponent {
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
return (
|
return (
|
||||||
<div className="cms-content-header-info">
|
<div className="cms-content-header-info">
|
||||||
<div className="breadcrumbs-wrapper">
|
<div className="breadcrumbs-wrapper">
|
||||||
<h2 id="page-title-heading">
|
<h2 id="page-title-heading">
|
||||||
{this.getBreadcrumbs()}
|
{this.getBreadcrumbs()}
|
||||||
</h2>
|
</h2>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
getBreadcrumbs() {
|
||||||
|
if (typeof this.props.crumbs === 'undefined') {
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
getBreadcrumbs() {
|
const breadcrumbs = this.props.crumbs.map((crumb, index, crumbs) => {
|
||||||
if (typeof this.props.crumbs === 'undefined') {
|
let component;
|
||||||
return null;
|
// If its the last item in the array
|
||||||
}
|
if (index === crumbs.length - 1) {
|
||||||
|
component = <span key={index} className="crumb last">{crumb.text}</span>;
|
||||||
|
} else {
|
||||||
|
component = [
|
||||||
|
<a key={index} className="cms-panel-link crumb" href={crumb.href}>{crumb.text}</a>,
|
||||||
|
<span className="sep">/</span>,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
return component;
|
||||||
|
});
|
||||||
|
|
||||||
var breadcrumbs = this.props.crumbs.map((crumb, index, crumbs) => {
|
return breadcrumbs;
|
||||||
// If its the last item in the array
|
}
|
||||||
if (index === crumbs.length - 1) {
|
|
||||||
return <span key={index} className="crumb last">{crumb.text}</span>;
|
|
||||||
} else {
|
|
||||||
return [
|
|
||||||
<a key={index} className="cms-panel-link crumb" href={crumb.href}>{crumb.text}</a>,
|
|
||||||
<span className="sep">/</span>
|
|
||||||
];
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return breadcrumbs;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,40 +1,40 @@
|
||||||
jest.dontMock('../index');
|
jest.dontMock('../index');
|
||||||
|
|
||||||
const React = require('react'),
|
const React = require('react'),
|
||||||
ReactTestUtils = require('react-addons-test-utils'),
|
ReactTestUtils = require('react-addons-test-utils'),
|
||||||
NorthHeaderBreadcrumbsComponent = require('../index').default;
|
NorthHeaderBreadcrumbsComponent = require('../index').default;
|
||||||
|
|
||||||
describe('NorthHeaderBreadcrumbsComponent', () => {
|
describe('NorthHeaderBreadcrumbsComponent', () => {
|
||||||
var props;
|
var props;
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
props = {};
|
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(
|
||||||
|
<NorthHeaderBreadcrumbsComponent {...props} />
|
||||||
|
);
|
||||||
|
|
||||||
|
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()', () => {
|
it('should return null if props.crumbs is not set', () => {
|
||||||
var northHeaderBreadcrumbs;
|
northHeaderBreadcrumbs = ReactTestUtils.renderIntoDocument(
|
||||||
|
<NorthHeaderBreadcrumbsComponent {...props} />
|
||||||
|
);
|
||||||
|
|
||||||
it('should convert the props.crumbs array into jsx to be rendered', () => {
|
expect(northHeaderBreadcrumbs.getBreadcrumbs()).toBe(null);
|
||||||
props.crumbs = [
|
|
||||||
{ text: 'breadcrumb1', href: 'href1'},
|
|
||||||
{ text: 'breadcrumb2', href: 'href2'}
|
|
||||||
];
|
|
||||||
|
|
||||||
northHeaderBreadcrumbs = ReactTestUtils.renderIntoDocument(
|
|
||||||
<NorthHeaderBreadcrumbsComponent {...props} />
|
|
||||||
);
|
|
||||||
|
|
||||||
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(
|
|
||||||
<NorthHeaderBreadcrumbsComponent {...props} />
|
|
||||||
);
|
|
||||||
|
|
||||||
expect(northHeaderBreadcrumbs.getBreadcrumbs()).toBe(null);
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -4,26 +4,26 @@ import SilverStripeComponent from 'silverstripe-component';
|
||||||
|
|
||||||
class NorthHeaderComponent extends SilverStripeComponent {
|
class NorthHeaderComponent extends SilverStripeComponent {
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
return (
|
return (
|
||||||
<div className="north-header-component">
|
<div className="north-header-component">
|
||||||
<NorthHeaderBreadcrumbsComponent crumbs={this.getBreadcrumbs()}/>
|
<NorthHeaderBreadcrumbsComponent crumbs={this.getBreadcrumbs()} />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
getBreadcrumbs() {
|
getBreadcrumbs() {
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
text: 'Campaigns',
|
text: 'Campaigns',
|
||||||
href: 'admin/campaigns'
|
href: 'admin/campaigns',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
text: 'March release',
|
text: 'March release',
|
||||||
href: 'admin/campaigns/show/1'
|
href: 'admin/campaigns/show/1',
|
||||||
}
|
},
|
||||||
]
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -3,53 +3,53 @@ import SilverStripeComponent from 'silverstripe-component';
|
||||||
|
|
||||||
class TextFieldComponent extends SilverStripeComponent {
|
class TextFieldComponent extends SilverStripeComponent {
|
||||||
|
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
|
|
||||||
this.handleChange = this.handleChange.bind(this);
|
this.handleChange = this.handleChange.bind(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
return (
|
return (
|
||||||
<div className='field text'>
|
<div className="field text">
|
||||||
{this.props.label &&
|
{this.props.label &&
|
||||||
<label className='left' htmlFor={'gallery_' + this.props.name}>
|
<label className="left" htmlFor={`gallery_${this.props.name}`}>
|
||||||
{this.props.label}
|
{this.props.label}
|
||||||
</label>
|
</label>
|
||||||
}
|
|
||||||
<div className='middleColumn'>
|
|
||||||
<input {...this.getInputProps()} />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
<div className="middleColumn">
|
||||||
|
<input {...this.getInputProps()} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
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 = {
|
TextFieldComponent.propTypes = {
|
||||||
label: React.PropTypes.string,
|
label: React.PropTypes.string,
|
||||||
extraClass: React.PropTypes.string,
|
extraClass: React.PropTypes.string,
|
||||||
name: React.PropTypes.string.isRequired,
|
name: React.PropTypes.string.isRequired,
|
||||||
onChange: React.PropTypes.func,
|
onChange: React.PropTypes.func,
|
||||||
value: React.PropTypes.string
|
value: React.PropTypes.string,
|
||||||
};
|
};
|
||||||
|
|
||||||
export default TextFieldComponent;
|
export default TextFieldComponent;
|
||||||
|
|
|
@ -8,30 +8,30 @@ import TextFieldComponent from '../';
|
||||||
|
|
||||||
describe('TextFieldComponent', function() {
|
describe('TextFieldComponent', function() {
|
||||||
|
|
||||||
var props;
|
var props;
|
||||||
|
|
||||||
|
beforeEach(function () {
|
||||||
|
props = {
|
||||||
|
label: '',
|
||||||
|
name: '',
|
||||||
|
value: '',
|
||||||
|
onChange: jest.genMockFunction()
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('handleChange()', function () {
|
||||||
|
var textField;
|
||||||
|
|
||||||
beforeEach(function () {
|
beforeEach(function () {
|
||||||
props = {
|
textField = ReactTestUtils.renderIntoDocument(
|
||||||
label: '',
|
<TextFieldComponent {...props} />
|
||||||
name: '',
|
);
|
||||||
value: '',
|
|
||||||
onChange: jest.genMockFunction()
|
|
||||||
};
|
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('handleChange()', function () {
|
it('should call the onChange function on props', function () {
|
||||||
var textField;
|
textField.handleChange();
|
||||||
|
|
||||||
beforeEach(function () {
|
expect(textField.props.onChange.mock.calls.length).toBe(1);
|
||||||
textField = ReactTestUtils.renderIntoDocument(
|
|
||||||
<TextFieldComponent {...props} />
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should call the onChange function on props', function () {
|
|
||||||
textField.handleChange();
|
|
||||||
|
|
||||||
expect(textField.props.onChange.mock.calls.length).toBe(1);
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -6,39 +6,37 @@
|
||||||
*/
|
*/
|
||||||
class Config {
|
class Config {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets the the config for a specific section.
|
* Gets the the config for a specific section.
|
||||||
*
|
*
|
||||||
* @param string key - The section config key.
|
* @param string key - The section config key.
|
||||||
*
|
*
|
||||||
* @return object|undefined
|
* @return object|undefined
|
||||||
*/
|
*/
|
||||||
static getSection(key) {
|
static getSection(key) {
|
||||||
return window.ss.config.sections[key];
|
return window.ss.config.sections[key];
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets a de-duped list of routes for top level controllers. E.g. 'assets', 'pages', etc.
|
* Gets a de-duped list of routes for top level controllers. E.g. 'assets', 'pages', etc.
|
||||||
*
|
*
|
||||||
* @return array
|
* @return array
|
||||||
*/
|
*/
|
||||||
static getTopLevelRoutes() {
|
static getTopLevelRoutes() {
|
||||||
var topLevelRoutes = [];
|
const topLevelRoutes = [];
|
||||||
|
|
||||||
Object.keys(window.ss.config.sections).forEach((key) => {
|
Object.keys(window.ss.config.sections).forEach((key) => {
|
||||||
const route = window.ss.config.sections[key].route;
|
const route = window.ss.config.sections[key].route;
|
||||||
const isTopLevelRoute = route.indexOf('/') === -1;
|
const isTopLevelRoute = route.indexOf('/') === -1;
|
||||||
const isUnique = topLevelRoutes.indexOf(route) === -1;
|
const isUnique = topLevelRoutes.indexOf(route) === -1;
|
||||||
|
|
||||||
//console.log(this.getSection(key).route);
|
if (isTopLevelRoute && isUnique) {
|
||||||
|
topLevelRoutes.push(route);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
if (isTopLevelRoute && isUnique) {
|
return topLevelRoutes;
|
||||||
topLevelRoutes.push(route);
|
}
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return topLevelRoutes;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,15 +1,17 @@
|
||||||
|
/* globals jest */
|
||||||
|
|
||||||
function jQuery() {
|
function jQuery() {
|
||||||
return {
|
return {
|
||||||
// Add jQuery methods such as 'find', 'change', 'trigger' as needed.
|
// Add jQuery methods such as 'find', 'change', 'trigger' as needed.
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
var mockAjaxFn = jest.genMockFunction();
|
const mockAjaxFn = jest.genMockFunction();
|
||||||
|
|
||||||
mockAjaxFn.mockReturnValue({
|
mockAjaxFn.mockReturnValue({
|
||||||
done: jest.genMockFunction(),
|
done: jest.genMockFunction(),
|
||||||
fail: jest.genMockFunction(),
|
fail: jest.genMockFunction(),
|
||||||
always: jest.genMockFunction()
|
always: jest.genMockFunction(),
|
||||||
});
|
});
|
||||||
|
|
||||||
jQuery.ajax = mockAjaxFn;
|
jQuery.ajax = mockAjaxFn;
|
||||||
|
|
|
@ -1,15 +1,5 @@
|
||||||
import { Component } from 'react';
|
import { Component } from 'react';
|
||||||
|
|
||||||
|
// eslint-disable-next-line react/prefer-stateless-function
|
||||||
export default class SilverStripeComponent extends Component {
|
export default class SilverStripeComponent extends Component {
|
||||||
constructor(props) {
|
}
|
||||||
super(props);
|
|
||||||
}
|
|
||||||
|
|
||||||
componentDidMount() {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
componentWillUnmount() {
|
|
||||||
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
|
@ -2,63 +2,62 @@
|
||||||
* The register of Redux reducers.
|
* The register of Redux reducers.
|
||||||
* @private
|
* @private
|
||||||
*/
|
*/
|
||||||
var register = {};
|
const register = {};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The central register of Redux reducers for the CMS. All registered reducers are combined when the application boots.
|
* The central register of Redux reducers for the CMS.
|
||||||
|
* All registered reducers are combined when the application boots.
|
||||||
*/
|
*/
|
||||||
class ReducerRegister {
|
class ReducerRegister {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Adds a reducer to the register.
|
* Adds a reducer to the register.
|
||||||
*
|
*
|
||||||
* @param string key - The key to register the reducer against.
|
* @param string key - The key to register the reducer against.
|
||||||
* @param object reducer - Redux reducer.
|
* @param object reducer - Redux reducer.
|
||||||
*/
|
*/
|
||||||
add(key, reducer) {
|
add(key, reducer) {
|
||||||
if (typeof register[key] !== 'undefined') {
|
if (typeof register[key] !== 'undefined') {
|
||||||
throw new Error(`Reducer already exists at '${key}'`);
|
throw new Error(`Reducer already exists at '${key}'`);
|
||||||
}
|
|
||||||
|
|
||||||
register[key] = reducer;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
register[key] = reducer;
|
||||||
* Gets all reducers from the register.
|
}
|
||||||
*
|
|
||||||
* @return object
|
|
||||||
*/
|
|
||||||
getAll() {
|
|
||||||
return register;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets a reducer from the register.
|
* Gets all reducers from the register.
|
||||||
*
|
*
|
||||||
* @param string [key] - The key the reducer is registered against.
|
* @return object
|
||||||
*
|
*/
|
||||||
* @return object|undefined
|
getAll() {
|
||||||
*/
|
return register;
|
||||||
getByKey(key) {
|
}
|
||||||
return register[key];
|
|
||||||
}
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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.
|
||||||
* Removes a reducer from the register.
|
*
|
||||||
*
|
* @param string key - The key the reducer is registered against.
|
||||||
* @param string key - The key the reducer is registered against.
|
*/
|
||||||
*/
|
remove(key) {
|
||||||
remove(key) {
|
delete register[key];
|
||||||
delete register[key];
|
}
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create an instance to export. The same instance is exported to
|
// Create an instance to export. The same instance is exported to
|
||||||
// each script which imports the reducerRegister. This means the
|
// each script which imports the reducerRegister. This means the
|
||||||
// same register is available throughout the application.
|
// same register is available throughout the application.
|
||||||
let reducerRegister = new ReducerRegister();
|
const reducerRegister = new ReducerRegister();
|
||||||
|
|
||||||
export default reducerRegister;
|
export default reducerRegister;
|
||||||
|
|
|
@ -8,48 +8,49 @@ import FormBuilder from 'components/form-builder/index';
|
||||||
|
|
||||||
class CampaignAdminContainer extends SilverStripeComponent {
|
class CampaignAdminContainer extends SilverStripeComponent {
|
||||||
|
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
|
|
||||||
this.addCampaign = this.addCampaign.bind(this);
|
this.addCampaign = this.addCampaign.bind(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const schemaUrl = this.props.config.forms.editForm.schemaUrl;
|
const schemaUrl = this.props.config.forms.editForm.schemaUrl;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<NorthHeader />
|
<NorthHeader />
|
||||||
<FormAction
|
<FormAction
|
||||||
label={i18n._t('Campaigns.ADDCAMPAIGN')}
|
label={i18n._t('Campaigns.ADDCAMPAIGN')}
|
||||||
icon={'plus-circled'}
|
icon={'plus-circled'}
|
||||||
handleClick={this.addCampaign} />
|
handleClick={this.addCampaign}
|
||||||
<FormBuilder schemaUrl={schemaUrl} />
|
/>
|
||||||
</div>
|
<FormBuilder schemaUrl={schemaUrl} />
|
||||||
);
|
</div>
|
||||||
}
|
);
|
||||||
|
}
|
||||||
|
|
||||||
addCampaign() {
|
addCampaign() {
|
||||||
//Add campaign
|
// Add campaign
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
CampaignAdminContainer.propTypes = {
|
CampaignAdminContainer.propTypes = {
|
||||||
config: React.PropTypes.shape({
|
config: React.PropTypes.shape({
|
||||||
forms: React.PropTypes.shape({
|
forms: React.PropTypes.shape({
|
||||||
editForm: React.PropTypes.shape({
|
editForm: React.PropTypes.shape({
|
||||||
schemaUrl: React.PropTypes.string
|
schemaUrl: React.PropTypes.string,
|
||||||
})
|
}),
|
||||||
})
|
|
||||||
}),
|
}),
|
||||||
sectionConfigKey: React.PropTypes.string.isRequired
|
}),
|
||||||
|
sectionConfigKey: React.PropTypes.string.isRequired,
|
||||||
};
|
};
|
||||||
|
|
||||||
function mapStateToProps(state, ownProps) {
|
function mapStateToProps(state, ownProps) {
|
||||||
return {
|
return {
|
||||||
config: state.config.sections[ownProps.sectionConfigKey]
|
config: state.config.sections[ownProps.sectionConfigKey],
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export default connect(mapStateToProps)(CampaignAdminContainer);
|
export default connect(mapStateToProps)(CampaignAdminContainer);
|
||||||
|
|
|
@ -1,24 +1,22 @@
|
||||||
import reducerRegister from 'reducer-register';
|
|
||||||
import $ from 'jQuery';
|
import $ from 'jQuery';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import ReactDOM from 'react-dom';
|
import ReactDOM from 'react-dom';
|
||||||
import { Provider } from 'react-redux';
|
import { Provider } from 'react-redux';
|
||||||
import CampaignAdmin from './controller';
|
import CampaignAdmin from './controller';
|
||||||
|
|
||||||
$.entwine('ss', function ($) {
|
// eslint-disable-next-line no-shadow
|
||||||
|
$.entwine('ss', ($) => {
|
||||||
$('.cms-content.CampaignAdmin').entwine({
|
$('.cms-content.CampaignAdmin').entwine({
|
||||||
onadd: function () {
|
onadd() {
|
||||||
ReactDOM.render(
|
ReactDOM.render(
|
||||||
<Provider store={window.store}>
|
<Provider store={window.store}>
|
||||||
<CampaignAdmin sectionConfigKey='CampaignAdmin' />
|
<CampaignAdmin sectionConfigKey="CampaignAdmin" />
|
||||||
</Provider>
|
</Provider>
|
||||||
, this[0]);
|
, this[0]);
|
||||||
},
|
},
|
||||||
|
|
||||||
onremove: function () {
|
|
||||||
ReactDOM.unmountComponentAtNode(this[0]);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
|
onremove() {
|
||||||
|
ReactDOM.unmountComponentAtNode(this[0]);
|
||||||
|
},
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -6,73 +6,77 @@ es6promise.polyfill();
|
||||||
* @see https://github.com/github/fetch#handling-http-error-statuses
|
* @see https://github.com/github/fetch#handling-http-error-statuses
|
||||||
*/
|
*/
|
||||||
function checkStatus(response) {
|
function checkStatus(response) {
|
||||||
|
let ret;
|
||||||
|
let error;
|
||||||
if (response.status >= 200 && response.status < 300) {
|
if (response.status >= 200 && response.status < 300) {
|
||||||
return response
|
ret = response;
|
||||||
} else {
|
} else {
|
||||||
var error = new Error(response.statusText)
|
error = new Error(response.statusText);
|
||||||
error.response = response
|
error.response = response;
|
||||||
throw error
|
throw error;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
class SilverStripeBackend {
|
class SilverStripeBackend {
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
// Allow mocking
|
// Allow mocking
|
||||||
this.fetch = fetch;
|
this.fetch = fetch;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Makes a network request using the GET HTTP verb.
|
* Makes a network request using the GET HTTP verb.
|
||||||
*
|
*
|
||||||
* @param string url - Endpoint URL.
|
* @param string url - Endpoint URL.
|
||||||
* @return object - Promise
|
* @return object - Promise
|
||||||
*/
|
*/
|
||||||
get(url) {
|
get(url) {
|
||||||
return this.fetch(url, { method: 'get', credentials: 'same-origin' })
|
return this.fetch(url, { method: 'get', credentials: 'same-origin' })
|
||||||
.then(checkStatus);
|
.then(checkStatus);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Makes a network request using the POST HTTP verb.
|
* Makes a network request using the POST HTTP verb.
|
||||||
*
|
*
|
||||||
* @param string url - Endpoint URL.
|
* @param string url - Endpoint URL.
|
||||||
* @param object data - Data to send with the request.
|
* @param object data - Data to send with the request.
|
||||||
* @return object - Promise
|
* @return object - Promise
|
||||||
*/
|
*/
|
||||||
post(url, data) {
|
post(url, data) {
|
||||||
return this.fetch(url, { method: 'post', credentials: 'same-origin', body: data })
|
return this.fetch(url, { method: 'post', credentials: 'same-origin', body: data })
|
||||||
.then(checkStatus);
|
.then(checkStatus);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Makes a newtwork request using the PUT HTTP verb.
|
* Makes a newtwork request using the PUT HTTP verb.
|
||||||
*
|
*
|
||||||
* @param string url - Endpoint URL.
|
* @param string url - Endpoint URL.
|
||||||
* @param object data - Data to send with the request.
|
* @param object data - Data to send with the request.
|
||||||
* @return object - Promise
|
* @return object - Promise
|
||||||
*/
|
*/
|
||||||
put(url, data) {
|
put(url, data) {
|
||||||
return this.fetch(url, { method: 'put', credentials: 'same-origin', body: data })
|
return this.fetch(url, { method: 'put', credentials: 'same-origin', body: data })
|
||||||
.then(checkStatus);
|
.then(checkStatus);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Makes a newtwork request using the DELETE HTTP verb.
|
* Makes a newtwork request using the DELETE HTTP verb.
|
||||||
*
|
*
|
||||||
* @param string url - Endpoint URL.
|
* @param string url - Endpoint URL.
|
||||||
* @param object data - Data to send with the request.
|
* @param object data - Data to send with the request.
|
||||||
* @return object - Promise
|
* @return object - Promise
|
||||||
*/
|
*/
|
||||||
delete(url, data) {
|
delete(url, data) {
|
||||||
return this.fetch(url, { method: 'delete', credentials: 'same-origin', body: data })
|
return this.fetch(url, { method: 'delete', credentials: 'same-origin', body: data })
|
||||||
.then(checkStatus);
|
.then(checkStatus);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Exported as a singleton so we can implement things like
|
// Exported as a singleton so we can implement things like
|
||||||
// global caching and request batching at some stage.
|
// global caching and request batching at some stage.
|
||||||
let backend = new SilverStripeBackend();
|
const backend = new SilverStripeBackend();
|
||||||
|
|
||||||
export default backend;
|
export default backend;
|
||||||
|
|
|
@ -2,54 +2,107 @@
|
||||||
* @file Base component which all SilverStripe ReactJS components should extend from.
|
* @file Base component which all SilverStripe ReactJS components should extend from.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React, { PropTypes, Component } from 'react';
|
import React, { Component } from 'react';
|
||||||
import $ from '../../../javascript/src/jQuery';
|
import $ from '../../../javascript/src/jQuery';
|
||||||
|
|
||||||
class SilverStripeComponent extends Component {
|
class SilverStripeComponent extends Component {
|
||||||
|
|
||||||
/**
|
constructor(props) {
|
||||||
* @func componentDidMount
|
super(props);
|
||||||
* @desc Bind event listeners which are triggered by legacy-land JavaScript.
|
|
||||||
* This lets us update the component when something happens in the outside world.
|
|
||||||
*/
|
|
||||||
componentDidMount() {
|
|
||||||
if (typeof this.props.cmsEvents === 'undefined') {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Save some props for later. When we come to unbind these listeners
|
// Setup component routing.
|
||||||
// there's no guarantee these props will be the same or even present.
|
if (typeof this.props.route !== 'undefined') {
|
||||||
this.cmsEvents = this.props.cmsEvents;
|
// 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) {
|
this.render = () => {
|
||||||
$(document).on(cmsEvent, this.cmsEvents[cmsEvent].bind(this));
|
let component = null;
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
if (this.isComponentRoute()) {
|
||||||
* @func componentWillUnmount
|
component = this._render();
|
||||||
* @desc Unbind the event listeners we added in componentDidMount.
|
}
|
||||||
*/
|
|
||||||
componentWillUnmount() {
|
|
||||||
for (let cmsEvent in this.cmsEvents) {
|
|
||||||
$(document).off(cmsEvent);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
return component;
|
||||||
* @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.
|
window.ss.router(this.props.route, (ctx, next) => {
|
||||||
* @desc Notifies legacy-land something has changed within our component.
|
this.handleEnterRoute(ctx, next);
|
||||||
*/
|
});
|
||||||
emitCmsEvent(componentEvent, data) {
|
window.ss.router.exit(this.props.route, (ctx, next) => {
|
||||||
$(document).trigger(componentEvent, data);
|
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 = {
|
SilverStripeComponent.propTypes = {
|
||||||
'cmsEvents': React.PropTypes.object
|
cmsEvents: React.PropTypes.object,
|
||||||
|
route: React.PropTypes.string,
|
||||||
};
|
};
|
||||||
|
|
||||||
export default SilverStripeComponent;
|
export default SilverStripeComponent;
|
||||||
|
|
|
@ -35,24 +35,24 @@ var $window = $( window ),
|
||||||
// URL as well as some other commonly used sub-parts. When used with RegExp.exec()
|
// 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:
|
// 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
|
// [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
|
// [1]: http://jblas:password@mycompany.com:8080/mail/inbox?msg=1234&type=unread
|
||||||
// [2]: http://jblas:password@mycompany.com:8080/mail/inbox
|
// [2]: http://jblas:password@mycompany.com:8080/mail/inbox
|
||||||
// [3]: http://jblas:password@mycompany.com:8080
|
// [3]: http://jblas:password@mycompany.com:8080
|
||||||
// [4]: http:
|
// [4]: http:
|
||||||
// [5]: //
|
// [5]: //
|
||||||
// [6]: jblas:password@mycompany.com:8080
|
// [6]: jblas:password@mycompany.com:8080
|
||||||
// [7]: jblas:password
|
// [7]: jblas:password
|
||||||
// [8]: jblas
|
// [8]: jblas
|
||||||
// [9]: password
|
// [9]: password
|
||||||
// [10]: mycompany.com:8080
|
// [10]: mycompany.com:8080
|
||||||
// [11]: mycompany.com
|
// [11]: mycompany.com
|
||||||
// [12]: 8080
|
// [12]: 8080
|
||||||
// [13]: /mail/inbox
|
// [13]: /mail/inbox
|
||||||
// [14]: /mail/
|
// [14]: /mail/
|
||||||
// [15]: inbox
|
// [15]: inbox
|
||||||
// [16]: ?msg=1234&type=unread
|
// [16]: ?msg=1234&type=unread
|
||||||
// [17]: #msg-content
|
// [17]: #msg-content
|
||||||
//
|
//
|
||||||
urlParseRE: /^(((([^:\/#\?]+:)?(?:(\/\/)((?:(([^:@\/#\?]+)(?:\:([^:@\/#\?]+))?)@)?(([^:\/#\?\]\[]+|\[[^\/\]@#?]+\])(?:\:([0-9]+))?))?)?)?((\/?(?:[^\/\?#]+\/+)*)([^\?#]*)))?(\?[^#]+)?)(#.*)?/,
|
urlParseRE: /^(((([^:\/#\?]+:)?(?:(\/\/)((?:(([^:@\/#\?]+)(?:\:([^:@\/#\?]+))?)@)?(([^:\/#\?\]\[]+|\[[^\/\]@#?]+\])(?:\:([0-9]+))?))?)?)?((\/?(?:[^\/\?#]+\/+)*)([^\?#]*)))?(\?[^#]+)?)(#.*)?/,
|
||||||
|
|
||||||
|
@ -72,23 +72,23 @@ var $window = $( window ),
|
||||||
// like all other browsers do, so we normalize everything so its consistent
|
// like all other browsers do, so we normalize everything so its consistent
|
||||||
// no matter what browser we're running on.
|
// no matter what browser we're running on.
|
||||||
return {
|
return {
|
||||||
href: matches[ 0 ] || "",
|
href: matches[ 0 ] || "",
|
||||||
hrefNoHash: matches[ 1 ] || "",
|
hrefNoHash: matches[ 1 ] || "",
|
||||||
hrefNoSearch: matches[ 2 ] || "",
|
hrefNoSearch: matches[ 2 ] || "",
|
||||||
domain: matches[ 3 ] || "",
|
domain: matches[ 3 ] || "",
|
||||||
protocol: matches[ 4 ] || "",
|
protocol: matches[ 4 ] || "",
|
||||||
doubleSlash: matches[ 5 ] || "",
|
doubleSlash: matches[ 5 ] || "",
|
||||||
authority: matches[ 6 ] || "",
|
authority: matches[ 6 ] || "",
|
||||||
username: matches[ 8 ] || "",
|
username: matches[ 8 ] || "",
|
||||||
password: matches[ 9 ] || "",
|
password: matches[ 9 ] || "",
|
||||||
host: matches[ 10 ] || "",
|
host: matches[ 10 ] || "",
|
||||||
hostname: matches[ 11 ] || "",
|
hostname: matches[ 11 ] || "",
|
||||||
port: matches[ 12 ] || "",
|
port: matches[ 12 ] || "",
|
||||||
pathname: matches[ 13 ] || "",
|
pathname: matches[ 13 ] || "",
|
||||||
directory: matches[ 14 ] || "",
|
directory: matches[ 14 ] || "",
|
||||||
filename: matches[ 15 ] || "",
|
filename: matches[ 15 ] || "",
|
||||||
search: matches[ 16 ] || "",
|
search: matches[ 16 ] || "",
|
||||||
hash: matches[ 17 ] || ""
|
hash: matches[ 17 ] || ""
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
|
@ -1,3 +1,3 @@
|
||||||
export default {
|
export default {
|
||||||
SET_CONFIG: 'SET_CONFIG'
|
SET_CONFIG: 'SET_CONFIG',
|
||||||
}
|
};
|
||||||
|
|
|
@ -6,10 +6,9 @@ import ACTION_TYPES from './action-types';
|
||||||
* @param object config
|
* @param object config
|
||||||
*/
|
*/
|
||||||
export function setConfig(config) {
|
export function setConfig(config) {
|
||||||
return (dispatch, getState) => {
|
return (dispatch) =>
|
||||||
return dispatch({
|
dispatch({
|
||||||
type: ACTION_TYPES.SET_CONFIG,
|
type: ACTION_TYPES.SET_CONFIG,
|
||||||
payload: { config }
|
payload: { config },
|
||||||
});
|
});
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,17 +2,15 @@ import deepFreeze from 'deep-freeze';
|
||||||
import ACTION_TYPES from './action-types';
|
import ACTION_TYPES from './action-types';
|
||||||
|
|
||||||
function configReducer(state = {}, action) {
|
function configReducer(state = {}, action) {
|
||||||
|
switch (action.type) {
|
||||||
|
|
||||||
switch (action.type) {
|
case ACTION_TYPES.SET_CONFIG:
|
||||||
|
return deepFreeze(Object.assign({}, state, action.payload.config));
|
||||||
|
|
||||||
case ACTION_TYPES.SET_CONFIG:
|
default:
|
||||||
return deepFreeze(Object.assign({}, state, action.payload.config));
|
return state;
|
||||||
|
|
||||||
default:
|
|
||||||
return state;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default configReducer;
|
export default configReducer;
|
||||||
|
|
|
@ -1,11 +1,11 @@
|
||||||
export default {
|
export default {
|
||||||
CREATE_RECORD: 'CREATE_RECORD',
|
CREATE_RECORD: 'CREATE_RECORD',
|
||||||
UPDATE_RECORD: 'UPDATE_RECORD',
|
UPDATE_RECORD: 'UPDATE_RECORD',
|
||||||
DELETE_RECORD: 'DELETE_RECORD',
|
DELETE_RECORD: 'DELETE_RECORD',
|
||||||
FETCH_RECORDS_REQUEST: 'FETCH_RECORDS_REQUEST',
|
FETCH_RECORDS_REQUEST: 'FETCH_RECORDS_REQUEST',
|
||||||
FETCH_RECORDS_FAILURE: 'FETCH_RECORDS_FAILURE',
|
FETCH_RECORDS_FAILURE: 'FETCH_RECORDS_FAILURE',
|
||||||
FETCH_RECORDS_SUCCESS: 'FETCH_RECORDS_SUCCESS',
|
FETCH_RECORDS_SUCCESS: 'FETCH_RECORDS_SUCCESS',
|
||||||
DELETE_RECORD_REQUEST: 'DELETE_RECORD_REQUEST',
|
DELETE_RECORD_REQUEST: 'DELETE_RECORD_REQUEST',
|
||||||
DELETE_RECORD_FAILURE: 'DELETE_RECORD_FAILURE',
|
DELETE_RECORD_FAILURE: 'DELETE_RECORD_FAILURE',
|
||||||
DELETE_RECORD_SUCCESS: 'DELETE_RECORD_SUCCESS'
|
DELETE_RECORD_SUCCESS: 'DELETE_RECORD_SUCCESS',
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
import ACTION_TYPES from './action-types';
|
import ACTION_TYPES from './action-types';
|
||||||
import fetch from 'isomorphic-fetch';
|
|
||||||
import backend from 'silverstripe-backend.js';
|
import backend from 'silverstripe-backend.js';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -14,8 +13,8 @@ import backend from 'silverstripe-backend.js';
|
||||||
* @return string
|
* @return string
|
||||||
*/
|
*/
|
||||||
function populate(str, params) {
|
function populate(str, params) {
|
||||||
let names = ['id'];
|
const names = ['id'];
|
||||||
return names.reduce((str, name) => str.replace(`:${name}`, params[name]), str);
|
return names.reduce((acc, name) => acc.replace(`:${name}`, params[name]), str);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -26,19 +25,27 @@ function populate(str, params) {
|
||||||
* @param string url API endpoint
|
* @param string url API endpoint
|
||||||
*/
|
*/
|
||||||
export function fetchRecords(recordType, method, url) {
|
export function fetchRecords(recordType, method, url) {
|
||||||
let payload = {recordType: recordType};
|
const payload = { recordType };
|
||||||
url = populate(url, payload);
|
return (dispatch) => {
|
||||||
return (dispatch, getState) => {
|
dispatch({
|
||||||
dispatch ({type: ACTION_TYPES.FETCH_RECORDS_REQUEST, payload: payload});
|
type: ACTION_TYPES.FETCH_RECORDS_REQUEST,
|
||||||
return backend[method.toLowerCase()](url)
|
payload,
|
||||||
.then(response => response.json())
|
});
|
||||||
.then(json => {
|
return backend[method.toLowerCase()](populate(url, payload))
|
||||||
dispatch({type: ACTION_TYPES.FETCH_RECORDS_SUCCESS, payload: {recordType: recordType, data: json}})
|
.then(response => response.json())
|
||||||
})
|
.then(json => {
|
||||||
.catch((err) => {
|
dispatch({
|
||||||
dispatch({type: ACTION_TYPES.FETCH_RECORDS_FAILURE, payload: {error: err, recordType: recordType}})
|
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
|
* @param string url API endpoint
|
||||||
*/
|
*/
|
||||||
export function deleteRecord(recordType, id, method, url) {
|
export function deleteRecord(recordType, id, method, url) {
|
||||||
let payload = {recordType: recordType, id: id};
|
const payload = { recordType, id };
|
||||||
url = populate(url, payload);
|
return (dispatch) => {
|
||||||
return (dispatch, getState) => {
|
dispatch({
|
||||||
dispatch ({type: ACTION_TYPES.DELETE_RECORD_REQUEST, payload: payload});
|
type: ACTION_TYPES.DELETE_RECORD_REQUEST,
|
||||||
return backend[method.toLowerCase()](url)
|
payload,
|
||||||
.then(json => {
|
});
|
||||||
dispatch({type: ACTION_TYPES.DELETE_RECORD_SUCCESS, payload: {recordType: recordType, id: id}})
|
return backend[method.toLowerCase()](populate(url, payload))
|
||||||
})
|
.then(() => {
|
||||||
.catch((err) => {
|
dispatch({
|
||||||
dispatch({type: ACTION_TYPES.DELETE_RECORD_FAILURE, payload: {error: err, recordType: recordType, id: id}})
|
type: ACTION_TYPES.DELETE_RECORD_SUCCESS,
|
||||||
});
|
payload: { recordType, id },
|
||||||
}
|
});
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
dispatch({
|
||||||
|
type: ACTION_TYPES.DELETE_RECORD_FAILURE,
|
||||||
|
payload: { error: err, recordType, id },
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,59 +5,58 @@ const initialState = {
|
||||||
};
|
};
|
||||||
|
|
||||||
function recordsReducer(state = initialState, action) {
|
function recordsReducer(state = initialState, action) {
|
||||||
let records;
|
let records;
|
||||||
let recordType;
|
let recordType;
|
||||||
|
|
||||||
switch (action.type) {
|
switch (action.type) {
|
||||||
|
|
||||||
case ACTION_TYPES.CREATE_RECORD:
|
case ACTION_TYPES.CREATE_RECORD:
|
||||||
return deepFreeze(Object.assign({}, state, {
|
return deepFreeze(Object.assign({}, state, {
|
||||||
|
|
||||||
}));
|
}));
|
||||||
|
|
||||||
case ACTION_TYPES.UPDATE_RECORD:
|
case ACTION_TYPES.UPDATE_RECORD:
|
||||||
return deepFreeze(Object.assign({}, state, {
|
return deepFreeze(Object.assign({}, state, {
|
||||||
|
|
||||||
}));
|
}));
|
||||||
|
|
||||||
case ACTION_TYPES.DELETE_RECORD:
|
case ACTION_TYPES.DELETE_RECORD:
|
||||||
return deepFreeze(Object.assign({}, state, {
|
return deepFreeze(Object.assign({}, state, {
|
||||||
|
|
||||||
}));
|
}));
|
||||||
|
|
||||||
case ACTION_TYPES.FETCH_RECORDS_REQUEST:
|
case ACTION_TYPES.FETCH_RECORDS_REQUEST:
|
||||||
return state;
|
return state;
|
||||||
|
|
||||||
case ACTION_TYPES.FETCH_RECORDS_FAILURE:
|
case ACTION_TYPES.FETCH_RECORDS_FAILURE:
|
||||||
return state;
|
return state;
|
||||||
|
|
||||||
case ACTION_TYPES.FETCH_RECORDS_SUCCESS:
|
case ACTION_TYPES.FETCH_RECORDS_SUCCESS:
|
||||||
recordType = action.payload.recordType;
|
recordType = action.payload.recordType;
|
||||||
// TODO Automatic pluralisation from recordType
|
// TODO Automatic pluralisation from recordType
|
||||||
records = action.payload.data._embedded[recordType + 's'];
|
records = action.payload.data._embedded[`${recordType}s`];
|
||||||
return deepFreeze(Object.assign({}, state, {
|
return deepFreeze(Object.assign({}, state, {
|
||||||
[recordType]: records
|
[recordType]: records,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
case ACTION_TYPES.DELETE_RECORD_REQUEST:
|
case ACTION_TYPES.DELETE_RECORD_REQUEST:
|
||||||
return state;
|
return state;
|
||||||
|
|
||||||
case ACTION_TYPES.DELETE_RECORD_FAILURE:
|
case ACTION_TYPES.DELETE_RECORD_FAILURE:
|
||||||
return state;
|
return state;
|
||||||
|
|
||||||
case ACTION_TYPES.DELETE_RECORD_SUCCESS:
|
case ACTION_TYPES.DELETE_RECORD_SUCCESS:
|
||||||
recordType = action.payload.recordType;
|
recordType = action.payload.recordType;
|
||||||
records = state[recordType]
|
records = state[recordType]
|
||||||
.filter(record => record.ID != action.payload.id)
|
.filter(record => record.ID !== action.payload.id);
|
||||||
|
|
||||||
return deepFreeze(Object.assign({}, state, {
|
return deepFreeze(Object.assign({}, state, {
|
||||||
[recordType]: records
|
[recordType]: records,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
default:
|
|
||||||
return state;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
default:
|
||||||
|
return state;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default recordsReducer;
|
export default recordsReducer;
|
||||||
|
|
|
@ -3,34 +3,34 @@ jest.dontMock('../reducer');
|
||||||
jest.dontMock('../action-types');
|
jest.dontMock('../action-types');
|
||||||
|
|
||||||
var recordsReducer = require('../reducer').default,
|
var recordsReducer = require('../reducer').default,
|
||||||
ACTION_TYPES = require('../action-types').default;
|
ACTION_TYPES = require('../action-types').default;
|
||||||
|
|
||||||
describe('recordsReducer', () => {
|
describe('recordsReducer', () => {
|
||||||
|
|
||||||
describe('DELETE_RECORD_SUCCESS', () => {
|
describe('DELETE_RECORD_SUCCESS', () => {
|
||||||
const initialState = {
|
const initialState = {
|
||||||
TypeA: [
|
TypeA: [
|
||||||
{ID: 1},
|
{ID: 1},
|
||||||
{ID: 2}
|
{ID: 2}
|
||||||
],
|
],
|
||||||
TypeB: [
|
TypeB: [
|
||||||
{ID: 1},
|
{ID: 1},
|
||||||
{ID: 2}
|
{ID: 2}
|
||||||
]
|
]
|
||||||
};
|
};
|
||||||
|
|
||||||
it('removes records from the declared type', () => {
|
it('removes records from the declared type', () => {
|
||||||
const nextState = recordsReducer(initialState, {
|
const nextState = recordsReducer(initialState, {
|
||||||
type: ACTION_TYPES.DELETE_RECORD_SUCCESS,
|
type: ACTION_TYPES.DELETE_RECORD_SUCCESS,
|
||||||
payload: { recordType: 'TypeA', id: 2 }
|
payload: { recordType: 'TypeA', id: 2 }
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(nextState.TypeA.length).toBe(1);
|
expect(nextState.TypeA.length).toBe(1);
|
||||||
expect(nextState.TypeA[0].ID).toBe(1);
|
expect(nextState.TypeA[0].ID).toBe(1);
|
||||||
expect(nextState.TypeB.length).toBe(2);
|
expect(nextState.TypeB.length).toBe(2);
|
||||||
expect(nextState.TypeB[0].ID).toBe(1);
|
expect(nextState.TypeB[0].ID).toBe(1);
|
||||||
expect(nextState.TypeB[1].ID).toBe(2);
|
expect(nextState.TypeB[1].ID).toBe(2);
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
const ACTION_TYPES = {
|
const ACTION_TYPES = {
|
||||||
SET_SCHEMA: 'SET_SCHEMA'
|
SET_SCHEMA: 'SET_SCHEMA',
|
||||||
};
|
};
|
||||||
|
|
||||||
export default ACTION_TYPES;
|
export default ACTION_TYPES;
|
||||||
|
|
|
@ -6,10 +6,9 @@ import ACTION_TYPES from './action-types';
|
||||||
* @param string schema - JSON schema for the layout.
|
* @param string schema - JSON schema for the layout.
|
||||||
*/
|
*/
|
||||||
export function setSchema(schema) {
|
export function setSchema(schema) {
|
||||||
return (dispatch, getState) => {
|
return (dispatch) =>
|
||||||
return dispatch ({
|
dispatch({
|
||||||
type: ACTION_TYPES.SET_SCHEMA,
|
type: ACTION_TYPES.SET_SCHEMA,
|
||||||
payload: schema
|
payload: schema,
|
||||||
});
|
});
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,15 +4,14 @@ import ACTION_TYPES from './action-types';
|
||||||
const initialState = deepFreeze({});
|
const initialState = deepFreeze({});
|
||||||
|
|
||||||
export default function schemaReducer(state = initialState, action = null) {
|
export default function schemaReducer(state = initialState, action = null) {
|
||||||
|
switch (action.type) {
|
||||||
|
|
||||||
switch (action.type) {
|
case ACTION_TYPES.SET_SCHEMA: {
|
||||||
|
const id = action.payload.schema.schema_url;
|
||||||
case ACTION_TYPES.SET_SCHEMA:
|
return deepFreeze(Object.assign({}, state, { [id]: action.payload }));
|
||||||
const id = action.payload.schema.schema_url;
|
|
||||||
return deepFreeze(Object.assign({}, state, {[id]: action.payload}));
|
|
||||||
|
|
||||||
default:
|
|
||||||
return state;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
default:
|
||||||
|
return state;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,18 +7,18 @@ import ACTION_TYPES from '../action-types';
|
||||||
|
|
||||||
describe('schemaReducer', () => {
|
describe('schemaReducer', () => {
|
||||||
|
|
||||||
describe('SET_SCHEMA', () => {
|
describe('SET_SCHEMA', () => {
|
||||||
|
|
||||||
it('should create a new form', () => {
|
it('should create a new form', () => {
|
||||||
const initialState = { };
|
const initialState = { };
|
||||||
const serverResponse = { id: 'TestForm', schema_url: 'URL' };
|
const serverResponse = { id: 'TestForm', schema_url: 'URL' };
|
||||||
|
|
||||||
const nextState = schemaReducer(initialState, {
|
const nextState = schemaReducer(initialState, {
|
||||||
type: ACTION_TYPES.SET_SCHEMA,
|
type: ACTION_TYPES.SET_SCHEMA,
|
||||||
payload: { schema: serverResponse }
|
payload: { schema: serverResponse }
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(nextState.URL.schema.id).toBe('TestForm');
|
expect(nextState.URL.schema.id).toBe('TestForm');
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -4,41 +4,41 @@ var reducerRegister = require('../reducer-register').default;
|
||||||
|
|
||||||
describe('ReducerRegister', () => {
|
describe('ReducerRegister', () => {
|
||||||
|
|
||||||
var reducer = () => null;
|
var reducer = () => null;
|
||||||
|
|
||||||
it('should add a reducer to the register', () => {
|
it('should add a reducer to the register', () => {
|
||||||
expect(reducerRegister.getAll().test).toBe(undefined);
|
expect(reducerRegister.getAll().test).toBe(undefined);
|
||||||
|
|
||||||
reducerRegister.add('test', reducer);
|
reducerRegister.add('test', reducer);
|
||||||
expect(reducerRegister.getAll().test).toBe(reducer);
|
expect(reducerRegister.getAll().test).toBe(reducer);
|
||||||
|
|
||||||
reducerRegister.remove('test');
|
reducerRegister.remove('test');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should remove a reducer from the register', () => {
|
it('should remove a reducer from the register', () => {
|
||||||
reducerRegister.add('test', reducer);
|
reducerRegister.add('test', reducer);
|
||||||
expect(reducerRegister.getAll().test).toBe(reducer);
|
expect(reducerRegister.getAll().test).toBe(reducer);
|
||||||
|
|
||||||
reducerRegister.remove('test');
|
reducerRegister.remove('test');
|
||||||
expect(reducerRegister.getAll().test).toBe(undefined);
|
expect(reducerRegister.getAll().test).toBe(undefined);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should get all reducers from the register', () => {
|
it('should get all reducers from the register', () => {
|
||||||
reducerRegister.add('test1', reducer);
|
reducerRegister.add('test1', reducer);
|
||||||
reducerRegister.add('test2', reducer);
|
reducerRegister.add('test2', reducer);
|
||||||
|
|
||||||
expect(reducerRegister.getAll().test1).toBe(reducer);
|
expect(reducerRegister.getAll().test1).toBe(reducer);
|
||||||
expect(reducerRegister.getAll().test2).toBe(reducer);
|
expect(reducerRegister.getAll().test2).toBe(reducer);
|
||||||
|
|
||||||
reducerRegister.remove('test1');
|
reducerRegister.remove('test1');
|
||||||
reducerRegister.remove('test2');
|
reducerRegister.remove('test2');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should get a single reducer from the register', () => {
|
it('should get a single reducer from the register', () => {
|
||||||
reducerRegister.add('test', reducer);
|
reducerRegister.add('test', reducer);
|
||||||
expect(reducerRegister.getByKey('test')).toBe(reducer);
|
expect(reducerRegister.getByKey('test')).toBe(reducer);
|
||||||
|
|
||||||
reducerRegister.remove('test');
|
reducerRegister.remove('test');
|
||||||
});
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
|
@ -5,98 +5,98 @@ import fetch from 'isomorphic-fetch';
|
||||||
import backend from '../silverstripe-backend';
|
import backend from '../silverstripe-backend';
|
||||||
|
|
||||||
var getFetchMock = function(data) {
|
var getFetchMock = function(data) {
|
||||||
let mock = jest.genMockFunction();
|
let mock = jest.genMockFunction();
|
||||||
let promise = new Promise((resolve, reject) => {
|
let promise = new Promise((resolve, reject) => {
|
||||||
process.nextTick(() => resolve(data));
|
process.nextTick(() => resolve(data));
|
||||||
});
|
});
|
||||||
mock.mockReturnValue(promise);
|
mock.mockReturnValue(promise);
|
||||||
|
|
||||||
return mock;
|
return mock;
|
||||||
};
|
};
|
||||||
|
|
||||||
describe('SilverStripeBackend', () => {
|
describe('SilverStripeBackend', () => {
|
||||||
|
|
||||||
beforeAll(() => {
|
beforeAll(() => {
|
||||||
let fetchMock = getFetchMock();
|
let fetchMock = getFetchMock();
|
||||||
backend.fetch = fetchMock;
|
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 send a GET request to an endpoint', () => {
|
||||||
|
backend.get('http://example.com');
|
||||||
it('should return a promise', () => {
|
expect(backend.fetch).toBeCalledWith(
|
||||||
var promise = backend.get('http://example.com');
|
'http://example.com',
|
||||||
expect(typeof promise).toBe('object');
|
{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', () => {
|
describe('post()', () => {
|
||||||
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'}
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
|
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', () => {
|
backend.post('http://example.com', postData);
|
||||||
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'}
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
|
expect(backend.fetch).toBeCalledWith(
|
||||||
|
'http://example.com',
|
||||||
|
{method: 'post', body: postData, credentials: 'same-origin'}
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('delete()', () => {
|
});
|
||||||
|
|
||||||
it('should return a promise', () => {
|
describe('put()', () => {
|
||||||
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'}
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
|
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'}
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
499
gulpfile.js
499
gulpfile.js
|
@ -1,110 +1,112 @@
|
||||||
var packageJson = require('./package.json'),
|
const packageJson = require('./package.json');
|
||||||
autoprefixer = require('autoprefixer'),
|
const autoprefixer = require('autoprefixer');
|
||||||
babelify = require('babelify'),
|
const babelify = require('babelify'); // eslint-disable-line no-unused-vars
|
||||||
browserify = require('browserify'),
|
const browserify = require('browserify');
|
||||||
eventStream = require('event-stream'),
|
const eventStream = require('event-stream');
|
||||||
glob = require('glob'),
|
const glob = require('glob');
|
||||||
gulp = require('gulp'),
|
const gulp = require('gulp');
|
||||||
babel = require('gulp-babel'),
|
const babel = require('gulp-babel');
|
||||||
diff = require('gulp-diff'),
|
const diff = require('gulp-diff');
|
||||||
gulpif = require('gulp-if'),
|
const gulpif = require('gulp-if');
|
||||||
notify = require('gulp-notify'),
|
const notify = require('gulp-notify');
|
||||||
postcss = require('gulp-postcss'),
|
const postcss = require('gulp-postcss');
|
||||||
sass = require('gulp-sass'),
|
const sass = require('gulp-sass');
|
||||||
sourcemaps = require('gulp-sourcemaps'),
|
const sourcemaps = require('gulp-sourcemaps');
|
||||||
uglify = require('gulp-uglify'),
|
const uglify = require('gulp-uglify');
|
||||||
gulpUtil = require('gulp-util'),
|
const gulpUtil = require('gulp-util');
|
||||||
path = require('path'),
|
const path = require('path');
|
||||||
source = require('vinyl-source-stream'),
|
const source = require('vinyl-source-stream');
|
||||||
buffer = require('vinyl-buffer'),
|
const buffer = require('vinyl-buffer');
|
||||||
semver = require('semver'),
|
const semver = require('semver');
|
||||||
sprity = require('sprity'),
|
const sprity = require('sprity');
|
||||||
watchify = require('watchify');
|
const watchify = require('watchify');
|
||||||
|
|
||||||
var isDev = typeof process.env.npm_config_development !== 'undefined';
|
const isDev = typeof process.env.npm_config_development !== 'undefined';
|
||||||
|
|
||||||
var PATHS = {
|
process.env.NODE_ENV = isDev ? 'development' : 'production';
|
||||||
MODULES: './node_modules',
|
|
||||||
ADMIN: './admin',
|
const PATHS = {
|
||||||
ADMIN_IMAGES: './admin/images',
|
MODULES: './node_modules',
|
||||||
ADMIN_SCSS: './admin/scss',
|
ADMIN: './admin',
|
||||||
ADMIN_THIRDPARTY: './admin/thirdparty',
|
ADMIN_IMAGES: './admin/images',
|
||||||
ADMIN_JAVASCRIPT_SRC: './admin/javascript/src',
|
ADMIN_SCSS: './admin/scss',
|
||||||
ADMIN_JAVASCRIPT_DIST: './admin/javascript/dist',
|
ADMIN_THIRDPARTY: './admin/thirdparty',
|
||||||
FRAMEWORK: '.',
|
ADMIN_JAVASCRIPT_SRC: './admin/javascript/src',
|
||||||
FRAMEWORK_THIRDPARTY: './thirdparty',
|
ADMIN_JAVASCRIPT_DIST: './admin/javascript/dist',
|
||||||
FRAMEWORK_DEV_INSTALL: './dev/install',
|
FRAMEWORK: '.',
|
||||||
FRAMEWORK_JAVASCRIPT_SRC: './javascript/src',
|
FRAMEWORK_THIRDPARTY: './thirdparty',
|
||||||
FRAMEWORK_JAVASCRIPT_DIST: './javascript/dist'
|
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
|
// Folders which contain both scss and css folders to be compiled
|
||||||
var rootCompileFolders = [PATHS.FRAMEWORK, PATHS.ADMIN, PATHS.FRAMEWORK_DEV_INSTALL]
|
const rootCompileFolders = [PATHS.FRAMEWORK, PATHS.ADMIN, PATHS.FRAMEWORK_DEV_INSTALL];
|
||||||
|
|
||||||
var browserifyOptions = {
|
const browserifyOptions = {
|
||||||
debug: true,
|
debug: true,
|
||||||
paths: [PATHS.ADMIN_JAVASCRIPT_SRC, PATHS.FRAMEWORK_JAVASCRIPT_SRC]
|
paths: [PATHS.ADMIN_JAVASCRIPT_SRC, PATHS.FRAMEWORK_JAVASCRIPT_SRC],
|
||||||
};
|
};
|
||||||
|
|
||||||
var babelifyOptions = {
|
const babelifyOptions = {
|
||||||
presets: ['es2015', 'react'],
|
presets: ['es2015', 'react'],
|
||||||
ignore: /(node_modules|thirdparty)/,
|
ignore: /(node_modules|thirdparty)/,
|
||||||
comments: false
|
comments: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
// Used for autoprefixing css properties (same as Bootstrap Aplha.2 defaults)
|
// Used for autoprefixing css properties (same as Bootstrap Aplha.2 defaults)
|
||||||
var supportedBrowsers = [
|
const supportedBrowsers = [
|
||||||
'Chrome >= 35',
|
'Chrome >= 35',
|
||||||
'Firefox >= 31',
|
'Firefox >= 31',
|
||||||
'Edge >= 12',
|
'Edge >= 12',
|
||||||
'Explorer >= 9',
|
'Explorer >= 9',
|
||||||
'iOS >= 8',
|
'iOS >= 8',
|
||||||
'Safari >= 8',
|
'Safari >= 8',
|
||||||
'Android 2.3',
|
'Android 2.3',
|
||||||
'Android >= 4',
|
'Android >= 4',
|
||||||
'Opera >= 12'
|
'Opera >= 12',
|
||||||
];
|
];
|
||||||
|
|
||||||
var blueimpFileUploadConfig = {
|
const blueimpFileUploadConfig = {
|
||||||
src: PATHS.MODULES + '/blueimp-file-upload',
|
src: `${PATHS.MODULES}/blueimp-file-upload`,
|
||||||
dest: PATHS.FRAMEWORK_THIRDPARTY + '/jquery-fileupload',
|
dest: PATHS.FRAMEWORK_THIRDPARTY + '/jquery-fileupload',
|
||||||
files: [
|
files: [
|
||||||
'/cors/jquery.postmessage-transport.js',
|
'/cors/jquery.postmessage-transport.js',
|
||||||
'/cors/jquery.xdr-transport.js',
|
'/cors/jquery.xdr-transport.js',
|
||||||
'/jquery.fileupload-ui.js',
|
'/jquery.fileupload-ui.js',
|
||||||
'/jquery.fileupload.js',
|
'/jquery.fileupload.js',
|
||||||
'/jquery.iframe-transport.js'
|
'/jquery.iframe-transport.js'
|
||||||
]
|
]
|
||||||
};
|
};
|
||||||
|
|
||||||
var blueimpLoadImageConfig = {
|
var blueimpLoadImageConfig = {
|
||||||
src: PATHS.MODULES + '/blueimp-load-image',
|
src: PATHS.MODULES + '/blueimp-load-image',
|
||||||
dest: PATHS.FRAMEWORK_THIRDPARTY + '/javascript-loadimage',
|
dest: PATHS.FRAMEWORK_THIRDPARTY + '/javascript-loadimage',
|
||||||
files: ['/load-image.js']
|
files: ['/load-image.js']
|
||||||
};
|
};
|
||||||
|
|
||||||
var blueimpTmplConfig = {
|
var blueimpTmplConfig = {
|
||||||
src: PATHS.MODULES + '/blueimp-tmpl',
|
src: PATHS.MODULES + '/blueimp-tmpl',
|
||||||
dest: PATHS.FRAMEWORK_THIRDPARTY + '/javascript-templates',
|
dest: PATHS.FRAMEWORK_THIRDPARTY + '/javascript-templates',
|
||||||
files: ['/tmpl.js']
|
files: ['/tmpl.js']
|
||||||
};
|
};
|
||||||
|
|
||||||
var jquerySizesConfig = {
|
var jquerySizesConfig = {
|
||||||
src: PATHS.MODULES + '/jquery-sizes',
|
src: PATHS.MODULES + '/jquery-sizes',
|
||||||
dest: PATHS.ADMIN_THIRDPARTY + '/jsizes',
|
dest: PATHS.ADMIN_THIRDPARTY + '/jsizes',
|
||||||
files: ['/lib/jquery.sizes.js']
|
files: ['/lib/jquery.sizes.js']
|
||||||
};
|
};
|
||||||
|
|
||||||
var tinymceConfig = {
|
var tinymceConfig = {
|
||||||
src: PATHS.MODULES + '/tinymce',
|
src: PATHS.MODULES + '/tinymce',
|
||||||
dest: PATHS.FRAMEWORK_THIRDPARTY + '/tinymce',
|
dest: PATHS.FRAMEWORK_THIRDPARTY + '/tinymce',
|
||||||
files: [
|
files: [
|
||||||
'/tinymce.min.js', // Exclude unminified file to keep repository size down
|
'/tinymce.min.js', // Exclude unminified file to keep repository size down
|
||||||
'/jquery.tinymce.min.js',
|
'/jquery.tinymce.min.js',
|
||||||
'/themes/**',
|
'/themes/**',
|
||||||
'/skins/**',
|
'/skins/**',
|
||||||
'/plugins/**'
|
'/plugins/**'
|
||||||
]
|
]
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -116,12 +118,12 @@ var tinymceConfig = {
|
||||||
* @param array libConfig.files - The list of files to copy from the source to the destination directory
|
* @param array libConfig.files - The list of files to copy from the source to the destination directory
|
||||||
*/
|
*/
|
||||||
function copyFiles(libConfig) {
|
function copyFiles(libConfig) {
|
||||||
libConfig.files.forEach(function (file) {
|
libConfig.files.forEach(function (file) {
|
||||||
var dir = path.parse(file).dir;
|
var dir = path.parse(file).dir;
|
||||||
|
|
||||||
gulp.src(libConfig.src + file)
|
gulp.src(libConfig.src + file)
|
||||||
.pipe(gulp.dest(libConfig.dest + dir));
|
.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
|
* @param array libConfig.files - The list of files to copy from the source to the destination directory
|
||||||
*/
|
*/
|
||||||
function diffFiles(libConfig) {
|
function diffFiles(libConfig) {
|
||||||
libConfig.files.forEach(function (file) {
|
libConfig.files.forEach(function (file) {
|
||||||
var dir = path.parse(file).dir;
|
var dir = path.parse(file).dir;
|
||||||
|
|
||||||
gulp.src(libConfig.src + file)
|
gulp.src(libConfig.src + file)
|
||||||
.pipe(diff(libConfig.dest + dir))
|
.pipe(diff(libConfig.dest + dir))
|
||||||
.pipe(diff.reporter({ fail: true, quiet: true }))
|
.pipe(diff.reporter({ fail: true, quiet: true }))
|
||||||
.on('error', function (error) {
|
.on('error', function (error) {
|
||||||
console.error(new Error('Sanity check failed. \'' + libConfig.dest + file + '\' has been modified.'));
|
console.error(new Error('Sanity check failed. \'' + libConfig.dest + file + '\' has been modified.'));
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -154,31 +156,31 @@ function diffFiles(libConfig) {
|
||||||
* @return object
|
* @return object
|
||||||
*/
|
*/
|
||||||
function transformToUmd(files, dest) {
|
function transformToUmd(files, dest) {
|
||||||
return eventStream.merge(files.map(function (file) {
|
return eventStream.merge(files.map(function (file) {
|
||||||
return gulp.src(file)
|
return gulp.src(file)
|
||||||
.pipe(babel({
|
.pipe(babel({
|
||||||
presets: ['es2015'],
|
presets: ['es2015'],
|
||||||
moduleId: 'ss.' + path.parse(file).name,
|
moduleId: 'ss.' + path.parse(file).name,
|
||||||
plugins: ['transform-es2015-modules-umd'],
|
plugins: ['transform-es2015-modules-umd'],
|
||||||
comments: false
|
comments: false
|
||||||
}))
|
}))
|
||||||
.on('error', notify.onError({
|
.on('error', notify.onError({
|
||||||
message: 'Error: <%= error.message %>',
|
message: 'Error: <%= error.message %>',
|
||||||
}))
|
}))
|
||||||
.pipe(gulp.dest(dest));
|
.pipe(gulp.dest(dest));
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Make sure the version of Node being used is valid.
|
// Make sure the version of Node being used is valid.
|
||||||
if (!semver.satisfies(process.versions.node, packageJson.engines.node)) {
|
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');
|
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);
|
process.exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isDev) {
|
if (isDev) {
|
||||||
browserifyOptions.cache = {};
|
browserifyOptions.cache = {};
|
||||||
browserifyOptions.packageCache = {};
|
browserifyOptions.packageCache = {};
|
||||||
browserifyOptions.plugin = [watchify];
|
browserifyOptions.plugin = [watchify];
|
||||||
}
|
}
|
||||||
|
|
||||||
gulp.task('build', ['umd', 'bundle']);
|
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', ['bundle-lib', 'bundle-legacy', 'bundle-framework']);
|
||||||
|
|
||||||
gulp.task('bundle-lib', function bundleLib() {
|
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' }))
|
return browserify(Object.assign({}, browserifyOptions, { entries: PATHS.ADMIN_JAVASCRIPT_SRC + '/bundles/lib.js' }))
|
||||||
.on('update', bundleLib)
|
.on('update', bundleLib)
|
||||||
.on('log', function (msg) { gulpUtil.log('Finished', 'bundled ' + bundleFileName + ' ' + msg) })
|
.on('log', function (msg) { gulpUtil.log('Finished', 'bundled ' + bundleFileName + ' ' + msg) })
|
||||||
.transform('babelify', babelifyOptions)
|
.transform('babelify', babelifyOptions)
|
||||||
.require('deep-freeze', { expose: 'deep-freeze' })
|
.require('deep-freeze', { expose: 'deep-freeze' })
|
||||||
.require('react', { expose: 'react' })
|
.require('react', { expose: 'react' })
|
||||||
.require('react-addons-css-transition-group', { expose: 'react-addons-css-transition-group' })
|
.require('react-addons-css-transition-group', { expose: 'react-addons-css-transition-group' })
|
||||||
.require('react-addons-test-utils', { expose: 'react-addons-test-utils' })
|
.require('react-addons-test-utils', { expose: 'react-addons-test-utils' })
|
||||||
.require('react-dom', { expose: 'react-dom' })
|
.require('react-dom', { expose: 'react-dom' })
|
||||||
.require('react-redux', { expose: 'react-redux' })
|
.require('react-redux', { expose: 'react-redux' })
|
||||||
.require('redux', { expose: 'redux' })
|
.require('redux', { expose: 'redux' })
|
||||||
.require('redux-thunk', { expose: 'redux-thunk' })
|
.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/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-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/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/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/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', { 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/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/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/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/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/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/index', { expose: 'components/north-header/index' })
|
||||||
.require(PATHS.ADMIN_JAVASCRIPT_SRC + '/components/north-header-breadcrumbs/index', { expose: 'components/north-header-breadcrumbs/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 + '/i18n.js', { expose: 'i18n' })
|
||||||
.require(PATHS.FRAMEWORK_JAVASCRIPT_SRC + '/jQuery.js', { expose: 'jQuery' })
|
.require(PATHS.FRAMEWORK_JAVASCRIPT_SRC + '/jQuery.js', { expose: 'jQuery' })
|
||||||
.require(PATHS.ADMIN_JAVASCRIPT_SRC + '/reducer-register.js', { expose: 'reducer-register' })
|
.require(PATHS.ADMIN_JAVASCRIPT_SRC + '/reducer-register.js', { expose: 'reducer-register' })
|
||||||
.require(PATHS.FRAMEWORK_JAVASCRIPT_SRC + '/router.js', { expose: 'router' })
|
.require(PATHS.FRAMEWORK_JAVASCRIPT_SRC + '/router.js', { expose: 'router' })
|
||||||
.require(PATHS.ADMIN_JAVASCRIPT_SRC + '/silverstripe-component', { expose: 'silverstripe-component' })
|
.require(PATHS.ADMIN_JAVASCRIPT_SRC + '/silverstripe-component', { expose: 'silverstripe-component' })
|
||||||
.bundle()
|
.bundle()
|
||||||
.on('update', bundleLib)
|
.on('error', notify.onError({ message: bundleFileName + ': <%= error.message %>' }))
|
||||||
.on('error', notify.onError({ message: bundleFileName + ': <%= error.message %>' }))
|
.pipe(source(bundleFileName))
|
||||||
.pipe(source(bundleFileName))
|
.pipe(buffer())
|
||||||
.pipe(buffer())
|
.pipe(sourcemaps.init({ loadMaps: true }))
|
||||||
.pipe(sourcemaps.init({ loadMaps: true }))
|
.pipe(gulpif(!isDev, uglify()))
|
||||||
.pipe(gulpif(!isDev, uglify()))
|
.pipe(sourcemaps.write('./'))
|
||||||
.pipe(sourcemaps.write('./'))
|
.pipe(gulp.dest(PATHS.ADMIN_JAVASCRIPT_DIST));
|
||||||
.pipe(gulp.dest(PATHS.ADMIN_JAVASCRIPT_DIST));
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
gulp.task('bundle-legacy', function bundleLeftAndMain() {
|
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' }))
|
return browserify(Object.assign({}, browserifyOptions, { entries: PATHS.ADMIN_JAVASCRIPT_SRC + '/bundles/legacy.js' }))
|
||||||
.on('update', bundleLeftAndMain)
|
.on('update', bundleLeftAndMain)
|
||||||
.on('log', function (msg) { gulpUtil.log('Finished', 'bundled ' + bundleFileName + ' ' + msg) })
|
.on('log', function (msg) { gulpUtil.log('Finished', 'bundled ' + bundleFileName + ' ' + msg) })
|
||||||
.transform('babelify', babelifyOptions)
|
.transform('babelify', babelifyOptions)
|
||||||
.external('jQuery')
|
.external('jQuery')
|
||||||
.external('i18n')
|
.external('i18n')
|
||||||
.external('router')
|
.external('router')
|
||||||
.bundle()
|
.bundle()
|
||||||
.on('update', bundleLeftAndMain)
|
.on('update', bundleLeftAndMain)
|
||||||
.on('error', notify.onError({ message: bundleFileName + ': <%= error.message %>' }))
|
.on('error', notify.onError({ message: bundleFileName + ': <%= error.message %>' }))
|
||||||
.pipe(source(bundleFileName))
|
.pipe(source(bundleFileName))
|
||||||
.pipe(buffer())
|
.pipe(buffer())
|
||||||
.pipe(sourcemaps.init({ loadMaps: true }))
|
.pipe(sourcemaps.init({ loadMaps: true }))
|
||||||
.pipe(gulpif(!isDev, uglify()))
|
.pipe(gulpif(!isDev, uglify()))
|
||||||
.pipe(sourcemaps.write('./'))
|
.pipe(sourcemaps.write('./'))
|
||||||
.pipe(gulp.dest(PATHS.ADMIN_JAVASCRIPT_DIST));
|
.pipe(gulp.dest(PATHS.ADMIN_JAVASCRIPT_DIST));
|
||||||
});
|
});
|
||||||
|
|
||||||
gulp.task('bundle-framework', function bundleBoot() {
|
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' }))
|
return browserify(Object.assign({}, browserifyOptions, { entries: PATHS.ADMIN_JAVASCRIPT_SRC + '/boot/index.js' }))
|
||||||
.on('update', bundleBoot)
|
.on('update', bundleBoot)
|
||||||
.on('log', function (msg) { gulpUtil.log('Finished', 'bundled ' + bundleFileName + ' ' + msg) })
|
.on('log', function (msg) { gulpUtil.log('Finished', 'bundled ' + bundleFileName + ' ' + msg) })
|
||||||
.transform('babelify', babelifyOptions)
|
.transform('babelify', babelifyOptions)
|
||||||
.external('components/action-button/index')
|
.external('components/action-button/index')
|
||||||
.external('components/north-header/index')
|
.external('components/north-header/index')
|
||||||
.external('components/form-builder/index')
|
.external('components/form-builder/index')
|
||||||
.external('deep-freeze')
|
.external('deep-freeze')
|
||||||
.external('components/grid-field/index')
|
.external('components/grid-field/index')
|
||||||
.external('i18n')
|
.external('i18n')
|
||||||
.external('jQuery')
|
.external('jQuery')
|
||||||
.external('page.js')
|
.external('page.js')
|
||||||
.external('react-addons-test-utils')
|
.external('react-addons-test-utils')
|
||||||
.external('react-dom')
|
.external('react-dom')
|
||||||
.external('react-redux')
|
.external('react-redux')
|
||||||
.external('react')
|
.external('react')
|
||||||
.external('reducer-register')
|
.external('reducer-register')
|
||||||
.external('redux-thunk')
|
.external('redux-thunk')
|
||||||
.external('redux')
|
.external('redux')
|
||||||
.external('silverstripe-component')
|
.external('silverstripe-component')
|
||||||
.bundle()
|
.bundle()
|
||||||
.on('update', bundleBoot)
|
.on('update', bundleBoot)
|
||||||
.on('error', notify.onError({ message: bundleFileName + ': <%= error.message %>' }))
|
.on('error', notify.onError({ message: bundleFileName + ': <%= error.message %>' }))
|
||||||
.pipe(source(bundleFileName))
|
.pipe(source(bundleFileName))
|
||||||
.pipe(buffer())
|
.pipe(buffer())
|
||||||
.pipe(sourcemaps.init({ loadMaps: true }))
|
.pipe(sourcemaps.init({ loadMaps: true }))
|
||||||
.pipe(gulpif(!isDev, uglify()))
|
.pipe(gulpif(!isDev, uglify()))
|
||||||
.pipe(sourcemaps.write('./'))
|
.pipe(sourcemaps.write('./'))
|
||||||
.pipe(gulp.dest(PATHS.ADMIN_JAVASCRIPT_DIST));
|
.pipe(gulp.dest(PATHS.ADMIN_JAVASCRIPT_DIST));
|
||||||
});
|
});
|
||||||
|
|
||||||
gulp.task('sanity', function () {
|
gulp.task('sanity', function () {
|
||||||
diffFiles(blueimpFileUploadConfig);
|
diffFiles(blueimpFileUploadConfig);
|
||||||
diffFiles(blueimpLoadImageConfig);
|
diffFiles(blueimpLoadImageConfig);
|
||||||
diffFiles(blueimpTmplConfig);
|
diffFiles(blueimpTmplConfig);
|
||||||
diffFiles(jquerySizesConfig);
|
diffFiles(jquerySizesConfig);
|
||||||
diffFiles(tinymceConfig);
|
diffFiles(tinymceConfig);
|
||||||
});
|
});
|
||||||
|
|
||||||
gulp.task('thirdparty', function () {
|
gulp.task('thirdparty', function () {
|
||||||
copyFiles(blueimpFileUploadConfig);
|
copyFiles(blueimpFileUploadConfig);
|
||||||
copyFiles(blueimpLoadImageConfig);
|
copyFiles(blueimpLoadImageConfig);
|
||||||
copyFiles(blueimpTmplConfig);
|
copyFiles(blueimpTmplConfig);
|
||||||
copyFiles(jquerySizesConfig);
|
copyFiles(jquerySizesConfig);
|
||||||
copyFiles(tinymceConfig);
|
copyFiles(tinymceConfig);
|
||||||
});
|
});
|
||||||
|
|
||||||
gulp.task('umd', ['umd-admin', 'umd-framework'], function () {
|
gulp.task('umd', ['umd-admin', 'umd-framework'], function () {
|
||||||
if (isDev) {
|
if (isDev) {
|
||||||
gulp.watch(PATHS.ADMIN_JAVASCRIPT_SRC + '/*.js', ['umd-admin']);
|
gulp.watch(PATHS.ADMIN_JAVASCRIPT_SRC + '/*.js', ['umd-admin']);
|
||||||
gulp.watch(PATHS.FRAMEWORK_JAVASCRIPT_SRC + '/*.js', ['umd-framework']);
|
gulp.watch(PATHS.FRAMEWORK_JAVASCRIPT_SRC + '/*.js', ['umd-framework']);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
gulp.task('umd-admin', function () {
|
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 () {
|
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
|
* Takes individual images and compiles them together into sprites
|
||||||
*/
|
*/
|
||||||
gulp.task('sprites', function () {
|
gulp.task('sprites', function () {
|
||||||
return sprity.src({
|
return sprity.src({
|
||||||
src: PATHS.ADMIN_IMAGES + '/sprites/src/**/*.{png,jpg}',
|
src: PATHS.ADMIN_IMAGES + '/sprites/src/**/*.{png,jpg}',
|
||||||
cssPath: '../images/sprites/dist',
|
cssPath: '../images/sprites/dist',
|
||||||
style: './_spritey.scss',
|
style: './_spritey.scss',
|
||||||
processor: 'sass',
|
processor: 'sass',
|
||||||
split: true,
|
split: true,
|
||||||
margin: 0
|
margin: 0
|
||||||
})
|
})
|
||||||
.pipe(gulpif('*.png', gulp.dest(PATHS.ADMIN_IMAGES + '/sprites/dist'), gulp.dest(PATHS.ADMIN_SCSS)))
|
.pipe(gulpif('*.png', gulp.dest(PATHS.ADMIN_IMAGES + '/sprites/dist'), gulp.dest(PATHS.ADMIN_SCSS)))
|
||||||
});
|
});
|
||||||
|
|
||||||
gulp.task('css', ['compile:css'], function () {
|
gulp.task('css', ['compile:css'], function () {
|
||||||
if (isDev) {
|
if (isDev) {
|
||||||
rootCompileFolders.forEach(function (folder) {
|
rootCompileFolders.forEach(function (folder) {
|
||||||
gulp.watch(folder + '/scss/**/*.scss', ['compile:css']);
|
gulp.watch(folder + '/scss/**/*.scss', ['compile:css']);
|
||||||
});
|
});
|
||||||
|
|
||||||
// Watch the .scss files in react components
|
// Watch the .scss files in react components
|
||||||
gulp.watch('./admin/javascript/src/**/*.scss', ['compile:css']);
|
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
|
* Watches for changes if --development flag is given
|
||||||
*/
|
*/
|
||||||
gulp.task('compile:css', function () {
|
gulp.task('compile:css', function () {
|
||||||
var outputStyle = isDev ? 'expanded' : 'compressed';
|
var outputStyle = isDev ? 'expanded' : 'compressed';
|
||||||
|
|
||||||
var tasks = rootCompileFolders.map(function(folder) {
|
var tasks = rootCompileFolders.map(function(folder) {
|
||||||
return gulp.src(folder + '/scss/**/*.scss')
|
return gulp.src(folder + '/scss/**/*.scss')
|
||||||
.pipe(sourcemaps.init())
|
.pipe(sourcemaps.init())
|
||||||
.pipe(sass({ outputStyle: outputStyle })
|
.pipe(sass({ outputStyle: outputStyle })
|
||||||
.on('error', notify.onError({
|
.on('error', notify.onError({
|
||||||
message: 'Error: <%= error.message %>'
|
message: 'Error: <%= error.message %>'
|
||||||
}))
|
}))
|
||||||
)
|
)
|
||||||
.pipe(postcss([autoprefixer({ browsers: supportedBrowsers })]))
|
.pipe(postcss([autoprefixer({ browsers: supportedBrowsers })]))
|
||||||
.pipe(sourcemaps.write())
|
.pipe(sourcemaps.write())
|
||||||
.pipe(gulp.dest(folder + '/css'))
|
.pipe(gulp.dest(folder + '/css'))
|
||||||
});
|
});
|
||||||
|
|
||||||
return tasks;
|
return tasks;
|
||||||
});
|
});
|
||||||
|
|
|
@ -27,7 +27,7 @@ ss.editorWrappers.tinyMCE = (function() {
|
||||||
* Initialise the editor
|
* Initialise the editor
|
||||||
*
|
*
|
||||||
* @param {String} ID of parent textarea domID
|
* @param {String} ID of parent textarea domID
|
||||||
*/
|
*/
|
||||||
init: function(ID) {
|
init: function(ID) {
|
||||||
editorID = ID;
|
editorID = ID;
|
||||||
|
|
||||||
|
@ -45,7 +45,7 @@ ss.editorWrappers.tinyMCE = (function() {
|
||||||
* Get TinyMCE Editor instance
|
* Get TinyMCE Editor instance
|
||||||
*
|
*
|
||||||
* @returns Editor
|
* @returns Editor
|
||||||
*/
|
*/
|
||||||
getInstance: function() {
|
getInstance: function() {
|
||||||
return tinymce.EditorManager.get(editorID);
|
return tinymce.EditorManager.get(editorID);
|
||||||
},
|
},
|
||||||
|
@ -68,7 +68,7 @@ ss.editorWrappers.tinyMCE = (function() {
|
||||||
* Get config for this data
|
* Get config for this data
|
||||||
*
|
*
|
||||||
* @returns array
|
* @returns array
|
||||||
*/
|
*/
|
||||||
getConfig: function() {
|
getConfig: function() {
|
||||||
var selector = "#" + editorID,
|
var selector = "#" + editorID,
|
||||||
config = $(selector).data('config'),
|
config = $(selector).data('config'),
|
||||||
|
@ -581,8 +581,8 @@ $.entwine('ss', function($) {
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'file':
|
case 'file':
|
||||||
var fileid = this.find('.ss-uploadfield .ss-uploadfield-item').attr('data-fileid');
|
var fileid = this.find('.ss-uploadfield .ss-uploadfield-item').attr('data-fileid');
|
||||||
href = fileid ? '[file_link,id=' + fileid + ']' : '';
|
href = fileid ? '[file_link,id=' + fileid + ']' : '';
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'email':
|
case 'email':
|
||||||
|
|
|
@ -85,7 +85,7 @@ $.widget('blueimpUIX.fileupload', $.blueimpUI.fileupload, {
|
||||||
e.preventDefault(); // Avoid a form submit
|
e.preventDefault(); // Avoid a form submit
|
||||||
return false;
|
return false;
|
||||||
});
|
});
|
||||||
} else { //regular file upload
|
} else { //regular file upload
|
||||||
return $.blueimpUI.fileupload.prototype._onSend.call(that, e, data);
|
return $.blueimpUI.fileupload.prototype._onSend.call(that, e, data);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,19 +8,20 @@ import page from 'page.js';
|
||||||
* Wrapper for `page.show()` with SilverStripe specific behaviour.
|
* Wrapper for `page.show()` with SilverStripe specific behaviour.
|
||||||
*/
|
*/
|
||||||
function show(pageShow) {
|
function show(pageShow) {
|
||||||
return (path, state, dispatch, push) => {
|
return (path, state, dispatch, push) => {
|
||||||
// Normalise `path` so that pattern matching is more robust.
|
// Normalise `path` so that pattern matching is more robust.
|
||||||
// For example if your route is '/pages' it should match when `path` is
|
// For example if your route is '/pages' it should match when `path` is
|
||||||
// 'http://foo.com/admin/pages', '/pages', and 'pages'.
|
// 'http://foo.com/admin/pages', '/pages', and 'pages'.
|
||||||
var el = document.createElement('a');
|
const el = document.createElement('a');
|
||||||
el.href = path;
|
let pathWithSearch;
|
||||||
path = el.pathname;
|
el.href = path;
|
||||||
if(el.search) {
|
pathWithSearch = el.pathname;
|
||||||
path += el.search;
|
if (el.search) {
|
||||||
}
|
pathWithSearch += el.search;
|
||||||
|
|
||||||
return pageShow(path, state, dispatch, push);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return pageShow(pathWithSearch, state, dispatch, push);
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
page.show = show(page.show);
|
page.show = show(page.show);
|
||||||
|
|
|
@ -17,7 +17,8 @@
|
||||||
"sprites": "gulp sprites",
|
"sprites": "gulp sprites",
|
||||||
"test": "NODE_PATH=\"./javascript/src:./admin/javascript/src\" jest",
|
"test": "NODE_PATH=\"./javascript/src:./admin/javascript/src\" jest",
|
||||||
"coverage": "NODE_PATH=\"./javascript/src:./admin/javascript/src\" jest --coverage",
|
"coverage": "NODE_PATH=\"./javascript/src:./admin/javascript/src\" jest --coverage",
|
||||||
"thirdparty": "gulp thirdparty"
|
"thirdparty": "gulp thirdparty",
|
||||||
|
"lint": "eslint javascript/src & eslint admin/javascript/src"
|
||||||
},
|
},
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
|
@ -60,6 +61,9 @@
|
||||||
"babel-preset-react": "^6.5.0",
|
"babel-preset-react": "^6.5.0",
|
||||||
"babelify": "^7.2.0",
|
"babelify": "^7.2.0",
|
||||||
"browserify": "^13.0.0",
|
"browserify": "^13.0.0",
|
||||||
|
"eslint": "^2.5.3",
|
||||||
|
"eslint-config-airbnb": "^6.2.0",
|
||||||
|
"eslint-plugin-react": "^4.2.3",
|
||||||
"event-stream": "^3.3.2",
|
"event-stream": "^3.3.2",
|
||||||
"glob": "^6.0.4",
|
"glob": "^6.0.4",
|
||||||
"gulp": "^3.9.0",
|
"gulp": "^3.9.0",
|
||||||
|
|
Loading…
Reference in New Issue
Block a user