work in progress on campaign edit
This commit is contained in:
parent
0e0fb944e3
commit
ade0fc87f2
10 changed files with 231 additions and 81 deletions
|
@ -2,10 +2,7 @@
|
||||||
|
|
||||||
import React, {Component} from 'react';
|
import React, {Component} from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import {
|
import {translate} from 'react-i18next';
|
||||||
Trans,
|
|
||||||
translate
|
|
||||||
} from 'react-i18next';
|
|
||||||
import {
|
import {
|
||||||
NavButton,
|
NavButton,
|
||||||
requiresAuthenticatedUser,
|
requiresAuthenticatedUser,
|
||||||
|
@ -13,7 +10,6 @@ import {
|
||||||
withPageHelpers
|
withPageHelpers
|
||||||
} from '../lib/page'
|
} from '../lib/page'
|
||||||
import {
|
import {
|
||||||
ACEEditor,
|
|
||||||
AlignedRow,
|
AlignedRow,
|
||||||
Button,
|
Button,
|
||||||
ButtonRow,
|
ButtonRow,
|
||||||
|
@ -42,14 +38,18 @@ import {
|
||||||
getTemplateTypes,
|
getTemplateTypes,
|
||||||
getTypeForm
|
getTypeForm
|
||||||
} from '../templates/helpers';
|
} from '../templates/helpers';
|
||||||
import {ActionLink} from "../lib/bootstrap-components";
|
|
||||||
import axios from '../lib/axios';
|
import axios from '../lib/axios';
|
||||||
import styles from "../lib/styles.scss";
|
import styles from "../lib/styles.scss";
|
||||||
import {getUrl} from "../lib/urls";
|
import {getUrl} from "../lib/urls";
|
||||||
import {CampaignType, CampaignSource} from "../../../shared/campaigns";
|
import {
|
||||||
|
CampaignSource,
|
||||||
|
CampaignType
|
||||||
|
} from "../../../shared/campaigns";
|
||||||
import moment from 'moment';
|
import moment from 'moment';
|
||||||
import {getMailerTypes} from "../send-configurations/helpers";
|
import {getMailerTypes} from "../send-configurations/helpers";
|
||||||
|
import {ResourceType} from "../lib/mosaico";
|
||||||
|
|
||||||
|
const overridables = ['from_name', 'from_email', 'reply_to', 'subject'];
|
||||||
|
|
||||||
@translate()
|
@translate()
|
||||||
@withForm
|
@withForm
|
||||||
|
@ -60,12 +60,44 @@ export default class CUD extends Component {
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
|
|
||||||
this.templateTypes = getTemplateTypes(props.t, 'data_sourceCustom_');
|
const t = props.t;
|
||||||
|
|
||||||
|
this.templateTypes = getTemplateTypes(props.t, 'data_sourceCustom_', ResourceType.CAMPAIGN);
|
||||||
this.mailerTypes = getMailerTypes(props.t);
|
this.mailerTypes = getMailerTypes(props.t);
|
||||||
|
|
||||||
|
this.createTitles = {
|
||||||
|
[CampaignType.REGULAR]: t('Create Regular Campaign'),
|
||||||
|
[CampaignType.RSS]: t('Create RSS Campaign'),
|
||||||
|
[CampaignType.TRIGGERED]: t('Create Triggered Campaign'),
|
||||||
|
};
|
||||||
|
|
||||||
|
this.editTitles = {
|
||||||
|
[CampaignType.REGULAR]: t('Edit Regular Campaign'),
|
||||||
|
[CampaignType.RSS]: t('Edit RSS Campaign'),
|
||||||
|
[CampaignType.TRIGGERED]: t('Edit Triggered Campaign'),
|
||||||
|
};
|
||||||
|
|
||||||
|
this.sourceLabels = {
|
||||||
|
[CampaignSource.TEMPLATE]: t('Template'),
|
||||||
|
[CampaignSource.CUSTOM_FROM_TEMPLATE]: t('Custom content'),
|
||||||
|
[CampaignSource.CUSTOM]: t('Custom content'),
|
||||||
|
[CampaignSource.URL]: t('URL')
|
||||||
|
};
|
||||||
|
|
||||||
|
this.sourceOptions = [];
|
||||||
|
for (const key in this.sourceLabels) {
|
||||||
|
this.sourceOptions.push({key, label: this.sourceLabels[key]});
|
||||||
|
}
|
||||||
|
|
||||||
|
this.customTemplateTypeOptions = [];
|
||||||
|
for (const key of mailtrainConfig.editors) {
|
||||||
|
this.customTemplateTypeOptions.push({key, label: this.templateTypes[key].typeName});
|
||||||
|
}
|
||||||
|
|
||||||
this.state = {
|
this.state = {
|
||||||
showMergeTagReference: false,
|
showMergeTagReference: false,
|
||||||
elementInFullscreen: false,
|
elementInFullscreen: false,
|
||||||
|
sendConfiguration: null
|
||||||
};
|
};
|
||||||
|
|
||||||
this.initForm({
|
this.initForm({
|
||||||
|
@ -82,7 +114,7 @@ export default class CUD extends Component {
|
||||||
action: PropTypes.string.isRequired,
|
action: PropTypes.string.isRequired,
|
||||||
wizard: PropTypes.string,
|
wizard: PropTypes.string,
|
||||||
entity: PropTypes.object,
|
entity: PropTypes.object,
|
||||||
type: PropTypes.number.isRequired
|
type: PropTypes.number
|
||||||
}
|
}
|
||||||
|
|
||||||
onCustomTemplateTypeChanged(mutState, key, oldType, type) {
|
onCustomTemplateTypeChanged(mutState, key, oldType, type) {
|
||||||
|
@ -91,6 +123,24 @@ export default class CUD extends Component {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
onSendConfigurationChanged(newState, key, oldValue, sendConfigurationId) {
|
||||||
|
newState.sendConfiguration = null;
|
||||||
|
this.fetchSendConfiguration(sendConfigurationId);
|
||||||
|
}
|
||||||
|
|
||||||
|
@withAsyncErrorHandler
|
||||||
|
async fetchSendConfiguration(sendConfigurationId) {
|
||||||
|
this.fetchSendConfigurationId = sendConfigurationId;
|
||||||
|
|
||||||
|
const result = await axios.get(getUrl(`rest/send-configurations-public/${sendConfigurationId}`));
|
||||||
|
|
||||||
|
if (sendConfigurationId === this.fetchSendConfigurationId) {
|
||||||
|
this.setState({
|
||||||
|
sendConfiguration: result.data
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
if (this.props.entity) {
|
if (this.props.entity) {
|
||||||
this.getFormValuesFromEntity(this.props.entity, data => {
|
this.getFormValuesFromEntity(this.props.entity, data => {
|
||||||
|
@ -101,12 +151,12 @@ export default class CUD extends Component {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (data.source === CampaignSource.CUSTOM || data.source === CampaignSource.CUSTOM_FROM_TEMPLATE) {
|
if (data.source === CampaignSource.CUSTOM || data.source === CampaignSource.CUSTOM_FROM_TEMPLATE) {
|
||||||
data.data_sourceCustom_type = data.data.source.type;
|
data.data_sourceCustom_type = data.data.sourceCustom.type;
|
||||||
data.data_sourceCustom_data = data.data.source.data;
|
data.data_sourceCustom_data = data.data.sourceCustom.data;
|
||||||
data.data_sourceCustom_html = data.data.source.html;
|
data.data_sourceCustom_html = data.data.sourceCustom.html;
|
||||||
data.data_sourceCustom_text = data.data.source.text;
|
data.data_sourceCustom_text = data.data.sourceCustom.text;
|
||||||
|
|
||||||
this.templateTypes[data.type].afterLoad(data);
|
this.templateTypes[data.data.sourceCustom.type].afterLoad(data);
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
data.data_sourceCustom_type = null;
|
data.data_sourceCustom_type = null;
|
||||||
|
@ -128,12 +178,24 @@ export default class CUD extends Component {
|
||||||
}
|
}
|
||||||
|
|
||||||
data.useSegmentation = !!data.segment;
|
data.useSegmentation = !!data.segment;
|
||||||
|
|
||||||
|
for (const overridable of overridables) {
|
||||||
|
data[overridable + '_overriden'] = !!data[overridable + '_override'];
|
||||||
|
}
|
||||||
|
|
||||||
this.fetchSendConfiguration(data.send_configuration);
|
this.fetchSendConfiguration(data.send_configuration);
|
||||||
});
|
});
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
|
const data = {};
|
||||||
|
for (const overridable of overridables) {
|
||||||
|
data[overridable + '_override'] = '';
|
||||||
|
data[overridable + '_overriden'] = false;
|
||||||
|
}
|
||||||
|
|
||||||
this.populateFormValues({
|
this.populateFormValues({
|
||||||
|
...data,
|
||||||
|
|
||||||
type: this.props.type,
|
type: this.props.type,
|
||||||
|
|
||||||
name: '',
|
name: '',
|
||||||
|
@ -143,14 +205,7 @@ export default class CUD extends Component {
|
||||||
useSegmentation: false,
|
useSegmentation: false,
|
||||||
send_configuration: null,
|
send_configuration: null,
|
||||||
namespace: mailtrainConfig.user.namespace,
|
namespace: mailtrainConfig.user.namespace,
|
||||||
from_name_override: '',
|
|
||||||
from_name_overriden: false,
|
|
||||||
from_email_override: '',
|
|
||||||
from_email_overriden: false,
|
|
||||||
reply_to_override: '',
|
|
||||||
reply_to_overriden: false,
|
|
||||||
subject_override: '',
|
|
||||||
subject_overriden: false,
|
|
||||||
click_tracking_disabled: false,
|
click_tracking_disabled: false,
|
||||||
open_trackings_disabled: false,
|
open_trackings_disabled: false,
|
||||||
|
|
||||||
|
@ -285,7 +340,7 @@ export default class CUD extends Component {
|
||||||
if (data.source === CampaignSource.CUSTOM || data.source === CampaignSource.CUSTOM_FROM_TEMPLATE) {
|
if (data.source === CampaignSource.CUSTOM || data.source === CampaignSource.CUSTOM_FROM_TEMPLATE) {
|
||||||
this.templateTypes[data.data_sourceCustom_type].beforeSave(data);
|
this.templateTypes[data.data_sourceCustom_type].beforeSave(data);
|
||||||
|
|
||||||
data.data.source = {
|
data.data.sourceCustom = {
|
||||||
type: data.data_sourceCustom_type,
|
type: data.data_sourceCustom_type,
|
||||||
data: data.data_sourceCustom_data,
|
data: data.data_sourceCustom_data,
|
||||||
html: data.data_sourceCustom_html,
|
html: data.data_sourceCustom_html,
|
||||||
|
@ -301,6 +356,13 @@ export default class CUD extends Component {
|
||||||
data.data.feedUrl = data.data_feedUrl;
|
data.data.feedUrl = data.data_feedUrl;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for (const overridable of overridables) {
|
||||||
|
if (!data[overridable + '_overriden']) {
|
||||||
|
data[overridable + '_override'] = null;
|
||||||
|
}
|
||||||
|
delete data[overridable + '_overriden'];
|
||||||
|
}
|
||||||
|
|
||||||
for (const key in data) {
|
for (const key in data) {
|
||||||
if (key.startsWith('data_')) {
|
if (key.startsWith('data_')) {
|
||||||
delete data[key];
|
delete data[key];
|
||||||
|
@ -335,6 +397,8 @@ export default class CUD extends Component {
|
||||||
|
|
||||||
this.disableForm();
|
this.disableForm();
|
||||||
|
|
||||||
|
console.log(html);
|
||||||
|
|
||||||
const response = await axios.post(getUrl('rest/html-to-text', { html }));
|
const response = await axios.post(getUrl('rest/html-to-text', { html }));
|
||||||
|
|
||||||
this.updateFormValue('data_sourceCustom_text', response.data.text);
|
this.updateFormValue('data_sourceCustom_text', response.data.text);
|
||||||
|
@ -358,7 +422,6 @@ export default class CUD extends Component {
|
||||||
const t = this.props.t;
|
const t = this.props.t;
|
||||||
const isEdit = !!this.props.entity;
|
const isEdit = !!this.props.entity;
|
||||||
const canDelete = isEdit && this.props.entity.permissions.includes('delete');
|
const canDelete = isEdit && this.props.entity.permissions.includes('delete');
|
||||||
const useSaveAndEditLabel = !isEdit;
|
|
||||||
|
|
||||||
let templateEdit = null;
|
let templateEdit = null;
|
||||||
let extraSettings = null;
|
let extraSettings = null;
|
||||||
|
@ -366,6 +429,15 @@ export default class CUD extends Component {
|
||||||
const sourceTypeKey = this.getFormValue('source');
|
const sourceTypeKey = this.getFormValue('source');
|
||||||
const campaignTypeKey = this.getFormValue('type');
|
const campaignTypeKey = this.getFormValue('type');
|
||||||
|
|
||||||
|
|
||||||
|
let sourceEdit;
|
||||||
|
if (isEdit) {
|
||||||
|
sourceEdit = <StaticField id="source" className={styles.formDisabled} label={t('Content source')}>{this.sourceLabels[sourceTypeKey]}</StaticField>;
|
||||||
|
} else {
|
||||||
|
sourceEdit = <Dropdown id="source" label={t('Content source')} options={this.sourceOptions}/>
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
if (sourceTypeKey === CampaignSource.TEMPLATE || (!isEdit && sourceTypeKey === CampaignSource.CUSTOM_FROM_TEMPLATE)) {
|
if (sourceTypeKey === CampaignSource.TEMPLATE || (!isEdit && sourceTypeKey === CampaignSource.CUSTOM_FROM_TEMPLATE)) {
|
||||||
const templatesColumns = [
|
const templatesColumns = [
|
||||||
{ data: 1, title: t('Name') },
|
{ data: 1, title: t('Name') },
|
||||||
|
@ -375,14 +447,14 @@ export default class CUD extends Component {
|
||||||
{ data: 5, title: t('Namespace') },
|
{ data: 5, title: t('Namespace') },
|
||||||
];
|
];
|
||||||
|
|
||||||
templateEdit = <TableSelect id="data_sourceTemplate" label={t('Template')} withHeader dropdown dataUrl='rest/templates-table' columns={templatesColumns} selectionLabelIndex={1} />;
|
let help = null;
|
||||||
|
if (sourceTypeKey === CampaignSource.CUSTOM_FROM_TEMPLATE) {
|
||||||
} else if (sourceTypeKey === CampaignSource.CUSTOM || (isEdit && sourceTypeKey === CampaignSource.CUSTOM_FROM_TEMPLATE)) {
|
help = t('Selecting a template creates a campaign specific copy from it.');
|
||||||
const customTemplateTypeOptions = [];
|
|
||||||
for (const key of mailtrainConfig.editors) {
|
|
||||||
customTemplateTypeOptions.push({key, label: this.templateTypes[key].typeName});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
templateEdit = <TableSelect id="data_sourceTemplate" label={t('Template')} withHeader dropdown dataUrl='rest/templates-table' columns={templatesColumns} selectionLabelIndex={1} help={help}/>;
|
||||||
|
|
||||||
|
} else if (sourceTypeKey === CampaignSource.CUSTOM || (isEdit && sourceTypeKey === CampaignSource.CUSTOM_FROM_TEMPLATE)) {
|
||||||
// TODO: Toggle HTML preview
|
// TODO: Toggle HTML preview
|
||||||
|
|
||||||
const customTemplateTypeKey = this.getFormValue('data_sourceCustom_type');
|
const customTemplateTypeKey = this.getFormValue('data_sourceCustom_type');
|
||||||
|
@ -401,11 +473,11 @@ export default class CUD extends Component {
|
||||||
templateEdit = <div>
|
templateEdit = <div>
|
||||||
{isEdit
|
{isEdit
|
||||||
?
|
?
|
||||||
<StaticField id="data_sourceCustom_type" className={styles.formDisabled} label={t('Type')}>
|
<StaticField id="data_sourceCustom_type" className={styles.formDisabled} label={t('Custom template editor')}>
|
||||||
{customTemplateTypeKey && this.templateTypes[customTemplateTypeKey].typeName}
|
{customTemplateTypeKey && this.templateTypes[customTemplateTypeKey].typeName}
|
||||||
</StaticField>
|
</StaticField>
|
||||||
:
|
:
|
||||||
<Dropdown id="data_sourceCustom_type" label={t('Type')} options={customTemplateTypeOptions}/>
|
<Dropdown id="data_sourceCustom_type" label={t('Type')} options={this.customTemplateTypeOptions}/>
|
||||||
}
|
}
|
||||||
|
|
||||||
{customTemplateTypeForm}
|
{customTemplateTypeForm}
|
||||||
|
@ -414,7 +486,7 @@ export default class CUD extends Component {
|
||||||
</div>;
|
</div>;
|
||||||
|
|
||||||
} else if (sourceTypeKey === CampaignSource.URL) {
|
} else if (sourceTypeKey === CampaignSource.URL) {
|
||||||
templateEdit = <InputField id="data_sourceUrl" label={t('Render URL')}/>
|
templateEdit = <InputField id="data_sourceUrl" label={t('Render URL')} help={t('If a message is sent then this URL will be POSTed to using Merge Tags as POST body. Use this if you want to generate the HTML message yourself.')}/>
|
||||||
}
|
}
|
||||||
|
|
||||||
if (campaignTypeKey === CampaignType.RSS) {
|
if (campaignTypeKey === CampaignType.RSS) {
|
||||||
|
@ -442,6 +514,37 @@ export default class CUD extends Component {
|
||||||
{ data: 5, title: t('Namespace') }
|
{ data: 5, title: t('Namespace') }
|
||||||
];
|
];
|
||||||
|
|
||||||
|
let sendSettings;
|
||||||
|
if (this.getFormValue('send_configuration')) {
|
||||||
|
if (this.state.sendConfiguration) {
|
||||||
|
sendSettings = [];
|
||||||
|
|
||||||
|
const addOverridable = (id, label) => {
|
||||||
|
sendSettings.push(<CheckBox key={id + '_overriden'} id={id + '_overriden'} label={label} text={t('Override')}/>);
|
||||||
|
|
||||||
|
if (this.getFormValue(id + '_overriden')) {
|
||||||
|
sendSettings.push(<InputField key={id + '_override'} id={id + '_override'}/>);
|
||||||
|
} else {
|
||||||
|
sendSettings.push(
|
||||||
|
<StaticField key={id + '_original'} id={id + '_original'} className={styles.formDisabled}>
|
||||||
|
{this.state.sendConfiguration[id]}
|
||||||
|
</StaticField>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
addOverridable('from_name', t('"From" name'));
|
||||||
|
addOverridable('from_email', t('"From" email address'));
|
||||||
|
addOverridable('reply_to', t('"Reply-to" email address'));
|
||||||
|
addOverridable('subject', t('"Subject" line'));
|
||||||
|
} else {
|
||||||
|
sendSettings = <AlignedRow>{t('Loading send configuration ...')}</AlignedRow>
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
sendSettings = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={this.state.elementInFullscreen ? styles.withElementInFullscreen : ''}>
|
<div className={this.state.elementInFullscreen ? styles.withElementInFullscreen : ''}>
|
||||||
{canDelete &&
|
{canDelete &&
|
||||||
|
@ -455,41 +558,40 @@ export default class CUD extends Component {
|
||||||
deletedMsg={t('Campaign deleted')}/>
|
deletedMsg={t('Campaign deleted')}/>
|
||||||
}
|
}
|
||||||
|
|
||||||
<Title>{isEdit ? t('Edit Campaign') : t('Create Campaign')}</Title>
|
<Title>{isEdit ? this.editTitles[this.getFormValue('type')] : this.createTitles[this.getFormValue('type')]}</Title>
|
||||||
|
|
||||||
<Form stateOwner={this} onSubmitAsync={::this.submitHandler}>
|
<Form stateOwner={this} onSubmitAsync={::this.submitHandler}>
|
||||||
<InputField id="name" label={t('Name')}/>
|
<InputField id="name" label={t('Name')}/>
|
||||||
<TextArea id="description" label={t('Description')}/>
|
<TextArea id="description" label={t('Description')}/>
|
||||||
|
|
||||||
<TableSelect id="list" label={t('List')} withHeader dropdown dataUrl='rest/lists-table' columns={listsColumns} selectionLabelIndex={1} />
|
|
||||||
|
|
||||||
<CheckBox id="useSegmentation" text={t('Use segmentation')}/>
|
|
||||||
{this.getFormValue('useSegmentation') &&
|
|
||||||
<TableSelect id="segment" label={t('Segment')} withHeader dropdown dataUrl='rest/segments-table' columns={segmentsColumns} selectionLabelIndex={1} />
|
|
||||||
}
|
|
||||||
|
|
||||||
{extraSettings}
|
{extraSettings}
|
||||||
|
|
||||||
<NamespaceSelect/>
|
<NamespaceSelect/>
|
||||||
|
|
||||||
|
<hr/>
|
||||||
|
|
||||||
|
<TableSelect id="list" label={t('List')} withHeader dropdown dataUrl='rest/lists-table' columns={listsColumns} selectionLabelIndex={1} />
|
||||||
|
|
||||||
|
<CheckBox id="useSegmentation" label={t('Segment')} text={t('Use a particular segment')}/>
|
||||||
|
{this.getFormValue('useSegmentation') &&
|
||||||
|
<TableSelect id="segment" withHeader dropdown dataUrl={`rest/segments-table/${this.getFormValue('list')}`} columns={segmentsColumns} selectionLabelIndex={1} />
|
||||||
|
}
|
||||||
|
|
||||||
|
<hr/>
|
||||||
|
|
||||||
<TableSelect id="send_configuration" label={t('Send configuration')} withHeader dropdown dataUrl='rest/send-configurations-table' columns={sendConfigurationsColumns} selectionLabelIndex={1} />
|
<TableSelect id="send_configuration" label={t('Send configuration')} withHeader dropdown dataUrl='rest/send-configurations-table' columns={sendConfigurationsColumns} selectionLabelIndex={1} />
|
||||||
|
|
||||||
<CheckBox id="from_name_overriden" text={t('Override email "From" name')}/>
|
{sendSettings}
|
||||||
{ this.getFormValue('from_name_overriden') && <InputField id="from_name_override" label={t('Email "From" name')}/> }
|
|
||||||
|
|
||||||
<CheckBox id="from_email_overriden" text={t('Override email "From" address')}/>
|
|
||||||
{ this.getFormValue('from_email_overriden') && <InputField id="from_email_override" label={t('Email "From" address')}/> }
|
|
||||||
|
|
||||||
<CheckBox id="reply_to_overriden" text={t('Override email "Reply-to" address')}/>
|
|
||||||
{ this.getFormValue('reply_to_overriden') && <InputField id="reply_to_override" label={t('Email "Reply-to" address')}/> }
|
|
||||||
|
|
||||||
<CheckBox id="subject_overriden" text={t('Override email "Subject" line')}/>
|
|
||||||
{ this.getFormValue('subject_overriden') && <InputField id="subject_override" label={t('Email "Subject" line')}/> }
|
|
||||||
|
|
||||||
|
<hr/>
|
||||||
|
|
||||||
<CheckBox id="open_trackings_disabled" text={t('Disable opened tracking')}/>
|
<CheckBox id="open_trackings_disabled" text={t('Disable opened tracking')}/>
|
||||||
<CheckBox id="click_tracking_disabled" text={t('Disable clicked tracking')}/>
|
<CheckBox id="click_tracking_disabled" text={t('Disable clicked tracking')}/>
|
||||||
|
|
||||||
|
<hr/>
|
||||||
|
|
||||||
|
{sourceEdit}
|
||||||
|
|
||||||
{templateEdit}
|
{templateEdit}
|
||||||
|
|
||||||
<ButtonRow>
|
<ButtonRow>
|
||||||
|
|
|
@ -2,9 +2,12 @@
|
||||||
|
|
||||||
import React, {Component} from 'react';
|
import React, {Component} from 'react';
|
||||||
import {translate} from 'react-i18next';
|
import {translate} from 'react-i18next';
|
||||||
import {Icon} from '../lib/bootstrap-components';
|
|
||||||
import {
|
import {
|
||||||
NavButton,
|
DropdownMenu,
|
||||||
|
Icon
|
||||||
|
} from '../lib/bootstrap-components';
|
||||||
|
import {
|
||||||
|
MenuLink,
|
||||||
requiresAuthenticatedUser,
|
requiresAuthenticatedUser,
|
||||||
Title,
|
Title,
|
||||||
Toolbar,
|
Toolbar,
|
||||||
|
@ -131,7 +134,11 @@ export default class List extends Component {
|
||||||
<div>
|
<div>
|
||||||
<Toolbar>
|
<Toolbar>
|
||||||
{this.state.createPermitted &&
|
{this.state.createPermitted &&
|
||||||
<NavButton linkTo="/campaigns/create" className="btn-primary" icon="plus" label={t('Create Campaign')}/>
|
<DropdownMenu className="btn-primary" label={t('Create Campaign')}>
|
||||||
|
<MenuLink to="/campaigns/create-regular">{t('Regular')}</MenuLink>
|
||||||
|
<MenuLink to="/campaigns/create-rss">{t('RSS')}</MenuLink>
|
||||||
|
<MenuLink to="/campaigns/create-triggered">{t('Triggered')}</MenuLink>
|
||||||
|
</DropdownMenu>
|
||||||
}
|
}
|
||||||
</Toolbar>
|
</Toolbar>
|
||||||
|
|
||||||
|
|
|
@ -221,7 +221,7 @@ function wrapInput(id, htmlId, owner, format, rightContainerClass, label, help,
|
||||||
class StaticField extends Component {
|
class StaticField extends Component {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
id: PropTypes.string.isRequired,
|
id: PropTypes.string.isRequired,
|
||||||
label: PropTypes.string.isRequired,
|
label: PropTypes.string,
|
||||||
help: PropTypes.oneOfType([PropTypes.string, PropTypes.object]),
|
help: PropTypes.oneOfType([PropTypes.string, PropTypes.object]),
|
||||||
className: PropTypes.string,
|
className: PropTypes.string,
|
||||||
format: PropTypes.string
|
format: PropTypes.string
|
||||||
|
@ -247,7 +247,7 @@ class StaticField extends Component {
|
||||||
class InputField extends Component {
|
class InputField extends Component {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
id: PropTypes.string.isRequired,
|
id: PropTypes.string.isRequired,
|
||||||
label: PropTypes.string.isRequired,
|
label: PropTypes.string,
|
||||||
placeholder: PropTypes.string,
|
placeholder: PropTypes.string,
|
||||||
type: PropTypes.string,
|
type: PropTypes.string,
|
||||||
help: PropTypes.oneOfType([PropTypes.string, PropTypes.object]),
|
help: PropTypes.oneOfType([PropTypes.string, PropTypes.object]),
|
||||||
|
@ -662,7 +662,7 @@ class ButtonRow extends Component {
|
||||||
class TreeTableSelect extends Component {
|
class TreeTableSelect extends Component {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
id: PropTypes.string.isRequired,
|
id: PropTypes.string.isRequired,
|
||||||
label: PropTypes.string.isRequired,
|
label: PropTypes.string,
|
||||||
dataUrl: PropTypes.string,
|
dataUrl: PropTypes.string,
|
||||||
data: PropTypes.array,
|
data: PropTypes.array,
|
||||||
help: PropTypes.oneOfType([PropTypes.string, PropTypes.object]),
|
help: PropTypes.oneOfType([PropTypes.string, PropTypes.object]),
|
||||||
|
@ -713,7 +713,7 @@ class TableSelect extends Component {
|
||||||
dropdown: PropTypes.bool,
|
dropdown: PropTypes.bool,
|
||||||
|
|
||||||
id: PropTypes.string.isRequired,
|
id: PropTypes.string.isRequired,
|
||||||
label: PropTypes.string.isRequired,
|
label: PropTypes.string,
|
||||||
help: PropTypes.oneOfType([PropTypes.string, PropTypes.object]),
|
help: PropTypes.oneOfType([PropTypes.string, PropTypes.object]),
|
||||||
format: PropTypes.string,
|
format: PropTypes.string,
|
||||||
disabled: PropTypes.bool
|
disabled: PropTypes.bool
|
||||||
|
|
|
@ -37,6 +37,7 @@ export class MosaicoEditor extends Component {
|
||||||
entity: PropTypes.object,
|
entity: PropTypes.object,
|
||||||
title: PropTypes.string,
|
title: PropTypes.string,
|
||||||
onFullscreenAsync: PropTypes.func,
|
onFullscreenAsync: PropTypes.func,
|
||||||
|
templateId: PropTypes.number,
|
||||||
templatePath: PropTypes.string,
|
templatePath: PropTypes.string,
|
||||||
initialModel: PropTypes.string,
|
initialModel: PropTypes.string,
|
||||||
initialMetadata: PropTypes.string
|
initialMetadata: PropTypes.string
|
||||||
|
@ -60,6 +61,7 @@ export class MosaicoEditor extends Component {
|
||||||
const mosaicoData = {
|
const mosaicoData = {
|
||||||
entityTypeId: this.props.entityTypeId,
|
entityTypeId: this.props.entityTypeId,
|
||||||
entityId: this.props.entity.id,
|
entityId: this.props.entity.id,
|
||||||
|
templateId: this.props.templateId,
|
||||||
templatePath: this.props.templatePath,
|
templatePath: this.props.templatePath,
|
||||||
initialModel: this.props.initialModel,
|
initialModel: this.props.initialModel,
|
||||||
initialMetadata: this.props.initialMetadata
|
initialMetadata: this.props.initialMetadata
|
||||||
|
@ -96,6 +98,7 @@ export class MosaicoSandbox extends Component {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
entityTypeId: PropTypes.string,
|
entityTypeId: PropTypes.string,
|
||||||
entityId: PropTypes.number,
|
entityId: PropTypes.number,
|
||||||
|
templateId: PropTypes.number,
|
||||||
templatePath: PropTypes.string,
|
templatePath: PropTypes.string,
|
||||||
initialModel: PropTypes.string,
|
initialModel: PropTypes.string,
|
||||||
initialMetadata: PropTypes.string
|
initialMetadata: PropTypes.string
|
||||||
|
@ -156,7 +159,7 @@ export class MosaicoSandbox extends Component {
|
||||||
const trustedUrlBase = getTrustedUrl();
|
const trustedUrlBase = getTrustedUrl();
|
||||||
const metadata = this.props.initialMetadata && JSON.parse(base(this.props.initialMetadata, trustedUrlBase, sandboxUrlBase));
|
const metadata = this.props.initialMetadata && JSON.parse(base(this.props.initialMetadata, trustedUrlBase, sandboxUrlBase));
|
||||||
const model = this.props.initialModel && JSON.parse(base(this.props.initialModel, trustedUrlBase, sandboxUrlBase));
|
const model = this.props.initialModel && JSON.parse(base(this.props.initialModel, trustedUrlBase, sandboxUrlBase));
|
||||||
const template = this.props.templatePath;
|
const template = this.props.templateId ? getSandboxUrl(`mosaico/templates/${this.props.templateId}/index.html`) : this.props.templatePath;
|
||||||
|
|
||||||
const allPlugins = plugins.concat(window.mosaicoPlugins);
|
const allPlugins = plugins.concat(window.mosaicoPlugins);
|
||||||
|
|
||||||
|
|
|
@ -59,6 +59,7 @@
|
||||||
.tableSelectTable.tableSelectTableHidden {
|
.tableSelectTable.tableSelectTableHidden {
|
||||||
visibility: hidden;
|
visibility: hidden;
|
||||||
height: 0px;
|
height: 0px;
|
||||||
|
margin-top: -15px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.tableSelectDropdown input[readonly] {
|
.tableSelectDropdown input[readonly] {
|
||||||
|
|
|
@ -6,6 +6,7 @@ import {
|
||||||
AlignedRow,
|
AlignedRow,
|
||||||
CKEditor,
|
CKEditor,
|
||||||
Dropdown,
|
Dropdown,
|
||||||
|
StaticField,
|
||||||
TableSelect
|
TableSelect
|
||||||
} from "../lib/form";
|
} from "../lib/form";
|
||||||
import 'brace/mode/text';
|
import 'brace/mode/text';
|
||||||
|
@ -19,9 +20,16 @@ import {
|
||||||
import {getTemplateTypes as getMosaicoTemplateTypes} from './mosaico/helpers';
|
import {getTemplateTypes as getMosaicoTemplateTypes} from './mosaico/helpers';
|
||||||
import {getSandboxUrl} from "../lib/urls";
|
import {getSandboxUrl} from "../lib/urls";
|
||||||
import mailtrainConfig from 'mailtrainConfig';
|
import mailtrainConfig from 'mailtrainConfig';
|
||||||
|
import {
|
||||||
|
ActionLink,
|
||||||
|
Button
|
||||||
|
} from "../lib/bootstrap-components";
|
||||||
|
import {Trans} from "react-i18next";
|
||||||
|
|
||||||
|
import styles from "../lib/styles.scss";
|
||||||
|
|
||||||
|
|
||||||
export function getTemplateTypes(t, prefix = '') {
|
export function getTemplateTypes(t, prefix = '', entityTypeId = ResourceType.TEMPLATE) {
|
||||||
// The prefix is used to to enable use within other forms (i.e. campaign form)
|
// The prefix is used to to enable use within other forms (i.e. campaign form)
|
||||||
const templateTypes = {};
|
const templateTypes = {};
|
||||||
|
|
||||||
|
@ -64,8 +72,8 @@ export function getTemplateTypes(t, prefix = '') {
|
||||||
entity={owner.props.entity}
|
entity={owner.props.entity}
|
||||||
initialModel={owner.getFormValue(prefix + 'mosaicoData').model}
|
initialModel={owner.getFormValue(prefix + 'mosaicoData').model}
|
||||||
initialMetadata={owner.getFormValue(prefix + 'mosaicoData').metadata}
|
initialMetadata={owner.getFormValue(prefix + 'mosaicoData').metadata}
|
||||||
templatePath={getSandboxUrl(`mosaico/templates/${owner.getFormValue(prefix + 'mosaicoTemplate')}/index.html`)}
|
templateId={owner.getFormValue(prefix + 'mosaicoTemplate')}
|
||||||
entityTypeId={ResourceType.TEMPLATE}
|
entityTypeId={entityTypeId}
|
||||||
title={t('Mosaico Template Designer')}
|
title={t('Mosaico Template Designer')}
|
||||||
onFullscreenAsync={::owner.setElementInFullscreen}/>
|
onFullscreenAsync={::owner.setElementInFullscreen}/>
|
||||||
</AlignedRow>,
|
</AlignedRow>,
|
||||||
|
@ -110,11 +118,17 @@ export function getTemplateTypes(t, prefix = '') {
|
||||||
};
|
};
|
||||||
|
|
||||||
const mosaicoFsTemplatesOptions = mailtrainConfig.mosaico.fsTemplates.map(([key, label]) => ({key, label}));
|
const mosaicoFsTemplatesOptions = mailtrainConfig.mosaico.fsTemplates.map(([key, label]) => ({key, label}));
|
||||||
|
const mosaicoFsTemplatesLabels = new Map(mailtrainConfig.mosaico.fsTemplates);
|
||||||
|
|
||||||
templateTypes.mosaicoWithFsTemplate = {
|
templateTypes.mosaicoWithFsTemplate = {
|
||||||
typeName: t('Mosaico with predefined templates'),
|
typeName: t('Mosaico with predefined templates'),
|
||||||
getTypeForm: (owner, isEdit) =>
|
getTypeForm: (owner, isEdit) => {
|
||||||
<Dropdown id={prefix + 'mosaicoFsTemplate'} label={t('Mosaico Template')} options={mosaicoFsTemplatesOptions}/>,
|
if (isEdit) {
|
||||||
|
return <StaticField id={prefix + 'mosaicoFsTemplate'} className={styles.formDisabled} label={t('Mosaico Template')}>{mosaicoFsTemplatesLabels.get(owner.getFormValue(prefix + 'mosaicoFsTemplate'))}</StaticField>;
|
||||||
|
} else {
|
||||||
|
return <Dropdown id={prefix + 'mosaicoFsTemplate'} label={t('Mosaico Template')} options={mosaicoFsTemplatesOptions}/>;
|
||||||
|
}
|
||||||
|
},
|
||||||
getHTMLEditor: owner =>
|
getHTMLEditor: owner =>
|
||||||
<AlignedRow label={t('Template content (HTML)')}>
|
<AlignedRow label={t('Template content (HTML)')}>
|
||||||
<MosaicoEditor
|
<MosaicoEditor
|
||||||
|
@ -123,7 +137,7 @@ export function getTemplateTypes(t, prefix = '') {
|
||||||
initialModel={owner.getFormValue(prefix + 'mosaicoData').model}
|
initialModel={owner.getFormValue(prefix + 'mosaicoData').model}
|
||||||
initialMetadata={owner.getFormValue(prefix + 'mosaicoData').metadata}
|
initialMetadata={owner.getFormValue(prefix + 'mosaicoData').metadata}
|
||||||
templatePath={getSandboxUrl(`public/mosaico/templates/${owner.getFormValue(prefix + 'mosaicoFsTemplate')}/index.html`)}
|
templatePath={getSandboxUrl(`public/mosaico/templates/${owner.getFormValue(prefix + 'mosaicoFsTemplate')}/index.html`)}
|
||||||
entityTypeId={ResourceType.TEMPLATE}
|
entityTypeId={entityTypeId}
|
||||||
title={t('Mosaico Template Designer')}
|
title={t('Mosaico Template Designer')}
|
||||||
onFullscreenAsync={::owner.setElementInFullscreen}/>
|
onFullscreenAsync={::owner.setElementInFullscreen}/>
|
||||||
</AlignedRow>,
|
</AlignedRow>,
|
||||||
|
@ -140,7 +154,7 @@ export function getTemplateTypes(t, prefix = '') {
|
||||||
mosaicoData: {}
|
mosaicoData: {}
|
||||||
}),
|
}),
|
||||||
afterLoad: data => {
|
afterLoad: data => {
|
||||||
data['mosaicoFsTemplate'] = data[prefix + 'data'].mosaicoFsTemplate;
|
data[prefix + 'mosaicoFsTemplate'] = data[prefix + 'data'].mosaicoFsTemplate;
|
||||||
data[prefix + 'mosaicoData'] = {
|
data[prefix + 'mosaicoData'] = {
|
||||||
metadata: data[prefix + 'data'].metadata,
|
metadata: data[prefix + 'data'].metadata,
|
||||||
model: data[prefix + 'data'].model
|
model: data[prefix + 'data'].model
|
||||||
|
@ -310,7 +324,7 @@ export function getEditForm(owner, typeKey, prefix = '') {
|
||||||
|
|
||||||
export function getTypeForm(owner, typeKey, isEdit) {
|
export function getTypeForm(owner, typeKey, isEdit) {
|
||||||
return <div>
|
return <div>
|
||||||
{owner.templateTypes[typeKey].getTypeForm(this, isEdit)}
|
{owner.templateTypes[typeKey].getTypeForm(owner, isEdit)}
|
||||||
</div>;
|
</div>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,21 +1,27 @@
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
const knex = require('../lib/knex');
|
const knex = require('../lib/knex');
|
||||||
|
const hasher = require('node-object-hash')();
|
||||||
const dtHelpers = require('../lib/dt-helpers');
|
const dtHelpers = require('../lib/dt-helpers');
|
||||||
const interoperableErrors = require('../shared/interoperable-errors');
|
const interoperableErrors = require('../shared/interoperable-errors');
|
||||||
const shortid = require('shortid');
|
const shortid = require('shortid');
|
||||||
|
const { enforce, filterObject } = require('../lib/helpers');
|
||||||
const shares = require('./shares');
|
const shares = require('./shares');
|
||||||
|
const namespaceHelpers = require('../lib/namespace-helpers');
|
||||||
const files = require('./files');
|
const files = require('./files');
|
||||||
const { CampaignSource, CampaignType} = require('../shared/campaigns');
|
const { CampaignSource, CampaignType} = require('../shared/campaigns');
|
||||||
const segments = require('./segments');
|
const segments = require('./segments');
|
||||||
|
|
||||||
const allowedKeysCommon = ['name', 'description', 'list', 'segment', 'namespace',
|
const allowedKeysCommon = ['name', 'description', 'list', 'segment', 'namespace',
|
||||||
'send_configuration', 'from_name_override', 'from_email_override', 'reply_to_override', 'subject_override',
|
'send_configuration', 'from_name_override', 'from_email_override', 'reply_to_override', 'subject_override', 'data', 'click_tracking_disabled', 'open_tracking_disabled'];
|
||||||
'source', 'data', 'click_tracking_disabled', 'open_tracking_disabled'];
|
|
||||||
|
|
||||||
const allowedKeysCreate = new Set(['type', ...allowedKeysCommon]);
|
const allowedKeysCreate = new Set(['type', 'source', ...allowedKeysCommon]);
|
||||||
const allowedKeysUpdate = new Set([...allowedKeysCommon]);
|
const allowedKeysUpdate = new Set([...allowedKeysCommon]);
|
||||||
|
|
||||||
|
function hash(entity) {
|
||||||
|
return hasher.hash(filterObject(entity, allowedKeysUpdate));
|
||||||
|
}
|
||||||
|
|
||||||
async function listDTAjax(context, params) {
|
async function listDTAjax(context, params) {
|
||||||
return await dtHelpers.ajaxListWithPermissions(
|
return await dtHelpers.ajaxListWithPermissions(
|
||||||
context,
|
context,
|
||||||
|
@ -29,9 +35,13 @@ async function listDTAjax(context, params) {
|
||||||
|
|
||||||
async function getById(context, id) {
|
async function getById(context, id) {
|
||||||
return await knex.transaction(async tx => {
|
return await knex.transaction(async tx => {
|
||||||
await shares.enforceEntityPermissionTx(tx, context, 'campaign', 'view');
|
await shares.enforceEntityPermissionTx(tx, context, 'campaign', id, 'view');
|
||||||
const entity = await tx('campaigns').where('id', id).first();
|
const entity = await tx('campaigns').where('id', id).first();
|
||||||
|
|
||||||
entity.permissions = await shares.getPermissionsTx(tx, context, 'campaign', id);
|
entity.permissions = await shares.getPermissionsTx(tx, context, 'campaign', id);
|
||||||
|
|
||||||
|
entity.data = JSON.parse(entity.data);
|
||||||
|
|
||||||
return entity;
|
return entity;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -41,6 +51,10 @@ async function _validateAndPreprocess(tx, context, entity, isCreate) {
|
||||||
|
|
||||||
if (isCreate) {
|
if (isCreate) {
|
||||||
enforce(entity.type === CampaignType.REGULAR && entity.type === CampaignType.RSS && entity.type === CampaignType.TRIGGERED, 'Unknown campaign type');
|
enforce(entity.type === CampaignType.REGULAR && entity.type === CampaignType.RSS && entity.type === CampaignType.TRIGGERED, 'Unknown campaign type');
|
||||||
|
|
||||||
|
if (entity.source === CampaignSource.TEMPLATE || entity.source === CampaignSource.CUSTOM_FROM_TEMPLATE) {
|
||||||
|
await shares.enforceEntityPermissionTx(tx, context, 'template', entity.data.sourceTemplate, 'view');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
enforce(entity.source >= CampaignSource.MIN && entity.source <= CampaignSource.MAX, 'Unknown campaign source');
|
enforce(entity.source >= CampaignSource.MIN && entity.source <= CampaignSource.MAX, 'Unknown campaign source');
|
||||||
|
@ -52,11 +66,7 @@ async function _validateAndPreprocess(tx, context, entity, isCreate) {
|
||||||
await segments.getByIdTx(tx, context, entity.list, entity.segment);
|
await segments.getByIdTx(tx, context, entity.list, entity.segment);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (entity.source === CampaignSource.TEMPLATE || (isCreate && entity.source === CampaignSource.CUSTOM_FROM_TEMPLATE)) {
|
entity.data = JSON.stringify(entity.data);
|
||||||
await shares.enforceEntityPermissionTx(tx, context, 'template', entity.data.sourceTemplate, 'view');
|
|
||||||
}
|
|
||||||
|
|
||||||
entity.data = JSON.stringify(data);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async function create(context, entity) {
|
async function create(context, entity) {
|
||||||
|
@ -126,6 +136,7 @@ async function updateWithConsistencyCheck(context, entity) {
|
||||||
throw new interoperableErrors.NotFoundError();
|
throw new interoperableErrors.NotFoundError();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
existing.data = JSON.parse(existing.data);
|
||||||
const existingHash = hash(existing);
|
const existingHash = hash(existing);
|
||||||
if (existingHash !== entity.originalHash) {
|
if (existingHash !== entity.originalHash) {
|
||||||
throw new interoperableErrors.ChangedError();
|
throw new interoperableErrors.ChangedError();
|
||||||
|
@ -145,6 +156,8 @@ async function remove(context, id) {
|
||||||
await knex.transaction(async tx => {
|
await knex.transaction(async tx => {
|
||||||
await shares.enforceEntityPermissionTx(tx, context, 'campaign', id, 'delete');
|
await shares.enforceEntityPermissionTx(tx, context, 'campaign', id, 'delete');
|
||||||
|
|
||||||
|
// FIXME - deal with deletion of dependent entities (files)
|
||||||
|
|
||||||
await tx('campaigns').where('id', id).del();
|
await tx('campaigns').where('id', id).del();
|
||||||
await knex.schema.dropTableIfExists('campaign__' + id);
|
await knex.schema.dropTableIfExists('campaign__' + id);
|
||||||
await knex.schema.dropTableIfExists('campaign_tracker__' + id);
|
await knex.schema.dropTableIfExists('campaign_tracker__' + id);
|
||||||
|
@ -153,6 +166,10 @@ async function remove(context, id) {
|
||||||
|
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
|
hash,
|
||||||
listDTAjax,
|
listDTAjax,
|
||||||
getById
|
getById,
|
||||||
|
create,
|
||||||
|
updateWithConsistencyCheck,
|
||||||
|
remove
|
||||||
};
|
};
|
|
@ -92,7 +92,7 @@ async function remove(context, id) {
|
||||||
await knex.transaction(async tx => {
|
await knex.transaction(async tx => {
|
||||||
await shares.enforceEntityPermissionTx(tx, context, 'template', id, 'delete');
|
await shares.enforceEntityPermissionTx(tx, context, 'template', id, 'delete');
|
||||||
|
|
||||||
// FIXME - deal with deletion of dependent entities
|
// FIXME - deal with deletion of dependent entities (files, etc.)
|
||||||
|
|
||||||
await tx('templates').where('id', id).del();
|
await tx('templates').where('id', id).del();
|
||||||
});
|
});
|
||||||
|
|
|
@ -10,7 +10,7 @@ router.postAsync('/campaigns-table', passport.loggedIn, async (req, res) => {
|
||||||
return res.json(await campaigns.listDTAjax(req.context, req.body));
|
return res.json(await campaigns.listDTAjax(req.context, req.body));
|
||||||
});
|
});
|
||||||
|
|
||||||
router.getAsync('/campaings/:campaignId', passport.loggedIn, async (req, res) => {
|
router.getAsync('/campaigns/:campaignId', passport.loggedIn, async (req, res) => {
|
||||||
const campaign = await campaigns.getById(req.context, req.params.campaignId);
|
const campaign = await campaigns.getById(req.context, req.params.campaignId);
|
||||||
campaign.hash = campaigns.hash(campaign);
|
campaign.hash = campaigns.hash(campaign);
|
||||||
return res.json(campaign);
|
return res.json(campaign);
|
||||||
|
|
|
@ -81,6 +81,12 @@ exports.up = (knex, Promise) => (async() => {
|
||||||
editorType = 'ckeditor';
|
editorType = 'ckeditor';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (editorType == 'mosaico') {
|
||||||
|
editorType = 'mosaicoWithFsTemplate';
|
||||||
|
editorData.mosaicoFsTemplate = editorData.template;
|
||||||
|
delete editorData.template;
|
||||||
|
}
|
||||||
|
|
||||||
campaign.source = CampaignSource.CUSTOM_FROM_TEMPLATE;
|
campaign.source = CampaignSource.CUSTOM_FROM_TEMPLATE;
|
||||||
data.sourceCustom = {
|
data.sourceCustom = {
|
||||||
type: editorType,
|
type: editorType,
|
||||||
|
|
Loading…
Reference in a new issue