Merge branch 'pull/637' into integration-637
# Conflicts: # client/src/lists/forms/CUD.js
This commit is contained in:
commit
6eeef7a991
7 changed files with 126 additions and 54 deletions
|
@ -10,6 +10,7 @@ import {
|
||||||
AlignedRow,
|
AlignedRow,
|
||||||
Button,
|
Button,
|
||||||
ButtonRow,
|
ButtonRow,
|
||||||
|
CheckBox,
|
||||||
Dropdown,
|
Dropdown,
|
||||||
Fieldset,
|
Fieldset,
|
||||||
filterData,
|
filterData,
|
||||||
|
@ -45,7 +46,9 @@ export default class CUD extends Component {
|
||||||
|
|
||||||
this.state = {
|
this.state = {
|
||||||
previewContents: null,
|
previewContents: null,
|
||||||
previewFullscreen: false
|
previewFullscreen: false,
|
||||||
|
fromSourceCustomForms: false,
|
||||||
|
sourceCustomForms: null,
|
||||||
};
|
};
|
||||||
|
|
||||||
this.serverValidatedFields = [
|
this.serverValidatedFields = [
|
||||||
|
@ -330,6 +333,8 @@ export default class CUD extends Component {
|
||||||
const data = {
|
const data = {
|
||||||
name: '',
|
name: '',
|
||||||
description: '',
|
description: '',
|
||||||
|
fromSourceCustomForms: false,
|
||||||
|
sourceCustomForms: null,
|
||||||
selectedTemplate: 'layout',
|
selectedTemplate: 'layout',
|
||||||
namespace: mailtrainConfig.user.namespace
|
namespace: mailtrainConfig.user.namespace
|
||||||
};
|
};
|
||||||
|
@ -350,6 +355,12 @@ export default class CUD extends Component {
|
||||||
|
|
||||||
validateNamespace(t, state);
|
validateNamespace(t, state);
|
||||||
|
|
||||||
|
if (state.getIn(['fromSourceCustomForms', 'value']) && !state.getIn(['sourceCustomForms', 'value'])) {
|
||||||
|
state.setIn(['sourceCustomForms', 'error'], t('sourceCustomFormsMustNotBeEmpty'));
|
||||||
|
} else {
|
||||||
|
state.setIn(['sourceCustomForms', 'error'], null);
|
||||||
|
}
|
||||||
|
|
||||||
let formsServerValidationRunning = false;
|
let formsServerValidationRunning = false;
|
||||||
const formsErrors = [];
|
const formsErrors = [];
|
||||||
|
|
||||||
|
@ -386,10 +397,14 @@ export default class CUD extends Component {
|
||||||
let sendMethod, url;
|
let sendMethod, url;
|
||||||
if (this.props.entity) {
|
if (this.props.entity) {
|
||||||
sendMethod = FormSendMethod.PUT;
|
sendMethod = FormSendMethod.PUT;
|
||||||
url = `rest/forms/${this.props.entity.id}`
|
url = `rest/forms/${this.props.entity.id}`;
|
||||||
} else {
|
} else {
|
||||||
sendMethod = FormSendMethod.POST;
|
sendMethod = FormSendMethod.POST;
|
||||||
url = 'rest/forms'
|
if (this.getFormValue('sourceCustomForms') !== null) {
|
||||||
|
url = `rest/forms/${this.getFormValue('sourceCustomForms')}`;
|
||||||
|
} else {
|
||||||
|
url = 'rest/forms';
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this.disableForm();
|
this.disableForm();
|
||||||
|
@ -456,6 +471,12 @@ export default class CUD extends Component {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const customFormsColumns = [
|
||||||
|
{ data: 1, title: t('name') },
|
||||||
|
{ data: 2, title: t('description') },
|
||||||
|
{ data: 3, title: t('namespace') }
|
||||||
|
];
|
||||||
|
|
||||||
const listsColumns = [
|
const listsColumns = [
|
||||||
{ data: 0, title: "#" },
|
{ data: 0, title: "#" },
|
||||||
{ data: 1, title: t('name') },
|
{ data: 1, title: t('name') },
|
||||||
|
@ -488,60 +509,70 @@ export default class CUD extends Component {
|
||||||
|
|
||||||
<NamespaceSelect/>
|
<NamespaceSelect/>
|
||||||
|
|
||||||
<Fieldset label={t('formsPreview')}>
|
{!isEdit &&
|
||||||
<TableSelect id="previewList" label={t('listToPreviewOn')} withHeader dropdown dataUrl='rest/lists-table' columns={listsColumns} selectionLabelIndex={1} help={t('selectListWhoseFieldsWillBeUsedToPreview')}/>
|
<CheckBox id="fromSourceCustomForms" label={t('customForms')} text={t('cloneFromAnExistingCustomForms')}/>
|
||||||
|
}
|
||||||
|
|
||||||
{ previewListId &&
|
{this.getFormValue('fromSourceCustomForms') &&
|
||||||
<div>
|
<TableSelect key="sourceCustomFormsKey" id="sourceCustomForms" withHeader dropdown dataUrl='rest/forms-table' columns={customFormsColumns} selectionLabelIndex={1} />
|
||||||
<AlignedRow>
|
}
|
||||||
<div>
|
|
||||||
<small>
|
{!this.getFormValue('fromSourceCustomForms') &&
|
||||||
{t('noteTheseLinksAreSolelyForAQuickPreview')}
|
<Fieldset label={t('formsPreview')}>
|
||||||
</small>
|
<TableSelect id="previewList" label={t('listToPreviewOn')} withHeader dropdown dataUrl='rest/lists-table' columns={listsColumns} selectionLabelIndex={1} help={t('selectListWhoseFieldsWillBeUsedToPreview')}/>
|
||||||
</div>
|
|
||||||
<p>
|
{ previewListId &&
|
||||||
<ActionLink onClickAsync={async () => await this.preview('web_subscribe')}>Subscribe</ActionLink>
|
<div>
|
||||||
{' | '}
|
<AlignedRow>
|
||||||
<ActionLink onClickAsync={async () => await this.preview('web_confirm_subscription_notice')}>Confirm Subscription Notice</ActionLink>
|
<div>
|
||||||
{' | '}
|
<small>
|
||||||
<ActionLink onClickAsync={async () => await this.preview('web_confirm_unsubscription_notice')}>Confirm Unsubscription Notice</ActionLink>
|
{t('noteTheseLinksAreSolelyForAQuickPreview')}
|
||||||
{' | '}
|
</small>
|
||||||
<ActionLink onClickAsync={async () => await this.preview('web_subscribed_notice')}>Subscribed Notice</ActionLink>
|
|
||||||
{' | '}
|
|
||||||
<ActionLink onClickAsync={async () => await this.preview('web_updated_notice')}>Updated Notice</ActionLink>
|
|
||||||
{' | '}
|
|
||||||
<ActionLink onClickAsync={async () => await this.preview('web_unsubscribed_notice')}>Unsubscribed Notice</ActionLink>
|
|
||||||
{' | '}
|
|
||||||
<ActionLink onClickAsync={async () => await this.preview('web_manual_unsubscribe_notice')}>Manual Unsubscribe Notice</ActionLink>
|
|
||||||
{' | '}
|
|
||||||
<ActionLink onClickAsync={async () => await this.preview('web_unsubscribe')}>Unsubscribe</ActionLink>
|
|
||||||
{' | '}
|
|
||||||
<ActionLink onClickAsync={async () => await this.preview('web_manage')}>Manage</ActionLink>
|
|
||||||
{' | '}
|
|
||||||
<ActionLink onClickAsync={async () => await this.preview('web_manage_address')}>Manage Address</ActionLink>
|
|
||||||
{' | '}
|
|
||||||
<ActionLink onClickAsync={async () => await this.preview('web_privacy_policy_notice')}>Privacy Policy</ActionLink>
|
|
||||||
</p>
|
|
||||||
</AlignedRow>
|
|
||||||
{this.state.previewContents &&
|
|
||||||
<div className={this.state.previewFullscreen ? formsStyles.editorFullscreen : formsStyles.editor}>
|
|
||||||
<div className={formsStyles.navbar}>
|
|
||||||
<div className={formsStyles.navbarLeft}>
|
|
||||||
{this.state.fullscreen && <img className={formsStyles.logo} src={getTrustedUrl('static/mailtrain-notext.png')}/>}
|
|
||||||
<div className={formsStyles.title}>{t('formPreview') + ' ' + this.state.previewLabel}</div>
|
|
||||||
</div>
|
</div>
|
||||||
<div className={formsStyles.navbarRight}>
|
<p>
|
||||||
<a className={formsStyles.btn} onClick={() => this.preview(this.state.previewKey)} title={t('refresh')}><Icon icon="sync-alt"/></a>
|
<ActionLink onClickAsync={async () => await this.preview('web_subscribe')}>Subscribe</ActionLink>
|
||||||
<a className={formsStyles.btn} onClick={() => this.setState({previewFullscreen: !this.state.previewFullscreen})} title={t('maximizeEditor')}><Icon icon="window-maximize"/></a>
|
{' | '}
|
||||||
<a className={formsStyles.btn} onClick={() => this.setState({previewContents: null, previewFullscreen: false})} title={t('closePreview')}><Icon icon="window-close"/></a>
|
<ActionLink onClickAsync={async () => await this.preview('web_confirm_subscription_notice')}>Confirm Subscription Notice</ActionLink>
|
||||||
|
{' | '}
|
||||||
|
<ActionLink onClickAsync={async () => await this.preview('web_confirm_unsubscription_notice')}>Confirm Unsubscription Notice</ActionLink>
|
||||||
|
{' | '}
|
||||||
|
<ActionLink onClickAsync={async () => await this.preview('web_subscribed_notice')}>Subscribed Notice</ActionLink>
|
||||||
|
{' | '}
|
||||||
|
<ActionLink onClickAsync={async () => await this.preview('web_updated_notice')}>Updated Notice</ActionLink>
|
||||||
|
{' | '}
|
||||||
|
<ActionLink onClickAsync={async () => await this.preview('web_unsubscribed_notice')}>Unsubscribed Notice</ActionLink>
|
||||||
|
{' | '}
|
||||||
|
<ActionLink onClickAsync={async () => await this.preview('web_manual_unsubscribe_notice')}>Manual Unsubscribe Notice</ActionLink>
|
||||||
|
{' | '}
|
||||||
|
<ActionLink onClickAsync={async () => await this.preview('web_unsubscribe')}>Unsubscribe</ActionLink>
|
||||||
|
{' | '}
|
||||||
|
<ActionLink onClickAsync={async () => await this.preview('web_manage')}>Manage</ActionLink>
|
||||||
|
{' | '}
|
||||||
|
<ActionLink onClickAsync={async () => await this.preview('web_manage_address')}>Manage Address</ActionLink>
|
||||||
|
{' | '}
|
||||||
|
<ActionLink onClickAsync={async () => await this.preview('web_privacy_policy_notice')}>Privacy Policy</ActionLink>
|
||||||
|
</p>
|
||||||
|
</AlignedRow>
|
||||||
|
{this.state.previewContents &&
|
||||||
|
<div className={this.state.previewFullscreen ? formsStyles.editorFullscreen : formsStyles.editor}>
|
||||||
|
<div className={formsStyles.navbar}>
|
||||||
|
<div className={formsStyles.navbarLeft}>
|
||||||
|
{this.state.fullscreen && <img className={formsStyles.logo} src={getTrustedUrl('static/mailtrain-notext.png')}/>}
|
||||||
|
<div className={formsStyles.title}>{t('formPreview') + ' ' + this.state.previewLabel}</div>
|
||||||
|
</div>
|
||||||
|
<div className={formsStyles.navbarRight}>
|
||||||
|
<a className={formsStyles.btn} onClick={() => this.preview(this.state.previewKey)} title={t('refresh')}><Icon icon="sync-alt"/></a>
|
||||||
|
<a className={formsStyles.btn} onClick={() => this.setState({previewFullscreen: !this.state.previewFullscreen})} title={t('maximizeEditor')}><Icon icon="window-maximize"/></a>
|
||||||
|
<a className={formsStyles.btn} onClick={() => this.setState({previewContents: null, previewFullscreen: false})} title={t('closePreview')}><Icon icon="window-close"/></a>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<iframe className={formsStyles.host} src={"data:text/html;charset=utf-8," + encodeURIComponent(this.state.previewContents)}></iframe>
|
||||||
</div>
|
</div>
|
||||||
<iframe className={formsStyles.host} src={"data:text/html;charset=utf-8," + encodeURIComponent(this.state.previewContents)}></iframe>
|
}
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
</div>
|
</Fieldset>
|
||||||
}
|
}
|
||||||
</Fieldset>
|
|
||||||
|
|
||||||
{ selectedTemplate &&
|
{ selectedTemplate &&
|
||||||
<Fieldset label={t('templates')}>
|
<Fieldset label={t('templates')}>
|
||||||
|
|
|
@ -100,6 +100,7 @@
|
||||||
"customTemplateEditor": "Custom template editor",
|
"customTemplateEditor": "Custom template editor",
|
||||||
"save": "Save",
|
"save": "Save",
|
||||||
"saveAndLeave": "Save and leave",
|
"saveAndLeave": "Save and leave",
|
||||||
|
"copy": "Copy",
|
||||||
"saveAndGoToStatus": "Save and go to status",
|
"saveAndGoToStatus": "Save and go to status",
|
||||||
"testSend": "Test send",
|
"testSend": "Test send",
|
||||||
"createRegularCampaign": "Create Regular Campaign",
|
"createRegularCampaign": "Create Regular Campaign",
|
||||||
|
@ -923,6 +924,7 @@
|
||||||
"editTemplate": "Edit Template",
|
"editTemplate": "Edit Template",
|
||||||
"createTemplate": "Create Template",
|
"createTemplate": "Create Template",
|
||||||
"cloneFromAnExistingTemplate": "Clone from an existing template",
|
"cloneFromAnExistingTemplate": "Clone from an existing template",
|
||||||
|
"cloneFromAnExistingCustomForms": "Clone from an existing custom forms",
|
||||||
"mosaico": "Mosaico",
|
"mosaico": "Mosaico",
|
||||||
"templateContentHtml": "Template content (HTML)",
|
"templateContentHtml": "Template content (HTML)",
|
||||||
"mosaicoTemplateDesigner": "Mosaico Template Designer",
|
"mosaicoTemplateDesigner": "Mosaico Template Designer",
|
||||||
|
|
|
@ -100,6 +100,7 @@
|
||||||
"customTemplateEditor": "Custom template editor",
|
"customTemplateEditor": "Custom template editor",
|
||||||
"save": "Save",
|
"save": "Save",
|
||||||
"saveAndLeave": "Save and leave",
|
"saveAndLeave": "Save and leave",
|
||||||
|
"copy": "Copy",
|
||||||
"saveAndGoToStatus": "Save and go to status",
|
"saveAndGoToStatus": "Save and go to status",
|
||||||
"testSend": "Test send",
|
"testSend": "Test send",
|
||||||
"createRegularCampaign": "Create Regular Campaign",
|
"createRegularCampaign": "Create Regular Campaign",
|
||||||
|
@ -923,6 +924,7 @@
|
||||||
"editTemplate": "Edit Template",
|
"editTemplate": "Edit Template",
|
||||||
"createTemplate": "Create Template",
|
"createTemplate": "Create Template",
|
||||||
"cloneFromAnExistingTemplate": "Clone from an existing template",
|
"cloneFromAnExistingTemplate": "Clone from an existing template",
|
||||||
|
"cloneFromAnExistingCustomForms": "Clone from an existing custom forms",
|
||||||
"mosaico": "Mosaico",
|
"mosaico": "Mosaico",
|
||||||
"templateContentHtml": "Template content (HTML)",
|
"templateContentHtml": "Template content (HTML)",
|
||||||
"mosaicoTemplateDesigner": "Mosaico Template Designer",
|
"mosaicoTemplateDesigner": "Mosaico Template Designer",
|
||||||
|
|
|
@ -105,6 +105,7 @@
|
||||||
"save": "Save",
|
"save": "Save",
|
||||||
"saveAndLeave": "Save and leave",
|
"saveAndLeave": "Save and leave",
|
||||||
"saveAndLeave - TODO: update line above and then delete this line to mark that the translation has been fixed": "Save and leave",
|
"saveAndLeave - TODO: update line above and then delete this line to mark that the translation has been fixed": "Save and leave",
|
||||||
|
"copy": "Copiar",
|
||||||
"saveAndGoToStatus": "Save and go to status",
|
"saveAndGoToStatus": "Save and go to status",
|
||||||
"saveAndGoToStatus - TODO: update line above and then delete this line to mark that the translation has been fixed": "Save and go to status",
|
"saveAndGoToStatus - TODO: update line above and then delete this line to mark that the translation has been fixed": "Save and go to status",
|
||||||
"testSend": "Test send",
|
"testSend": "Test send",
|
||||||
|
@ -992,8 +993,8 @@
|
||||||
"templateDeleted": "Template deleted",
|
"templateDeleted": "Template deleted",
|
||||||
"editTemplate": "Edit Template",
|
"editTemplate": "Edit Template",
|
||||||
"createTemplate": "Create Template",
|
"createTemplate": "Create Template",
|
||||||
"cloneFromAnExistingTemplate": "Clone from an existing template",
|
"cloneFromAnExistingTemplate": "Clonar a partir de templates ya existentes",
|
||||||
"cloneFromAnExistingTemplate - TODO: update line above and then delete this line to mark that the translation has been fixed": "Clone from an existing template",
|
"cloneFromAnExistingCustomForms": "Clonar a partir de formularios personalizados ya existentes",
|
||||||
"mosaico": "Mosaico",
|
"mosaico": "Mosaico",
|
||||||
"templateContentHtml": "Template content (HTML)",
|
"templateContentHtml": "Template content (HTML)",
|
||||||
"mosaicoTemplateDesigner": "Mosaico Template Designer",
|
"mosaicoTemplateDesigner": "Mosaico Template Designer",
|
||||||
|
|
|
@ -105,6 +105,8 @@
|
||||||
"save": "Salvar",
|
"save": "Salvar",
|
||||||
"saveAndLeave": "Salvar e sair",
|
"saveAndLeave": "Salvar e sair",
|
||||||
"saveAndLeave - TODO: update line above and then delete this line to mark that the translation has been fixed": "Save and leave",
|
"saveAndLeave - TODO: update line above and then delete this line to mark that the translation has been fixed": "Save and leave",
|
||||||
|
"copy": "Copia",
|
||||||
|
"copy - TODO: update line above and then delete this line to mark that the translation has been fixed": "Copy",
|
||||||
"saveAndGoToStatus": "Save and go to status",
|
"saveAndGoToStatus": "Save and go to status",
|
||||||
"saveAndGoToStatus - TODO: update line above and then delete this line to mark that the translation has been fixed": "Save and go to status",
|
"saveAndGoToStatus - TODO: update line above and then delete this line to mark that the translation has been fixed": "Save and go to status",
|
||||||
"testSend": "Testar envio",
|
"testSend": "Testar envio",
|
||||||
|
@ -994,6 +996,8 @@
|
||||||
"createTemplate": "Criar modelo",
|
"createTemplate": "Criar modelo",
|
||||||
"cloneFromAnExistingTemplate": "Clone from an existing template",
|
"cloneFromAnExistingTemplate": "Clone from an existing template",
|
||||||
"cloneFromAnExistingTemplate - TODO: update line above and then delete this line to mark that the translation has been fixed": "Clone from an existing template",
|
"cloneFromAnExistingTemplate - TODO: update line above and then delete this line to mark that the translation has been fixed": "Clone from an existing template",
|
||||||
|
"cloneFromAnExistingCustomForms": "Clone from an existing custom forms",
|
||||||
|
"cloneFromAnExistingCustomForms - TODO: update line above and then delete this line to mark that the translation has been fixed": "Clone from an existing custom forms",
|
||||||
"mosaico": "Mosaico",
|
"mosaico": "Mosaico",
|
||||||
"templateContentHtml": "Conteúdo do modelo (HTML)",
|
"templateContentHtml": "Conteúdo do modelo (HTML)",
|
||||||
"mosaicoTemplateDesigner": "Editor do Modelo Mosaico",
|
"mosaicoTemplateDesigner": "Editor do Modelo Mosaico",
|
||||||
|
|
|
@ -175,6 +175,31 @@ async function updateWithConsistencyCheck(context, entity) {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function copy(context, entity, formId) {
|
||||||
|
return await knex.transaction(async tx => {
|
||||||
|
await shares.enforceEntityPermissionTx(tx, context, 'namespace', entity.namespace, 'createCustomForm');
|
||||||
|
const existing = await _getById(tx, formId);
|
||||||
|
await namespaceHelpers.validateEntity(tx, entity);
|
||||||
|
|
||||||
|
const form = filterObject(existing, allowedFormKeys);
|
||||||
|
enforce(!Object.keys(checkForMjmlErrors(form)).length, 'Error(s) in form templates');
|
||||||
|
|
||||||
|
const ids = await tx('custom_forms').insert(filterObject(entity, formAllowedKeys));
|
||||||
|
const id = ids[0];
|
||||||
|
|
||||||
|
for (const formKey in form) {
|
||||||
|
await tx('custom_forms_data').insert({
|
||||||
|
form: id,
|
||||||
|
data_key: formKey,
|
||||||
|
data_value: form[formKey]
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
await shares.rebuildPermissionsTx(tx, { entityTypeId: 'customForm', entityId: id });
|
||||||
|
return id;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
async function remove(context, id) {
|
async function remove(context, id) {
|
||||||
await knex.transaction(async tx => {
|
await knex.transaction(async tx => {
|
||||||
await shares.enforceEntityPermissionTx(tx, context, 'customForm', id, 'delete');
|
await shares.enforceEntityPermissionTx(tx, context, 'customForm', id, 'delete');
|
||||||
|
@ -285,6 +310,7 @@ module.exports.hash = hash;
|
||||||
module.exports.getById = getById;
|
module.exports.getById = getById;
|
||||||
module.exports.create = create;
|
module.exports.create = create;
|
||||||
module.exports.updateWithConsistencyCheck = updateWithConsistencyCheck;
|
module.exports.updateWithConsistencyCheck = updateWithConsistencyCheck;
|
||||||
|
module.exports.copy = copy;
|
||||||
module.exports.remove = remove;
|
module.exports.remove = remove;
|
||||||
module.exports.getDefaultCustomFormValues = getDefaultCustomFormValues;
|
module.exports.getDefaultCustomFormValues = getDefaultCustomFormValues;
|
||||||
module.exports.serverValidate = serverValidate;
|
module.exports.serverValidate = serverValidate;
|
||||||
|
|
|
@ -26,6 +26,12 @@ router.postAsync('/forms', passport.loggedIn, passport.csrfProtection, async (re
|
||||||
return res.json(await forms.create(req.context, req.body));
|
return res.json(await forms.create(req.context, req.body));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
router.postAsync('/forms/:formId', passport.loggedIn, passport.csrfProtection, async (req, res) => {
|
||||||
|
const entity = req.body;
|
||||||
|
const formId= castToInteger(req.params.formId);
|
||||||
|
return res.json(await forms.copy(req.context, entity, formId));
|
||||||
|
});
|
||||||
|
|
||||||
router.putAsync('/forms/:formId', passport.loggedIn, passport.csrfProtection, async (req, res) => {
|
router.putAsync('/forms/:formId', passport.loggedIn, passport.csrfProtection, async (req, res) => {
|
||||||
const entity = req.body;
|
const entity = req.body;
|
||||||
entity.id = castToInteger(req.params.formId);
|
entity.id = castToInteger(req.params.formId);
|
||||||
|
|
Loading…
Reference in a new issue