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:
parent
4f77272042
commit
48dcf2c701
399 changed files with 4032 additions and 77702 deletions
|
@ -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`}/> }
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
2
client/src/lib/bootstrap-components.js
vendored
2
client/src/lib/bootstrap-components.js
vendored
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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}
|
||||
|
|
|
@ -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 {};
|
||||
});
|
||||
|
|
|
@ -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>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -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 = '#';
|
||||
});
|
||||
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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`}/>
|
||||
}
|
||||
|
|
|
@ -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`}/>
|
||||
}
|
||||
|
|
|
@ -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`}/>
|
||||
}
|
||||
|
|
|
@ -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>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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}
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue