Mosaico upgraded to 0.17.5

Work started on confirmation dialogs displayed when one navigates from a page with unsaved changes
This commit is contained in:
Tomas Bures 2019-05-08 19:54:19 +02:00
parent 4f77272042
commit 48dcf2c701
399 changed files with 4032 additions and 77702 deletions

View file

@ -776,8 +776,8 @@ export default class CUD extends Component {
:
<>
<Button type="submit" className="btn-primary" icon="check" label={t('Save')}/>
<Button type="submit" className="btn-primary" icon="check" label={t('Save and leave')} onClickAsync={async () => this.submitHandler(CUD.AfterSubmitAction.LEAVE)}/>
<Button type="submit" className="btn-primary" icon="check" label={t('Save and go to status')} onClickAsync={async () => this.submitHandler(CUD.AfterSubmitAction.STATUS)}/>
<Button type="submit" className="btn-primary" icon="check" label={t('Save and leave')} onClickAsync={async () => await this.submitHandler(CUD.AfterSubmitAction.LEAVE)}/>
<Button type="submit" className="btn-primary" icon="check" label={t('Save and go to status')} onClickAsync={async () => await this.submitHandler(CUD.AfterSubmitAction.STATUS)}/>
</>
}
{canDelete && <LinkButton className="btn-danger" icon="trash-alt" label={t('delete')} to={`/campaigns/${this.props.entity.id}/delete`}/> }

View file

