mirror of
https://github.com/silverstripe/silverstripe-framework
synced 2024-10-22 14:05:37 +02:00
Campaign publish feature
This commit is contained in:
parent
3820314a11
commit
107e38b7a7
@ -18,6 +18,7 @@ class CampaignAdmin extends LeftAndMain implements PermissionProvider {
|
|||||||
'readCampaign',
|
'readCampaign',
|
||||||
'updateCampaign',
|
'updateCampaign',
|
||||||
'deleteCampaign',
|
'deleteCampaign',
|
||||||
|
'publishCampaign',
|
||||||
];
|
];
|
||||||
|
|
||||||
private static $menu_priority = 11;
|
private static $menu_priority = 11;
|
||||||
@ -28,6 +29,7 @@ class CampaignAdmin extends LeftAndMain implements PermissionProvider {
|
|||||||
'GET sets' => 'readCampaigns',
|
'GET sets' => 'readCampaigns',
|
||||||
'POST set/$ID' => 'createCampaign',
|
'POST set/$ID' => 'createCampaign',
|
||||||
'GET set/$ID/$Name' => 'readCampaign',
|
'GET set/$ID/$Name' => 'readCampaign',
|
||||||
|
'PUT set/$ID/publish' => 'publishCampaign',
|
||||||
'PUT set/$ID' => 'updateCampaign',
|
'PUT set/$ID' => 'updateCampaign',
|
||||||
'DELETE set/$ID' => 'deleteCampaign',
|
'DELETE set/$ID' => 'deleteCampaign',
|
||||||
];
|
];
|
||||||
@ -62,6 +64,10 @@ class CampaignAdmin extends LeftAndMain implements PermissionProvider {
|
|||||||
],
|
],
|
||||||
'campaignViewRoute' => $urlSegment . '/:type?/:id?/:view?',
|
'campaignViewRoute' => $urlSegment . '/:type?/:id?/:view?',
|
||||||
'itemListViewEndpoint' => $this->Link('set/:id/show'),
|
'itemListViewEndpoint' => $this->Link('set/:id/show'),
|
||||||
|
'publishEndpoint' => [
|
||||||
|
'url' => $this->Link('set/:id/publish'),
|
||||||
|
'method' => 'put'
|
||||||
|
]
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -258,6 +264,8 @@ JSON;
|
|||||||
'Created' => $changeSet->Created,
|
'Created' => $changeSet->Created,
|
||||||
'LastEdited' => $changeSet->LastEdited,
|
'LastEdited' => $changeSet->LastEdited,
|
||||||
'State' => $changeSet->State,
|
'State' => $changeSet->State,
|
||||||
|
'canEdit' => $changeSet->canEdit(),
|
||||||
|
'canPublish' => $changeSet->canPublish(),
|
||||||
'_embedded' => ['ChangeSetItems' => []]
|
'_embedded' => ['ChangeSetItems' => []]
|
||||||
];
|
];
|
||||||
foreach($changeSet->Changes() as $changeSetItem) {
|
foreach($changeSet->Changes() as $changeSetItem) {
|
||||||
@ -415,6 +423,44 @@ JSON;
|
|||||||
return (new SS_HTTPResponse('', 204));
|
return (new SS_HTTPResponse('', 204));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* REST endpoint to publish a {@link ChangeSet} and all of its items.
|
||||||
|
*
|
||||||
|
* @param SS_HTTPRequest $request
|
||||||
|
*
|
||||||
|
* @return SS_HTTPResponse
|
||||||
|
*/
|
||||||
|
public function publishCampaign(SS_HTTPRequest $request) {
|
||||||
|
$id = $request->param('ID');
|
||||||
|
if(!$id || !is_numeric($id)) {
|
||||||
|
return (new SS_HTTPResponse(json_encode(['status' => 'error']), 400))
|
||||||
|
->addHeader('Content-Type', 'application/json');
|
||||||
|
}
|
||||||
|
|
||||||
|
$record = ChangeSet::get()->byID($id);
|
||||||
|
if(!$record) {
|
||||||
|
return (new SS_HTTPResponse(json_encode(['status' => 'error']), 404))
|
||||||
|
->addHeader('Content-Type', 'application/json');
|
||||||
|
}
|
||||||
|
|
||||||
|
if(!$record->canPublish()) {
|
||||||
|
return (new SS_HTTPResponse(json_encode(['status' => 'error']), 401))
|
||||||
|
->addHeader('Content-Type', 'application/json');
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
$record->publish();
|
||||||
|
} catch(LogicException $e) {
|
||||||
|
return (new SS_HTTPResponse(json_encode(['status' => 'error', 'message' => $e->getMessage()]), 401))
|
||||||
|
->addHeader('Content-Type', 'application/json');
|
||||||
|
}
|
||||||
|
|
||||||
|
return (new SS_HTTPResponse(
|
||||||
|
Convert::raw2json($this->getChangeSetResource($record)),
|
||||||
|
200
|
||||||
|
))->addHeader('Content-Type', 'application/json');
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @todo Use GridFieldDetailForm once it can handle structured data and form schemas
|
* @todo Use GridFieldDetailForm once it can handle structured data and form schemas
|
||||||
*
|
*
|
||||||
|
@ -19,6 +19,9 @@ if(typeof(ss) == 'undefined' || typeof(ss.i18n) == 'undefined') {
|
|||||||
"ModelAdmin.DELETED": "Deleted",
|
"ModelAdmin.DELETED": "Deleted",
|
||||||
"ModelAdmin.VALIDATIONERROR": "Validation Error",
|
"ModelAdmin.VALIDATIONERROR": "Validation Error",
|
||||||
"LeftAndMain.PAGEWASDELETED": "This page was deleted. To edit a page, select it from the left.",
|
"LeftAndMain.PAGEWASDELETED": "This page was deleted. To edit a page, select it from the left.",
|
||||||
"Campaigns.ADDCAMPAIGN": "Add campaign"
|
"Campaigns.ADDCAMPAIGN": "Add campaign",
|
||||||
|
"Campaigns.PUBLISHCAMPAIGN": "Publish campaign",
|
||||||
|
"Campaigns.ITEM_SUMMARY_SINGULAR": "%s item",
|
||||||
|
"Campaigns.ITEM_SUMMARY_PLURAL": "%s items",
|
||||||
});
|
});
|
||||||
}
|
}
|
@ -14,5 +14,6 @@
|
|||||||
"ModelAdmin.DELETED": "Deleted",
|
"ModelAdmin.DELETED": "Deleted",
|
||||||
"ModelAdmin.VALIDATIONERROR": "Validation Error",
|
"ModelAdmin.VALIDATIONERROR": "Validation Error",
|
||||||
"LeftAndMain.PAGEWASDELETED": "This page was deleted. To edit a page, select it from the left.",
|
"LeftAndMain.PAGEWASDELETED": "This page was deleted. To edit a page, select it from the left.",
|
||||||
"Campaigns.ADDCAMPAIGN": "Add campaign"
|
"Campaigns.ADDCAMPAIGN": "Add campaign",
|
||||||
|
"Campaigns.PUBLISHCAMPAIGN": "Publish campaign",
|
||||||
}
|
}
|
15
admin/javascript/src/components/label/README.md
Normal file
15
admin/javascript/src/components/label/README.md
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
# Label
|
||||||
|
|
||||||
|
Small and adaptive tag for adding context to just about any content.
|
||||||
|
Extends the [Label component](http://v4-alpha.getbootstrap.com/components/label/)
|
||||||
|
in Bootstrap.
|
||||||
|
|
||||||
|
## Variations
|
||||||
|
|
||||||
|
Empty label - a round indicator demonstrating a status.
|
||||||
|
Use together with a "contextual variation" like `label-warning`
|
||||||
|
to get colour value.
|
||||||
|
|
||||||
|
```html
|
||||||
|
<span class="label label-warning label--empty" />
|
||||||
|
```
|
6
admin/javascript/src/components/label/styles.scss
Normal file
6
admin/javascript/src/components/label/styles.scss
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
.label--empty {
|
||||||
|
border-radius: 50%;
|
||||||
|
height: 10px;
|
||||||
|
width: 10px;
|
||||||
|
top: 1px;
|
||||||
|
}
|
@ -1,6 +1,7 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { bindActionCreators } from 'redux';
|
import { bindActionCreators } from 'redux';
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
|
import backend from 'silverstripe-backend';
|
||||||
import * as actions from 'state/campaign/actions';
|
import * as actions from 'state/campaign/actions';
|
||||||
import SilverStripeComponent from 'silverstripe-component';
|
import SilverStripeComponent from 'silverstripe-component';
|
||||||
import FormAction from 'components/form-action/index';
|
import FormAction from 'components/form-action/index';
|
||||||
@ -16,6 +17,13 @@ class CampaignAdminContainer extends SilverStripeComponent {
|
|||||||
|
|
||||||
this.addCampaign = this.addCampaign.bind(this);
|
this.addCampaign = this.addCampaign.bind(this);
|
||||||
this.createFn = this.createFn.bind(this);
|
this.createFn = this.createFn.bind(this);
|
||||||
|
this.publishApi = backend.createEndpointFetcher({
|
||||||
|
url: this.props.config.publishEndpoint.url,
|
||||||
|
method: this.props.config.publishEndpoint.method,
|
||||||
|
payloadSchema: {
|
||||||
|
id: { urlReplacement: ':id', remove: true },
|
||||||
|
},
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
@ -73,6 +81,7 @@ class CampaignAdminContainer extends SilverStripeComponent {
|
|||||||
const props = {
|
const props = {
|
||||||
campaignId: this.props.campaignId,
|
campaignId: this.props.campaignId,
|
||||||
itemListViewEndpoint: this.props.config.itemListViewEndpoint,
|
itemListViewEndpoint: this.props.config.itemListViewEndpoint,
|
||||||
|
publishApi: this.publishApi,
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -7,7 +7,7 @@ import i18n from 'i18n';
|
|||||||
*/
|
*/
|
||||||
class CampaignItem extends SilverStripeComponent {
|
class CampaignItem extends SilverStripeComponent {
|
||||||
render() {
|
render() {
|
||||||
let thumbnail = '';
|
let thumbnail = null;
|
||||||
const badge = {};
|
const badge = {};
|
||||||
const item = this.props.item;
|
const item = this.props.item;
|
||||||
|
|
||||||
|
@ -1,24 +1,33 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { bindActionCreators } from 'redux';
|
import { bindActionCreators } from 'redux';
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import * as actions from 'state/records/actions';
|
import * as recordActions from 'state/records/actions';
|
||||||
|
import * as campaignActions from 'state/campaign/actions';
|
||||||
import SilverStripeComponent from 'silverstripe-component';
|
import SilverStripeComponent from 'silverstripe-component';
|
||||||
import Accordion from 'components/accordion/index';
|
import Accordion from 'components/accordion/index';
|
||||||
import AccordionGroup from 'components/accordion/group';
|
import AccordionGroup from 'components/accordion/group';
|
||||||
import AccordionItem from 'components/accordion/item';
|
import AccordionItem from 'components/accordion/item';
|
||||||
import NorthHeader from 'components/north-header/index';
|
import NorthHeader from 'components/north-header/index';
|
||||||
|
import FormAction from 'components/form-action/index';
|
||||||
import CampaignItem from './item';
|
import CampaignItem from './item';
|
||||||
import CampaignPreview from './preview';
|
import CampaignPreview from './preview';
|
||||||
|
import i18n from 'i18n';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Represents a campaign list view
|
* Represents a campaign list view
|
||||||
*/
|
*/
|
||||||
class CampaignListContainer extends SilverStripeComponent {
|
class CampaignListContainer extends SilverStripeComponent {
|
||||||
|
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
|
||||||
|
this.handlePublish = this.handlePublish.bind(this);
|
||||||
|
}
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
const fetchURL = this.props.itemListViewEndpoint.replace(/:id/, this.props.campaignId);
|
const fetchURL = this.props.itemListViewEndpoint.replace(/:id/, this.props.campaignId);
|
||||||
super.componentDidMount();
|
super.componentDidMount();
|
||||||
this.props.actions.fetchRecord('ChangeSet', 'get', fetchURL);
|
this.props.recordActions.fetchRecord('ChangeSet', 'get', fetchURL);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -79,12 +88,60 @@ class CampaignListContainer extends SilverStripeComponent {
|
|||||||
{accordionGroups}
|
{accordionGroups}
|
||||||
</Accordion>
|
</Accordion>
|
||||||
</div>
|
</div>
|
||||||
|
<div className="cms-south-actions">
|
||||||
|
{this.renderButtonToolbar()}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{ previewUrl && <CampaignPreview previewUrl={previewUrl} /> }
|
{ previewUrl && <CampaignPreview previewUrl={previewUrl} /> }
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
renderButtonToolbar() {
|
||||||
|
const items = this.getItems(this.props.campaignId);
|
||||||
|
|
||||||
|
let itemSummaryLabel;
|
||||||
|
if (items) {
|
||||||
|
itemSummaryLabel = i18n.sprintf(
|
||||||
|
(items.length === 1) ?
|
||||||
|
i18n._t('Campaigns.ITEM_SUMMARY_SINGULAR')
|
||||||
|
: i18n._t('Campaigns.ITEM_SUMMARY_PLURAL'),
|
||||||
|
items.length
|
||||||
|
);
|
||||||
|
|
||||||
|
let button;
|
||||||
|
if (this.props.record.State === 'open') {
|
||||||
|
button = (
|
||||||
|
<FormAction
|
||||||
|
label={i18n._t('Campaigns.PUBLISHCAMPAIGN')}
|
||||||
|
style={'success'}
|
||||||
|
handleClick={this.handlePublish}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
} else if (this.props.record.State === 'published') {
|
||||||
|
// TODO Implement "revert" feature
|
||||||
|
button = (
|
||||||
|
<FormAction
|
||||||
|
label={i18n._t('Campaigns.PUBLISHCAMPAIGN')}
|
||||||
|
style={'success'}
|
||||||
|
disabled
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="btn-toolbar">
|
||||||
|
{button}
|
||||||
|
<span className="text-muted">
|
||||||
|
<span className="label label-warning label--empty"> </span>
|
||||||
|
{itemSummaryLabel}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return <div className="btn-toolbar"></div>;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets preview URL for itemid
|
* Gets preview URL for itemid
|
||||||
@ -100,6 +157,17 @@ class CampaignListContainer extends SilverStripeComponent {
|
|||||||
return document.getElementsByTagName('base')[0].href;
|
return document.getElementsByTagName('base')[0].href;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return {Array}
|
||||||
|
*/
|
||||||
|
getItems() {
|
||||||
|
if (this.props.record && this.props.record._embedded) {
|
||||||
|
return this.props.record._embedded.ChangeSetItems;
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Group items for changeset display
|
* Group items for changeset display
|
||||||
*
|
*
|
||||||
@ -107,10 +175,10 @@ class CampaignListContainer extends SilverStripeComponent {
|
|||||||
*/
|
*/
|
||||||
groupItemsForSet() {
|
groupItemsForSet() {
|
||||||
const groups = {};
|
const groups = {};
|
||||||
if (!this.props.record || !this.props.record._embedded) {
|
const items = this.getItems();
|
||||||
|
if (!items) {
|
||||||
return groups;
|
return groups;
|
||||||
}
|
}
|
||||||
const items = this.props.record._embedded.ChangeSetItems;
|
|
||||||
|
|
||||||
// group by whatever
|
// group by whatever
|
||||||
items.forEach(item => {
|
items.forEach(item => {
|
||||||
@ -132,8 +200,20 @@ class CampaignListContainer extends SilverStripeComponent {
|
|||||||
return groups;
|
return groups;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
handlePublish(e) {
|
||||||
|
e.preventDefault();
|
||||||
|
this.props.campaignActions.publishCampaign(
|
||||||
|
this.props.publishApi,
|
||||||
|
this.props.campaignId
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
CampaignListContainer.propTypes = {
|
||||||
|
publishApi: React.PropTypes.func.isRequired,
|
||||||
|
};
|
||||||
|
|
||||||
function mapStateToProps(state, ownProps) {
|
function mapStateToProps(state, ownProps) {
|
||||||
// Find record specific to this item
|
// Find record specific to this item
|
||||||
let record = null;
|
let record = null;
|
||||||
@ -149,7 +229,8 @@ function mapStateToProps(state, ownProps) {
|
|||||||
|
|
||||||
function mapDispatchToProps(dispatch) {
|
function mapDispatchToProps(dispatch) {
|
||||||
return {
|
return {
|
||||||
actions: bindActionCreators(actions, dispatch),
|
recordActions: bindActionCreators(recordActions, dispatch),
|
||||||
|
campaignActions: bindActionCreators(campaignActions, dispatch),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,3 +1,6 @@
|
|||||||
export default {
|
export default {
|
||||||
SET_CAMPAIGN_ACTIVE_CHANGESET: 'SET_CAMPAIGN_ACTIVE_CHANGESET',
|
SET_CAMPAIGN_ACTIVE_CHANGESET: 'SET_CAMPAIGN_ACTIVE_CHANGESET',
|
||||||
|
PUBLISH_CAMPAIGN_REQUEST: 'PUBLISH_CAMPAIGN_REQUEST',
|
||||||
|
PUBLISH_CAMPAIGN_SUCCESS: 'PUBLISH_CAMPAIGN_SUCCESS',
|
||||||
|
PUBLISH_CAMPAIGN_FAILURE: 'PUBLISH_CAMPAIGN_FAILURE',
|
||||||
};
|
};
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import ACTION_TYPES from './action-types';
|
import ACTION_TYPES from './action-types';
|
||||||
|
import RECORD_ACTION_TYPES from 'state/records/action-types';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Show specified campaign set
|
* Show specified campaign set
|
||||||
@ -13,3 +14,36 @@ export function showCampaignView(campaignId, view) {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Publish a campaign and all its items
|
||||||
|
*
|
||||||
|
* @param {Function} publishApi See silverstripe-backend.js
|
||||||
|
* @param {number} campaignId
|
||||||
|
* @return {Object}
|
||||||
|
*/
|
||||||
|
export function publishCampaign(publishApi, campaignId) {
|
||||||
|
return (dispatch) => {
|
||||||
|
dispatch({
|
||||||
|
type: ACTION_TYPES.PUBLISH_CAMPAIGN_REQUEST,
|
||||||
|
payload: { campaignId },
|
||||||
|
});
|
||||||
|
|
||||||
|
publishApi({ id: campaignId })
|
||||||
|
.then((data) => {
|
||||||
|
dispatch({
|
||||||
|
type: ACTION_TYPES.PUBLISH_CAMPAIGN_SUCCESS,
|
||||||
|
payload: { campaignId },
|
||||||
|
});
|
||||||
|
dispatch({
|
||||||
|
type: RECORD_ACTION_TYPES.FETCH_RECORD_SUCCESS,
|
||||||
|
payload: { recordType: 'ChangeSet', data },
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
dispatch({
|
||||||
|
type: ACTION_TYPES.PUBLISH_CAMPAIGN_FAILURE,
|
||||||
|
payload: { error },
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
}
|
||||||
|
@ -27,16 +27,19 @@ function populate(str, params) {
|
|||||||
export function fetchRecords(recordType, method, url) {
|
export function fetchRecords(recordType, method, url) {
|
||||||
const payload = { recordType };
|
const payload = { recordType };
|
||||||
const headers = { Accept: 'text/json' };
|
const headers = { Accept: 'text/json' };
|
||||||
|
const methodToLowerCase = method.toLowerCase();
|
||||||
|
|
||||||
return (dispatch) => {
|
return (dispatch) => {
|
||||||
dispatch({
|
dispatch({
|
||||||
type: ACTION_TYPES.FETCH_RECORDS_REQUEST,
|
type: ACTION_TYPES.FETCH_RECORDS_REQUEST,
|
||||||
payload,
|
payload,
|
||||||
});
|
});
|
||||||
const args = method.toLowerCase() === 'get'
|
|
||||||
|
const args = methodToLowerCase === 'get'
|
||||||
? [populate(url, payload), headers]
|
? [populate(url, payload), headers]
|
||||||
: [populate(url, payload), {}, headers];
|
: [populate(url, payload), {}, headers];
|
||||||
|
|
||||||
return backend[method.toLowerCase()](...args)
|
return backend[methodToLowerCase](...args)
|
||||||
.then(response => response.json())
|
.then(response => response.json())
|
||||||
.then(json => {
|
.then(json => {
|
||||||
dispatch({
|
dispatch({
|
||||||
@ -64,16 +67,19 @@ export function fetchRecords(recordType, method, url) {
|
|||||||
export function fetchRecord(recordType, method, url) {
|
export function fetchRecord(recordType, method, url) {
|
||||||
const payload = { recordType };
|
const payload = { recordType };
|
||||||
const headers = { Accept: 'text/json' };
|
const headers = { Accept: 'text/json' };
|
||||||
|
const methodToLowerCase = method.toLowerCase();
|
||||||
|
|
||||||
return (dispatch) => {
|
return (dispatch) => {
|
||||||
dispatch({
|
dispatch({
|
||||||
type: ACTION_TYPES.FETCH_RECORD_REQUEST,
|
type: ACTION_TYPES.FETCH_RECORD_REQUEST,
|
||||||
payload,
|
payload,
|
||||||
});
|
});
|
||||||
const args = method.toLowerCase() === 'get'
|
|
||||||
|
const args = methodToLowerCase === 'get'
|
||||||
? [populate(url, payload), headers]
|
? [populate(url, payload), headers]
|
||||||
: [populate(url, payload), {}, headers];
|
: [populate(url, payload), {}, headers];
|
||||||
|
|
||||||
return backend[method.toLowerCase()](...args)
|
return backend[methodToLowerCase](...args)
|
||||||
.then(response => response.json())
|
.then(response => response.json())
|
||||||
.then(json => {
|
.then(json => {
|
||||||
dispatch({
|
dispatch({
|
||||||
|
9
admin/javascript/src/styles/_layout.scss
Normal file
9
admin/javascript/src/styles/_layout.scss
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
.cms-south-actions {
|
||||||
|
height: 53px;
|
||||||
|
position: absolute;
|
||||||
|
bottom: 0;
|
||||||
|
width: 100%;
|
||||||
|
border-top: 1px solid #ddd;
|
||||||
|
background-color: #f6f7f8;
|
||||||
|
padding: 8px 15px;
|
||||||
|
}
|
@ -1,13 +1,13 @@
|
|||||||
/** -----------------------------
|
// Components
|
||||||
* Sections
|
|
||||||
* ------------------------------ */
|
|
||||||
@import "../sections/campaign-admin/styles";
|
|
||||||
|
|
||||||
/** -----------------------------
|
|
||||||
* components
|
|
||||||
* ------------------------------ */
|
|
||||||
@import "../components/grid-field/styles";
|
@import "../components/grid-field/styles";
|
||||||
@import "../components/north-header/styles";
|
@import "../components/north-header/styles";
|
||||||
@import "../components/north-header-breadcrumbs/styles";
|
@import "../components/north-header-breadcrumbs/styles";
|
||||||
@import "../components/form-action/styles";
|
@import "../components/form-action/styles";
|
||||||
@import "../components/hidden-field/styles";
|
@import "../components/hidden-field/styles";
|
||||||
|
@import "../components/label/styles";
|
||||||
|
|
||||||
|
// Structural
|
||||||
|
@import "layout";
|
||||||
|
|
||||||
|
// Sections
|
||||||
|
@import "../sections/campaign-admin/styles";
|
||||||
|
@ -32,11 +32,15 @@ describe('SilverStripeBackend', () => {
|
|||||||
|
|
||||||
it('should send a GET request to an endpoint', () => {
|
it('should send a GET request to an endpoint', () => {
|
||||||
backend.get('http://example.com');
|
backend.get('http://example.com');
|
||||||
expect(backend.fetch).toBeCalled();
|
|
||||||
expect(backend.fetch.mock.calls[0][0]).toEqual('http://example.com');
|
expect(backend.fetch).toBeCalledWith(
|
||||||
expect(backend.fetch.mock.calls[0][1]).toEqual(jasmine.objectContaining({
|
'http://example.com',
|
||||||
method: 'get'
|
{
|
||||||
}));
|
method: 'get',
|
||||||
|
credentials: 'same-origin',
|
||||||
|
headers: {},
|
||||||
|
}
|
||||||
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -51,12 +55,17 @@ describe('SilverStripeBackend', () => {
|
|||||||
|
|
||||||
backend.post('http://example.com', postData);
|
backend.post('http://example.com', postData);
|
||||||
|
|
||||||
expect(backend.fetch).toBeCalled();
|
expect(backend.fetch).toBeCalledWith(
|
||||||
expect(backend.fetch.mock.calls[0][0]).toEqual('http://example.com');
|
'http://example.com',
|
||||||
expect(backend.fetch.mock.calls[0][1]).toEqual(jasmine.objectContaining({
|
{
|
||||||
method: 'post',
|
method: 'post',
|
||||||
body: postData
|
credentials: 'same-origin',
|
||||||
}));
|
headers: {
|
||||||
|
'Content-Type': 'application/x-www-form-urlencoded',
|
||||||
|
},
|
||||||
|
body: postData,
|
||||||
|
}
|
||||||
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -71,12 +80,15 @@ describe('SilverStripeBackend', () => {
|
|||||||
|
|
||||||
backend.put('http://example.com', putData);
|
backend.put('http://example.com', putData);
|
||||||
|
|
||||||
expect(backend.fetch).toBeCalled();
|
expect(backend.fetch).toBeCalledWith(
|
||||||
expect(backend.fetch.mock.calls[0][0]).toEqual('http://example.com');
|
'http://example.com',
|
||||||
expect(backend.fetch.mock.calls[0][1]).toEqual(jasmine.objectContaining({
|
{
|
||||||
method: 'put',
|
method: 'put',
|
||||||
body: putData
|
credentials: 'same-origin',
|
||||||
}));
|
headers: {},
|
||||||
|
body: putData,
|
||||||
|
}
|
||||||
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -91,12 +103,15 @@ describe('SilverStripeBackend', () => {
|
|||||||
|
|
||||||
backend.delete('http://example.com', deleteData);
|
backend.delete('http://example.com', deleteData);
|
||||||
|
|
||||||
expect(backend.fetch).toBeCalled();
|
expect(backend.fetch).toBeCalledWith(
|
||||||
expect(backend.fetch.mock.calls[0][0]).toEqual('http://example.com');
|
'http://example.com',
|
||||||
expect(backend.fetch.mock.calls[0][1]).toEqual(jasmine.objectContaining({
|
{
|
||||||
method: 'delete',
|
method: 'delete',
|
||||||
body: deleteData
|
credentials: 'same-origin',
|
||||||
}));
|
headers: {},
|
||||||
|
body: deleteData,
|
||||||
|
}
|
||||||
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -134,7 +149,7 @@ describe('SilverStripeBackend', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
pit('should pass a JSON payload', () => {
|
it('should pass a JSON payload', () => {
|
||||||
const mock = getBackendMock({
|
const mock = getBackendMock({
|
||||||
text: () => Promise.resolve('{"status":"ok","message":"happy"}'),
|
text: () => Promise.resolve('{"status":"ok","message":"happy"}'),
|
||||||
headers: new Headers({
|
headers: new Headers({
|
||||||
|
@ -459,13 +459,12 @@ gulp.task('css', ['compile:css'], () => {
|
|||||||
* Watches for changes if --development flag is given
|
* Watches for changes if --development flag is given
|
||||||
*/
|
*/
|
||||||
gulp.task('compile:css', () => {
|
gulp.task('compile:css', () => {
|
||||||
const outputStyle = isDev ? 'expanded' : 'compressed';
|
|
||||||
const tasks = rootCompileFolders.map((folder) => { // eslint-disable-line
|
const tasks = rootCompileFolders.map((folder) => { // eslint-disable-line
|
||||||
return gulp.src(`${folder}/scss/**/*.scss`)
|
return gulp.src(`${folder}/scss/**/*.scss`)
|
||||||
.pipe(sourcemaps.init())
|
.pipe(sourcemaps.init())
|
||||||
.pipe(
|
.pipe(
|
||||||
sass({
|
sass({
|
||||||
outputStyle,
|
outputStyle: 'compressed',
|
||||||
importer: (url, prev, done) => {
|
importer: (url, prev, done) => {
|
||||||
if (url.match(/^compass\//)) {
|
if (url.match(/^compass\//)) {
|
||||||
done({ file: 'scss/_compasscompat.scss' });
|
done({ file: 'scss/_compasscompat.scss' });
|
||||||
|
@ -90,7 +90,7 @@ class ChangeSet extends DataObject {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
if(!$this->canPublish()) {
|
if(!$this->canPublish()) {
|
||||||
throw new Exception("The current member does not have permission to publish this ChangeSet.");
|
throw new LogicException("The current member does not have permission to publish this ChangeSet.");
|
||||||
}
|
}
|
||||||
|
|
||||||
DB::get_conn()->withTransaction(function(){
|
DB::get_conn()->withTransaction(function(){
|
||||||
|
Loading…
Reference in New Issue
Block a user