diff --git a/client/src/campaigns/CUD.js b/client/src/campaigns/CUD.js index c37a195f..a51139e5 100644 --- a/client/src/campaigns/CUD.js +++ b/client/src/campaigns/CUD.js @@ -25,7 +25,7 @@ import {withAsyncErrorHandler, withErrorHandling} from '../lib/error-handling'; import {NamespaceSelect, validateNamespace} from '../lib/namespace'; import {DeleteModalDialog} from "../lib/modals"; import mailtrainConfig from 'mailtrainConfig'; -import {getTemplateTypes, getTypeForm, ResourceType} from '../templates/helpers'; +import {getTagLanguages, getTemplateTypes, getTypeForm, ResourceType} from '../templates/helpers'; import axios from '../lib/axios'; import styles from "../lib/styles.scss"; import campaignsStyles from "./styles.scss"; @@ -50,6 +50,8 @@ export default class CUD extends Component { const t = props.t; this.templateTypes = getTemplateTypes(props.t, 'data_sourceCustom_', ResourceType.CAMPAIGN); + this.tagLanguages = getTagLanguages(props.t); + this.mailerTypes = getMailerTypes(props.t); const { campaignTypeLabels } = getCampaignLabels(t); @@ -85,6 +87,11 @@ export default class CUD extends Component { this.customTemplateTypeOptions.push({key, label: this.templateTypes[key].typeName}); } + this.customTemplateTagLanguageOptions = []; + for (const key of mailtrainConfig.tagLanguages) { + this.customTemplateTagLanguageOptions.push({key, label: this.tagLanguages[key].name}); + } + this.state = { sendConfiguration: null }; @@ -120,6 +127,14 @@ export default class CUD extends Component { } } + if (key === undefined || key === 'data_sourceCustom_tag_language') { + if (newValue) { + const isEdit = !!this.props.entity; + const type = mutStateData.getIn(['data_sourceCustom_tag_language', 'value']); + this.templateTypes[type].afterTagLanguageChange(mutStateData, isEdit); + } + } + if (key === undefined || (match = key.match(/^(lists_[0-9]+_)list$/))) { const prefix = match[1]; mutStateData.setIn([prefix + 'segment', 'value'], null); @@ -202,6 +217,7 @@ export default class CUD extends Component { data.data.sourceCustom = { type: data.data_sourceCustom_type, + tag_language: data.data_sourceCustom_tag_language, data: data.data_sourceCustom_data, html: data.data_sourceCustom_html, text: data.data_sourceCustom_text @@ -257,7 +273,7 @@ export default class CUD extends Component { if (this.props.entity.status === CampaignStatus.SENDING) { this.disableForm(); } - + } else { const data = {}; for (const overridable of campaignOverridables) { @@ -301,6 +317,7 @@ export default class CUD extends Component { // This is for CampaignSource.CUSTOM data_sourceCustom_type: mailtrainConfig.editors[0], + data_sourceCustom_tag_language: mailtrainConfig.tagLanguages[0], data_sourceCustom_data: {}, data_sourceCustom_html: '', data_sourceCustom_text: '', @@ -362,6 +379,10 @@ export default class CUD extends Component { state.setIn(['data_sourceCustom_type', 'error'], t('typeMustBeSelected')); } + if (!state.getIn(['data_sourceCustom_tag_language', 'value'])) { + state.setIn(['data_sourceCustom_tag_language', 'error'], t('Tag language must be selected')); + } + if (customTemplateTypeKey) { this.templateTypes[customTemplateTypeKey].validate(state); } @@ -654,8 +675,8 @@ export default class CUD extends Component { { 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') }, + { data: 5, title: t('created'), render: data => moment(data).fromNow() }, + { data: 6, title: t('namespace') }, ]; let help = null; @@ -690,6 +711,8 @@ export default class CUD extends Component { templateEdit =
+ + {customTemplateTypeForm}
; diff --git a/client/src/campaigns/Content.js b/client/src/campaigns/Content.js index 0a2da44c..d7f91576 100644 --- a/client/src/campaigns/Content.js +++ b/client/src/campaigns/Content.js @@ -7,6 +7,7 @@ import {requiresAuthenticatedUser, Title, withPageHelpers} from '../lib/page' import { Button, ButtonRow, + Dropdown, filterData, Form, FormSendMethod, @@ -16,7 +17,7 @@ import { } from '../lib/form'; import {withErrorHandling} from '../lib/error-handling'; import mailtrainConfig from 'mailtrainConfig'; -import {getEditForm, getTemplateTypes, getTypeForm, ResourceType} from '../templates/helpers'; +import {getEditForm, getTagLanguages, getTemplateTypes, getTypeForm, ResourceType} from '../templates/helpers'; import axios from '../lib/axios'; import styles from "../lib/styles.scss"; import {getUrl} from "../lib/urls"; @@ -39,12 +40,18 @@ export default class CustomContent extends Component { const t = props.t; this.templateTypes = getTemplateTypes(props.t, 'data_sourceCustom_', ResourceType.CAMPAIGN); + this.tagLanguages = getTagLanguages(props.t); this.customTemplateTypeOptions = []; for (const key of mailtrainConfig.editors) { this.customTemplateTypeOptions.push({key, label: this.templateTypes[key].typeName}); } + this.customTemplateTagLanguageOptions = []; + for (const key of mailtrainConfig.tagLanguages) { + this.customTemplateTagLanguageOptions.push({key, label: this.tagLanguages[key].name}); + } + this.state = { showMergeTagReference: false, elementInFullscreen: false, @@ -56,6 +63,9 @@ export default class CustomContent extends Component { this.initForm({ getPreSubmitUpdater: ::this.getPreSubmitFormValuesUpdater, + onChangeBeforeValidation: { + data_sourceCustom_tag_language: ::this.onTagLanguageChanged + } }); this.sendModalGetDataHandler = ::this.sendModalGetData; @@ -71,9 +81,16 @@ export default class CustomContent extends Component { setPanelInFullScreen: PropTypes.func } + onTagLanguageChanged(mutStateData, key, oldTagLanguage, tagLanguage) { + if (tagLanguage) { + const type = mutStateData.getIn(['data_sourceCustom_tag_language', 'value']); + this.tagLanguages[type].afterTagLanguageChange(mutStateData, true); + } + } getFormValuesMutator(data) { data.data_sourceCustom_type = data.data.sourceCustom.type; + data.data_sourceCustom_tag_language = data.data.sourceCustom.tag_language; data.data_sourceCustom_data = data.data.sourceCustom.data; data.data_sourceCustom_html = data.data.sourceCustom.html; data.data_sourceCustom_text = data.data.sourceCustom.text; @@ -86,6 +103,7 @@ export default class CustomContent extends Component { data.data.sourceCustom = { type: data.data_sourceCustom_type, + tag_language: data.data_sourceCustom_tag_language, data: data.data_sourceCustom_data, html: data.data_sourceCustom_html, text: data.data_sourceCustom_text @@ -112,6 +130,12 @@ export default class CustomContent extends Component { localValidateFormValues(state) { const t = this.props.t; + if (!state.getIn(['data_sourceCustom_tag_language', 'value'])) { + state.setIn(['data_sourceCustom_tag_language', 'error'], t('Tag language must be selected')); + } else { + state.setIn(['data_sourceCustom_tag_language', 'error'], null); + } + const customTemplateTypeKey = state.getIn(['data_sourceCustom_type', 'value']); if (customTemplateTypeKey) { @@ -229,8 +253,6 @@ export default class CustomContent extends Component { const customTemplateTypeKey = this.getFormValue('data_sourceCustom_type'); - // FIXME - data_sourceCustom_type is initialized only after first render - return (
+ + {customTemplateTypeKey && getTypeForm(this, customTemplateTypeKey, true)} {customTemplateTypeKey && getEditForm(this, customTemplateTypeKey, 'data_sourceCustom_')} diff --git a/client/src/lib/form.js b/client/src/lib/form.js index 73c9baf0..c510c1db 100644 --- a/client/src/lib/form.js +++ b/client/src/lib/form.js @@ -712,7 +712,8 @@ class Dropdown extends Component { help: PropTypes.oneOfType([PropTypes.string, PropTypes.object]), options: PropTypes.array, className: PropTypes.string, - format: PropTypes.string + format: PropTypes.string, + disabled: PropTypes.bool } render() { @@ -740,7 +741,7 @@ class Dropdown extends Component { const className = owner.addFormValidationClass('form-control ' + (props.className || '') , id); return wrapInput(id, htmlId, owner, props.format, '', props.label, props.help, - owner.updateFormValue(id, evt.target.value)} disabled={props.disabled}> {options} ); diff --git a/client/src/lib/tree.js b/client/src/lib/tree.js index 7f98e77c..e6267daf 100644 --- a/client/src/lib/tree.js +++ b/client/src/lib/tree.js @@ -33,6 +33,8 @@ class TreeTable extends Component { constructor(props) { super(props); + this.isComponentMounted = false; + this.state = { treeData: [] }; @@ -68,9 +70,11 @@ class TreeTable extends Component { } } - this.setState({ - treeData - }); + if (this.isComponentMounted) { + this.setState({ + treeData + }); + } } static propTypes = { @@ -109,6 +113,8 @@ class TreeTable extends Component { } componentDidMount() { + this.isComponentMounted = true; + if (!this.props.data && this.props.dataUrl) { // noinspection JSIgnoredPromiseFromCall this.loadData(); @@ -221,6 +227,10 @@ class TreeTable extends Component { } } + componentWillUnmount() { + this.isComponentMounted = false; + } + updateSelection() { const tree = this.tree; if (this.selectMode === TreeSelectMode.MULTI) { diff --git a/client/src/templates/CUD.js b/client/src/templates/CUD.js index dd9f5029..16d65b42 100644 --- a/client/src/templates/CUD.js +++ b/client/src/templates/CUD.js @@ -23,7 +23,7 @@ import {withErrorHandling} from '../lib/error-handling'; import {NamespaceSelect, validateNamespace} from '../lib/namespace'; import {ContentModalDialog, DeleteModalDialog} from "../lib/modals"; import mailtrainConfig from 'mailtrainConfig'; -import {getEditForm, getTemplateTypes, getTypeForm} from './helpers'; +import {getEditForm, getTagLanguages, getTemplateTypes, getTypeForm} from './helpers'; import axios from '../lib/axios'; import styles from "../lib/styles.scss"; import {getUrl} from "../lib/urls"; @@ -44,6 +44,7 @@ export default class CUD extends Component { super(props); this.templateTypes = getTemplateTypes(props.t); + this.tagLanguages = getTagLanguages(props.t); this.state = { showMergeTagReference: false, @@ -57,7 +58,8 @@ export default class CUD extends Component { this.initForm({ getPreSubmitUpdater: ::this.getPreSubmitFormValuesUpdater, onChangeBeforeValidation: { - type: ::this.onTypeChanged + type: ::this.onTypeChanged, + tag_language: ::this.onTagLanguageChanged } }); @@ -82,13 +84,21 @@ export default class CUD extends Component { } } + onTagLanguageChanged(mutStateData, key, oldTagLanguage, tagLanguage) { + if (tagLanguage) { + const isEdit = !!this.props.entity; + const type = mutStateData.getIn(['type', 'value']); + this.templateTypes[type].afterTagLanguageChange(mutStateData, isEdit); + } + } + getFormValuesMutator(data) { this.templateTypes[data.type].afterLoad(data); } submitFormValuesMutator(data) { this.templateTypes[data.type].beforeSave(data); - return filterData(data, ['name', 'description', 'type', 'data', 'html', 'text', 'namespace']); + return filterData(data, ['name', 'description', 'type', 'tag_language', 'data', 'html', 'text', 'namespace']); } async getPreSubmitFormValuesUpdater() { @@ -115,6 +125,7 @@ export default class CUD extends Component { description: '', namespace: mailtrainConfig.user.namespace, type: mailtrainConfig.editors[0], + tag_language: mailtrainConfig.tagLanguages[0], fromSourceTemplate: false, sourceTemplate: null, @@ -143,6 +154,10 @@ export default class CUD extends Component { state.setIn(['type', 'error'], t('typeMustBeSelected')); } + if (!state.getIn(['tag_language', 'value'])) { + state.setIn(['tag_language', 'error'], t('Tag language must be selected')); + } + if (state.getIn(['fromSourceTemplate', 'value']) && !state.getIn(['sourceTemplate', 'value'])) { state.setIn(['sourceTemplate', 'error'], t('sourceTemplateMustNotBeEmpty')); } else { @@ -274,6 +289,11 @@ export default class CUD extends Component { typeOptions.push({key, label: this.templateTypes[key].typeName}); } + const tagLanguageOptions = []; + for (const key of mailtrainConfig.tagLanguages) { + tagLanguageOptions.push({key, label: this.tagLanguages[key].name}); + } + const typeKey = this.getFormValue('type'); let editForm = null; @@ -290,8 +310,9 @@ export default class CUD extends Component { { 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') }, + { data: 4, title: t('Tag language'), render: data => this.tagLanguages[data].name }, + { data: 5, title: t('created'), render: data => moment(data).fromNow() }, + { data: 6, title: t('namespace') }, ]; return ( @@ -347,6 +368,8 @@ export default class CUD extends Component { } + + {editForm} diff --git a/client/src/templates/List.js b/client/src/templates/List.js index 511717f7..43582bea 100644 --- a/client/src/templates/List.js +++ b/client/src/templates/List.js @@ -7,11 +7,12 @@ import {LinkButton, requiresAuthenticatedUser, Title, Toolbar, withPageHelpers} import {withAsyncErrorHandler, withErrorHandling} from '../lib/error-handling'; import {Table} from '../lib/table'; import moment from 'moment'; -import {getTemplateTypes} from './helpers'; +import {getTemplateTypes, getTagLanguages} from './helpers'; import {checkPermissions} from "../lib/permissions"; import {tableAddDeleteButton, tableRestActionDialogInit, tableRestActionDialogRender} from "../lib/modals"; import {withComponentMixins} from "../lib/decorator-helpers"; + @withComponentMixins([ withTranslation, withErrorHandling, @@ -23,6 +24,7 @@ export default class List extends Component { super(props); this.templateTypes = getTemplateTypes(props.t); + this.tagLanguages = getTagLanguages(props.t); this.state = {}; tableRestActionDialogInit(this); @@ -63,12 +65,13 @@ export default class List extends Component { { 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') }, + { data: 4, title: t('Tag language'), render: data => this.tagLanguages[data].name }, + { data: 5, title: t('created'), render: data => moment(data).fromNow() }, + { data: 6, title: t('namespace') }, { actions: data => { const actions = []; - const perms = data[6]; + const perms = data[7]; if (perms.includes('edit')) { actions.push({ diff --git a/client/src/templates/helpers.js b/client/src/templates/helpers.js index 83cc88a8..a99f8bdb 100644 --- a/client/src/templates/helpers.js +++ b/client/src/templates/helpers.js @@ -19,12 +19,24 @@ import {getSandboxUrl} from "../lib/urls"; import mailtrainConfig from 'mailtrainConfig'; import {ActionLink, Button} from "../lib/bootstrap-components"; import {Trans} from "react-i18next"; +import {TagLanguages} from "../../../shared/templates"; import styles from "../lib/styles.scss"; export const ResourceType = { TEMPLATE: 'template', CAMPAIGN: 'campaign' +}; + +export function getTagLanguages(t) { + return { + [TagLanguages.SIMPLE]: { + name: t('Simple') + }, + [TagLanguages.HBS]: { + name: t('Handlebars') + } + }; } export function getTemplateTypes(t, prefix = '', entityTypeId = ResourceType.TEMPLATE) { @@ -67,23 +79,29 @@ export function getTemplateTypes(t, prefix = '', entityTypeId = ResourceType.TEM render: data => mosaicoTemplateTypes[data].typeName }, { - data: 5, + data: 6, title: t('namespace') }, ]; templateTypes.mosaico = { typeName: t('mosaico'), - getTypeForm: (owner, isEdit) => - , + getTypeForm: (owner, isEdit) => { + const tagLanguageKey = owner.getFormValue(prefix + 'tag_language'); + if (tagLanguageKey) { + return + } else { + return null; + } + }, getHTMLEditor: owner => @@ -144,6 +162,12 @@ export function getTemplateTypes(t, prefix = '', entityTypeId = ResourceType.TEM afterTypeChange: mutState => { initFieldsIfMissing(mutState, 'mosaico'); }, + afterTagLanguageChange: (mutState, isEdit) => { + if (!isEdit) { + mutState.setIn([prefix + 'mosaicoTemplate', 'value'], null); + } + }, + isTagLanguageSelectorDisabledForEdit: true, validate: state => { const mosaicoTemplate = state.getIn([prefix + 'mosaicoTemplate', 'value']); if (!mosaicoTemplate) { @@ -230,6 +254,9 @@ export function getTemplateTypes(t, prefix = '', entityTypeId = ResourceType.TEM afterTypeChange: mutState => { initFieldsIfMissing(mutState, 'mosaicoWithFsTemplate'); }, + afterTagLanguageChange: (mutState, isEdit) => { + }, + isTagLanguageSelectorDisabledForEdit: false, validate: state => { } }; @@ -317,6 +344,9 @@ export function getTemplateTypes(t, prefix = '', entityTypeId = ResourceType.TEM afterTypeChange: mutState => { initFieldsIfMissing(mutState, 'grapesjs'); }, + afterTagLanguageChange: (mutState, isEdit) => { + }, + isTagLanguageSelectorDisabledForEdit: false, validate: state => { } }; @@ -376,6 +406,9 @@ export function getTemplateTypes(t, prefix = '', entityTypeId = ResourceType.TEM afterTypeChange: mutState => { initFieldsIfMissing(mutState, 'ckeditor4'); }, + afterTagLanguageChange: (mutState, isEdit) => { + }, + isTagLanguageSelectorDisabledForEdit: false, validate: state => { } }; @@ -460,6 +493,9 @@ export function getTemplateTypes(t, prefix = '', entityTypeId = ResourceType.TEM afterTypeChange: mutState => { initFieldsIfMissing(mutState, 'codeeditor'); }, + afterTagLanguageChange: (mutState, isEdit) => { + }, + isTagLanguageSelectorDisabledForEdit: false, validate: state => { } }; diff --git a/client/src/templates/mosaico/CUD.js b/client/src/templates/mosaico/CUD.js index 570e9cd2..f5c4609a 100644 --- a/client/src/templates/mosaico/CUD.js +++ b/client/src/templates/mosaico/CUD.js @@ -20,11 +20,12 @@ import { import {withErrorHandling} from '../../lib/error-handling'; import {NamespaceSelect, validateNamespace} from '../../lib/namespace'; import {DeleteModalDialog} from "../../lib/modals"; - +import mailtrainConfig from 'mailtrainConfig'; import {getMJMLSample, getVersafix} from "../../../../shared/mosaico-templates"; import {getTemplateTypes, getTemplateTypesOrder} from "./helpers"; import {withComponentMixins} from "../../lib/decorator-helpers"; import styles from "../../lib/styles.scss"; +import {getTagLanguages} from "../helpers"; @withComponentMixins([ withTranslation, @@ -38,6 +39,7 @@ export default class CUD extends Component { super(props); this.templateTypes = getTemplateTypes(props.t); + this.tagLanguages = getTagLanguages(props.t); this.typeOptions = []; for (const type of getTemplateTypesOrder()) { @@ -63,8 +65,16 @@ export default class CUD extends Component { } submitFormValuesMutator(data) { + const wizard = this.props.wizard; + + if (wizard === 'versafix') { + data.html = getVersafix(data.tag_language); + } else if (wizard === 'mjml-sample') { + data.mjml = getMJMLSample(data.tag_language); + } + this.templateTypes[data.type].beforeSave(this, data); - return filterData(data, ['name', 'description', 'type', 'data', 'namespace']); + return filterData(data, ['name', 'description', 'type', 'tag_language', 'data', 'namespace']); } componentDidMount() { @@ -80,7 +90,7 @@ export default class CUD extends Component { description: '', namespace: mailtrainConfig.user.namespace, type: 'html', - html: getVersafix() + tag_language: mailtrainConfig.tagLanguages[0] }); } else if (wizard === 'mjml-sample') { @@ -89,7 +99,7 @@ export default class CUD extends Component { description: '', namespace: mailtrainConfig.user.namespace, type: 'mjml', - mjml: getMJMLSample() + tag_language: mailtrainConfig.tagLanguages[0] }); } else { @@ -98,6 +108,7 @@ export default class CUD extends Component { description: '', namespace: mailtrainConfig.user.namespace, type: 'html', + tag_language: mailtrainConfig.tagLanguages[0], html: '' }); } @@ -119,6 +130,12 @@ export default class CUD extends Component { state.setIn(['type', 'error'], null); } + if (!state.getIn(['tag_language', 'value'])) { + state.setIn(['tag_language', 'error'], t('Tag language must be selected')); + } else { + state.setIn(['tag_language', 'error'], null); + } + validateNamespace(t, state); } @@ -169,6 +186,11 @@ export default class CUD extends Component { const typeKey = this.getFormValue('type'); + const tagLanguageOptions = []; + for (const key of mailtrainConfig.tagLanguages) { + tagLanguageOptions.push({key, label: this.tagLanguages[key].name}); + } + return (
{canDelete && @@ -194,6 +216,9 @@ export default class CUD extends Component { : } + + + {isEdit && typeKey && this.templateTypes[typeKey].getForm(this)} diff --git a/client/src/templates/mosaico/List.js b/client/src/templates/mosaico/List.js index b7a29ea7..5ea9dde9 100644 --- a/client/src/templates/mosaico/List.js +++ b/client/src/templates/mosaico/List.js @@ -8,6 +8,7 @@ import {withAsyncErrorHandler, withErrorHandling} from '../../lib/error-handling import {Table} from '../../lib/table'; import moment from 'moment'; import {getTemplateTypes} from './helpers'; +import {getTagLanguages} from '../helpers'; import {checkPermissions} from "../../lib/permissions"; import {tableAddDeleteButton, tableRestActionDialogInit, tableRestActionDialogRender} from "../../lib/modals"; import {withComponentMixins} from "../../lib/decorator-helpers"; @@ -24,6 +25,7 @@ export default class List extends Component { super(props); this.templateTypes = getTemplateTypes(props.t); + this.tagLanguages = getTagLanguages(props.t); this.state = {}; tableRestActionDialogInit(this); @@ -55,12 +57,13 @@ export default class List extends Component { { 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') }, + { data: 4, title: t('Tag language'), render: data => this.tagLanguages[data].name }, + { data: 5, title: t('created'), render: data => moment(data).fromNow() }, + { data: 6, title: t('namespace') }, { actions: data => { const actions = []; - const perms = data[6]; + const perms = data[7]; if (perms.includes('edit')) { actions.push({ diff --git a/client/src/templates/mosaico/helpers.js b/client/src/templates/mosaico/helpers.js index 07b8321b..7f7a2f75 100644 --- a/client/src/templates/mosaico/helpers.js +++ b/client/src/templates/mosaico/helpers.js @@ -20,9 +20,7 @@ export function getTemplateTypes(t) { return owner.getFormValue('mjml') || ''; } - function generateHtmlFromMjml(owner) { - const mjml = getMjml(owner); - + function generateHtmlFromMjml(mjml) { try { const res = mjml2html(mjml); return res.html; @@ -88,7 +86,7 @@ export function getTemplateTypes(t) { typeName: t('mjml'), getForm: owner => ( <> - generateHtmlFromMjml(owner)} onHide={() => setExportModalVisibility(owner, false)}/> + generateHtmlFromMjml(getMjml(owner))} onHide={() => setExportModalVisibility(owner, false)}/> ), @@ -98,7 +96,7 @@ export function getTemplateTypes(t) { beforeSave: (owner, data) => { data.data = { mjml: data.mjml, - html: generateHtmlFromMjml(owner) + html: generateHtmlFromMjml(data.mjml) }; clearBeforeSend(data); diff --git a/server/config/default.yaml b/server/config/default.yaml index f400f2ef..127223c6 100644 --- a/server/config/default.yaml +++ b/server/config/default.yaml @@ -36,6 +36,11 @@ editors: - ckeditor4 - codeeditor +# Enabled tag languages +tagLanguages: +- simple # e.g. [FIRST_NAME] - this the style of merge tags found in Mailtrain v1 +- hbs # e.g. {{#if FIRST_NAME}}Hello {{firstName}}!{{else}}Hello!{{/if}} - this syntax uses Handlebars templating language (http://handlebarsjs.com) + # Default language to use defaultLanguage: en-US diff --git a/server/lib/client-helpers.js b/server/lib/client-helpers.js index ebf0c3ca..4bf6a766 100644 --- a/server/lib/client-helpers.js +++ b/server/lib/client-helpers.js @@ -44,6 +44,7 @@ async function getAuthenticatedConfig(context) { }, globalPermissions, editors: config.editors, + tagLanguages: config.tagLanguages, mosaico: config.mosaico, verpEnabled: config.verp.enabled, reportsEnabled: config.reports.enabled, diff --git a/server/models/campaigns.js b/server/models/campaigns.js index 7a06731d..5a9c2689 100644 --- a/server/models/campaigns.js +++ b/server/models/campaigns.js @@ -10,6 +10,7 @@ const shares = require('./shares'); const namespaceHelpers = require('../lib/namespace-helpers'); const files = require('./files'); const templates = require('./templates'); +const { allTagLanguages } = require('../../shared/templates'); const { CampaignStatus, CampaignSource, CampaignType, getSendConfigurationPermissionRequiredForSend } = require('../../shared/campaigns'); const sendConfigurations = require('./send-configurations'); const triggers = require('./triggers'); @@ -445,6 +446,10 @@ async function _validateAndPreprocess(tx, context, entity, isCreate, content) { await shares.enforceEntityPermissionTx(tx, context, 'sendConfiguration', entity.send_configuration, 'viewPublic'); } + + if ((isCreate && entity.source === CampaignSource.CUSTOM) || (content === Content.ONLY_SOURCE_CUSTOM)) { + enforce(allTagLanguages.includes(entity.data.sourceCustom.tag_language), `Invalid tag language '${entity.data.sourceCustom.tag_language}'`); + } } async function _createTx(tx, context, entity, content) { @@ -462,6 +467,7 @@ async function _createTx(tx, context, entity, content) { entity.data.sourceCustom = { type: template.type, + tag_language: template.tag_language, data: template.data, html: template.html, text: template.text diff --git a/server/models/mosaico-templates.js b/server/models/mosaico-templates.js index 4f3f5df9..db9055f9 100644 --- a/server/models/mosaico-templates.js +++ b/server/models/mosaico-templates.js @@ -9,8 +9,9 @@ const namespaceHelpers = require('../lib/namespace-helpers'); const shares = require('./shares'); const files = require('./files'); const dependencyHelpers = require('../lib/dependency-helpers'); +const { allTagLanguages } = require('../../shared/templates'); -const allowedKeys = new Set(['name', 'description', 'type', 'data', 'namespace']); +const allowedKeys = new Set(['name', 'description', 'type', 'tag_language', 'data', 'namespace']); function hash(entity) { return hasher.hash(filterObject(entity, allowedKeys)); @@ -32,11 +33,25 @@ async function listDTAjax(context, params) { [{ entityTypeId: 'mosaicoTemplate', requiredOperations: ['view'] }], params, builder => builder.from('mosaico_templates').innerJoin('namespaces', 'namespaces.id', 'mosaico_templates.namespace'), - [ 'mosaico_templates.id', 'mosaico_templates.name', 'mosaico_templates.description', 'mosaico_templates.type', 'mosaico_templates.created', 'namespaces.name' ] + [ 'mosaico_templates.id', 'mosaico_templates.name', 'mosaico_templates.description', 'mosaico_templates.type', 'mosaico_templates.tag_language', 'mosaico_templates.created', 'namespaces.name' ] + ); +} + +async function listByTagLanguageDTAjax(context, tagLanguage, params) { + return await dtHelpers.ajaxListWithPermissions( + context, + [{ entityTypeId: 'mosaicoTemplate', requiredOperations: ['view'] }], + params, + builder => builder.from('mosaico_templates') + .innerJoin('namespaces', 'namespaces.id', 'mosaico_templates.namespace') + .where('mosaico_templates.tag_language', tagLanguage), + [ 'mosaico_templates.id', 'mosaico_templates.name', 'mosaico_templates.description', 'mosaico_templates.type', 'mosaico_templates.tag_language', 'mosaico_templates.created', 'namespaces.name' ] ); } async function _validateAndPreprocess(tx, entity) { + enforce(allTagLanguages.includes(entity.tag_language), `Invalid tag language '${entity.tag_language}'`); + entity.data = JSON.stringify(entity.data); await namespaceHelpers.validateEntity(tx, entity); } @@ -119,6 +134,7 @@ async function remove(context, id) { module.exports.hash = hash; module.exports.getById = getById; module.exports.listDTAjax = listDTAjax; +module.exports.listByTagLanguageDTAjax = listByTagLanguageDTAjax; module.exports.create = create; module.exports.updateWithConsistencyCheck = updateWithConsistencyCheck; module.exports.remove = remove; diff --git a/server/models/templates.js b/server/models/templates.js index cd7c22ca..f957aaaa 100644 --- a/server/models/templates.js +++ b/server/models/templates.js @@ -14,11 +14,11 @@ const {convertFileURLs} = require('../lib/campaign-content'); const mailers = require('../lib/mailers'); const tools = require('../lib/tools'); const sendConfigurations = require('./send-configurations'); -const { getMergeTagsForBases } = require('../../shared/templates'); +const { getMergeTagsForBases, allTagLanguages } = require('../../shared/templates'); const { getTrustedUrl, getSandboxUrl, getPublicUrl } = require('../lib/urls'); const htmlToText = require('html-to-text'); -const allowedKeys = new Set(['name', 'description', 'type', 'data', 'html', 'text', 'namespace']); +const allowedKeys = new Set(['name', 'description', 'type', 'tag_language', 'data', 'html', 'text', 'namespace']); function hash(entity) { return hasher.hash(filterObject(entity, allowedKeys)); @@ -54,7 +54,7 @@ async function _listDTAjax(context, namespaceId, params) { } return builder; }, - [ 'templates.id', 'templates.name', 'templates.description', 'templates.type', 'templates.created', 'namespaces.name' ] + [ 'templates.id', 'templates.name', 'templates.description', 'templates.type', 'templates.tag_language', 'templates.created', 'namespaces.name' ] ); } @@ -69,6 +69,8 @@ async function listByNamespaceDTAjax(context, namespaceId, params) { async function _validateAndPreprocess(tx, entity) { await namespaceHelpers.validateEntity(tx, entity); + enforce(allTagLanguages.includes(entity.tag_language), `Invalid tag language '${entity.tag_language}'`); + // We don't check contents of the "data" because it is processed solely on the client. The client generates the HTML code we use when sending out campaigns. entity.data = JSON.stringify(entity.data); @@ -82,6 +84,7 @@ async function create(context, entity) { const template = await getByIdTx(tx, context, entity.sourceTemplate, false); entity.type = template.type; + entity.tag_language = template.tag_language; entity.data = template.data; entity.html = template.html; entity.text = template.text; diff --git a/server/routes/rest/mosaico-templates.js b/server/routes/rest/mosaico-templates.js index e55cae54..52bd2357 100644 --- a/server/routes/rest/mosaico-templates.js +++ b/server/routes/rest/mosaico-templates.js @@ -33,4 +33,9 @@ router.postAsync('/mosaico-templates-table', passport.loggedIn, async (req, res) return res.json(await mosaicoTemplates.listDTAjax(req.context, req.body)); }); +router.postAsync('/mosaico-templates-by-tag-language-table/:tagLanguage', passport.loggedIn, async (req, res) => { + return res.json(await mosaicoTemplates.listByTagLanguageDTAjax(req.context, req.params.tagLanguage, req.body)); +}); + + module.exports = router; \ No newline at end of file diff --git a/server/setup/knex/migrations/20170506102634_v1_to_v2.js b/server/setup/knex/migrations/20170506102634_v1_to_v2.js index 9e1c2b45..41f9b127 100644 --- a/server/setup/knex/migrations/20170506102634_v1_to_v2.js +++ b/server/setup/knex/migrations/20170506102634_v1_to_v2.js @@ -2,6 +2,7 @@ const { CampaignSource, CampaignType} = require('../../../../shared/campaigns'); const files = require('../../../models/files'); const contextHelpers = require('../../../lib/context-helpers'); const mosaicoTemplates = require('../../../../shared/mosaico-templates'); +const {TagLanguages} = require('../../../../shared/templates'); const {getGlobalNamespaceId} = require('../../../../shared/namespaces'); const {getAdminId} = require('../../../../shared/users'); const { MailerType, ZoneMTAType, getSystemSendConfigurationId, getSystemSendConfigurationCid } = require('../../../../shared/send-configurations'); @@ -905,7 +906,7 @@ async function addMosaicoTemplates(knex) { type: 'html', namespace: 1, data: JSON.stringify({ - html: mosaicoTemplates.getVersafix() + html: mosaicoTemplates.getVersafix(TagLanguages.SIMPLE) }) }; diff --git a/server/setup/knex/migrations/20190629170000_generalization_of_queued.js b/server/setup/knex/migrations/20190629170000_generalization_of_queued.js index ef8dee76..86be168c 100644 --- a/server/setup/knex/migrations/20190629170000_generalization_of_queued.js +++ b/server/setup/knex/migrations/20190629170000_generalization_of_queued.js @@ -7,7 +7,7 @@ exports.up = (knex, Promise) => (async() => { data.listId = queuedEntry.list; data.subscriptionId = queuedEntry.subscription; - knex('queued') + await knex('queued') .where('id', queuedEntry.id) .update({ data: JSON.stringify(data) diff --git a/server/setup/knex/migrations/20190630210000_tag_language.js b/server/setup/knex/migrations/20190630210000_tag_language.js new file mode 100644 index 00000000..4feddca7 --- /dev/null +++ b/server/setup/knex/migrations/20190630210000_tag_language.js @@ -0,0 +1,41 @@ +const { CampaignSource } = require('../../../../shared/campaigns'); +const { TagLanguages } = require('../../../../shared/templates'); + +exports.up = (knex, Promise) => (async() => { + await knex.schema.table('templates', table => { + table.string('tag_language', 48); + }); + + await knex('templates').update({ + tag_language: 'simple' + }); + + await knex.schema.table('templates', table => { + table.string('tag_language', 48).notNullable().index().alter(); + }); + + + await knex.schema.table('mosaico_templates', table => { + table.string('tag_language', 48); + }); + + await knex('mosaico_templates').update({ + tag_language: TagLanguages.SIMPLE + }); + + await knex.schema.table('mosaico_templates', table => { + table.string('tag_language', 48).notNullable().index().alter(); + }); + + const rows = await knex('campaigns').whereIn('source', [CampaignSource.CUSTOM, CampaignSource.CUSTOM_FROM_CAMPAIGN, CampaignSource.CUSTOM_FROM_TEMPLATE]); + for (const row of rows) { + const data = JSON.parse(row.data); + + data.sourceCustom.tag_language = TagLanguages.SIMPLE; + + await knex('campaigns').where('id', row.id).update({data: JSON.stringify(data)}); + } +})(); + +exports.down = (knex, Promise) => (async() => { +})(); diff --git a/shared/mosaico-templates.js b/shared/mosaico-templates.js index 8f18f5e5..eba1423b 100644 --- a/shared/mosaico-templates.js +++ b/shared/mosaico-templates.js @@ -1,1869 +1,1881 @@ 'use strict'; -const versafix = '\n' + - '\n' + - '\n' + - ' \n' + - ' \n' + - ' \n' + - ' TITLE\n' + - ' \n' + - ' \n' + - ' \n' + - ' \n' + - ' \n' + - '\n' + - '\n' + - '
\n' + - '\n' + - '
\n' + - '\n' + - ' \n' + - ' \n' + - ' \n' + - ' \n' + - ' \n' + - ' \n' + - ' \n' + - '\n' + - '
\n' + - '\n' + - '
\n' + - '\n' + - ' \n' + - ' \n' + - ' \n' + - ' \n' + - ' \n' + - ' \n' + - ' \n' + - '\n' + - ' \n' + - ' \n' + - ' \n' + - ' \n' + - ' \n' + - ' \n' + - ' \n' + - '\n' + - ' \n' + - ' \n' + - ' \n' + - ' \n' + - ' \n' + - ' \n' + - ' \n' + - '\n' + - ' \n' + - ' \n' + - ' \n' + - ' \n' + - ' \n' + - ' \n' + - ' \n' + - '\n' + - ' \n' + - ' \n' + - ' \n' + - ' \n' + - ' \n' + - ' \n' + - ' \n' + - '\n' + - ' \n' + - ' \n' + - ' \n' + - ' \n' + - ' \n' + - ' \n' + - ' \n' + - '\n' + - ' \n' + - ' \n' + - ' \n' + - ' \n' + - ' \n' + - ' \n' + - ' \n' + - '\n' + - ' \n' + - ' \n' + - ' \n' + - ' \n' + - ' \n' + - ' \n' + - ' \n' + - '\n' + - ' \n' + - ' \n' + - ' \n' + - ' \n' + - ' \n' + - ' \n' + - ' \n' + - '\n' + - ' \n' + - ' \n' + - ' \n' + - ' \n' + - ' \n' + - ' \n' + - ' \n' + - '\n' + - ' \n' + - ' \n' + - ' \n' + - ' \n' + - ' \n' + - ' \n' + - ' \n' + - '\n' + - ' \n' + - ' \n' + - ' \n' + - ' \n' + - ' \n' + - ' \n' + - ' \n' + - '\n' + - ' \n' + - ' \n' + - ' \n' + - ' \n' + - ' \n' + - ' \n' + - ' \n' + - '\n' + - ' \n' + - ' \n' + - ' \n' + - ' \n' + - ' \n' + - ' \n' + - ' \n' + - '\n' + - ' \n' + - ' \n' + - ' \n' + - ' \n' + - ' \n' + - ' \n' + - ' \n' + - '\n' + - '\n' + - ' \n' + - ' \n' + - ' \n' + - ' \n' + - ' \n' + - ' \n' + - ' \n' + - '\n' + - '
\n' + - '\n' + - ' \n' + - ' \n' + - ' \n' + - ' \n' + - ' \n' + - ' \n' + - ' \n' + - '
'; +const {TagLanguages} = require('./templates'); +function renderTag(tagLanguage, tag) { + if (tagLanguage === TagLanguages.SIMPLE) { + return `[${tag}]`; + } else if (tagLanguage === TagLanguages.HBS) { + return `{{${tag}}}`; + } +} -const mjmlSample = '\n' + - ' \n' + - ' \n' + - ' \n' + - ' \n' + - ' \n' + - ' \n' + - ' \n' + - ' \n' + - ' \n' + - ' \n' + - '\n' + - ' \n' + - '\n' + - ' \n' + - ' \n' + - ' \n' + - '\n' + - ' \n' + - '\n' + - ' \n' + - ' \n' + - ' \n' + - ' \n' + - ' \n' + - ' \n' + - ' \n' + - ' \n' + - ' \n' + - ' \n' + - ' \n' + - ' \n' + - ' \n' + - ' \n' + - '\n' + - ' \n' + - ' \n' + - ' \n' + - ' p {\n' + - ' font-family: Ubuntu, Helvetica, Arial, sans-serif, Helvetica, Arial, sans-serif;\n' + - ' font-size: 12px;\n' + - ' color: #9da3a3;\n' + - ' margin-top: 8px;\n' + - ' }\n' + - ' \n' + - ' h2 {\n' + - ' margin: 5px 0px 0px 0px;\n' + - ' font-size: 15px;\n' + - ' font-weight: normal;\n' + - ' }\n' + - ' \n' + - ' .header a {\n' + - ' text-decoration: none;\n' + - ' color: white;\n' + - ' }\n' + - ' \n' + - ' .feature p {\n' + - ' text-align: center;\n' + - ' }\n' + - ' \n' + - ' .feature h2 {\n' + - ' color: #e85034;\n' + - ' text-align: center;\n' + - ' }\n' + - ' \n' + - ' .divider h2 {\n' + - ' color: white;\n' + - ' text-align: center;\n' + - ' }\n' + - ' \n' + - ' .divider p {\n' + - ' color: #f8d5d1;\n' + - ' text-align: center;\n' + - ' }\n' + - '\n' + - ' .article h2 {\n' + - ' font-weight: bold;\n' + - ' margin-top: 10px;\n' + - ' }\n' + - ' \n' + - ' .article p {\n' + - ' color: #45474e;\n' + - ' }\n' + - '\n' + - ' .footer a {\n' + - ' color: #3A3A3A;\n' + - ' }\n' + - '\n' + - ' .footer p {\n' + - ' font-family: arial,helvetica neue,helvetica,sans-serif;\n' + - ' font-size: 12px;\n' + - ' text-align: center;\n' + - ' }\n' + - ' \n' + - ' .caption p {\n' + - ' font-size: 10px;\n' + - ' text-align: center;\n' + - ' margin-top: 0px;\n' + - ' }\n' + - ' \n' + - ' \n' + - ' \n' + - ' \n' + - ' \n' + - ' \n' + - ' \n' + - ' Subscribe\n' + - ' \n' + - ' \n' + - ' View in browser\n' + - ' \n' + - ' \n' + - ' \n' + - ' \n' + - ' \n' + - ' \n' + - ' \n' + - ' \n' + - ' \n' + - ' \n' + - '\n' + - ' \n' + - ' \n' + - ' \n' + - ' \n' + - ' \n' + - ' \n' + - ' \n' + - '

Lorem ipsum dolor

\n' + - '
\n' + - '

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque eleifend sagittis nunc, et fermentum est ullamcorper dignissim.

\n' + - '
\n' + - '
\n' + - '
\n' + - ' \n' + - ' \n' + - ' \n' + - '

Lorem ipsum dolor

\n' + - '
\n' + - '

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque eleifend sagittis nunc, et fermentum est ullamcorper dignissim.

\n' + - '
\n' + - '
\n' + - '
\n' + - ' \n' + - ' \n' + - ' \n' + - '

Lorem ipsum dolor

\n' + - '
\n' + - '

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque eleifend sagittis nunc, et fermentum est ullamcorper dignissim.

\n' + - '
\n' + - '
\n' + - '
\n' + - '
\n' + - '
\n' + - '\n' + - ' \n' + - ' \n' + - ' \n' + - ' \n' + - '

Lorem ipsum dolor

\n' + - '
\n' + - ' \n' + - ' \n' + - '
\n' + - '

Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris. Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris.

\n' + - '
\n' + - '
\n' + - '
\n' + - '
\n' + - '
\n' + - ' \n' + - ' \n' + - ' \n' + - ' \n' + - ' \n' + - ' \n' + - ' \n' + - '
\n' + - '
\n' + - '
\n' + - '
\n' + - ' \n' + - ' \n' + - '

Lorem ipsum dolor

\n' + - '
\n' + - '

Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris. Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris.

\n' + - '
\n' + - '
\n' + - ' Read more ...\n' + - '
\n' + - '
\n' + - '
\n' + - '\n' + - ' \n' + - ' \n' + - ' \n' + - ' \n' + - '
\n' + - '

Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris. Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris.

\n' + - '
\n' + - '
\n' + - '
\n' + - '
\n' + - '
\n' + - ' \n' + - ' \n' + - ' \n' + - ' \n' + - ' \n' + - ' \n' + - ' \n' + - '
\n' + - '
\n' + - '
\n' + - '
\n' + - '
\n' + - '
\n' + - ' \n' + - ' \n' + - ' \n' + - ' \n' + - ' \n' + - ' \n' + - ' \n' + - '
\n' + - '
\n' + - '
\n' + - '
\n' + - ' \n' + - ' \n' + - ' \n' + - ' \n' + - '
\n' + - '
\n' + - '
\n' + - '
\n' + - '
\n' + - '
\n' + - ' \n' + - '
\n' + - '\n' + - ' \n' + - ' \n' + - ' \n' + - ' Share on Facebook\n' + - ' Tweet\n' + - ' \n' + - ' \n' + - ' \n' + - ' \n' + - ' \n' + - ' \n' + - ' \n' + - '

This email was sent to [EMAIL]

\n' + - '

  Unsubscribe from this list     Update subscription preferences  

\n' + - '

Your address XXXXXX

\n' + - '
\n' + - '
\n' + - '
\n' + - ' \n' + - '
\n' + - '
'; +function getVersafix(tagLanguage) { + const tg = tag => renderTag(tagLanguage, tag); + const versafix = '\n' + + '\n' + + '\n' + + ' \n' + + ' \n' + + ' \n' + + ' TITLE\n' + + ' \n' + + ' \n' + + ' \n' + + ' \n' + + ' \n' + + '\n' + + '\n' + + '
\n' + + '\n' + + '
\n' + + '\n' + + ' \n' + + ' \n' + + ' \n' + + ' \n' + + ' \n' + + ' \n' + + ' \n' + + '\n' + + '
\n' + + '\n' + + '
\n' + + '\n' + + ' \n' + + ' \n' + + ' \n' + + ' \n' + + ' \n' + + ' \n' + + ' \n' + + '\n' + + ' \n' + + ' \n' + + ' \n' + + ' \n' + + ' \n' + + ' \n' + + ' \n' + + '\n' + + ' \n' + + ' \n' + + ' \n' + + ' \n' + + ' \n' + + ' \n' + + ' \n' + + '\n' + + ' \n' + + ' \n' + + ' \n' + + ' \n' + + ' \n' + + ' \n' + + ' \n' + + '\n' + + ' \n' + + ' \n' + + ' \n' + + ' \n' + + ' \n' + + ' \n' + + ' \n' + + '\n' + + ' \n' + + ' \n' + + ' \n' + + ' \n' + + ' \n' + + ' \n' + + ' \n' + + '\n' + + ' \n' + + ' \n' + + ' \n' + + ' \n' + + ' \n' + + ' \n' + + ' \n' + + '\n' + + ' \n' + + ' \n' + + ' \n' + + ' \n' + + ' \n' + + ' \n' + + ' \n' + + '\n' + + ' \n' + + ' \n' + + ' \n' + + ' \n' + + ' \n' + + ' \n' + + ' \n' + + '\n' + + ' \n' + + ' \n' + + ' \n' + + ' \n' + + ' \n' + + ' \n' + + ' \n' + + '\n' + + ' \n' + + ' \n' + + ' \n' + + ' \n' + + ' \n' + + ' \n' + + ' \n' + + '\n' + + ' \n' + + ' \n' + + ' \n' + + ' \n' + + ' \n' + + ' \n' + + ' \n' + + '\n' + + ' \n' + + ' \n' + + ' \n' + + ' \n' + + ' \n' + + ' \n' + + ' \n' + + '\n' + + ' \n' + + ' \n' + + ' \n' + + ' \n' + + ' \n' + + ' \n' + + ' \n' + + '\n' + + ' \n' + + ' \n' + + ' \n' + + ' \n' + + ' \n' + + ' \n' + + ' \n' + + '\n' + + '\n' + + ' \n' + + ' \n' + + ' \n' + + ' \n' + + ' \n' + + ' \n' + + ' \n' + + '\n' + + '
\n' + + '\n' + + ' \n' + + ' \n' + + ' \n' + + ' \n' + + ' \n' + + ' \n' + + ' \n' + + '
'; -function getVersafix() { return versafix; } -function getMJMLSample() { +function getMJMLSample(tagLanguage) { + const tg = tag => renderTag(tagLanguage, tag); + + const mjmlSample = '\n' + + ' \n' + + ' \n' + + ' \n' + + ' \n' + + ' \n' + + ' \n' + + ' \n' + + ' \n' + + ' \n' + + ' \n' + + '\n' + + ' \n' + + '\n' + + ' \n' + + ' \n' + + ' \n' + + '\n' + + ' \n' + + '\n' + + ' \n' + + ' \n' + + ' \n' + + ' \n' + + ' \n' + + ' \n' + + ' \n' + + ' \n' + + ' \n' + + ' \n' + + ' \n' + + ' \n' + + ' \n' + + ' \n' + + '\n' + + ' \n' + + ' \n' + + ' \n' + + ' p {\n' + + ' font-family: Ubuntu, Helvetica, Arial, sans-serif, Helvetica, Arial, sans-serif;\n' + + ' font-size: 12px;\n' + + ' color: #9da3a3;\n' + + ' margin-top: 8px;\n' + + ' }\n' + + ' \n' + + ' h2 {\n' + + ' margin: 5px 0px 0px 0px;\n' + + ' font-size: 15px;\n' + + ' font-weight: normal;\n' + + ' }\n' + + ' \n' + + ' .header a {\n' + + ' text-decoration: none;\n' + + ' color: white;\n' + + ' }\n' + + ' \n' + + ' .feature p {\n' + + ' text-align: center;\n' + + ' }\n' + + ' \n' + + ' .feature h2 {\n' + + ' color: #e85034;\n' + + ' text-align: center;\n' + + ' }\n' + + ' \n' + + ' .divider h2 {\n' + + ' color: white;\n' + + ' text-align: center;\n' + + ' }\n' + + ' \n' + + ' .divider p {\n' + + ' color: #f8d5d1;\n' + + ' text-align: center;\n' + + ' }\n' + + '\n' + + ' .article h2 {\n' + + ' font-weight: bold;\n' + + ' margin-top: 10px;\n' + + ' }\n' + + ' \n' + + ' .article p {\n' + + ' color: #45474e;\n' + + ' }\n' + + '\n' + + ' .footer a {\n' + + ' color: #3A3A3A;\n' + + ' }\n' + + '\n' + + ' .footer p {\n' + + ' font-family: arial,helvetica neue,helvetica,sans-serif;\n' + + ' font-size: 12px;\n' + + ' text-align: center;\n' + + ' }\n' + + ' \n' + + ' .caption p {\n' + + ' font-size: 10px;\n' + + ' text-align: center;\n' + + ' margin-top: 0px;\n' + + ' }\n' + + ' \n' + + ' \n' + + ' \n' + + ' \n' + + ' \n' + + ' \n' + + ' \n' + + ' Subscribe\n' + + ' \n' + + ' \n' + + ' View in browser\n' + + ' \n' + + ' \n' + + ' \n' + + ' \n' + + ' \n' + + ' \n' + + ' \n' + + ' \n' + + ' \n' + + ' \n' + + '\n' + + ' \n' + + ' \n' + + ' \n' + + ' \n' + + ' \n' + + ' \n' + + ' \n' + + '

Lorem ipsum dolor

\n' + + '
\n' + + '

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque eleifend sagittis nunc, et fermentum est ullamcorper dignissim.

\n' + + '
\n' + + '
\n' + + '
\n' + + ' \n' + + ' \n' + + ' \n' + + '

Lorem ipsum dolor

\n' + + '
\n' + + '

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque eleifend sagittis nunc, et fermentum est ullamcorper dignissim.

\n' + + '
\n' + + '
\n' + + '
\n' + + ' \n' + + ' \n' + + ' \n' + + '

Lorem ipsum dolor

\n' + + '
\n' + + '

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque eleifend sagittis nunc, et fermentum est ullamcorper dignissim.

\n' + + '
\n' + + '
\n' + + '
\n' + + '
\n' + + '
\n' + + '\n' + + ' \n' + + ' \n' + + ' \n' + + ' \n' + + '

Lorem ipsum dolor

\n' + + '
\n' + + ' \n' + + ' \n' + + '
\n' + + '

Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris. Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris.

\n' + + '
\n' + + '
\n' + + '
\n' + + '
\n' + + '
\n' + + ' \n' + + ' \n' + + ' \n' + + ' \n' + + ' \n' + + ' \n' + + ' \n' + + '
\n' + + '
\n' + + '
\n' + + '
\n' + + ' \n' + + ' \n' + + '

Lorem ipsum dolor

\n' + + '
\n' + + '

Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris. Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris.

\n' + + '
\n' + + '
\n' + + ' Read more ...\n' + + '
\n' + + '
\n' + + '
\n' + + '\n' + + ' \n' + + ' \n' + + ' \n' + + ' \n' + + '
\n' + + '

Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris. Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris.

\n' + + '
\n' + + '
\n' + + '
\n' + + '
\n' + + '
\n' + + ' \n' + + ' \n' + + ' \n' + + ' \n' + + ' \n' + + ' \n' + + ' \n' + + '
\n' + + '
\n' + + '
\n' + + '
\n' + + '
\n' + + '
\n' + + ' \n' + + ' \n' + + ' \n' + + ' \n' + + ' \n' + + ' \n' + + ' \n' + + '
\n' + + '
\n' + + '
\n' + + '
\n' + + ' \n' + + ' \n' + + ' \n' + + ' \n' + + '
\n' + + '
\n' + + '
\n' + + '
\n' + + '
\n' + + '
\n' + + ' \n' + + '
\n' + + '\n' + + ' \n' + + ' \n' + + ' \n' + + ' Share on Facebook\n' + + ' Tweet\n' + + ' \n' + + ' \n' + + ' \n' + + ' \n' + + ' \n' + + ' \n' + + ' \n' + + '

This email was sent to ' + tg('EMAIL') + '

\n' + + '

  Unsubscribe from this list     Update subscription preferences  

\n' + + '

Your address XXXXXX

\n' + + '
\n' + + '
\n' + + '
\n' + + ' \n' + + '
\n' + + '
'; + return mjmlSample; } diff --git a/shared/templates.js b/shared/templates.js index 796a923b..7df28cc4 100644 --- a/shared/templates.js +++ b/shared/templates.js @@ -1,5 +1,12 @@ 'use strict'; +const TagLanguages = { + SIMPLE: 'simple', + HBS: 'hbs' +}; + +const allTagLanguages = [TagLanguages.SIMPLE, TagLanguages.HBS]; + function _getBases(trustedBaseUrl, sandboxBaseUrl, publicBaseUrl) { if (trustedBaseUrl.endsWith('/')) { trustedBaseUrl = trustedBaseUrl.substring(0, trustedBaseUrl.length - 1); @@ -58,5 +65,7 @@ function unbase(text, trustedBaseUrl, sandboxBaseUrl, publicBaseUrl, treatAllAsP module.exports = { base, unbase, - getMergeTagsForBases + getMergeTagsForBases, + TagLanguages, + allTagLanguages }; \ No newline at end of file