@ -263,8 +263,8 @@ export default class CustomContent extends Component {
<ButtonRow>
<Button type="submit" className="btn-primary" icon="check" label={t('Save')}/>
<Button type="submit" className="btn-primary" icon="check" label={t('Save and leave')} onClickAsync={async () => this.submitHandler(CustomContent.AfterSubmitAction.LEAVE)}/>
<Button type="submit" className="btn-primary" icon="check" label={t('Save and go to status')} onClickAsync={async () => this.submitHandler(CustomContent.AfterSubmitAction.STATUS)}/>
<Button type="submit" className="btn-primary" icon="check" label={t('Save and leave')} onClickAsync={async () => await this.submitHandler(CustomContent.AfterSubmitAction.LEAVE)}/>
<Button type="submit" className="btn-primary" icon="check" label={t('Save and go to status')} onClickAsync={async () => await this.submitHandler(CustomContent.AfterSubmitAction.STATUS)}/>
<Button className="btn-success" icon="at" label={t('testSend')} onClickAsync={async () => this.setState({showTestSendModal: true})}/>
</ButtonRow>
</Form>

View file

@ -252,7 +252,7 @@ export default class CUD extends Component {
<ButtonRow>
<Button type="submit" className="btn-primary" icon="check" label={t('Save')}/>
<Button type="submit" className="btn-primary" icon="check" label={t('Save and leave')} onClickAsync={async () => this.submitHandler(true)}/>
<Button type="submit" className="btn-primary" icon="check" label={t('Save and leave')} onClickAsync={async () => await this.submitHandler(true)}/>
{isEdit && <LinkButton className="btn-danger" icon="trash-alt" label={t('delete')} to={`/campaigns/${this.props.campaign.id}/triggers/${this.props.entity.id}/delete`}/>}
</ButtonRow>
</Form>

View file

@ -301,7 +301,7 @@ export class ModalDialog extends Component {
buttons = [];
for (let idx = 0; idx < this.props.buttons.length; idx++) {
const buttonSpec = this.props.buttons[idx];
const button = <Button key={idx} label={buttonSpec.label} className={buttonSpec.className} onClickAsync={async () => this.onButtonClick(idx)} />
const button = <Button key={idx} label={buttonSpec.label} className={buttonSpec.className} onClickAsync={async () => await this.onButtonClick(idx)} />
buttons.push(button);
}
}

View file

@ -12,6 +12,7 @@ import {TreeSelectMode, TreeTable} from './tree';
import {Table, TableSelectMode} from './table';
import {Button} from "./bootstrap-components";
import {SketchPicker} from 'react-color';
import deepEqual from "fast-deep-equal";
import ACEEditorRaw from 'react-ace';
import 'brace/theme/github';
@ -49,11 +50,20 @@ export const FormStateOwnerContext = React.createContext(null);
const withFormStateOwner = createComponentMixin([{context: FormStateOwnerContext, propName: 'formStateOwner'}], [], (TargetClass, InnerClass) => {
InnerClass.prototype.getFormStateOwner = function() {
return this.props.formStateOwner;
}
};
return {};
});
export function withFormErrorHandlers(target, name, descriptor) {
const asyncFn = descriptor.value;
descriptor.value = async function(...args) {
await this.formHandleErrors(async () => await asyncFn.apply(this, args));
};
return descriptor;
}
@withComponentMixins([
withTranslation,
@ -61,6 +71,15 @@ const withFormStateOwner = createComponentMixin([{context: FormStateOwnerContext
withPageHelpers
])
class Form extends Component {
constructor(props) {
super(props);
this.beforeUnloadHandlers = {
handler: () => this.props.stateOwner.isFormChanged(),
handlerAsync: async () => await this.props.stateOwner.isFormChangedAsync()
};
}
static propTypes = {
stateOwner: PropTypes.object.isRequired,
onSubmitAsync: PropTypes.func,
@ -68,6 +87,14 @@ class Form extends Component {
noStatus: PropTypes.bool
}
componentDidMount() {
this.registerBeforeUnloadHandlers(this.beforeUnloadHandlers);
}
componentWillUnmount() {
this.deregisterBeforeUnloadHandlers(this.beforeUnloadHandlers);
}
@withAsyncErrorHandler
async onSubmit(evt) {
const t = this.props.t;
@ -77,7 +104,7 @@ class Form extends Component {
evt.preventDefault();
if (this.props.onSubmitAsync) {
await owner.formHandleChangedError(async () => await this.props.onSubmitAsync());
await this.props.onSubmitAsync();
}
}
@ -952,6 +979,7 @@ const withForm = createComponentMixin([], [], (TargetClass, InnerClass) => {
isDisabled: false,
statusMessageText: '',
data: Immutable.Map(),
savedData: Immutable.Map(),
isServerValidationRunning: false
});
@ -1061,12 +1089,14 @@ const withForm = createComponentMixin([], [], (TargetClass, InnerClass) => {
});
};
proto.getFormValuesFromEntity = function(entity, mutator) {
proto.getFormValuesFromEntity = function(entity) {
const settings = this.state.formSettings;
const data = Object.assign({}, entity);
data.originalHash = data.hash;
delete data.hash;
const mutator = settings.loadMutator;
if (mutator) {
mutator(data);
}
@ -1074,7 +1104,8 @@ const withForm = createComponentMixin([], [], (TargetClass, InnerClass) => {
this.populateFormValues(data);
};
proto.getFormValuesFromURL = async function(url, mutator) {
proto.getFormValuesFromURL = async function(url) {
const settings = this.state.formSettings;
setTimeout(() => {
this.setState(previousState => {
if (previousState.formState.get('state') === FormState.Loading) {
@ -1092,6 +1123,7 @@ const withForm = createComponentMixin([], [], (TargetClass, InnerClass) => {
data.originalHash = data.hash;
delete data.hash;
const mutator = settings.loadMutator;
if (mutator) {
const newData = mutator(data);
@ -1103,12 +1135,26 @@ const withForm = createComponentMixin([], [], (TargetClass, InnerClass) => {
this.populateFormValues(data);
};
proto.validateAndSendFormValuesToURL = async function(method, url, mutator) {
proto.validateAndSendFormValuesToURL = async function(method, url) {
const settings = this.state.formSettings;
await this.waitForFormServerValidated();
if (this.isFormWithoutErrors()) {
if (settings.getPreSubmitUpdater) {
const preSubmitUpdater = await settings.getPreSubmitUpdater();
await new Promise((resolve, reject) => {
this.setState(previousState => ({
formState: previousState.formState.withMutations(mutState => {
mutState.update('data', stateData => stateData.withMutations(preSubmitUpdater));
})
}), resolve);
});
}
let data = this.getFormValues();
const mutator = settings.submitMutator;
if (mutator) {
const newData = mutator(data);
if (newData !== undefined) {
@ -1118,6 +1164,12 @@ const withForm = createComponentMixin([], [], (TargetClass, InnerClass) => {
const response = await axios.method(method, getUrl(url), data);
await new Promise((resolve, reject) => {
this.setState(previousState => ({
formState: previousState.formState.set('savedData', previousState.formState.get('data'))
}), resolve);
});
return response.data || true;
} else {
@ -1140,6 +1192,8 @@ const withForm = createComponentMixin([], [], (TargetClass, InnerClass) => {
}
}));
mutState.set('savedData', mutState.get('data'));
validateFormState(this, mutState);
})
}));
@ -1263,6 +1317,57 @@ const withForm = createComponentMixin([], [], (TargetClass, InnerClass) => {
return this.state.formState.get('state') === FormState.Ready;
};
const _isFormChanged = self => {
const settings = self.state.formSettings;
const mutateData = data => {
if (settings.submitMutator) {
const newData = settings.submitMutator(data);
if (newData !== undefined) {
data = newData;
}
}
return data;
};
const currentData = mutateData(self.state.formState.get('data').map(attr => attr.get('value')).toJS());
const savedData = mutateData(self.state.formState.get('savedData').map(attr => attr.get('value')).toJS());
return !deepEqual(currentData, savedData);
};
proto.isFormChanged = function() {
const settings = this.state.formSettings;
if (settings.getPreSubmitUpdater) {
// getPreSubmitUpdater is an async function. We cannot do anything async here. So to be on the safe side,
// we simply assume that the form has been changed.
return true;
}
return _isFormChanged(this);
};
proto.isFormChangedAsync = async function() {
const settings = this.state.formSettings;
if (settings.getPreSubmitUpdater) {
const preSubmitUpdater = await settings.getPreSubmitUpdater();
await new Promise((resolve, reject) => {
this.setState(previousState => ({
formState: previousState.formState.withMutations(mutState => {
mutState.update('data', stateData => stateData.withMutations(preSubmitUpdater));
})
}), resolve);
});
}
return _isFormChanged(this);
};
proto.isFormValidationShown = function() {
return this.state.formState.get('isValidationShown');
};
@ -1341,7 +1446,7 @@ const withForm = createComponentMixin([], [], (TargetClass, InnerClass) => {
return this.state.formState.get('isDisabled');
};
proto.formHandleChangedError = async function(fn) {
proto.formHandleErrors = async function(fn) {
const t = this.props.t;
try {
await fn();
@ -1386,6 +1491,23 @@ const withForm = createComponentMixin([], [], (TargetClass, InnerClass) => {
return {};
});
function filterData(obj, allowedKeys) {
const result = {};
for (const key in obj) {
if (key === 'originalHash') {
result[key] = obj[key];
} else {
for (const allowedKey of allowedKeys) {
if ((typeof allowedKey === 'function' && allowedKey(key)) || allowedKey === key) {
result[key] = obj[key];
break;
}
}
}
}
return result;
}
export {
withForm,
@ -1407,5 +1529,6 @@ export {
TableSelect,
TableSelectMode,
ACEEditor,
FormSendMethod
FormSendMethod,
filterData
}

View file

@ -90,7 +90,7 @@ export class RestActionModalDialog extends Component {
return (
<ModalDialog hidden={!this.props.visible} title={this.props.title} onCloseAsync={() => this.hideModal(true)} buttons={[
{ label: t('no'), className: 'btn-primary', onClickAsync: async () => this.hideModal(true) },
{ label: t('no'), className: 'btn-primary', onClickAsync: async () => await this.hideModal(true) },
{ label: t('yes'), className: 'btn-danger', onClickAsync: ::this.performAction }
]}>
{this.props.message}

View file

@ -3,7 +3,6 @@
import React, {Component} from "react";
import PropTypes from "prop-types";
import {Redirect, Route, Switch} from "react-router-dom";
import {withRouter} from "react-router";
import {withAsyncErrorHandler, withErrorHandling} from "./error-handling";
import axios from "../lib/axios";
import {getUrl} from "./urls";
@ -362,15 +361,23 @@ export const withPageHelpers = createComponentMixin([{context: SectionContentCon
InnerClass.prototype.navigateTo = function(path) {
return this.props.sectionContent.navigateTo(path);
}
};
InnerClass.prototype.navigateBack = function() {
return this.props.sectionContent.navigateBack();
}
};
InnerClass.prototype.navigateToWithFlashMessage = function(path, severity, text) {
return this.props.sectionContent.navigateToWithFlashMessage(path, severity, text);
}
};
InnerClass.prototype.registerBeforeUnloadHandlers = function(handlers) {
return this.props.sectionContent.registerBeforeUnloadHandlers(handlers);
};
InnerClass.prototype.deregisterBeforeUnloadHandlers = function(handlers) {
return this.props.sectionContent.deregisterBeforeUnloadHandlers(handlers);
};
return {};
});

View file

@ -331,11 +331,41 @@ class PanelRoute extends Component {
}
export class BeforeUnloadListeners {
constructor() {
this.listeners = new Set();
}
register(listener) {
this.listeners.add(listener);
}
deregister(listener) {
this.listeners.delete(listener);
}
shouldUnloadBeCancelled() {
for (const lst of this.listeners) {
if (lst.handler()) return true;
}
return false;
}
async shouldUnloadBeCancelledAsync() {
for (const lst of this.listeners) {
if (await lst.handlerAsync()) return true;
}
return false;
}
}
@withRouter
@withComponentMixins([
withTranslation,
withErrorHandling
])
], ['onNavigationConfirmationDialog'])
export class SectionContent extends Component {
constructor(props) {
super(props);
@ -348,6 +378,10 @@ export class SectionContent extends Component {
// noinspection JSIgnoredPromiseFromCall
this.closeFlashMessage();
});
this.beforeUnloadListeners = new BeforeUnloadListeners();
this.beforeUnloadHandler = ::this.onBeforeUnload;
this.historyUnblock = null;
}
static propTypes = {
@ -355,6 +389,34 @@ export class SectionContent extends Component {
root: PropTypes.string.isRequired
}
onBeforeUnload(event) {
if (this.beforeUnloadListeners.shouldUnloadBeCancelled()) {
event.preventDefault();
event.returnValue = '';
}
}
onNavigationConfirmationDialog(message, callback) {
this.beforeUnloadListeners.shouldUnloadBeCancelledAsync().then(res => {
if (res) {
const allowTransition = window.confirm(message);
callback(allowTransition);
} else {
callback(true);
}
});
}
componentDidMount() {
window.addEventListener('beforeunload', this.beforeUnloadHandler);
this.historyUnblock = this.props.history.block('Changes you made may not be saved. Are you sure you want to leave this page?');
}
componentWillUnmount() {
window.removeEventListener('beforeunload', this.beforeUnloadHandler);
this.historyUnblock();
}
setFlashMessage(severity, text) {
this.setState({
flashMessageText: text,
@ -381,6 +443,14 @@ export class SectionContent extends Component {
}
}
registerBeforeUnloadHandlers(handlers) {
this.beforeUnloadListeners.register(handlers);
}
deregisterBeforeUnloadHandlers(handlers) {
this.beforeUnloadListeners.deregister(handlers);
}
errorHandler(error) {
if (error instanceof interoperableErrors.NotLoggedInError) {
if (window.location.pathname !== '/login') { // There may be multiple async requests failing at the same time. So we take the pathname only from the first one.
@ -440,6 +510,8 @@ export class SectionContent extends Component {
export class Section extends Component {
constructor(props) {
super(props);
this.getUserConfirmationHandler = ::this.onGetUserConfirmation;
this.sectionContent = null;
}
static propTypes = {
@ -447,6 +519,10 @@ export class Section extends Component {
root: PropTypes.string.isRequired
}
onGetUserConfirmation(message, callback) {
this.sectionContent.onNavigationConfirmationDialog(message, callback);
}
render() {
let structure = this.props.structure;
if (typeof structure === 'function') {
@ -454,8 +530,8 @@ export class Section extends Component {
}
return (
<Router basename={getBaseDir()}>
<SectionContent root={this.props.root} structure={structure} />
<Router basename={getBaseDir()} getUserConfirmation={this.getUserConfirmationHandler}>
<SectionContent wrappedComponentRef={node => this.sectionContent = node} root={this.props.root} structure={structure} />
</Router>
);
}

View file

@ -13,6 +13,7 @@ import {base, unbase} from "../../../shared/templates";
import {withComponentMixins} from "./decorator-helpers";
import juice from "juice";
@withComponentMixins([
withTranslation
])
@ -56,7 +57,8 @@ class MosaicoSandbox extends Component {
...
</div>
*/
const html = juice(this.viewModel.exportHTML());
let html = this.viewModel.exportHTML();
html = juice(html);
return {
html: unbase(html, trustedUrlBase, sandboxUrlBase, publicUrlBase, true),
@ -99,7 +101,7 @@ class MosaicoSandbox extends Component {
plugins.unshift(vm => {
// This is an override of the default paths in Mosaico
vm.logoPath = getTrustedUrl('static/mosaico/img/mosaico32.png');
vm.logoPath = getTrustedUrl('static/mosaico/rs/img/mosaico32.png');
vm.logoUrl = '#';
});

View file

@ -15,7 +15,7 @@ import {
Button,
ButtonRow,
CheckBox,
Dropdown,
Dropdown, filterData,
Form,
FormSendMethod,
InputField,
@ -51,7 +51,10 @@ export default class CUD extends Component {
this.state = {};
this.initForm();
this.initForm({
loadMutator: ::this.getFormValuesMutator,
submitMutator: ::this.submitFormValuesMutator
});
this.mailerTypes = getMailerTypes(props.t);
}
@ -66,9 +69,21 @@ export default class CUD extends Component {
data.listunsubscribe_disabled = !!data.listunsubscribe_disabled;
}
submitFormValuesMutator(data) {
if (data.form === 'default') {
data.default_form = null;
}
if (data.fieldWizard === FieldWizard.FIRST_LAST_NAME || data.fieldWizard === FieldWizard.NAME) {
data.to_name = null;
}
return filterData(data, ['name', 'description', 'default_form', 'public_subscribe', 'unsubscription_mode', 'contact_email', 'homepage', 'namespace', 'to_name', 'listunsubscribe_disabled', 'send_configuration']);
}
componentDidMount() {
if (this.props.entity) {
this.getFormValuesFromEntity(this.props.entity, ::this.getFormValuesMutator);
this.getFormValuesFromEntity(this.props.entity);
} else {
this.populateFormValues({
@ -128,23 +143,14 @@ export default class CUD extends Component {
this.disableForm();
this.setFormStatusMessage('info', t('saving'));
const submitResult = await this.validateAndSendFormValuesToURL(sendMethod, url, data => {
if (data.form === 'default') {
data.default_form = null;
}
delete data.form;
if (data.fieldWizard === FieldWizard.FIRST_LAST_NAME || data.fieldWizard === FieldWizard.NAME) {
data.to_name = null;
}
});
const submitResult = await this.validateAndSendFormValuesToURL(sendMethod, url);
if (submitResult) {
if (this.props.entity) {
if (submitAndLeave) {
this.navigateToWithFlashMessage('/lists', 'success', t('List updated'));
} else {
await this.getFormValuesFromURL(`rest/lists/${this.props.entity.id}`, ::this.getFormValuesMutator);
await this.getFormValuesFromURL(`rest/lists/${this.props.entity.id}`);
this.enableForm();
this.setFormStatusMessage('success', t('List updated'));
}
@ -288,7 +294,7 @@ export default class CUD extends Component {
<ButtonRow>
<Button type="submit" className="btn-primary" icon="check" label={t('Save')}/>
<Button type="submit" className="btn-primary" icon="check" label={t('Save and leave')} onClickAsync={async () => this.submitHandler(true)}/>
<Button type="submit" className="btn-primary" icon="check" label={t('Save and leave')} onClickAsync={async () => await this.submitHandler(true)}/>
{canDelete && <LinkButton className="btn-danger" icon="trash-alt" label={t('delete')} to={`/lists/${this.props.entity.id}/delete`}/>}
</ButtonRow>
</Form>

View file

@ -525,7 +525,7 @@ export default class CUD extends Component {
<ButtonRow>
<Button type="submit" className="btn-primary" icon="check" label={t('Save')}/>
<Button type="submit" className="btn-primary" icon="check" label={t('Save and leave')} onClickAsync={async () => this.submitHandler(true)}/>
<Button type="submit" className="btn-primary" icon="check" label={t('Save and leave')} onClickAsync={async () => await this.submitHandler(true)}/>
{isEdit && <LinkButton className="btn-danger" icon="trash-alt" label={t('delete')} to={`/lists/${this.props.list.id}/fields/${this.props.entity.id}/delete`}/>}
</ButtonRow>
</Form>

View file

@ -549,7 +549,7 @@ export default class CUD extends Component {
<ButtonRow>
<Button type="submit" className="btn-primary" icon="check" label={t('Save')}/>
<Button type="submit" className="btn-primary" icon="check" label={t('Save and leave')} onClickAsync={async () => this.submitHandler(true)}/>
<Button type="submit" className="btn-primary" icon="check" label={t('Save and leave')} onClickAsync={async () => await this.submitHandler(true)}/>
{canDelete && <LinkButton className="btn-danger" icon="trash-alt" label={t('delete')} to={`/lists/forms/${this.props.entity.id}/delete`}/>}
</ButtonRow>
</Form>

View file

@ -404,8 +404,8 @@ export default class CUD extends Component {
<hr/>
<ButtonRow format="wide" className={`col-12 ${styles.toolbar}`}>
<Button type="submit" className="btn-primary" icon="check" label={t('save')} onClickAsync={async () => this.submitHandler(false)}/>
<Button type="submit" className="btn-primary" icon="check" label={t('Save and leave')} onClickAsync={async () => this.submitHandler(true)}/>
<Button type="submit" className="btn-primary" icon="check" label={t('save')} onClickAsync={async () => await this.submitHandler(false)}/>
<Button type="submit" className="btn-primary" icon="check" label={t('Save and leave')} onClickAsync={async () => await this.submitHandler(true)}/>
{isEdit && <LinkButton className="btn-danger" icon="trash-alt" label={t('delete')} to={`/lists/${this.props.list.id}/segments/${this.props.entity.id}/delete`}/> }
</ButtonRow>

View file

@ -239,7 +239,7 @@ export default class CUD extends Component {
}
<ButtonRow>
<Button type="submit" className="btn-primary" icon="check" label={t('Save')}/>
<Button type="submit" className="btn-primary" icon="check" label={t('Save and leave')} onClickAsync={async () => this.submitHandler(true)}/>
<Button type="submit" className="btn-primary" icon="check" label={t('Save and leave')} onClickAsync={async () => await this.submitHandler(true)}/>
{isEdit && <LinkButton className="btn-danger" icon="trash-alt" label={t('delete')} to={`/lists/${this.props.list.id}/subscriptions/${this.props.entity.id}/delete`}/>}
</ButtonRow>
</Form>

View file

@ -222,7 +222,7 @@ export default class CUD extends Component {
<ButtonRow>
<Button type="submit" className="btn-primary" icon="check" label={t('Save')}/>
<Button type="submit" className="btn-primary" icon="check" label={t('Save and leave')} onClickAsync={async () => this.submitHandler(true)}/>
<Button type="submit" className="btn-primary" icon="check" label={t('Save and leave')} onClickAsync={async () => await this.submitHandler(true)}/>
{canDelete && <LinkButton className="btn-danger" icon="trash-alt" label={t('delete')} to={`/namespaces/${this.props.entity.id}/delete`}/>}
</ButtonRow>
</Form>

View file

@ -292,7 +292,7 @@ export default class CUD extends Component {
<ButtonRow>
<Button type="submit" className="btn-primary" icon="check" label={t('Save')}/>
<Button type="submit" className="btn-primary" icon="check" label={t('Save and leave')} onClickAsync={async () => this.submitHandler(true)}/>
<Button type="submit" className="btn-primary" icon="check" label={t('Save and leave')} onClickAsync={async () => await this.submitHandler(true)}/>
{canDelete &&
<LinkButton className="btn-danger" icon="trash-alt" label={t('delete')} to={`/reports/${this.props.entity.id}/delete`}/>
}

View file

@ -326,7 +326,7 @@ export default class CUD extends Component {
<ButtonRow>
<Button type="submit" className="btn-primary" icon="check" label={t('Save')}/>
<Button type="submit" className="btn-primary" icon="check" label={t('Save and leave')} onClickAsync={async () => this.submitHandler(true)}/>
<Button type="submit" className="btn-primary" icon="check" label={t('Save and leave')} onClickAsync={async () => await this.submitHandler(true)}/>
{canDelete &&
<LinkButton className="btn-danger" icon="trash-alt" label={t('delete')} to={`/reports/templates/${this.props.entity.id}/delete`}/>
}

View file

@ -265,7 +265,7 @@ export default class CUD extends Component {
<ButtonRow>
<Button type="submit" className="btn-primary" icon="check" label={t('Save')}/>
<Button type="submit" className="btn-primary" icon="check" label={t('Save and leave')} onClickAsync={async () => this.submitHandler(true)}/>
<Button type="submit" className="btn-primary" icon="check" label={t('Save and leave')} onClickAsync={async () => await this.submitHandler(true)}/>
{canDelete &&
<LinkButton className="btn-danger" icon="trash-alt" label={t('delete')} to={`/send-configurations/${this.props.entity.id}/delete`}/>
}

View file

@ -93,9 +93,13 @@ export default class UserShares extends Component {
{renderSharesTable('namespace', t('namespaces'))}
{renderSharesTable('list', t('lists'))}
{renderSharesTable('template', t('Templates'))}
{renderSharesTable('mosaicoTemplate', t('Mosaico Templates'))}
{renderSharesTable('campaign', t('Campaigns'))}
{renderSharesTable('customForm', t('customForms-1'))}
{renderSharesTable('report', t('reports'))}
{renderSharesTable('reportTemplate', t('reportTemplates'))}
{renderSharesTable('sendConfiguration', t('Send Configurations'))}
</div>
);
}

View file

@ -8,14 +8,14 @@ import {
Button,
ButtonRow,
CheckBox,
Dropdown,
Dropdown, filterData,
Form,
FormSendMethod,
InputField,
StaticField,
TableSelect,
TextArea,
withForm
withForm, withFormErrorHandlers
} from '../lib/form';
import {withErrorHandling} from '../lib/error-handling';
import {NamespaceSelect, validateNamespace} from '../lib/namespace';
@ -28,6 +28,7 @@ import {getUrl} from "../lib/urls";
import {TestSendModalDialog} from "./TestSendModalDialog";
import {withComponentMixins} from "../lib/decorator-helpers";
import moment from 'moment';
import {FieldWizard} from "../../../shared/lists";
@withComponentMixins([
@ -53,6 +54,9 @@ export default class CUD extends Component {
};
this.initForm({
loadMutator: ::this.getFormValuesMutator,
submitMutator: ::this.submitFormValuesMutator,
getPreSubmitUpdater: ::this.getPreSubmitFormValuesUpdater,
onChangeBeforeValidation: {
type: ::this.onTypeChanged
}
@ -83,9 +87,28 @@ export default class CUD extends Component {
this.templateTypes[data.type].afterLoad(data);
}
submitFormValuesMutator(data) {
this.templateTypes[data.type].beforeSave(data);
return filterData(data, ['name', 'description', 'type', 'data', 'html', 'text', 'namespace']);
}
async getPreSubmitFormValuesUpdater() {
let exportedData = {};
if (this.props.entity) {
const typeKey = this.getFormValue('type');
exportedData = await this.templateTypes[typeKey].exportHTMLEditorData(this);
}
return mutStateData => {
for (const key in exportedData) {
mutStateData.setIn([key, 'value'], exportedData[key]);
}
};
}
componentDidMount() {
if (this.props.entity) {
this.getFormValuesFromEntity(this.props.entity, ::this.getFormValuesMutator);
this.getFormValuesFromEntity(this.props.entity);
} else {
this.populateFormValues({
@ -138,15 +161,10 @@ export default class CUD extends Component {
await this.submitHandler();
}
@withFormErrorHandlers
async submitHandler(submitAndLeave) {
const t = this.props.t;
let exportedData = {};
if (this.props.entity) {
const typeKey = this.getFormValue('type');
exportedData = await this.templateTypes[typeKey].exportHTMLEditorData(this);
}
let sendMethod, url;
if (this.props.entity) {
sendMethod = FormSendMethod.PUT;
@ -159,17 +177,14 @@ export default class CUD extends Component {
this.disableForm();
this.setFormStatusMessage('info', t('saving'));
const submitResult = await this.validateAndSendFormValuesToURL(sendMethod, url, data => {
Object.assign(data, exportedData);
this.templateTypes[data.type].beforeSave(data);
});
const submitResult = await this.validateAndSendFormValuesToURL(sendMethod, url);
if (submitResult) {
if (this.props.entity) {
if (submitAndLeave) {
this.navigateToWithFlashMessage('/templates', 'success', t('Template updated'));
} else {
await this.getFormValuesFromURL(`rest/templates/${this.props.entity.id}`, ::this.getFormValuesMutator);
await this.getFormValuesFromURL(`rest/templates/${this.props.entity.id}`);
this.enableForm();
this.setFormStatusMessage('success', t('Template updated'));
}
@ -338,7 +353,7 @@ export default class CUD extends Component {
<ButtonRow>
<Button type="submit" className="btn-primary" icon="check" label={t('Save')}/>
{isEdit && <Button type="submit" className="btn-primary" icon="check" label={t('Save and leave')} onClickAsync={async () => this.submitHandler(true)}/>}
{isEdit && <Button type="submit" className="btn-primary" icon="check" label={t('Save and leave')} onClickAsync={async () => await this.submitHandler(true)}/>}
{canDelete && <LinkButton className="btn-danger" icon="trash-alt" label={t('delete')} to={`/templates/${this.props.entity.id}/delete`}/> }
{isEdit && <Button className="btn-success" icon="at" label={t('testSend')} onClickAsync={async () => this.setState({showTestSendModal: true})}/> }
</ButtonRow>

View file

@ -199,7 +199,7 @@ export function getTemplateTypes(t, prefix = '', entityTypeId = ResourceType.TEM
entity={owner.props.entity}
initialModel={owner.getFormValue(prefix + 'mosaicoData').model}
initialMetadata={owner.getFormValue(prefix + 'mosaicoData').metadata}
templatePath={getSandboxUrl(`static/mosaico/templates/${owner.getFormValue(prefix + 'mosaicoFsTemplate')}/index.html`)}
templatePath={getSandboxUrl(`static/mosaico/templates/${owner.getFormValue(prefix + 'mosaicoFsTemplate')}/template-${owner.getFormValue(prefix + 'mosaicoFsTemplate')}.html`)}
entityTypeId={entityTypeId}
title={t('mosaicoTemplateDesigner')}
onSave={::owner.save}

View file

@ -205,7 +205,7 @@ export default class CUD extends Component {
<ButtonRow>
<Button type="submit" className="btn-primary" icon="check" label={t('save')}/>
<Button type="submit" className="btn-primary" icon="check" label={t('Save and leave')} onClickAsync={async () => this.submitHandler(true)}/>
<Button type="submit" className="btn-primary" icon="check" label={t('Save and leave')} onClickAsync={async () => await this.submitHandler(true)}/>
{canDelete && <LinkButton className="btn-danger" icon="trash-alt" label={t('delete')} to={`/templates/mosaico/${this.props.entity.id}/delete`}/>}
{isEdit && typeKey && this.templateTypes[typeKey].getButtons(this)}
</ButtonRow>

View file

@ -265,7 +265,7 @@ export default class CUD extends Component {
<ButtonRow>
<Button type="submit" className="btn-primary" icon="check" label={t('Save')}/>
<Button type="submit" className="btn-primary" icon="check" label={t('Save and leave')} onClickAsync={async () => this.submitHandler(true)}/>
<Button type="submit" className="btn-primary" icon="check" label={t('Save and leave')} onClickAsync={async () => await this.submitHandler(true)}/>
{canDelete && <LinkButton className="btn-danger" icon="trash-alt" label={t('deleteUser')} to={`/users/${this.props.entity.id}/delete`}/>}
</ButtonRow>
</Form>