diff --git a/client/src/lib/form.js b/client/src/lib/form.js
index 4c72c522..b7b79d73 100644
--- a/client/src/lib/form.js
+++ b/client/src/lib/form.js
@@ -1099,9 +1099,8 @@ const withForm = createComponentMixin([], [], (TargetClass, InnerClass) => {
data.originalHash = data.hash;
delete data.hash;
- const mutator = this.getFormValuesMutator;
- if (mutator) {
- mutator(data);
+ if (this.getFormValuesMutator) {
+ this.getFormValuesMutator(data);
}
this.populateFormValues(data);
@@ -1126,9 +1125,8 @@ const withForm = createComponentMixin([], [], (TargetClass, InnerClass) => {
data.originalHash = data.hash;
delete data.hash;
- const mutator = this.getFormValuesMutator;
- if (mutator) {
- const newData = mutator(data);
+ if (this.getFormValuesMutator) {
+ const newData = this.getFormValuesMutator(data);
if (newData !== undefined) {
data = newData;
@@ -1157,9 +1155,8 @@ const withForm = createComponentMixin([], [], (TargetClass, InnerClass) => {
let data = this.getFormValues();
- const mutator = this.submitFormValuesMutator;
- if (mutator) {
- const newData = mutator(data);
+ if (this.submitFormValuesMutator) {
+ const newData = this.submitFormValuesMutator(data);
if (newData !== undefined) {
data = newData;
}
diff --git a/client/src/lib/modals.js b/client/src/lib/modals.js
index 09cdddc7..be3e8bd2 100644
--- a/client/src/lib/modals.js
+++ b/client/src/lib/modals.js
@@ -197,10 +197,22 @@ export function tableRestActionDialogInit(owner) {
function _hide(owner, dontRefresh = false) {
- owner.tableRestActionDialogData = {};
+ const refreshTables = owner.tableRestActionDialogData.refreshTables;
+
owner.setState({ tableRestActionDialogShown: false });
+
if (!dontRefresh) {
- owner.table.refresh();
+ owner.tableRestActionDialogData = {};
+
+ if (refreshTables) {
+ refreshTables();
+ } else {
+ owner.table.refresh();
+ }
+ } else {
+ // _hide is called twice: (1) at performing action, and at (2) success. Here we keep the refreshTables
+ // reference till it is really needed in step #2.
+ owner.tableRestActionDialogData = { refreshTables };
}
}
@@ -268,14 +280,19 @@ export function tableAddRestActionButton(actions, owner, action, button, title,
actionData: action.data,
actionInProgressMsg: actionInProgressMsg,
actionDoneMsg: actionDoneMsg,
- onErrorAsync: onErrorAsync
+ onErrorAsync: onErrorAsync,
+ refreshTables: action.refreshTables
};
owner.setState({
tableRestActionDialogShown: true
});
- owner.table.refresh();
+ if (action.refreshTables) {
+ action.refreshTables();
+ } else {
+ owner.table.refresh();
+ }
}
});
}
diff --git a/client/src/shares/Share.js b/client/src/shares/Share.js
index 4a729bb2..f7ad7857 100644
--- a/client/src/shares/Share.js
+++ b/client/src/shares/Share.js
@@ -4,18 +4,16 @@ import React, {Component} from 'react';
import PropTypes from 'prop-types';
import {withTranslation} from '../lib/i18n';
import {requiresAuthenticatedUser, Title, withPageHelpers} from '../lib/page';
-import {withAsyncErrorHandler, withErrorHandling} from '../lib/error-handling';
import {Button, ButtonRow, Form, FormSendMethod, TableSelect, withForm, withFormErrorHandlers} from '../lib/form';
import {Table} from '../lib/table';
-import axios from '../lib/axios';
+import {HTTPMethod} from '../lib/axios';
import mailtrainConfig from 'mailtrainConfig';
-import {getUrl} from "../lib/urls";
import {withComponentMixins} from "../lib/decorator-helpers";
+import {tableAddRestActionButton, tableRestActionDialogInit, tableRestActionDialogRender} from "../lib/modals";
@withComponentMixins([
withTranslation,
withForm,
- withErrorHandling,
withPageHelpers,
requiresAuthenticatedUser
])
@@ -23,9 +21,13 @@ export default class Share extends Component {
constructor(props) {
super(props);
+ this.state = {};
+
this.initForm({
leaveConfirmation: false
});
+
+ tableRestActionDialogInit(this);
}
static propTypes = {
@@ -34,19 +36,6 @@ export default class Share extends Component {
entityTypeId: PropTypes.string
}
- @withAsyncErrorHandler
- async deleteShare(userId) {
- const data = {
- entityTypeId: this.props.entityTypeId,
- entityId: this.props.entity.id,
- userId
- };
-
- await axios.put(getUrl('rest/shares'), data);
- this.sharesTable.refresh();
- this.usersTableSelect.refresh();
- }
-
clearShareFields() {
this.populateFormValues({
entityTypeId: this.props.entityTypeId,
@@ -116,15 +105,37 @@ export default class Share extends Component {
const autoGenerated = data[4];
if (!autoGenerated) {
- actions.push({
- label: 'Delete',
- action: () => this.deleteShare(data[3])
- });
+ const username = data[0];
+ const userId = data[3];
+
+ tableAddRestActionButton(
+ actions,
+ this,
+ {
+ method: HTTPMethod.PUT,
+ url: 'rest/shares',
+ data: {
+ entityTypeId: this.props.entityTypeId,
+ entityId: this.props.entity.id,
+ userId
+ },
+ refreshTables: () => {
+ this.sharesTable.refresh();
+ this.usersTableSelect.refresh();
+ }
+ },
+ { icon: 'trash-alt', label: t('Unshare') },
+ t('Confirm Unsharing'),
+ t('Are you sure you want to remove the share to user "{{username}}"?', {username}),
+ t('Removing share for user "{{username}}"', {username}),
+ t('Share for user "{{username}}" removed', {username}),
+ null
+ );
}
return actions;
}
- })
+ });
let usersLabelIndex = 1;
const usersColumns = [
@@ -146,6 +157,7 @@ export default class Share extends Component {
return (
+ {tableRestActionDialogRender(this)}
{this.props.title}
{t('addUser')}
diff --git a/client/src/shares/UserShares.js b/client/src/shares/UserShares.js
index f86c2d76..975c779d 100644
--- a/client/src/shares/UserShares.js
+++ b/client/src/shares/UserShares.js
@@ -4,16 +4,13 @@ import React, {Component} from 'react';
import PropTypes from 'prop-types';
import {withTranslation} from '../lib/i18n';
import {requiresAuthenticatedUser, Title, withPageHelpers} from '../lib/page';
-import {withAsyncErrorHandler, withErrorHandling} from '../lib/error-handling';
import {Table} from '../lib/table';
-import axios from '../lib/axios';
-import {Icon} from "../lib/bootstrap-components";
-import {getUrl} from "../lib/urls";
+import {HTTPMethod} from '../lib/axios';
import {withComponentMixins} from "../lib/decorator-helpers";
+import {tableAddRestActionButton, tableRestActionDialogInit, tableRestActionDialogRender} from "../lib/modals";
@withComponentMixins([
withTranslation,
- withErrorHandling,
withPageHelpers,
requiresAuthenticatedUser
])
@@ -22,33 +19,19 @@ export default class UserShares extends Component {
super(props);
this.sharesTables = {};
+
+ this.state = {};
+ tableRestActionDialogInit(this);
}
static propTypes = {
user: PropTypes.object
}
- @withAsyncErrorHandler
- async deleteShare(entityTypeId, entityId) {
- const data = {
- entityTypeId,
- entityId,
- userId: this.props.user.id
- };
-
- await axios.put(getUrl('rest/shares'), data);
- for (const key in this.sharesTables) {
- this.sharesTables[key].refresh();
- }
- }
-
- componentDidMount() {
- }
-
render() {
const t = this.props.t;
- const renderSharesTable = (entityTypeId, title) => {
+ const renderSharesTable = (entityTypeId, title, typeName) => {
const columns = [
{ data: 0, title: t('name') },
{ data: 1, title: t('role') },
@@ -59,10 +42,33 @@ export default class UserShares extends Component {
const perms = data[4];
if (!autoGenerated && perms.includes('share')) {
- actions.push({
- label:
,
- action: () => this.deleteShare(entityTypeId, data[2])
- });
+ const name = data[0];
+ const entityId = data[2];
+
+ tableAddRestActionButton(
+ actions,
+ this,
+ {
+ method: HTTPMethod.PUT,
+ url: 'rest/shares',
+ data: {
+ entityTypeId,
+ entityId,
+ userId: this.props.user.id
+ },
+ refreshTables: () => {
+ for (const key in this.sharesTables) {
+ this.sharesTables[key].refresh();
+ }
+ }
+ },
+ { icon: 'trash-alt', label: t('Unshare') },
+ t('Confirm Unsharing'),
+ t('Are you sure you want to remove the sharing of the {{typeName}} "{{name}}"?', {typeName, name}),
+ t('Removing sharing of the {{typeName}} "{{name}}"', {typeName, name}),
+ t('Sharing of the {{typeName}} "{{name}}" removed', {typeName, name}),
+ null
+ );
}
return actions;
@@ -80,17 +86,18 @@ export default class UserShares extends Component {
return (
+ {tableRestActionDialogRender(this)}
{t('sharesForUserUsername', {username: this.props.user.username})}
- {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'))}
+ {renderSharesTable('namespace', t('namespaces'), t('namespace_lc'))}
+ {renderSharesTable('list', t('lists'), t('list_lc'))}
+ {renderSharesTable('template', t('Templates'), t('template_lc'))}
+ {renderSharesTable('mosaicoTemplate', t('Mosaico Templates'), t('Mosaico template'))}
+ {renderSharesTable('campaign', t('Campaigns'), t('campaign_lc'))}
+ {renderSharesTable('customForm', t('customForms-1', t('custom forms')))}
+ {renderSharesTable('report', t('reports'), t('report_lc'))}
+ {renderSharesTable('reportTemplate', t('reportTemplates'), t('report template'))}
+ {renderSharesTable('sendConfiguration', t('Send Configurations'), t('send configuration'))}
);
}
diff --git a/client/src/templates/helpers.js b/client/src/templates/helpers.js
index 54e53c10..b99d5a33 100644
--- a/client/src/templates/helpers.js
+++ b/client/src/templates/helpers.js
@@ -103,14 +103,19 @@ export function getTemplateTypes(t, prefix = '', entityTypeId = ResourceType.TEM
/>
,
exportHTMLEditorData: async owner => {
- const {html, metadata, model} = await owner.editorNode.exportState();
- return {
- [prefix + 'html']: html,
- [prefix + 'mosaicoData']: {
- metadata,
- model
- }
- };
+ const state = await owner.editorNode.exportState();
+ // If the sandbox is still loading, the exportState returns null.
+ if (state) {
+ return {
+ [prefix + 'html']: state.html,
+ [prefix + 'mosaicoData']: {
+ metadata: state.metadata,
+ model: state.model
+ }
+ };
+ } else {
+ return null;
+ }
},
exportContent: async (owner, contentType) => {
const {html, metadata, model} = await owner.editorNode.exportState();
@@ -184,14 +189,19 @@ export function getTemplateTypes(t, prefix = '', entityTypeId = ResourceType.TEM
/>
,
exportHTMLEditorData: async owner => {
- const {html, metadata, model} = await owner.editorNode.exportState();
- return {
- [prefix + 'html']: html,
- [prefix + 'mosaicoData']: {
- metadata,
- model
- }
- };
+ const state = await owner.editorNode.exportState();
+ // If the sandbox is still loading, the exportState returns null.
+ if (state) {
+ return {
+ [prefix + 'html']: state.html,
+ [prefix + 'mosaicoData']: {
+ metadata: state.metadata,
+ model: state.model
+ }
+ };
+ } else {
+ return null;
+ }
},
exportContent: async (owner, contentType) => {
const {html, metadata, model} = await owner.editorNode.exportState();
@@ -265,14 +275,19 @@ export function getTemplateTypes(t, prefix = '', entityTypeId = ResourceType.TEM
/>
,
exportHTMLEditorData: async owner => {
- const {html, source, style} = await owner.editorNode.exportState();
- return {
- [prefix + 'html']: html,
- [prefix + 'grapesJSData']: {
- source,
- style
- }
- };
+ const state = await owner.editorNode.exportState();
+ // If the sandbox is still loading, the exportState returns null.
+ if (state) {
+ return {
+ [prefix + 'html']: state.html,
+ [prefix + 'grapesJSData']: {
+ source: state.source,
+ style: state.style
+ }
+ };
+ } else {
+ return null;
+ }
},
exportContent: async (owner, contentType) => {
const {html, source, style} = await owner.editorNode.exportState();
@@ -326,13 +341,18 @@ export function getTemplateTypes(t, prefix = '', entityTypeId = ResourceType.TEM
/>
,
exportHTMLEditorData: async owner => {
- const {html, source} = await owner.editorNode.exportState();
- return {
- [prefix + 'html']: html,
- [prefix + 'ckeditor4Data']: {
- source
- }
- };
+ const state = await owner.editorNode.exportState();
+ // If the sandbox is still loading, the exportState returns null.
+ if (state) {
+ return {
+ [prefix + 'html']: state.html,
+ [prefix + 'ckeditor4Data']: {
+ source: state.source
+ }
+ };
+ } else {
+ return null;
+ }
},
exportContent: async (owner, contentType) => {
const {html, source} = await owner.editorNode.exportState();
@@ -401,13 +421,18 @@ export function getTemplateTypes(t, prefix = '', entityTypeId = ResourceType.TEM
/>
,
exportHTMLEditorData: async owner => {
- const {html, source} = await owner.editorNode.exportState();
- return {
- [prefix + 'html']: html,
- [prefix + 'codeEditorData']: {
- source
- }
- };
+ const state = await owner.editorNode.exportState();
+ // If the sandbox is still loading, the exportState returns null.
+ if (state) {
+ return {
+ [prefix + 'html']: state.html,
+ [prefix + 'codeEditorData']: {
+ source: state.source
+ }
+ };
+ } else {
+ return null;
+ }
},
exportContent: async (owner, contentType) => {
const {html, source} = await owner.editorNode.exportState();
diff --git a/locales/en-US/common.json b/locales/en-US/common.json
index 7dcce754..56aa1b0a 100644
--- a/locales/en-US/common.json
+++ b/locales/en-US/common.json
@@ -125,6 +125,7 @@
"editRssCampaign": "Edit RSS Campaign",
"editTriggeredCampaign": "Edit Triggered Campaign",
"template": "Template",
+ "template_lc": "template",
"template_plural": "Templates",
"customContentClonedFromTemplate": "Custom content cloned from template",
"customContentClonedFromAnotherCampaign": "Custom content cloned from another campaign",
@@ -147,6 +148,7 @@
"subscribers": "Subscribers",
"description": "Description",
"namespace": "Namespace",
+ "namespace_lc": "namespace",
"namespace_plural": "Namespaces",
"namespaceFiltering": "Namespace filtering",
"remove": "Remove",
@@ -154,6 +156,7 @@
"moveUp": "Move up",
"moveDown": "Move down",
"list": "List",
+ "list_lc": "list",
"list_plural": "Lists",
"segment": "Segment",
"useAParticularSegment": "Use a particular segment",
@@ -170,6 +173,7 @@
"contentSource": "Content source",
"selectingATemplateCreatesACampaign": "Selecting a template creates a campaign specific copy from it.",
"campaign": "Campaign",
+ "campaign_lc": "campaign",
"campaign_plural": "Campaigns",
"contentOfTheSelectedCampaignWillBeCopied": "Content of the selected campaign will be copied into this campaign.",
"renderUrl": "Render URL",
@@ -350,6 +354,7 @@
"itSeemsThatSomeoneElseHasDeletedThe-1": "It seems that someone else has deleted the entity in the meantime.",
"customForms": "Custom forms",
"report": "Report",
+ "report_lc": "report",
"report_plural": "Reports",
"reportTemplate": "Report template",
"reportTemplate_plural": "Report templates",