diff --git a/TODO.md b/TODO.md
index 6d063285..82f5c76f 100644
--- a/TODO.md
+++ b/TODO.md
@@ -21,3 +21,6 @@ Note that some of these may be already obsolete...
- Add field to subscriptions which says till when the consent has been given
- Provide a link (and merge tag) that will update the consent date to now
- Add campaign trigger that triggers if the consent for specific subscription field is about to expire (i.e. it is greater than now - seconds)
+
+### RSS Campaigns
+- Aggregated RSS campaigns
\ No newline at end of file
diff --git a/client/src/lib/decorator-helpers.js b/client/src/lib/decorator-helpers.js
index 628cb1ee..f5b0f3ab 100644
--- a/client/src/lib/decorator-helpers.js
+++ b/client/src/lib/decorator-helpers.js
@@ -2,17 +2,19 @@
import React from "react";
-export function createComponentMixin(contexts, deps, decoratorFn) {
+export function createComponentMixin(opts) {
return {
- contexts,
- deps,
- decoratorFn
+ contexts: opts.contexts || [],
+ deps: opts.deps || [],
+ delegateFuns: opts.delegateFuns || [],
+ decoratorFn: opts.decoratorFn
};
}
export function withComponentMixins(mixins, delegateFuns) {
const mixinsClosure = new Set();
for (const mixin of mixins) {
+ console.assert(mixin);
mixinsClosure.add(mixin);
for (const dep of mixin.deps) {
mixinsClosure.add(dep);
@@ -34,6 +36,10 @@ export function withComponentMixins(mixins, delegateFuns) {
mixinDelegateFuns.push(...delegateFuns);
}
+ for (const mixin of mixinsClosure.values()) {
+ mixinDelegateFuns.push(...mixin.delegateFuns);
+ }
+
function TargetClassWithCtors(props) {
if (!new.target) {
throw new TypeError();
@@ -55,54 +61,90 @@ export function withComponentMixins(mixins, delegateFuns) {
TargetClassWithCtors[attr] = TargetClass[attr];
}
+ function incorporateMixins(DecoratedInner) {
+ for (const mixin of mixinsClosure.values()) {
+ if (mixin.decoratorFn) {
+ const res = mixin.decoratorFn(DecoratedInner, TargetClassWithCtors);
- class ComponentMixinsInner extends React.Component {
- render() {
- const props = {
- ...this.props,
- ref: this.props._decoratorInnerInstanceRefFn
- };
- delete props._decoratorInnerInstanceRefFn;
+ if (res.cls) {
+ DecoratedInner = res.cls;
+ }
- return (
-
- );
+ if (res.ctor) {
+ ctors.push(res.ctor);
+ }
+ }
}
+
+ return DecoratedInner;
}
- let DecoratedInner = ComponentMixinsInner;
-
- for (const mixin of mixinsClosure.values()) {
- const res = mixin.decoratorFn(DecoratedInner, TargetClassWithCtors);
-
- if (res.cls) {
- DecoratedInner = res.cls;
- }
-
- if (res.ctor) {
- ctors.push(res.ctor);
- }
-
- if (res.delegateFuns) {
- mixinDelegateFuns.push(...res.delegateFuns);
- }
- }
-
- class ComponentMixinsOuter extends React.Component {
- constructor(props) {
- super(props);
-
- this._decoratorInnerInstanceRefFn = node => this._decoratorInnerInstance = node
- }
- render() {
- let innerFn = parentProps => {
+ if (mixinDelegateFuns.length > 0) {
+ class ComponentMixinsInner extends React.Component {
+ render() {
const props = {
- ...parentProps,
- _decoratorInnerInstanceRefFn: this._decoratorInnerInstanceRefFn
+ ...this.props,
+ ref: this.props._decoratorInnerInstanceRefFn
+ };
+ delete props._decoratorInnerInstanceRefFn;
+
+ return (
+
+ );
+ }
+ }
+
+ const DecoratedInner = incorporateMixins(ComponentMixinsInner);
+
+ class ComponentMixinsOuter extends React.Component {
+ constructor(props) {
+ super(props);
+
+ this._decoratorInnerInstanceRefFn = node => this._decoratorInnerInstance = node
+ }
+ render() {
+ let innerFn = parentProps => {
+ const props = {
+ ...parentProps,
+ _decoratorInnerInstanceRefFn: this._decoratorInnerInstanceRefFn
+ };
+
+ return
};
- return
+ for (const [propName, Context] of contexts.entries()) {
+ const existingInnerFn = innerFn;
+ innerFn = parentProps => (
+
+ {
+ value => existingInnerFn({
+ ...parentProps,
+ [propName]: value
+ })
+ }
+
+ );
+ }
+
+ return innerFn(this.props);
}
+ }
+
+ for (const fun of mixinDelegateFuns) {
+ ComponentMixinsOuter.prototype[fun] = function (...args) {
+ return this._decoratorInnerInstance[fun](...args);
+ }
+ }
+
+ return ComponentMixinsOuter;
+
+ } else {
+ const DecoratedInner = incorporateMixins(TargetClassWithCtors);
+
+ function ComponentContextProvider(props) {
+ let innerFn = props => {
+ return
+ };
for (const [propName, Context] of contexts.entries()) {
const existingInnerFn = innerFn;
@@ -118,17 +160,12 @@ export function withComponentMixins(mixins, delegateFuns) {
);
}
- return innerFn(this.props);
+ return innerFn(props);
}
+
+ return ComponentContextProvider;
}
- for (const fun of mixinDelegateFuns) {
- ComponentMixinsOuter.prototype[fun] = function (...args) {
- return this._decoratorInnerInstance[fun](...args);
- }
- }
-
- return ComponentMixinsOuter;
};
}
diff --git a/client/src/lib/error-handling.js b/client/src/lib/error-handling.js
index 09553c1a..50700e8e 100644
--- a/client/src/lib/error-handling.js
+++ b/client/src/lib/error-handling.js
@@ -21,33 +21,36 @@ function handleError(that, error) {
}
export const ParentErrorHandlerContext = React.createContext(null);
-export const withErrorHandling = createComponentMixin([{context: ParentErrorHandlerContext, propName: 'parentErrorHandler'}], [], (TargetClass, InnerClass) => {
- /* Example of use:
- this.getFormValuesFromURL(....).catch(error => this.handleError(error));
+export const withErrorHandling = createComponentMixin({
+ contexts: [{context: ParentErrorHandlerContext, propName: 'parentErrorHandler'}],
+ decoratorFn: (TargetClass, InnerClass) => {
+ /* Example of use:
+ this.getFormValuesFromURL(....).catch(error => this.handleError(error));
- It's equivalent to:
+ It's equivalent to:
- @withAsyncErrorHandler
- async loadFormValues() {
- await this.getFormValuesFromURL(...);
- }
- */
+ @withAsyncErrorHandler
+ async loadFormValues() {
+ await this.getFormValuesFromURL(...);
+ }
+ */
- const originalRender = InnerClass.prototype.render;
+ const originalRender = InnerClass.prototype.render;
- InnerClass.prototype.render = function() {
- return (
-
- {originalRender.apply(this)}
-
- );
+ InnerClass.prototype.render = function () {
+ return (
+
+ {originalRender.apply(this)}
+
+ );
+ }
+
+ InnerClass.prototype.handleError = function (error) {
+ handleError(this, error);
+ };
+
+ return {};
}
-
- InnerClass.prototype.handleError = function(error) {
- handleError(this, error);
- };
-
- return {};
});
export function withAsyncErrorHandler(target, name, descriptor) {
diff --git a/client/src/lib/form.js b/client/src/lib/form.js
index 40eede72..892e5919 100644
--- a/client/src/lib/form.js
+++ b/client/src/lib/form.js
@@ -46,12 +46,15 @@ const FormSendMethod = HTTPMethod;
export const FormStateOwnerContext = React.createContext(null);
-const withFormStateOwner = createComponentMixin([{context: FormStateOwnerContext, propName: 'formStateOwner'}], [], (TargetClass, InnerClass) => {
- InnerClass.prototype.getFormStateOwner = function() {
- return this.props.formStateOwner;
- };
+const withFormStateOwner = createComponentMixin({
+ contexts: [{context: FormStateOwnerContext, propName: 'formStateOwner'}],
+ decoratorFn: (TargetClass, InnerClass) => {
+ InnerClass.prototype.getFormStateOwner = function () {
+ return this.props.formStateOwner;
+ };
- return {};
+ return {};
+ }
});
export function withFormErrorHandlers(target, name, descriptor) {
@@ -345,9 +348,6 @@ class InputField extends Component {
}
}
-@withComponentMixins([
- withFormStateOwner
-])
class CheckBox extends Component {
static propTypes = {
id: PropTypes.string.isRequired,
@@ -359,19 +359,28 @@ class CheckBox extends Component {
}
render() {
- const props = this.props;
- const owner = this.getFormStateOwner();
- const id = this.props.id;
- const htmlId = 'form_' + id;
+ return (
+
+ {
+ owner => {
+ const props = this.props;
+ const id = this.props.id;
+ const htmlId = 'form_' + id;
- const inputClassName = owner.addFormValidationClass('form-check-input', id);
+ const inputClassName = owner.addFormValidationClass('form-check-input', id);
- return wrapInput(id, htmlId, owner, props.format, '', props.label, props.help,
-
- owner.updateFormValue(id, !owner.getFormValue(id))}/>
-
-
+ return wrapInput(id, htmlId, owner, props.format, '', props.label, props.help,
+
+ owner.updateFormValue(id, !owner.getFormValue(id))}/>
+
+
+ );
+ }
+
+ }
+
);
+
}
}
@@ -489,6 +498,15 @@ class RadioGroup extends Component {
withFormStateOwner
])
class TextArea extends Component {
+ constructor() {
+ super();
+ this.onChange = evt => {
+ const id = this.props.id;
+ const owner = this.getFormStateOwner();
+ owner.updateFormValue(id, evt.target.value);
+ }
+ }
+
static propTypes = {
id: PropTypes.string.isRequired,
label: PropTypes.string.isRequired,
@@ -506,7 +524,7 @@ class TextArea extends Component {
const className = owner.addFormValidationClass('form-control ' + (props.className || '') , id);
return wrapInput(id, htmlId, owner, props.format, '', props.label, props.help,
-
+
);
}
}
@@ -977,214 +995,443 @@ class ACEEditor extends Component {
}
-const withForm = createComponentMixin([], [], (TargetClass, InnerClass) => {
- const proto = InnerClass.prototype;
+const withForm = createComponentMixin({
+ decoratorFn: (TargetClass, InnerClass) => {
+ const proto = InnerClass.prototype;
- const cleanFormState = Immutable.Map({
- state: FormState.Loading,
- isValidationShown: false,
- isDisabled: false,
- statusMessageText: '',
- data: Immutable.Map(),
- savedData: Immutable.Map(),
- isServerValidationRunning: false
- });
+ const cleanFormState = Immutable.Map({
+ state: FormState.Loading,
+ isValidationShown: false,
+ isDisabled: false,
+ statusMessageText: '',
+ data: Immutable.Map(),
+ savedData: Immutable.Map(),
+ isServerValidationRunning: false
+ });
- const getSaveData = (self, formStateData) => {
- let data = formStateData.map(attr => attr.get('value')).toJS();
+ const getSaveData = (self, formStateData) => {
+ let data = formStateData.map(attr => attr.get('value')).toJS();
- if (self.submitFormValuesMutator) {
- const newData = self.submitFormValuesMutator(data, false);
- if (newData !== undefined) {
- data = newData;
+ if (self.submitFormValuesMutator) {
+ const newData = self.submitFormValuesMutator(data, false);
+ if (newData !== undefined) {
+ data = newData;
+ }
+ }
+
+ return data;
+ };
+
+ // formValidateResolve is called by "validateForm" once client receives validation response from server that does not
+ // trigger another server validation
+ let formValidateResolve = null;
+
+ function scheduleValidateForm(self) {
+ setTimeout(() => {
+ self.setState(previousState => ({
+ formState: previousState.formState.withMutations(mutState => {
+ validateFormState(self, mutState);
+ })
+ }));
+ }, 0);
+ }
+
+ function validateFormState(self, mutState) {
+ const settings = self.state.formSettings;
+
+ if (!mutState.get('isServerValidationRunning') && settings.serverValidation) {
+ const payload = {};
+ let payloadNotEmpty = false;
+
+ for (const attr of settings.serverValidation.extra || []) {
+ if (typeof attr === 'string') {
+ payload[attr] = mutState.getIn(['data', attr, 'value']);
+ } else {
+ const data = mutState.get('data').map(attr => attr.get('value')).toJS();
+ payload[attr.key] = attr.data(data);
+ }
+ }
+
+ for (const attr of settings.serverValidation.changed) {
+ const currValue = mutState.getIn(['data', attr, 'value']);
+ const serverValue = mutState.getIn(['data', attr, 'serverValue']);
+
+ // This really assumes that all form values are preinitialized (i.e. not undef)
+ if (currValue !== serverValue) {
+ mutState.setIn(['data', attr, 'serverValidated'], false);
+ payload[attr] = currValue;
+ payloadNotEmpty = true;
+ }
+ }
+
+ if (payloadNotEmpty) {
+ mutState.set('isServerValidationRunning', true);
+
+ axios.post(getUrl(settings.serverValidation.url), payload)
+ .then(response => {
+
+ if (self.isComponentMounted()) {
+ self.setState(previousState => ({
+ formState: previousState.formState.withMutations(mutState => {
+ mutState.set('isServerValidationRunning', false);
+
+ mutState.update('data', stateData => stateData.withMutations(mutStateData => {
+ for (const attr in payload) {
+ mutStateData.setIn([attr, 'serverValue'], payload[attr]);
+
+ if (payload[attr] === mutState.getIn(['data', attr, 'value'])) {
+ mutStateData.setIn([attr, 'serverValidated'], true);
+ mutStateData.setIn([attr, 'serverValidation'], response.data[attr] || true);
+ }
+ }
+ }));
+ })
+ }));
+
+ scheduleValidateForm(self);
+ }
+ })
+ .catch(error => {
+ if (self.isComponentMounted()) {
+ console.log('Error in "validateFormState": ' + error);
+
+ self.setState(previousState => ({
+ formState: previousState.formState.set('isServerValidationRunning', false)
+ }));
+
+ // TODO: It might be good not to give up immediatelly, but retry a couple of times
+ // scheduleValidateForm(self);
+ }
+ });
+ } else {
+ if (formValidateResolve) {
+ const resolve = formValidateResolve;
+ formValidateResolve = null;
+ resolve();
+ }
+ }
+ }
+
+ if (self.localValidateFormValues) {
+ mutState.update('data', stateData => stateData.withMutations(mutStateData => {
+ self.localValidateFormValues(mutStateData);
+ }));
}
}
- return data;
- };
+ const previousComponentDidMount = proto.componentDidMount;
+ proto.componentDidMount = function () {
+ this._isComponentMounted = true;
+ if (previousComponentDidMount) {
+ previousComponentDidMount.apply(this);
+ }
+ };
- // formValidateResolve is called by "validateForm" once client receives validation response from server that does not
- // trigger another server validation
- let formValidateResolve = null;
+ const previousComponentWillUnmount = proto.componentWillUnmount;
+ proto.componentWillUnmount = function () {
+ this._isComponentMounted = false;
+ if (previousComponentWillUnmount) {
+ previousComponentDidMount.apply(this);
+ }
+ };
- function scheduleValidateForm(self) {
- setTimeout(() => {
- self.setState(previousState => ({
+ proto.isComponentMounted = function () {
+ return !!this._isComponentMounted;
+ }
+
+ proto.initForm = function (settings) {
+ const state = this.state || {};
+ state.formState = cleanFormState;
+ state.formSettings = {
+ leaveConfirmation: true,
+ ...(settings || {})
+ };
+ this.state = state;
+ };
+
+ proto.resetFormState = function () {
+ this.setState({
+ formState: cleanFormState
+ });
+ };
+
+ proto.getFormValuesFromEntity = function (entity) {
+ const settings = this.state.formSettings;
+ const data = Object.assign({}, entity);
+
+ data.originalHash = data.hash;
+ delete data.hash;
+
+ if (this.getFormValuesMutator) {
+ this.getFormValuesMutator(data, this.getFormValues());
+ }
+
+ this.populateFormValues(data);
+ };
+
+ proto.getFormValuesFromURL = async function (url) {
+ const settings = this.state.formSettings;
+ setTimeout(() => {
+ this.setState(previousState => {
+ if (previousState.formState.get('state') === FormState.Loading) {
+ return {
+ formState: previousState.formState.set('state', FormState.LoadingWithNotice)
+ };
+ }
+ });
+ }, 500);
+
+ const response = await axios.get(getUrl(url));
+
+ let data = response.data;
+
+ data.originalHash = data.hash;
+ delete data.hash;
+
+ if (this.getFormValuesMutator) {
+ const newData = this.getFormValuesMutator(data, this.getFormValues());
+
+ if (newData !== undefined) {
+ data = newData;
+ }
+ }
+
+ this.populateFormValues(data);
+ };
+
+ 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();
+
+ if (this.submitFormValuesMutator) {
+ const newData = this.submitFormValuesMutator(data, true);
+ if (newData !== undefined) {
+ data = newData;
+ }
+ }
+
+ const response = await axios.method(method, getUrl(url), data);
+
+ if (settings.leaveConfirmation) {
+ await new Promise((resolve, reject) => {
+ this.setState(previousState => ({
+ formState: previousState.formState.set('savedData', getSaveData(this, previousState.formState.get('data')))
+ }), resolve);
+ });
+ }
+
+ return response.data || true;
+
+ } else {
+ this.showFormValidation();
+ return false;
+ }
+ };
+
+
+ proto.populateFormValues = function (data) {
+ const settings = this.state.formSettings;
+
+ this.setState(previousState => ({
formState: previousState.formState.withMutations(mutState => {
- validateFormState(self, mutState);
+ mutState.set('state', FormState.Ready);
+
+ mutState.update('data', stateData => stateData.withMutations(mutStateData => {
+ for (const key in data) {
+ mutStateData.set(key, Immutable.Map({
+ value: data[key]
+ }));
+ }
+ }));
+
+ if (settings.leaveConfirmation) {
+ mutState.set('savedData', getSaveData(this, mutState.get('data')));
+ }
+
+ validateFormState(this, mutState);
})
}));
- }, 0);
- }
-
- function validateFormState(self, mutState) {
- const settings = self.state.formSettings;
-
- if (!mutState.get('isServerValidationRunning') && settings.serverValidation) {
- const payload = {};
- let payloadNotEmpty = false;
-
- for (const attr of settings.serverValidation.extra || []) {
- if (typeof attr === 'string') {
- payload[attr] = mutState.getIn(['data', attr, 'value']);
- } else {
- const data = mutState.get('data').map(attr => attr.get('value')).toJS();
- payload[attr.key] = attr.data(data);
- }
- }
-
- for (const attr of settings.serverValidation.changed) {
- const currValue = mutState.getIn(['data', attr, 'value']);
- const serverValue = mutState.getIn(['data', attr, 'serverValue']);
-
- // This really assumes that all form values are preinitialized (i.e. not undef)
- if (currValue !== serverValue) {
- mutState.setIn(['data', attr, 'serverValidated'], false);
- payload[attr] = currValue;
- payloadNotEmpty = true;
- }
- }
-
- if (payloadNotEmpty) {
- mutState.set('isServerValidationRunning', true);
-
- axios.post(getUrl(settings.serverValidation.url), payload)
- .then(response => {
-
- if (self.isComponentMounted()) {
- self.setState(previousState => ({
- formState: previousState.formState.withMutations(mutState => {
- mutState.set('isServerValidationRunning', false);
-
- mutState.update('data', stateData => stateData.withMutations(mutStateData => {
- for (const attr in payload) {
- mutStateData.setIn([attr, 'serverValue'], payload[attr]);
-
- if (payload[attr] === mutState.getIn(['data', attr, 'value'])) {
- mutStateData.setIn([attr, 'serverValidated'], true);
- mutStateData.setIn([attr, 'serverValidation'], response.data[attr] || true);
- }
- }
- }));
- })
- }));
-
- scheduleValidateForm(self);
- }
- })
- .catch(error => {
- if (self.isComponentMounted()) {
- console.log('Error in "validateFormState": ' + error);
-
- self.setState(previousState => ({
- formState: previousState.formState.set('isServerValidationRunning', false)
- }));
-
- // TODO: It might be good not to give up immediatelly, but retry a couple of times
- // scheduleValidateForm(self);
- }
- });
- } else {
- if (formValidateResolve) {
- const resolve = formValidateResolve;
- formValidateResolve = null;
- resolve();
- }
- }
- }
-
- if (self.localValidateFormValues) {
- mutState.update('data', stateData => stateData.withMutations(mutStateData => {
- self.localValidateFormValues(mutStateData);
- }));
- }
- }
-
- const previousComponentDidMount = proto.componentDidMount;
- proto.componentDidMount = function() {
- this._isComponentMounted = true;
- if (previousComponentDidMount) {
- previousComponentDidMount.apply(this);
- }
- };
-
- const previousComponentWillUnmount = proto.componentWillUnmount;
- proto.componentWillUnmount = function() {
- this._isComponentMounted = false;
- if (previousComponentWillUnmount) {
- previousComponentDidMount.apply(this);
- }
- };
-
- proto.isComponentMounted = function() {
- return !!this._isComponentMounted;
- }
-
- proto.initForm = function(settings) {
- const state = this.state || {};
- state.formState = cleanFormState;
- state.formSettings = {
- leaveConfirmation: true,
- ...(settings || {})
};
- this.state = state;
- };
- proto.resetFormState = function() {
- this.setState({
- formState: cleanFormState
- });
- };
-
- proto.getFormValuesFromEntity = function(entity) {
- const settings = this.state.formSettings;
- const data = Object.assign({}, entity);
-
- data.originalHash = data.hash;
- delete data.hash;
-
- if (this.getFormValuesMutator) {
- this.getFormValuesMutator(data, this.getFormValues());
- }
-
- this.populateFormValues(data);
- };
-
- proto.getFormValuesFromURL = async function(url) {
- const settings = this.state.formSettings;
- setTimeout(() => {
- this.setState(previousState => {
- if (previousState.formState.get('state') === FormState.Loading) {
- return {
- formState: previousState.formState.set('state', FormState.LoadingWithNotice)
- };
- }
- });
- }, 500);
-
- const response = await axios.get(getUrl(url));
-
- let data = response.data;
-
- data.originalHash = data.hash;
- delete data.hash;
-
- if (this.getFormValuesMutator) {
- const newData = this.getFormValuesMutator(data, this.getFormValues());
-
- if (newData !== undefined) {
- data = newData;
+ proto.waitForFormServerValidated = async function () {
+ if (!this.isFormServerValidated()) {
+ await new Promise(resolve => {
+ formValidateResolve = resolve;
+ });
}
- }
+ };
- this.populateFormValues(data);
- };
+ proto.scheduleFormRevalidate = function () {
+ scheduleValidateForm(this);
+ };
- proto.validateAndSendFormValuesToURL = async function(method, url) {
- const settings = this.state.formSettings;
- await this.waitForFormServerValidated();
+ proto.updateForm = function (mutator) {
+ this.setState(previousState => {
+ const onChangeBeforeValidationCallback = this.state.formSettings.onChangeBeforeValidation || {};
+
+ const formState = previousState.formState.withMutations(mutState => {
+ mutState.update('data', stateData => stateData.withMutations(mutStateData => {
+ mutator(mutStateData);
+
+ if (typeof onChangeBeforeValidationCallback === 'object') {
+ for (const key in onChangeBeforeValidationCallback) {
+ const oldValue = previousState.formState.getIn(['data', key, 'value']);
+ const newValue = mutStateData.getIn([key, 'value']);
+ onChangeBeforeValidationCallback[key](mutStateData, key, oldValue, newValue);
+ }
+ } else {
+ onChangeBeforeValidationCallback(mutStateData);
+ }
+ }));
+
+ validateFormState(this, mutState);
+ });
+
+ let newState = {
+ formState
+ };
+
+
+ const onChangeCallback = this.state.formSettings.onChange || {};
+
+ if (typeof onChangeCallback === 'object') {
+ for (const key in onChangeCallback) {
+ const oldValue = previousState.formState.getIn(['data', key, 'value']);
+ const newValue = formState.getIn(['data', key, 'value']);
+ onChangeCallback[key](newState, key, oldValue, newValue);
+ }
+ } else {
+ onChangeCallback(newState);
+ }
+
+ return newState;
+ });
+ };
+
+ proto.updateFormValue = function (key, value) {
+ this.setState(previousState => {
+ const oldValue = previousState.formState.getIn(['data', key, 'value']);
+
+ const onChangeBeforeValidationCallback = this.state.formSettings.onChangeBeforeValidation || {};
+
+ const formState = previousState.formState.withMutations(mutState => {
+ mutState.update('data', stateData => stateData.withMutations(mutStateData => {
+ mutStateData.setIn([key, 'value'], value);
+
+ if (typeof onChangeBeforeValidationCallback === 'object') {
+ if (onChangeBeforeValidationCallback[key]) {
+ onChangeBeforeValidationCallback[key](mutStateData, key, oldValue, value);
+ }
+ } else {
+ onChangeBeforeValidationCallback(mutStateData, key, oldValue, value);
+ }
+ }));
+
+ validateFormState(this, mutState);
+ });
+
+ let newState = {
+ formState
+ };
+
+
+ const onChangeCallback = this.state.formSettings.onChange || {};
+
+ if (typeof onChangeCallback === 'object') {
+ if (onChangeCallback[key]) {
+ onChangeCallback[key](newState, key, oldValue, value);
+ }
+ } else {
+ onChangeCallback(newState, key, oldValue, value);
+ }
+
+ return newState;
+ });
+ };
+
+ proto.getFormValue = function (name) {
+ return this.state.formState.getIn(['data', name, 'value']);
+ };
+
+ proto.getFormValues = function (name) {
+ if (!this.state || !this.state.formState) return undefined;
+ return this.state.formState.get('data').map(attr => attr.get('value')).toJS();
+ };
+
+ proto.getFormError = function (name) {
+ return this.state.formState.getIn(['data', name, 'error']);
+ };
+
+ proto.isFormWithLoadingNotice = function () {
+ return this.state.formState.get('state') === FormState.LoadingWithNotice;
+ };
+
+ proto.isFormLoading = function () {
+ return this.state.formState.get('state') === FormState.Loading || this.state.formState.get('state') === FormState.LoadingWithNotice;
+ };
+
+ proto.isFormReady = function () {
+ return this.state.formState.get('state') === FormState.Ready;
+ };
+
+ const _isFormChanged = self => {
+ const currentData = getSaveData(self, self.state.formState.get('data'));
+ const savedData = self.state.formState.get('savedData');
+
+ function isDifferent(data1, data2, prefix) {
+ if (typeof data1 === 'object' && typeof data2 === 'object' && data1 && data2) {
+ const keys = new Set([...Object.keys(data1), ...Object.keys(data2)]);
+ for (const key of keys) {
+ if (isDifferent(data1[key], data2[key], `${prefix}/${key}`)) {
+ return true;
+ }
+ }
+ } else if (data1 !== data2) {
+ // console.log(prefix);
+ return true;
+ }
+ return false;
+ }
+
+ const result = isDifferent(currentData, savedData, '');
+
+ return result;
+ };
+
+ proto.isFormChanged = function () {
+ const settings = this.state.formSettings;
+
+ if (!settings.leaveConfirmation) return false;
+
+ 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.leaveConfirmation) return false;
- if (this.isFormWithoutErrors()) {
if (settings.getPreSubmitUpdater) {
const preSubmitUpdater = await settings.getPreSubmitUpdater();
@@ -1197,357 +1444,132 @@ const withForm = createComponentMixin([], [], (TargetClass, InnerClass) => {
});
}
- let data = this.getFormValues();
+ return _isFormChanged(this);
- if (this.submitFormValuesMutator) {
- const newData = this.submitFormValuesMutator(data, true);
- if (newData !== undefined) {
- data = newData;
- }
- }
+ };
- const response = await axios.method(method, getUrl(url), data);
+ proto.isFormValidationShown = function () {
+ return this.state.formState.get('isValidationShown');
+ };
- if (settings.leaveConfirmation) {
- await new Promise((resolve, reject) => {
- this.setState(previousState => ({
- formState: previousState.formState.set('savedData', getSaveData(this, previousState.formState.get('data')))
- }), resolve);
- });
- }
-
- return response.data || true;
-
- } else {
- this.showFormValidation();
- return false;
- }
- };
-
-
- proto.populateFormValues = function(data) {
- const settings = this.state.formSettings;
-
- this.setState(previousState => ({
- formState: previousState.formState.withMutations(mutState => {
- mutState.set('state', FormState.Ready);
-
- mutState.update('data', stateData => stateData.withMutations(mutStateData => {
- for (const key in data) {
- mutStateData.set(key, Immutable.Map({
- value: data[key]
- }));
- }
- }));
-
- if (settings.leaveConfirmation) {
- mutState.set('savedData', getSaveData(this, mutState.get('data')));
- }
-
- validateFormState(this, mutState);
- })
- }));
- };
-
- proto.waitForFormServerValidated = async function() {
- if (!this.isFormServerValidated()) {
- await new Promise(resolve => { formValidateResolve = resolve; });
- }
- };
-
- proto.scheduleFormRevalidate = function() {
- scheduleValidateForm(this);
- };
-
- proto.updateForm = function(mutator) {
- this.setState(previousState => {
- const onChangeBeforeValidationCallback = this.state.formSettings.onChangeBeforeValidation || {};
-
- const formState = previousState.formState.withMutations(mutState => {
- mutState.update('data', stateData => stateData.withMutations(mutStateData => {
- mutator(mutStateData);
-
- if (typeof onChangeBeforeValidationCallback === 'object') {
- for (const key in onChangeBeforeValidationCallback) {
- const oldValue = previousState.formState.getIn(['data', key, 'value']);
- const newValue = mutStateData.getIn([key, 'value']);
- onChangeBeforeValidationCallback[key](mutStateData, key, oldValue, newValue);
- }
- } else {
- onChangeBeforeValidationCallback(mutStateData);
- }
- }));
-
- validateFormState(this, mutState);
- });
-
- let newState = {
- formState
- };
-
-
- const onChangeCallback = this.state.formSettings.onChange || {};
-
- if (typeof onChangeCallback === 'object') {
- for (const key in onChangeCallback) {
- const oldValue = previousState.formState.getIn(['data', key, 'value']);
- const newValue = formState.getIn(['data', key, 'value']);
- onChangeCallback[key](newState, key, oldValue, newValue);
+ proto.addFormValidationClass = function (className, name) {
+ if (this.isFormValidationShown()) {
+ const error = this.getFormError(name);
+ if (error) {
+ return className + ' is-invalid';
+ } else {
+ return className + ' is-valid';
}
} else {
- onChangeCallback(newState);
+ return className;
}
+ };
- return newState;
- });
- };
-
- proto.updateFormValue = function(key, value) {
- this.setState(previousState => {
- const oldValue = previousState.formState.getIn(['data', key, 'value']);
-
- const onChangeBeforeValidationCallback = this.state.formSettings.onChangeBeforeValidation || {};
-
- const formState = previousState.formState.withMutations(mutState => {
- mutState.update('data', stateData => stateData.withMutations(mutStateData => {
- mutStateData.setIn([key, 'value'], value);
-
- if (typeof onChangeBeforeValidationCallback === 'object') {
- if (onChangeBeforeValidationCallback[key]) {
- onChangeBeforeValidationCallback[key](mutStateData, key, oldValue, value);
- }
- } else {
- onChangeBeforeValidationCallback(mutStateData, key, oldValue, value);
- }
- }));
-
- validateFormState(this, mutState);
- });
-
- let newState = {
- formState
- };
-
-
- const onChangeCallback = this.state.formSettings.onChange || {};
-
- if (typeof onChangeCallback === 'object') {
- if (onChangeCallback[key]) {
- onChangeCallback[key](newState, key, oldValue, value);
- }
+ proto.getFormValidationMessage = function (name) {
+ if (this.isFormValidationShown()) {
+ return this.getFormError(name);
} else {
- onChangeCallback(newState, key, oldValue, value);
+ return '';
}
+ };
- return newState;
- });
- };
+ proto.showFormValidation = function () {
+ this.setState(previousState => ({formState: previousState.formState.set('isValidationShown', true)}));
+ };
- proto.getFormValue = function(name) {
- return this.state.formState.getIn(['data', name, 'value']);
- };
+ proto.hideFormValidation = function () {
+ this.setState(previousState => ({formState: previousState.formState.set('isValidationShown', false)}));
+ };
- proto.getFormValues = function(name) {
- if (!this.state || !this.state.formState) return undefined;
- return this.state.formState.get('data').map(attr => attr.get('value')).toJS();
- };
+ proto.isFormWithoutErrors = function () {
+ return !this.state.formState.get('data').find(attr => attr.get('error'));
+ };
- proto.getFormError = function(name) {
- return this.state.formState.getIn(['data', name, 'error']);
- };
+ proto.isFormServerValidated = function () {
+ return !this.state.formSettings.serverValidation || this.state.formSettings.serverValidation.changed.every(attr => this.state.formState.getIn(['data', attr, 'serverValidated']));
+ };
- proto.isFormWithLoadingNotice = function() {
- return this.state.formState.get('state') === FormState.LoadingWithNotice;
- };
+ proto.getFormStatusMessageText = function () {
+ return this.state.formState.get('statusMessageText');
+ };
- proto.isFormLoading = function() {
- return this.state.formState.get('state') === FormState.Loading || this.state.formState.get('state') === FormState.LoadingWithNotice;
- };
+ proto.getFormStatusMessageSeverity = function () {
+ return this.state.formState.get('statusMessageSeverity');
+ };
- proto.isFormReady = function() {
- return this.state.formState.get('state') === FormState.Ready;
- };
+ proto.setFormStatusMessage = function (severity, text) {
+ this.setState(previousState => ({
+ formState: previousState.formState.withMutations(map => {
+ map.set('statusMessageText', text);
+ map.set('statusMessageSeverity', severity);
+ })
+ }));
+ };
- const _isFormChanged = self => {
- const currentData = getSaveData(self, self.state.formState.get('data'));
- const savedData = self.state.formState.get('savedData');
+ proto.clearFormStatusMessage = function () {
+ this.setState(previousState => ({
+ formState: previousState.formState.withMutations(map => {
+ map.set('statusMessageText', '');
+ })
+ }));
+ };
- function isDifferent(data1, data2, prefix) {
- if (typeof data1 === 'object' && typeof data2 === 'object' && data1 && data2) {
- const keys = new Set([...Object.keys(data1), ...Object.keys(data2)]);
- for (const key of keys) {
- if (isDifferent(data1[key], data2[key], `${prefix}/${key}`)) {
- return true;
- }
+ proto.enableForm = function () {
+ this.setState(previousState => ({formState: previousState.formState.set('isDisabled', false)}));
+ };
+
+ proto.disableForm = function () {
+ this.setState(previousState => ({formState: previousState.formState.set('isDisabled', true)}));
+ };
+
+ proto.isFormDisabled = function () {
+ return this.state.formState.get('isDisabled');
+ };
+
+ proto.formHandleErrors = async function (fn) {
+ const t = this.props.t;
+ try {
+ await fn();
+ } catch (error) {
+ if (error instanceof interoperableErrors.ChangedError) {
+ this.disableForm();
+ this.setFormStatusMessage('danger',
+
+ {t('yourUpdatesCannotBeSaved')}{' '}
+ {t('someoneElseHasIntroducedModificationIn')}
+
+ );
+ return;
}
- } else if (data1 !== data2) {
- // console.log(prefix);
- return true;
+
+ if (error instanceof interoperableErrors.NamespaceNotFoundError) {
+ this.disableForm();
+ this.setFormStatusMessage('danger',
+
+ {t('yourUpdatesCannotBeSaved')}{' '}
+ {t('itSeemsThatSomeoneElseHasDeletedThe')}
+
+ );
+ return;
+ }
+
+ if (error instanceof interoperableErrors.NotFoundError) {
+ this.disableForm();
+ this.setFormStatusMessage('danger',
+
+ {t('yourUpdatesCannotBeSaved')}{' '}
+ {t('itSeemsThatSomeoneElseHasDeletedThe-1')}
+
+ );
+ return;
+ }
+
+ throw error;
}
- return false;
- }
+ };
- const result = isDifferent(currentData, savedData, '');
-
- return result;
- };
-
- proto.isFormChanged = function() {
- const settings = this.state.formSettings;
-
- if (!settings.leaveConfirmation) return false;
-
- 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.leaveConfirmation) return false;
-
- 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');
- };
-
- proto.addFormValidationClass = function(className, name) {
- if (this.isFormValidationShown()) {
- const error = this.getFormError(name);
- if (error) {
- return className + ' is-invalid';
- } else {
- return className + ' is-valid';
- }
- } else {
- return className;
- }
- };
-
- proto.getFormValidationMessage = function(name) {
- if (this.isFormValidationShown()) {
- return this.getFormError(name);
- } else {
- return '';
- }
- };
-
- proto.showFormValidation = function() {
- this.setState(previousState => ({formState: previousState.formState.set('isValidationShown', true)}));
- };
-
- proto.hideFormValidation = function() {
- this.setState(previousState => ({formState: previousState.formState.set('isValidationShown', false)}));
- };
-
- proto.isFormWithoutErrors = function() {
- return !this.state.formState.get('data').find(attr => attr.get('error'));
- };
-
- proto.isFormServerValidated = function() {
- return !this.state.formSettings.serverValidation || this.state.formSettings.serverValidation.changed.every(attr => this.state.formState.getIn(['data', attr, 'serverValidated']));
- };
-
- proto.getFormStatusMessageText = function() {
- return this.state.formState.get('statusMessageText');
- };
-
- proto.getFormStatusMessageSeverity = function() {
- return this.state.formState.get('statusMessageSeverity');
- };
-
- proto.setFormStatusMessage = function(severity, text) {
- this.setState(previousState => ({
- formState: previousState.formState.withMutations(map => {
- map.set('statusMessageText', text);
- map.set('statusMessageSeverity', severity);
- })
- }));
- };
-
- proto.clearFormStatusMessage = function() {
- this.setState(previousState => ({
- formState: previousState.formState.withMutations(map => {
- map.set('statusMessageText', '');
- })
- }));
- };
-
- proto.enableForm = function() {
- this.setState(previousState => ({formState: previousState.formState.set('isDisabled', false)}));
- };
-
- proto.disableForm = function() {
- this.setState(previousState => ({formState: previousState.formState.set('isDisabled', true)}));
- };
-
- proto.isFormDisabled = function() {
- return this.state.formState.get('isDisabled');
- };
-
- proto.formHandleErrors = async function(fn) {
- const t = this.props.t;
- try {
- await fn();
- } catch (error) {
- if (error instanceof interoperableErrors.ChangedError) {
- this.disableForm();
- this.setFormStatusMessage('danger',
-
- {t('yourUpdatesCannotBeSaved')}{' '}
- {t('someoneElseHasIntroducedModificationIn')}
-
- );
- return;
- }
-
- if (error instanceof interoperableErrors.NamespaceNotFoundError) {
- this.disableForm();
- this.setFormStatusMessage('danger',
-
- {t('yourUpdatesCannotBeSaved')}{' '}
- {t('itSeemsThatSomeoneElseHasDeletedThe')}
-
- );
- return;
- }
-
- if (error instanceof interoperableErrors.NotFoundError) {
- this.disableForm();
- this.setFormStatusMessage('danger',
-
- {t('yourUpdatesCannotBeSaved')}{' '}
- {t('itSeemsThatSomeoneElseHasDeletedThe-1')}
-
- );
- return;
- }
-
- throw error;
- }
- };
-
- return {};
+ return {};
+ }
});
function filterData(obj, allowedKeys) {
diff --git a/client/src/lib/i18n.js b/client/src/lib/i18n.js
index 9e7d30bb..ba39afe7 100644
--- a/client/src/lib/i18n.js
+++ b/client/src/lib/i18n.js
@@ -1,8 +1,8 @@
'use strict';
import React from 'react';
+import {I18nextProvider, withNamespaces} from 'react-i18next';
import i18n from 'i18next';
-import {withNamespaces} from "react-i18next";
import LanguageDetector from 'i18next-browser-languagedetector';
import mailtrainConfig from 'mailtrainConfig';
@@ -63,12 +63,30 @@ i18n
export default i18n;
-export const withTranslation = createComponentMixin([], [], (TargetClass, InnerClass) => {
- return {
- cls: withNamespaces()(TargetClass)
- };
+export const TranslationContext = React.createContext(null);
+
+export const withTranslation = createComponentMixin({
+ contexts: [{context: TranslationContext, propName: 't'}]
});
+const TranslationContextProvider = withNamespaces()(props => {
+ return (
+
+ {props.children}
+
+ );
+});
+
+export function TranslationRoot(props) {
+ return (
+
+
+ {props.children}
+
+
+ );
+}
+
export function tMark(key) {
return key;
}
diff --git a/client/src/lib/page-common.js b/client/src/lib/page-common.js
index a812ce58..488b1399 100644
--- a/client/src/lib/page-common.js
+++ b/client/src/lib/page-common.js
@@ -354,30 +354,34 @@ export function renderRoute(route, panelRouteCtor, loadingMessageFn, flashMessag
}
export const SectionContentContext = React.createContext(null);
-export const withPageHelpers = createComponentMixin([{context: SectionContentContext, propName: 'sectionContent'}], [withErrorHandling], (TargetClass, InnerClass) => {
- InnerClass.prototype.setFlashMessage = function(severity, text) {
- return this.props.sectionContent.setFlashMessage(severity, text);
- };
+export const withPageHelpers = createComponentMixin({
+ contexts: [{context: SectionContentContext, propName: 'sectionContent'}],
+ deps: [withErrorHandling],
+ decoratorFn: (TargetClass, InnerClass) => {
+ InnerClass.prototype.setFlashMessage = function (severity, text) {
+ return this.props.sectionContent.setFlashMessage(severity, text);
+ };
- InnerClass.prototype.navigateTo = function(path) {
- return this.props.sectionContent.navigateTo(path);
- };
+ InnerClass.prototype.navigateTo = function (path) {
+ return this.props.sectionContent.navigateTo(path);
+ };
- InnerClass.prototype.navigateBack = function() {
- return this.props.sectionContent.navigateBack();
- };
+ 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.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.registerBeforeUnloadHandlers = function (handlers) {
+ return this.props.sectionContent.registerBeforeUnloadHandlers(handlers);
+ };
- InnerClass.prototype.deregisterBeforeUnloadHandlers = function(handlers) {
- return this.props.sectionContent.deregisterBeforeUnloadHandlers(handlers);
- };
+ InnerClass.prototype.deregisterBeforeUnloadHandlers = function (handlers) {
+ return this.props.sectionContent.deregisterBeforeUnloadHandlers(handlers);
+ };
- return {};
+ return {};
+ }
});
diff --git a/client/src/lib/page.js b/client/src/lib/page.js
index 5042b845..a6f8e271 100644
--- a/client/src/lib/page.js
+++ b/client/src/lib/page.js
@@ -683,21 +683,24 @@ export class NavDropdown extends Component {
}
-export const requiresAuthenticatedUser = createComponentMixin([], [withPageHelpers], (TargetClass, InnerClass) => {
- class RequiresAuthenticatedUser extends React.Component {
- constructor(props) {
- super(props);
- props.sectionContent.ensureAuthenticated();
+export const requiresAuthenticatedUser = createComponentMixin({
+ deps: [withPageHelpers],
+ decoratorFn: (TargetClass, InnerClass) => {
+ class RequiresAuthenticatedUser extends React.Component {
+ constructor(props) {
+ super(props);
+ props.sectionContent.ensureAuthenticated();
+ }
+
+ render() {
+ return
+ }
}
- render() {
- return
- }
+ return {
+ cls: RequiresAuthenticatedUser
+ };
}
-
- return {
- cls: RequiresAuthenticatedUser
- };
});
export function getLanguageChooser(t) {
diff --git a/client/src/lib/sandboxed-ckeditor-root.js b/client/src/lib/sandboxed-ckeditor-root.js
index 3f319a47..6ab2f49f 100644
--- a/client/src/lib/sandboxed-ckeditor-root.js
+++ b/client/src/lib/sandboxed-ckeditor-root.js
@@ -4,8 +4,7 @@ import './public-path';
import React, {Component} from 'react';
import ReactDOM from 'react-dom';
-import {I18nextProvider} from 'react-i18next';
-import i18n, {withTranslation} from './i18n';
+import {TranslationRoot, withTranslation} from './i18n';
import {parentRPC, UntrustedContentRoot} from './untrusted';
import PropTypes from "prop-types";
import styles from "./sandboxed-ckeditor.scss";
@@ -126,9 +125,9 @@ export default function() {
parentRPC.init();
ReactDOM.render(
-
+
} />
- ,
+ ,
document.getElementById('root')
);
};
diff --git a/client/src/lib/sandboxed-codeeditor-root.js b/client/src/lib/sandboxed-codeeditor-root.js
index e0803641..97326e06 100644
--- a/client/src/lib/sandboxed-codeeditor-root.js
+++ b/client/src/lib/sandboxed-codeeditor-root.js
@@ -4,8 +4,7 @@ import './public-path';
import React, {Component} from 'react';
import ReactDOM from 'react-dom';
-import {I18nextProvider} from 'react-i18next';
-import i18n, {withTranslation} from './i18n';
+import {TranslationRoot, withTranslation} from './i18n';
import {parentRPC, UntrustedContentRoot} from './untrusted';
import PropTypes from "prop-types";
import styles from "./sandboxed-codeeditor.scss";
@@ -211,9 +210,9 @@ export default function() {
parentRPC.init();
ReactDOM.render(
-
+
} />
- ,
+ ,
document.getElementById('root')
);
};
diff --git a/client/src/lib/sandboxed-grapesjs-root.js b/client/src/lib/sandboxed-grapesjs-root.js
index bbb6608e..f9c2f76d 100644
--- a/client/src/lib/sandboxed-grapesjs-root.js
+++ b/client/src/lib/sandboxed-grapesjs-root.js
@@ -4,8 +4,7 @@ import './public-path';
import React, {Component} from 'react';
import ReactDOM from 'react-dom';
-import {I18nextProvider} from 'react-i18next';
-import i18n, {withTranslation} from './i18n';
+import {TranslationRoot, withTranslation} from './i18n';
import {parentRPC, UntrustedContentRoot} from './untrusted';
import PropTypes from "prop-types";
import {getPublicUrl, getSandboxUrl, getTrustedUrl} from "./urls";
@@ -626,9 +625,9 @@ export default function() {
parentRPC.init();
ReactDOM.render(
-
+
} />
- ,
+ ,
document.getElementById('root')
);
};
diff --git a/client/src/lib/sandboxed-mosaico-root.js b/client/src/lib/sandboxed-mosaico-root.js
index b86403a8..4a3dfd5a 100644
--- a/client/src/lib/sandboxed-mosaico-root.js
+++ b/client/src/lib/sandboxed-mosaico-root.js
@@ -4,8 +4,7 @@ import './public-path';
import React, {Component} from 'react';
import ReactDOM from 'react-dom';
-import {I18nextProvider} from 'react-i18next';
-import i18n, {withTranslation} from './i18n';
+import {TranslationRoot, withTranslation} from './i18n';
import {parentRPC, UntrustedContentRoot} from './untrusted';
import PropTypes from "prop-types";
import {getPublicUrl, getSandboxUrl, getTrustedUrl} from "./urls";
@@ -149,9 +148,9 @@ export default function() {
parentRPC.init();
ReactDOM.render(
-
+
} />
- ,
+ ,
document.getElementById('root')
);
};
diff --git a/client/src/namespaces/CUD.js b/client/src/namespaces/CUD.js
index c90d27dd..9cc0abea 100644
--- a/client/src/namespaces/CUD.js
+++ b/client/src/namespaces/CUD.js
@@ -82,9 +82,11 @@ export default class CUD extends Component {
this.removeNsIdSubtree(data);
}
- this.setState({
- treeData: data
- });
+ if (this.isComponentMounted()) {
+ this.setState({
+ treeData: data
+ });
+ }
}
}
@@ -191,7 +193,7 @@ export default class CUD extends Component {
render() {
const t = this.props.t;
const isEdit = !!this.props.entity;
- const canDelete = isEdit && !this.isEditGlobal() && this.props.entity.permissions.includes('delete');
+ const canDelete = isEdit && !this.isEditGlobal() && mailtrainConfig.user.namespace !== this.props.entity.id && this.props.entity.permissions.includes('delete');
return (
diff --git a/client/src/namespaces/List.js b/client/src/namespaces/List.js
index 8185cd7f..56b952b0 100644
--- a/client/src/namespaces/List.js
+++ b/client/src/namespaces/List.js
@@ -10,6 +10,7 @@ import {checkPermissions} from "../lib/permissions";
import {tableAddDeleteButton, tableRestActionDialogInit, tableRestActionDialogRender} from "../lib/modals";
import {getGlobalNamespaceId} from "../../../shared/namespaces";
import {withComponentMixins} from "../lib/decorator-helpers";
+import mailtrainConfig from 'mailtrainConfig';
@withComponentMixins([
withTranslation,
@@ -64,7 +65,8 @@ export default class List extends Component {
});
}
- if (Number.parseInt(node.key) !== getGlobalNamespaceId()) {
+ const namespaceId = Number.parseInt(node.key);
+ if (namespaceId !== getGlobalNamespaceId() && mailtrainConfig.user.namespace !== namespaceId) {
tableAddDeleteButton(actions, this, node.data.permissions, `rest/namespaces/${node.key}`, node.data.unsanitizedTitle, t('deletingNamespace'), t('namespaceDeleted'));
}
diff --git a/client/src/root.js b/client/src/root.js
index 0e0e15ce..92bfba68 100644
--- a/client/src/root.js
+++ b/client/src/root.js
@@ -4,8 +4,7 @@ import './lib/public-path';
import React, {Component} from 'react';
import ReactDOM from 'react-dom';
-import {I18nextProvider} from 'react-i18next';
-import i18n, {withTranslation} from './lib/i18n';
+import {TranslationRoot, withTranslation} from './lib/i18n';
import account from './account/root';
import login from './login/root';
import blacklist from './blacklist/root';
@@ -139,7 +138,7 @@ class Root extends Component {
}
export default function() {
- ReactDOM.render(,document.getElementById('root'));
+ ReactDOM.render(,document.getElementById('root'));
};
diff --git a/server/lib/dependency-helpers.js b/server/lib/dependency-helpers.js
index 9992ff3a..9aaba342 100644
--- a/server/lib/dependency-helpers.js
+++ b/server/lib/dependency-helpers.js
@@ -37,17 +37,12 @@ async function ensureNoDependencies(tx, context, id, depSpecs) {
name: row.name,
link: entityType.clientLink(row.id)
});
- } else if (await shares.checkEntityPermissionTx(tx, context, depSpec.entityTypeId, row.id, 'view')) {
+ } else if (!depSpec.viewPermission && await shares.checkEntityPermissionTx(tx, context, depSpec.entityTypeId, row.id, 'view')) {
deps.push({
entityTypeId: depSpec.entityTypeId,
name: row.name,
link: entityType.clientLink(row.id)
});
- } else {
- deps.push({
- entityTypeId: depSpec.entityTypeId,
- id: row.id
- });
}
}
}