Send test functionality for templates and campaigns

This commit is contained in:
Tomas Bures 2018-11-14 22:29:31 +01:00
parent 7e52000219
commit 2c73c536b7
22 changed files with 719 additions and 69 deletions

View file

@ -27,6 +27,7 @@ import {
import axios from '../lib/axios';
import styles from "../lib/styles.scss";
import {getUrl} from "../lib/urls";
import {TestSendModalDialog} from "./TestSendModalDialog";
@translate()
@ -49,10 +50,13 @@ export default class CustomContent extends Component {
this.state = {
showMergeTagReference: false,
elementInFullscreen: false
elementInFullscreen: false,
showTestSendModal: false
};
this.initForm();
this.sendModalGetDataHandler = ::this.sendModalGetData;
}
static propTypes = {
@ -84,7 +88,7 @@ export default class CustomContent extends Component {
const t = this.props.t;
const customTemplateTypeKey = this.getFormValue('data_sourceCustom_type');
await this.templateTypes[customTemplateTypeKey].exportHTMLEditorData(this);
const exportedData = await this.templateTypes[customTemplateTypeKey].exportHTMLEditorData(this);
const sendMethod = FormSendMethod.PUT;
const url = `rest/campaigns-content/${this.props.entity.id}`;
@ -93,6 +97,7 @@ export default class CustomContent extends Component {
this.setFormStatusMessage('info', t('Saving ...'));
const submitResponse = await this.validateAndSendFormValuesToURL(sendMethod, url, data => {
Object.assign(data, exportedData);
this.templateTypes[data.data_sourceCustom_type].beforeSave(data);
data.data.sourceCustom = {
@ -123,9 +128,10 @@ export default class CustomContent extends Component {
async extractPlainText() {
const customTemplateTypeKey = this.getFormValue('data_sourceCustom_type');
await this.templateTypes[customTemplateTypeKey].exportHTMLEditorData(this);
const exportedData = await this.templateTypes[customTemplateTypeKey].exportHTMLEditorData(this);
const html = exportedData.data_sourceCustom_html;
const html = this.getFormValue('data_sourceCustom_html');
if (!html) {
return;
}
@ -155,6 +161,22 @@ export default class CustomContent extends Component {
});
}
showTestSendModal() {
this.setState({
showTestSendModal: true
});
}
async sendModalGetData() {
const customTemplateTypeKey = this.getFormValue('data_sourceCustom_type');
const exportedData = await this.templateTypes[customTemplateTypeKey].exportHTMLEditorData(this);
return {
html: exportedData.data_sourceCustom_html,
text: this.getFormValue('data_sourceCustom_text')
};
}
render() {
const t = this.props.t;
@ -166,6 +188,13 @@ export default class CustomContent extends Component {
return (
<div className={this.state.elementInFullscreen ? styles.withElementInFullscreen : ''}>
<TestSendModalDialog
visible={this.state.showTestSendModal}
onHide={() => this.setState({showTestSendModal: false})}
getDataAsync={this.sendModalGetDataHandler}
entity={this.props.entity}
/>
<Title>{t('Edit Custom Content')}</Title>
<Form stateOwner={this} onSubmitAsync={::this.submitHandler}>

View file

@ -0,0 +1,124 @@
'use strict';
import React, {Component} from 'react';
import {translate} from 'react-i18next';
import PropTypes
from 'prop-types';
import {ModalDialog} from "../lib/bootstrap-components";
import {
requiresAuthenticatedUser,
withPageHelpers
} from "../lib/page";
import {
Form,
TableSelect,
withForm
} from "../lib/form";
import {withErrorHandling} from "../lib/error-handling";
import {getMailerTypes} from "../send-configurations/helpers";
import axios from '../lib/axios';
import {} from '../lib/urls';
import {getUrl} from "../lib/urls";
@translate()
@withForm
@withPageHelpers
@withErrorHandling
@requiresAuthenticatedUser
export class TestSendModalDialog extends Component {
constructor(props) {
super(props);
this.mailerTypes = getMailerTypes(props.t);
this.initForm();
}
static propTypes = {
stateOwner: PropTypes.object,
visible: PropTypes.bool.isRequired,
onHide: PropTypes.func.isRequired,
getDataAsync: PropTypes.func.isRequired,
entity: PropTypes.object
}
componentDidMount() {
this.populateFormValues({
testUser: null,
});
}
async hideModal() {
this.props.onHide();
}
async performAction() {
const props = this.props;
const t = props.t;
if (this.isFormWithoutErrors()) {
try {
this.hideFormValidation();
this.disableForm();
this.setFormStatusMessage('info', t('Sending test email'));
const data = await this.props.getDataAsync();
const campaignCid = props.entity.cid;
const [listCid, subscriptionCid] = this.getFormValue('testUser').split(':');
data.listCid = listCid;
data.subscriptionCid = subscriptionCid;
data.sendConfigurationId = props.entity.send_configuration;
data.campaignId = props.entity.id;
console.log(await axios.post(getUrl('rest/template-test-send'), data));
this.clearFormStatusMessage();
this.enableForm();
await this.hideModal();
} catch (err) {
throw err;
}
} else {
this.showFormValidation();
}
}
localValidateFormValues(state) {
const t = this.props.t;
if (!state.getIn(['testUser', 'value'])) {
state.setIn(['testUser', 'error'], t('Subscription has to be selected.'))
} else {
state.setIn(['testUser', 'error'], null);
}
}
render() {
const t = this.props.t;
const testUsersColumns = [
{ data: 1, title: t('Email') },
{ data: 2, title: t('Subscription ID'), render: data => <code>{data}</code> },
{ data: 3, title: t('List ID'), render: data => <code>{data}</code> },
{ data: 4, title: t('List') },
{ data: 5, title: t('List namespace') }
];
return (
<ModalDialog hidden={!this.props.visible} title={t('Send Test Email')} onCloseAsync={() => this.hideModal()} buttons={[
{ label: t('Send'), className: 'btn-danger', onClickAsync: ::this.performAction },
{ label: t('Cancel'), className: 'btn-primary', onClickAsync: ::this.hideModal }
]}>
<Form stateOwner={this} format="wide">
<TableSelect id="testUser" format="wide" label={t('Subscription')} withHeader dropdown dataUrl={`rest/campaigns-test-users-table/${this.props.entity.id}`} columns={testUsersColumns} selectionLabelIndex={1} />
</Form>
</ModalDialog>
);
}
}

View file

@ -210,7 +210,8 @@ class ModalDialog extends Component {
onCloseAsync: PropTypes.func,
onButtonClickAsync: PropTypes.func,
buttons: PropTypes.array,
hidden: PropTypes.bool
hidden: PropTypes.bool,
className: PropTypes.string
}
/*
@ -281,7 +282,11 @@ class ModalDialog extends Component {
}
return (
<div ref={(domElem) => { this.domModal = domElem; }} className="modal fade" tabIndex="-1" role="dialog" aria-labelledby="myModalLabel">
<div
ref={(domElem) => { this.domModal = domElem; }}
className={'modal fade' + (props.className ? ' ' + props.className : '')}
tabIndex="-1" role="dialog" aria-labelledby="myModalLabel">
<div className="modal-dialog" role="document">
<div className="modal-content">
<div className="modal-header">

View file

@ -43,25 +43,32 @@ class CKEditorSandbox extends Component {
const trustedUrlBase = getTrustedUrl();
const sandboxUrlBase = getSandboxUrl();
const publicUrlBase = getPublicUrl();
const html = this.props.initialHtml && base(this.props.initialHtml, trustedUrlBase, sandboxUrlBase, publicUrlBase);
const source = this.props.initialSource && base(this.props.initialSource, trustedUrlBase, sandboxUrlBase, publicUrlBase);
this.state = {
html
source
};
}
static propTypes = {
entityTypeId: PropTypes.string,
entityId: PropTypes.number,
initialHtml: PropTypes.string
initialSource: PropTypes.string
}
async exportState(method, params) {
const trustedUrlBase = getTrustedUrl();
const sandboxUrlBase = getSandboxUrl();
const publicUrlBase = getPublicUrl();
const preHtml = '<!doctype html><html><head><meta charset="utf-8"><title></title></head><body>';
const postHtml = '</body></html>';
const unbasedSource = unbase(this.state.source, trustedUrlBase, sandboxUrlBase, publicUrlBase, true);
return {
html: unbase(this.state.html, trustedUrlBase, sandboxUrlBase, publicUrlBase, true)
source: unbasedSource,
html: preHtml + unbasedSource + postHtml
};
}
@ -119,9 +126,9 @@ class CKEditorSandbox extends Component {
return (
<div className={styles.sandbox}>
<CKEditor ref={node => this.node = node}
content={this.state.html}
content={this.state.source}
events={{
change: evt => this.setState({html: evt.editor.getData()}),
change: evt => this.setState({source: evt.editor.getData()}),
}}
config={config}
/>

View file

@ -29,8 +29,9 @@ export class CKEditorHost extends Component {
static propTypes = {
entityTypeId: PropTypes.string,
entity: PropTypes.object,
initialHtml: PropTypes.string,
initialSource: PropTypes.string,
title: PropTypes.string,
onTestSend: PropTypes.func,
onFullscreenAsync: PropTypes.func
}
@ -75,7 +76,7 @@ export class CKEditorHost extends Component {
const editorData = {
entityTypeId: this.props.entityTypeId,
entityId: this.props.entity.id,
initialHtml: this.props.initialHtml
initialSource: this.props.initialSource
};
const tokenData = {
@ -89,6 +90,7 @@ export class CKEditorHost extends Component {
{this.state.fullscreen && <img className={styles.logo} src={getTrustedUrl('static/mailtrain-notext.png')}/>}
<div className={styles.title}>{this.props.title}</div>
<a className={styles.btn} onClick={::this.toggleFullscreenAsync}><Icon icon="fullscreen"/></a>
<a className={styles.btn} onClick={this.props.onTestSend}><Icon icon="send"/></a>
</div>
<UntrustedContentHost ref={node => this.contentNode = node} className={styles.host} singleToken={true} contentProps={editorData} contentSrc="ckeditor/editor" tokenMethod="ckeditor" tokenParams={editorData}/>
</div>

View file

@ -39,6 +39,8 @@ import {CodeEditorSourceType} from "./sandboxed-codeeditor-shared";
import mjml2html from "mjml4-in-browser";
import juice from "juice";
const refreshTimeout = 1000;
@translate(null, { withRef: true })
class CodeEditorSandbox extends Component {
constructor(props) {
@ -85,6 +87,12 @@ class CodeEditorSandbox extends Component {
source,
preview: props.initialPreview
};
this.state.previewContents = this.getHtml();
this.onCodeChangedHandler = ::this.onCodeChanged;
this.refreshHandler = ::this.refresh;
this.refreshTimeoutId = null;
}
static propTypes = {
@ -100,6 +108,7 @@ class CodeEditorSandbox extends Component {
const sandboxUrlBase = getSandboxUrl();
const publicUrlBase = getPublicUrl();
return {
html: unbase(this.getHtml(), trustedUrlBase, sandboxUrlBase, publicUrlBase, true),
source: unbase(this.state.source, trustedUrlBase, sandboxUrlBase, publicUrlBase, true)
};
}
@ -115,9 +124,12 @@ class CodeEditorSandbox extends Component {
parentRPC.setMethodHandler('setPreview', ::this.setPreview);
}
render() {
let previewContents;
componentWillUnmount() {
clearTimeout(this.refreshTimeoutId);
}
getHtml() {
let previewContents;
if (this.props.sourceType === CodeEditorSourceType.MJML) {
const res = mjml2html(this.state.source);
previewContents = res.html;
@ -125,6 +137,28 @@ class CodeEditorSandbox extends Component {
previewContents = juice(this.state.source);
}
return previewContents;
}
onCodeChanged(data) {
this.setState({
source: data
});
if (!this.refreshTimeoutId) {
this.refreshTimeoutId = setTimeout(() => this.refresh(), refreshTimeout);
}
}
refresh() {
this.refreshTimeoutId = null;
this.setState({
previewContents: this.getHtml()
});
}
render() {
return (
<div className={styles.sandbox}>
<div className={this.state.preview ? styles.aceEditorWithPreview : styles.aceEditorWithoutPreview}>
@ -133,7 +167,7 @@ class CodeEditorSandbox extends Component {
theme="github"
width="100%"
height="100%"
onChange={data => this.setState({source: data})}
onChange={this.onCodeChangedHandler}
fontSize={12}
showPrintMargin={false}
value={this.state.source}
@ -144,7 +178,7 @@ class CodeEditorSandbox extends Component {
{
this.state.preview &&
<div className={styles.preview}>
<iframe src={"data:text/html;charset=utf-8," + escape(previewContents)}></iframe>
<iframe src={"data:text/html;charset=utf-8," + escape(this.state.previewContents)}></iframe>
</div>
}
</div>

View file

@ -28,6 +28,7 @@ export class CodeEditorHost extends Component {
initialSource: PropTypes.string,
sourceType: PropTypes.string,
title: PropTypes.string,
onTestSend: PropTypes.func,
onFullscreenAsync: PropTypes.func
}
@ -47,6 +48,7 @@ export class CodeEditorHost extends Component {
await this.contentNode.ask('setPreview', preview);
}
async exportState() {
return await this.contentNode.ask('exportState');
}
@ -73,6 +75,7 @@ export class CodeEditorHost extends Component {
{this.state.fullscreen && <img className={styles.logo} src={getTrustedUrl('static/mailtrain-notext.png')}/>}
<div className={styles.title}>{this.props.title}</div>
<a className={styles.btn} onClick={::this.toggleFullscreenAsync}><Icon icon="fullscreen"/></a>
<a className={styles.btn} onClick={this.props.onTestSend}><Icon icon="send"/></a>
<a className={styles.btn} onClick={::this.togglePreviewAsync}><Icon icon={this.state.preview ? 'eye-close': 'eye-open'}/></a>
</div>
<UntrustedContentHost ref={node => this.contentNode = node} className={styles.host} singleToken={true} contentProps={editorData} contentSrc="codeeditor/editor" tokenMethod="codeeditor" tokenParams={tokenData}/>

View file

@ -107,7 +107,11 @@ export class GrapesJSSandbox extends Component {
const commandManager = editor.Commands;
const cmdGetCode = commandManager.get('gjs-get-inlined-html');
html = cmdGetCode.run(editor);
const htmlBody = cmdGetCode.run(editor);
const preHtml = '<!doctype html><html><head><meta charset="utf-8"><title></title></head><body>';
const postHtml = '</body></html>';
html = preHtml + unbase(htmlBody, trustedUrlBase, sandboxUrlBase, publicUrlBase, true) + postHtml;
}

View file

@ -28,6 +28,7 @@ export class GrapesJSHost extends Component {
initialStyle: PropTypes.string,
sourceType: PropTypes.string,
title: PropTypes.string,
onTestSend: PropTypes.func,
onFullscreenAsync: PropTypes.func
}
@ -65,6 +66,7 @@ export class GrapesJSHost extends Component {
{this.state.fullscreen && <img className={styles.logo} src={getTrustedUrl('static/mailtrain-notext.png')}/>}
<div className={styles.title}>{this.props.title}</div>
<a className={styles.btn} onClick={::this.toggleFullscreenAsync}><Icon icon="fullscreen"/></a>
<a className={styles.btn} onClick={this.props.onTestSend}><Icon icon="send"/></a>
</div>
<UntrustedContentHost ref={node => this.contentNode = node} className={styles.host} singleToken={true} contentProps={editorData} contentSrc="grapesjs/editor" tokenMethod="grapesjs" tokenParams={tokenData}/>
</div>

View file

@ -26,6 +26,7 @@ export class MosaicoHost extends Component {
entityTypeId: PropTypes.string,
entity: PropTypes.object,
title: PropTypes.string,
onTestSend: PropTypes.func,
onFullscreenAsync: PropTypes.func,
templateId: PropTypes.number,
templatePath: PropTypes.string,
@ -68,6 +69,7 @@ export class MosaicoHost extends Component {
{this.state.fullscreen && <img className={styles.logo} src={getTrustedUrl('static/mailtrain-notext.png')}/>}
<div className={styles.title}>{this.props.title}</div>
<a className={styles.btn} onClick={::this.toggleFullscreenAsync}><Icon icon="fullscreen"/></a>
<a className={styles.btn} onClick={this.props.onTestSend}><Icon icon="send"/></a>
</div>
<UntrustedContentHost ref={node => this.contentNode = node} className={styles.host} singleToken={true} contentProps={editorData} contentSrc="mosaico/editor" tokenMethod="mosaico" tokenParams={tokenData}/>
</div>

View file

@ -145,4 +145,16 @@
.dependenciesList {
margin-bottom: 0px;
}
}
:global .modal-dialog {
@media (min-width: 768px) {
width: 700px;
}
@media (min-width: 1000px) {
width: 900px;
}
}

View file

@ -35,6 +35,7 @@ import {
import axios from '../lib/axios';
import styles from "../lib/styles.scss";
import {getUrl} from "../lib/urls";
import {TestSendModalDialog} from "./TestSendModalDialog";
@translate()
@ -50,7 +51,8 @@ export default class CUD extends Component {
this.state = {
showMergeTagReference: false,
elementInFullscreen: false
elementInFullscreen: false,
showTestSendModal: false
};
this.initForm({
@ -58,6 +60,8 @@ export default class CUD extends Component {
type: ::this.onTypeChanged
}
});
this.sendModalGetDataHandler = ::this.sendModalGetData;
}
static propTypes = {
@ -117,9 +121,10 @@ export default class CUD extends Component {
async submitHandler() {
const t = this.props.t;
let exportedData = {};
if (this.props.entity) {
const typeKey = this.getFormValue('type');
await this.templateTypes[typeKey].exportHTMLEditorData(this);
exportedData = await this.templateTypes[typeKey].exportHTMLEditorData(this);
}
let sendMethod, url;
@ -135,6 +140,7 @@ export default class CUD extends Component {
this.setFormStatusMessage('info', t('Saving ...'));
const submitResponse = await this.validateAndSendFormValuesToURL(sendMethod, url, data => {
Object.assign(data, exportedData);
this.templateTypes[data.type].beforeSave(data);
});
@ -152,9 +158,9 @@ export default class CUD extends Component {
async extractPlainText() {
const typeKey = this.getFormValue('type');
await this.templateTypes[typeKey].exportHTMLEditorData(this);
const exportedData = await this.templateTypes[typeKey].exportHTMLEditorData(this);
const html = this.getFormValue('html');
const html = exportedData.html;
if (!html) {
return;
}
@ -184,6 +190,22 @@ export default class CUD extends Component {
});
}
showTestSendModal() {
this.setState({
showTestSendModal: true
});
}
async sendModalGetData() {
const typeKey = this.getFormValue('type');
const exportedData = await this.templateTypes[typeKey].exportHTMLEditorData(this);
return {
html: exportedData.html,
text: this.getFormValue('text')
};
}
render() {
const t = this.props.t;
const isEdit = !!this.props.entity;
@ -194,8 +216,6 @@ export default class CUD extends Component {
typeOptions.push({key, label: this.templateTypes[key].typeName});
}
// TODO: Toggle HTML preview
const typeKey = this.getFormValue('type');
let editForm = null;
@ -211,6 +231,12 @@ export default class CUD extends Component {
return (
<div className={this.state.elementInFullscreen ? styles.withElementInFullscreen : ''}>
{isEdit &&
<TestSendModalDialog
visible={this.state.showTestSendModal}
onHide={() => this.setState({showTestSendModal: false})}
getDataAsync={this.sendModalGetDataHandler}/>
}
{canDelete &&
<DeleteModalDialog
stateOwner={this}
@ -246,6 +272,7 @@ export default class CUD extends Component {
<ButtonRow>
<Button type="submit" className="btn-primary" icon="ok" label={isEdit ? t('Save') : t('Save and edit template')}/>
{canDelete && <NavButton className="btn-danger" icon="remove" label={t('Delete')} linkTo={`/templates/${this.props.entity.id}/delete`}/> }
{isEdit && <Button className="btn-danger" icon="send" label={t('Test send')} onClickAsync={async () => this.setState({showTestSendModal: true})}/> }
</ButtonRow>
</Form>
</div>

View file

@ -0,0 +1,153 @@
'use strict';
import React, {Component} from 'react';
import {translate} from 'react-i18next';
import PropTypes
from 'prop-types';
import {ModalDialog} from "../lib/bootstrap-components";
import {
requiresAuthenticatedUser,
withPageHelpers
} from "../lib/page";
import {
Form,
TableSelect,
withForm
} from "../lib/form";
import {withErrorHandling} from "../lib/error-handling";
import moment
from "moment";
import {getMailerTypes} from "../send-configurations/helpers";
import axios from '../lib/axios';
import {getUrl} from "../lib/urls";
@translate()
@withForm
@withPageHelpers
@withErrorHandling
@requiresAuthenticatedUser
export class TestSendModalDialog extends Component {
constructor(props) {
super(props);
this.mailerTypes = getMailerTypes(props.t);
this.initForm();
}
static propTypes = {
stateOwner: PropTypes.object,
visible: PropTypes.bool.isRequired,
onHide: PropTypes.func.isRequired,
getDataAsync: PropTypes.func.isRequired
}
componentDidMount() {
this.populateFormValues({
list: null,
testUser: null,
sendConfiguration: null
});
}
async hideModal() {
this.props.onHide();
}
async performAction() {
const props = this.props;
const t = props.t;
if (this.isFormWithoutErrors()) {
try {
this.hideFormValidation();
this.disableForm();
this.setFormStatusMessage('info', t('Sending test email'));
const data = await this.props.getDataAsync();
data.listCid = this.getFormValue('list');
data.subscriptionCid = this.getFormValue('testUser');
data.sendConfigurationId = this.getFormValue('sendConfiguration');
console.log(await axios.post(getUrl('rest/template-test-send'), data));
this.clearFormStatusMessage();
this.enableForm();
await this.hideModal();
} catch (err) {
throw err;
}
} else {
this.showFormValidation();
}
}
localValidateFormValues(state) {
const t = this.props.t;
if (!state.getIn(['sendConfiguration', 'value'])) {
state.setIn(['sendConfiguration', 'error'], t('Send configuration has to be selected.'))
} else {
state.setIn(['sendConfiguration', 'error'], null);
}
if (!state.getIn(['list', 'value'])) {
state.setIn(['list', 'error'], t('List has to be selected.'))
} else {
state.setIn(['list', 'error'], null);
}
if (!state.getIn(['testUser', 'value'])) {
state.setIn(['testUser', 'error'], t('Subscription has to be selected.'))
} else {
state.setIn(['testUser', 'error'], null);
}
}
render() {
const t = this.props.t;
const listId = this.getFormValue('list');
const testUsersColumns = [
{ data: 1, title: t('Subscription ID'), render: data => <code>{data}</code> },
{ data: 2, title: t('Email') }
];
const listsColumns = [
{ data: 1, title: t('Name') },
{ data: 2, title: t('ID'), render: data => <code>{data}</code> },
{ data: 3, title: t('Subscribers') },
{ data: 4, title: t('Description') },
{ data: 5, title: t('Namespace') }
];
const sendConfigurationsColumns = [
{ data: 1, title: t('Name') },
{ data: 2, title: t('ID'), render: data => <code>{data}</code> },
{ data: 3, title: t('Description') },
{ data: 4, title: t('Type'), render: data => this.mailerTypes[data].typeName },
{ data: 5, title: t('Created'), render: data => moment(data).fromNow() },
{ data: 6, title: t('Namespace') }
];
return (
<ModalDialog hidden={!this.props.visible} title={t('Send Test Email')} onCloseAsync={() => this.hideModal()} buttons={[
{ label: t('Send'), className: 'btn-danger', onClickAsync: ::this.performAction },
{ label: t('Cancel'), className: 'btn-primary', onClickAsync: ::this.hideModal }
]}>
<Form stateOwner={this} format="wide">
<TableSelect id="sendConfiguration" format="wide" label={t('Send configuration')} withHeader dropdown dataUrl='rest/send-configurations-with-send-permission-table' columns={sendConfigurationsColumns} selectionLabelIndex={1} />
<TableSelect id="list" format="wide" label={t('List')} withHeader dropdown dataUrl={`rest/lists-table`} columns={listsColumns} selectionKeyIndex={2} selectionLabelIndex={1} />
{ listId &&
<TableSelect id="testUser" format="wide" label={t('Subscription')} withHeader dropdown dataUrl={`rest/subscriptions-test-user-table/${listId}`} columns={testUsersColumns} selectionKeyIndex={1} selectionLabelIndex={2} />
}
</Form>
</ModalDialog>
);
}
}

View file

@ -29,7 +29,11 @@ import {
} from "../lib/sandboxed-codeeditor-shared";
import {getTemplateTypes as getMosaicoTemplateTypes} from './mosaico/helpers';
import {getSandboxUrl} from "../lib/urls";
import {
getPublicUrl,
getSandboxUrl,
getTrustedUrl
} from "../lib/urls";
import mailtrainConfig from 'mailtrainConfig';
import {
ActionLink,
@ -38,6 +42,10 @@ import {
import {Trans} from "react-i18next";
import styles from "../lib/styles.scss";
import {
base,
unbase
} from "../../../shared/templates";
export const ResourceType = {
TEMPLATE: 'template',
@ -90,15 +98,19 @@ export function getTemplateTypes(t, prefix = '', entityTypeId = ResourceType.TEM
templateId={owner.getFormValue(prefix + 'mosaicoTemplate')}
entityTypeId={entityTypeId}
title={t('Mosaico Template Designer')}
onFullscreenAsync={::owner.setElementInFullscreen}/>
onTestSend={::owner.showTestSendModal}
onFullscreenAsync={::owner.setElementInFullscreen}
/>
</AlignedRow>,
exportHTMLEditorData: async owner => {
const {html, metadata, model} = await owner.editorNode.exportState();
owner.updateFormValue(prefix + 'html', html);
owner.updateFormValue(prefix + 'mosaicoData', {
metadata,
model
});
return {
[prefix + 'html']: html,
[prefix + 'mosaicoData']: {
metadata,
model
}
};
},
initData: () => ({
[prefix + 'mosaicoTemplate']: '',
@ -152,15 +164,19 @@ export function getTemplateTypes(t, prefix = '', entityTypeId = ResourceType.TEM
templatePath={getSandboxUrl(`static/mosaico/templates/${owner.getFormValue(prefix + 'mosaicoFsTemplate')}/index.html`)}
entityTypeId={entityTypeId}
title={t('Mosaico Template Designer')}
onFullscreenAsync={::owner.setElementInFullscreen}/>
onTestSend={::owner.showTestSendModal}
onFullscreenAsync={::owner.setElementInFullscreen}
/>
</AlignedRow>,
exportHTMLEditorData: async owner => {
const {html, metadata, model} = await owner.editorNode.exportState();
owner.updateFormValue(prefix + 'html', html);
owner.updateFormValue(prefix + 'mosaicoData', {
metadata,
model
});
return {
[prefix + 'html']: html,
[prefix + 'mosaicoData']: {
metadata,
model
}
};
},
initData: () => ({
[prefix + 'mosaicoFsTemplate']: mailtrainConfig.mosaico.fsTemplates[0].key,
@ -213,16 +229,19 @@ export function getTemplateTypes(t, prefix = '', entityTypeId = ResourceType.TEM
initialStyle={owner.getFormValue(prefix + 'grapesJSData').style}
sourceType={owner.getFormValue(prefix + 'grapesJSSourceType')}
title={t('GrapesJS Template Designer')}
onTestSend={::owner.showTestSendModal}
onFullscreenAsync={::owner.setElementInFullscreen}
/>
</AlignedRow>,
exportHTMLEditorData: async owner => {
const {html, source, style} = await owner.editorNode.exportState();
owner.updateFormValue(prefix + 'html', html);
owner.updateFormValue(prefix + 'grapesJSData', {
source,
style
});
return {
[prefix + 'html']: html,
[prefix + 'grapesJSData']: {
source,
style
}
};
},
initData: () => ({
[prefix + 'grapesJSSourceType']: GrapesJSSourceType.MJML,
@ -257,36 +276,82 @@ export function getTemplateTypes(t, prefix = '', entityTypeId = ResourceType.TEM
<CKEditorHost
ref={node => owner.editorNode = node}
entity={owner.props.entity}
initialHtml={owner.getFormValue(prefix + 'html')}
initialSource={owner.getFormValue(prefix + 'ckeditor4Data').source}
entityTypeId={entityTypeId}
title={t('CKEditor 4 Template Designer')}
onTestSend={::owner.showTestSendModal}
onFullscreenAsync={::owner.setElementInFullscreen}
/>
</AlignedRow>,
exportHTMLEditorData: async owner => {
const {html} = await owner.editorNode.exportState();
owner.updateFormValue(prefix + 'html', html);
const {html, source} = await owner.editorNode.exportState();
return {
[prefix + 'html']: html,
[prefix + 'ckeditor4Data']: {
source
}
};
},
initData: () => ({
[prefix + 'ckeditor4Data']: {}
}),
afterLoad: data => {
data[prefix + 'ckeditor4Data'] = {
source: data[prefix + 'data'].source
};
},
initData: () => ({}),
afterLoad: data => {},
beforeSave: data => {
data[prefix + 'data'] = {
source: data[prefix + 'ckeditor4Data'].source,
};
clearBeforeSave(data);
},
afterTypeChange: mutState => {},
afterTypeChange: mutState => {
initFieldsIfMissing(mutState, 'ckeditor4');
},
validate: state => {}
};
templateTypes.ckeditor5 = {
typeName: t('CKEditor 5'),
getTypeForm: (owner, isEdit) => null,
getHTMLEditor: owner => <CKEditor id={prefix + 'html'} height="600px" mode="html" label={t('Template content (HTML)')}/>,
exportHTMLEditorData: async owner => {},
initData: () => ({}),
afterLoad: data => {},
getHTMLEditor: owner => <CKEditor id={prefix + 'ckeditor5Source'} height="600px" mode="html" label={t('Template content (HTML)')}/>,
exportHTMLEditorData: async owner => {
const preHtml = '<!doctype html><html><head><meta charset="utf-8"><title></title></head><body>';
const postHtml = '</body></html>';
const trustedUrlBase = getTrustedUrl();
const sandboxUrlBase = getSandboxUrl();
const publicUrlBase = getPublicUrl();
const unbasedSource = unbase(owner.getFormValue(prefix + 'ckeditor5Source'), trustedUrlBase, sandboxUrlBase, publicUrlBase, true);
const html = preHtml + unbasedSource + postHtml
return {
[prefix + 'ckeditor5Source']: unbasedSource,
[prefix + 'html']: html
}
},
initData: () => ({
[prefix + 'ckeditor5Source']: ''
}),
afterLoad: data => {
const trustedUrlBase = getTrustedUrl();
const sandboxUrlBase = getSandboxUrl();
const publicUrlBase = getPublicUrl();
const source = base(data[prefix + 'data'].source, trustedUrlBase, sandboxUrlBase, publicUrlBase);
data[prefix + 'ckeditor5Source'] = source;
},
beforeSave: data => {
data[prefix + 'data'] = {
source: data[prefix + 'ckeditor5Source'],
};
clearBeforeSave(data);
},
afterTypeChange: mutState => {},
afterTypeChange: mutState => {
initFieldsIfMissing(mutState, 'ckeditor5');
},
validate: state => {}
};
@ -315,15 +380,18 @@ export function getTemplateTypes(t, prefix = '', entityTypeId = ResourceType.TEM
initialSource={owner.getFormValue(prefix + 'codeEditorData').source}
sourceType={owner.getFormValue(prefix + 'codeEditorSourceType')}
title={t('Code Editor Template Designer')}
onTestSend={::owner.showTestSendModal}
onFullscreenAsync={::owner.setElementInFullscreen}
/>
</AlignedRow>,
exportHTMLEditorData: async owner => {
const {html, source} = await owner.editorNode.exportState();
owner.updateFormValue(prefix + 'html', html);
owner.updateFormValue(prefix + 'codeEditorData', {
source
});
return {
[prefix + 'html']: html,
[prefix + 'codeEditorData']: {
source
}
};
},
initData: () => ({
[prefix + 'codeEditorSourceType']: CodeEditorSourceType.HTML,