Added feature to create template from another template.
This commit is contained in:
parent
031b346440
commit
8d95f43dbc
5 changed files with 90 additions and 41 deletions
|
@ -12,12 +12,12 @@ import {
|
||||||
} from '../lib/page'
|
} from '../lib/page'
|
||||||
import {
|
import {
|
||||||
Button,
|
Button,
|
||||||
ButtonRow,
|
ButtonRow, CheckBox,
|
||||||
Dropdown,
|
Dropdown,
|
||||||
Form,
|
Form,
|
||||||
FormSendMethod,
|
FormSendMethod,
|
||||||
InputField,
|
InputField,
|
||||||
StaticField,
|
StaticField, TableSelect,
|
||||||
TextArea,
|
TextArea,
|
||||||
withForm
|
withForm
|
||||||
} from '../lib/form';
|
} from '../lib/form';
|
||||||
|
@ -41,6 +41,8 @@ import styles
|
||||||
import {getUrl} from "../lib/urls";
|
import {getUrl} from "../lib/urls";
|
||||||
import {TestSendModalDialog} from "./TestSendModalDialog";
|
import {TestSendModalDialog} from "./TestSendModalDialog";
|
||||||
import {withComponentMixins} from "../lib/decorator-helpers";
|
import {withComponentMixins} from "../lib/decorator-helpers";
|
||||||
|
import moment
|
||||||
|
from 'moment';
|
||||||
|
|
||||||
|
|
||||||
@withComponentMixins([
|
@withComponentMixins([
|
||||||
|
@ -98,6 +100,10 @@ console.log('constructor')
|
||||||
description: '',
|
description: '',
|
||||||
namespace: mailtrainConfig.user.namespace,
|
namespace: mailtrainConfig.user.namespace,
|
||||||
type: mailtrainConfig.editors[0],
|
type: mailtrainConfig.editors[0],
|
||||||
|
|
||||||
|
fromSourceTemplate: false,
|
||||||
|
sourceTemplate: null,
|
||||||
|
|
||||||
text: '',
|
text: '',
|
||||||
html: '',
|
html: '',
|
||||||
data: {},
|
data: {},
|
||||||
|
@ -122,6 +128,12 @@ console.log('constructor')
|
||||||
state.setIn(['type', 'error'], t('typeMustBeSelected'));
|
state.setIn(['type', 'error'], t('typeMustBeSelected'));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (state.getIn(['fromSourceTemplate', 'value']) && !state.getIn(['sourceTemplate', 'value'])) {
|
||||||
|
state.setIn(['sourceTemplate', 'error'], t('Source template must not be empty'));
|
||||||
|
} else {
|
||||||
|
state.setIn(['sourceTemplate', 'error'], null);
|
||||||
|
}
|
||||||
|
|
||||||
validateNamespace(t, state);
|
validateNamespace(t, state);
|
||||||
|
|
||||||
if (typeKey) {
|
if (typeKey) {
|
||||||
|
@ -255,6 +267,13 @@ console.log('constructor')
|
||||||
typeForm = getTypeForm(this, typeKey, isEdit);
|
typeForm = getTypeForm(this, typeKey, isEdit);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const templatesColumns = [
|
||||||
|
{ data: 1, title: t('name') },
|
||||||
|
{ data: 2, title: t('description') },
|
||||||
|
{ data: 3, title: t('type'), render: data => this.templateTypes[data].typeName },
|
||||||
|
{ data: 4, title: t('created'), render: data => moment(data).fromNow() },
|
||||||
|
{ data: 5, title: t('namespace') },
|
||||||
|
];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={this.state.elementInFullscreen ? styles.withElementInFullscreen : ''}>
|
<div className={this.state.elementInFullscreen ? styles.withElementInFullscreen : ''}>
|
||||||
|
@ -281,8 +300,15 @@ console.log('constructor')
|
||||||
<InputField id="name" label={t('name')}/>
|
<InputField id="name" label={t('name')}/>
|
||||||
<TextArea id="description" label={t('description')}/>
|
<TextArea id="description" label={t('description')}/>
|
||||||
|
|
||||||
{isEdit
|
{!isEdit &&
|
||||||
?
|
<CheckBox id="fromSourceTemplate" label={t('template')} text={t('Clone from an existing template')}/>
|
||||||
|
}
|
||||||
|
|
||||||
|
{this.getFormValue('fromSourceTemplate') ?
|
||||||
|
<TableSelect key="templateSelect" id="sourceTemplate" withHeader dropdown dataUrl='rest/templates-table' columns={templatesColumns} selectionLabelIndex={1} />
|
||||||
|
:
|
||||||
|
<>
|
||||||
|
{isEdit ?
|
||||||
<StaticField id="type" className={styles.formDisabled} label={t('type')}>
|
<StaticField id="type" className={styles.formDisabled} label={t('type')}>
|
||||||
{typeKey && this.templateTypes[typeKey].typeName}
|
{typeKey && this.templateTypes[typeKey].typeName}
|
||||||
</StaticField>
|
</StaticField>
|
||||||
|
@ -291,6 +317,8 @@ console.log('constructor')
|
||||||
}
|
}
|
||||||
|
|
||||||
{typeForm}
|
{typeForm}
|
||||||
|
</>
|
||||||
|
}
|
||||||
|
|
||||||
<NamespaceSelect/>
|
<NamespaceSelect/>
|
||||||
|
|
||||||
|
|
|
@ -255,11 +255,11 @@ export function getTemplateTypes(t, prefix = '', entityTypeId = ResourceType.TEM
|
||||||
return <StaticField
|
return <StaticField
|
||||||
id={prefix + 'grapesJSSourceType'}
|
id={prefix + 'grapesJSSourceType'}
|
||||||
className={styles.formDisabled}
|
className={styles.formDisabled}
|
||||||
label={t('type')}>{grapesJSSourceTypeLabels[owner.getFormValue(prefix + 'grapesJSSourceType')]}</StaticField>;
|
label={t('Content')}>{grapesJSSourceTypeLabels[owner.getFormValue(prefix + 'grapesJSSourceType')]}</StaticField>;
|
||||||
} else {
|
} else {
|
||||||
return <Dropdown
|
return <Dropdown
|
||||||
id={prefix + 'grapesJSSourceType'}
|
id={prefix + 'grapesJSSourceType'}
|
||||||
label={t('type')}
|
label={t('Content')}
|
||||||
options={grapesJSSourceTypes}/>;
|
options={grapesJSSourceTypes}/>;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
32
server/lib/campaign-content.js
Normal file
32
server/lib/campaign-content.js
Normal file
|
@ -0,0 +1,32 @@
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
function convertFileURLs(sourceCustom, fromEntityType, fromEntityId, toEntityType, toEntityId) {
|
||||||
|
|
||||||
|
function convertText(text) {
|
||||||
|
if (text) {
|
||||||
|
const fromUrl = `/files/${fromEntityType}/file/${fromEntityId}`;
|
||||||
|
const toUrl = `/files/${toEntityType}/file/${toEntityId}`;
|
||||||
|
|
||||||
|
const encodedFromUrl = encodeURIComponent(fromUrl);
|
||||||
|
const encodedToUrl = encodeURIComponent(toUrl);
|
||||||
|
|
||||||
|
text = text.split('[URL_BASE]' + fromUrl).join('[URL_BASE]' + toUrl);
|
||||||
|
text = text.split('[SANDBOX_URL_BASE]' + fromUrl).join('[SANDBOX_URL_BASE]' + toUrl);
|
||||||
|
text = text.split('[ENCODED_URL_BASE]' + encodedFromUrl).join('[ENCODED_URL_BASE]' + encodedToUrl);
|
||||||
|
text = text.split('[ENCODED_SANDBOX_URL_BASE]' + encodedFromUrl).join('[ENCODED_SANDBOX_URL_BASE]' + encodedToUrl);
|
||||||
|
}
|
||||||
|
|
||||||
|
return text;
|
||||||
|
}
|
||||||
|
|
||||||
|
sourceCustom.html = convertText(sourceCustom.html);
|
||||||
|
sourceCustom.text = convertText(sourceCustom.text);
|
||||||
|
|
||||||
|
if (sourceCustom.type === 'mosaico' || sourceCustom.type === 'mosaicoWithFsTemplate') {
|
||||||
|
sourceCustom.data.model = convertText(sourceCustom.data.model);
|
||||||
|
sourceCustom.data.model = convertText(sourceCustom.data.model);
|
||||||
|
sourceCustom.data.metadata = convertText(sourceCustom.data.metadata);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports.convertFileURLs = convertFileURLs;
|
|
@ -20,6 +20,7 @@ const senders = require('../lib/senders');
|
||||||
const {LinkId} = require('./links');
|
const {LinkId} = require('./links');
|
||||||
const feedcheck = require('../lib/feedcheck');
|
const feedcheck = require('../lib/feedcheck');
|
||||||
const contextHelpers = require('../lib/context-helpers');
|
const contextHelpers = require('../lib/context-helpers');
|
||||||
|
const {convertFileURLs} = require('../lib/campaign-content');
|
||||||
|
|
||||||
const {EntityActivityType, CampaignActivityType} = require('../../shared/activity-log');
|
const {EntityActivityType, CampaignActivityType} = require('../../shared/activity-log');
|
||||||
const activityLog = require('../lib/activity-log');
|
const activityLog = require('../lib/activity-log');
|
||||||
|
@ -430,35 +431,6 @@ async function _validateAndPreprocess(tx, context, entity, isCreate, content) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function convertFileURLs(sourceCustom, fromEntityType, fromEntityId, toEntityType, toEntityId) {
|
|
||||||
|
|
||||||
function convertText(text) {
|
|
||||||
if (text) {
|
|
||||||
const fromUrl = `/files/${fromEntityType}/file/${fromEntityId}`;
|
|
||||||
const toUrl = `/files/${toEntityType}/file/${toEntityId}`;
|
|
||||||
|
|
||||||
const encodedFromUrl = encodeURIComponent(fromUrl);
|
|
||||||
const encodedToUrl = encodeURIComponent(toUrl);
|
|
||||||
|
|
||||||
text = text.split('[URL_BASE]' + fromUrl).join('[URL_BASE]' + toUrl);
|
|
||||||
text = text.split('[SANDBOX_URL_BASE]' + fromUrl).join('[SANDBOX_URL_BASE]' + toUrl);
|
|
||||||
text = text.split('[ENCODED_URL_BASE]' + encodedFromUrl).join('[ENCODED_URL_BASE]' + encodedToUrl);
|
|
||||||
text = text.split('[ENCODED_SANDBOX_URL_BASE]' + encodedFromUrl).join('[ENCODED_SANDBOX_URL_BASE]' + encodedToUrl);
|
|
||||||
}
|
|
||||||
|
|
||||||
return text;
|
|
||||||
}
|
|
||||||
|
|
||||||
sourceCustom.html = convertText(sourceCustom.html);
|
|
||||||
sourceCustom.text = convertText(sourceCustom.text);
|
|
||||||
|
|
||||||
if (sourceCustom.type === 'mosaico' || sourceCustom.type === 'mosaicoWithFsTemplate') {
|
|
||||||
sourceCustom.data.model = convertText(sourceCustom.data.model);
|
|
||||||
sourceCustom.data.model = convertText(sourceCustom.data.model);
|
|
||||||
sourceCustom.data.metadata = convertText(sourceCustom.data.metadata);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async function _createTx(tx, context, entity, content) {
|
async function _createTx(tx, context, entity, content) {
|
||||||
return await knex.transaction(async tx => {
|
return await knex.transaction(async tx => {
|
||||||
await shares.enforceEntityPermissionTx(tx, context, 'namespace', entity.namespace, 'createCampaign');
|
await shares.enforceEntityPermissionTx(tx, context, 'namespace', entity.namespace, 'createCampaign');
|
||||||
|
|
|
@ -10,6 +10,7 @@ const shares = require('./shares');
|
||||||
const reports = require('./reports');
|
const reports = require('./reports');
|
||||||
const files = require('./files');
|
const files = require('./files');
|
||||||
const dependencyHelpers = require('../lib/dependency-helpers');
|
const dependencyHelpers = require('../lib/dependency-helpers');
|
||||||
|
const {convertFileURLs} = require('../lib/campaign-content');
|
||||||
|
|
||||||
const allowedKeys = new Set(['name', 'description', 'type', 'data', 'html', 'text', 'namespace']);
|
const allowedKeys = new Set(['name', 'description', 'type', 'data', 'html', 'text', 'namespace']);
|
||||||
|
|
||||||
|
@ -57,6 +58,15 @@ async function create(context, entity) {
|
||||||
return await knex.transaction(async tx => {
|
return await knex.transaction(async tx => {
|
||||||
await shares.enforceEntityPermissionTx(tx, context, 'namespace', entity.namespace, 'createTemplate');
|
await shares.enforceEntityPermissionTx(tx, context, 'namespace', entity.namespace, 'createTemplate');
|
||||||
|
|
||||||
|
if (entity.fromSourceTemplate) {
|
||||||
|
const template = await getByIdTx(tx, context, entity.sourceTemplate, false);
|
||||||
|
|
||||||
|
entity.type = template.type;
|
||||||
|
entity.data = template.data;
|
||||||
|
entity.html = template.html;
|
||||||
|
entity.text = template.text;
|
||||||
|
}
|
||||||
|
|
||||||
await _validateAndPreprocess(tx, entity);
|
await _validateAndPreprocess(tx, entity);
|
||||||
|
|
||||||
const ids = await tx('templates').insert(filterObject(entity, allowedKeys));
|
const ids = await tx('templates').insert(filterObject(entity, allowedKeys));
|
||||||
|
@ -64,6 +74,13 @@ async function create(context, entity) {
|
||||||
|
|
||||||
await shares.rebuildPermissionsTx(tx, { entityTypeId: 'template', entityId: id });
|
await shares.rebuildPermissionsTx(tx, { entityTypeId: 'template', entityId: id });
|
||||||
|
|
||||||
|
if (entity.fromSourceTemplate) {
|
||||||
|
await files.copyAllTx(tx, context, 'template', 'file', entity.sourceTemplate, 'template', 'file', id);
|
||||||
|
|
||||||
|
convertFileURLs(entity, 'template', entity.sourceTemplate, 'template', id);
|
||||||
|
await tx('templates').update(filterObject(entity, allowedKeys)).where('id', id);
|
||||||
|
}
|
||||||
|
|
||||||
return id;
|
return id;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue