Line endings fixed so that we don't have CRLF in Git. Better now than later.
This commit is contained in:
parent
2fe7f82be3
commit
d482d214d9
69 changed files with 6405 additions and 6405 deletions
42
TODO.md
42
TODO.md
|
@ -1,21 +1,21 @@
|
||||||
### Front page
|
### Front page
|
||||||
- Some dashboard
|
- Some dashboard
|
||||||
|
|
||||||
### Campaigns
|
### Campaigns
|
||||||
- List of sent RSS campaigns (?)
|
- List of sent RSS campaigns (?)
|
||||||
|
|
||||||
### Pull requests
|
### Pull requests
|
||||||
- Support ldaps:// - 5325f2ea7864ce5f42a9a6df3408af7ffbd32591
|
- Support ldaps:// - 5325f2ea7864ce5f42a9a6df3408af7ffbd32591
|
||||||
- Support https - abd788d8f4d18b5a977226ba1224cba7f2b7fa9b
|
- Support https - abd788d8f4d18b5a977226ba1224cba7f2b7fa9b
|
||||||
- Support warn of failed login - 4bd1e994b27420ba366d9b0429e9014e5bf01f13
|
- Support warn of failed login - 4bd1e994b27420ba366d9b0429e9014e5bf01f13
|
||||||
- Add X-Mailer header option in settings to override or disable it - 44fe8882b876bdfd9990110496d16f819dc64ac3
|
- Add X-Mailer header option in settings to override or disable it - 44fe8882b876bdfd9990110496d16f819dc64ac3
|
||||||
- Add custom unsubscribe option in a campaign - 68cb8384f7dfdbcaf2932293ec5a2f1ec0a1554e
|
- Add custom unsubscribe option in a campaign - 68cb8384f7dfdbcaf2932293ec5a2f1ec0a1554e
|
||||||
|
|
||||||
### API
|
### API
|
||||||
- Add API extensions
|
- Add API extensions
|
||||||
|
|
||||||
### GDPR
|
### GDPR
|
||||||
- Refuse editing subscriptions which have been anonymized
|
- Refuse editing subscriptions which have been anonymized
|
||||||
- Add field to subscriptions which says till when the consent has been given
|
- 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
|
- 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)
|
- Add campaign trigger that triggers if the consent for specific subscription field is about to expire (i.e. it is greater than now - seconds)
|
||||||
|
|
42
UPGRADE.md
42
UPGRADE.md
|
@ -1,21 +1,21 @@
|
||||||
## Migration from Mailtrain v1 to Mailtrain v2
|
## Migration from Mailtrain v1 to Mailtrain v2
|
||||||
|
|
||||||
The migration should happen almost automatically. There are however the following caveats:
|
The migration should happen almost automatically. There are however the following caveats:
|
||||||
|
|
||||||
1. Structure of config files (under `config`) has changed at many places. Revisit the default config (`config/default.toml`)
|
1. Structure of config files (under `config`) has changed at many places. Revisit the default config (`config/default.toml`)
|
||||||
and update your configs accordingly.
|
and update your configs accordingly.
|
||||||
|
|
||||||
2. Images uploaded in a template editor (Mosaico, Grapesjs, etc.) need to be manually moved to a new destination (under `client`).
|
2. Images uploaded in a template editor (Mosaico, Grapesjs, etc.) need to be manually moved to a new destination (under `client`).
|
||||||
For Mosaico, this means to move folders named by a number from `public/mosaico` to `client/static/mosaico`.
|
For Mosaico, this means to move folders named by a number from `public/mosaico` to `client/static/mosaico`.
|
||||||
|
|
||||||
3. Directory for custom Mosaico templates has changed from `public/mosaico/templates` to `client/static/mosaico/templates`.
|
3. Directory for custom Mosaico templates has changed from `public/mosaico/templates` to `client/static/mosaico/templates`.
|
||||||
|
|
||||||
4. Imports are not migrated. If you have any pending imports, complete them before migration to v2.
|
4. Imports are not migrated. If you have any pending imports, complete them before migration to v2.
|
||||||
|
|
||||||
5. Zone MTA configuration endpoint (webhooks/zone-mta/sender-config) has changed. The send-configuration CID has to be
|
5. Zone MTA configuration endpoint (webhooks/zone-mta/sender-config) has changed. The send-configuration CID has to be
|
||||||
part of the URL - e.g. webhooks/zone-mta/sender-config/system.
|
part of the URL - e.g. webhooks/zone-mta/sender-config/system.
|
||||||
|
|
||||||
6. If there are lists that contain birthday or date fields that were created before
|
6. If there are lists that contain birthday or date fields that were created before
|
||||||
commit `bc73a0df0cab9943d726bd12fc1c6f2ff1279aa7` (on Jan 3, 2018), they still have TIMESTAMP data type in DB instead
|
commit `bc73a0df0cab9943d726bd12fc1c6f2ff1279aa7` (on Jan 3, 2018), they still have TIMESTAMP data type in DB instead
|
||||||
of DATETIME. The problem was that that commit did not introduce migration from TIMESTAMP to DATETIME.
|
of DATETIME. The problem was that that commit did not introduce migration from TIMESTAMP to DATETIME.
|
||||||
Mailtrain v2 does this migration, however in some corner cases, this may shift the date by a day back or forth.
|
Mailtrain v2 does this migration, however in some corner cases, this may shift the date by a day back or forth.
|
||||||
|
|
2
client/.gitignore
vendored
2
client/.gitignore
vendored
|
@ -1 +1 @@
|
||||||
/dist
|
/dist
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
.api {
|
.api {
|
||||||
:global .card h4 {
|
:global .card h4 {
|
||||||
margin-top: 0px;
|
margin-top: 0px;
|
||||||
}
|
}
|
||||||
|
|
||||||
h4 {
|
h4 {
|
||||||
margin-top: 45px;
|
margin-top: 45px;
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -1,34 +1,34 @@
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
CampaignStatus,
|
CampaignStatus,
|
||||||
CampaignType
|
CampaignType
|
||||||
} from "../../../shared/campaigns";
|
} from "../../../shared/campaigns";
|
||||||
|
|
||||||
export function getCampaignLabels(t) {
|
export function getCampaignLabels(t) {
|
||||||
|
|
||||||
const campaignTypeLabels = {
|
const campaignTypeLabels = {
|
||||||
[CampaignType.REGULAR]: t('regular'),
|
[CampaignType.REGULAR]: t('regular'),
|
||||||
[CampaignType.TRIGGERED]: t('triggered'),
|
[CampaignType.TRIGGERED]: t('triggered'),
|
||||||
[CampaignType.RSS]: t('rss')
|
[CampaignType.RSS]: t('rss')
|
||||||
};
|
};
|
||||||
|
|
||||||
const campaignStatusLabels = {
|
const campaignStatusLabels = {
|
||||||
[CampaignStatus.IDLE]: t('idle'),
|
[CampaignStatus.IDLE]: t('idle'),
|
||||||
[CampaignStatus.SCHEDULED]: t('scheduled'),
|
[CampaignStatus.SCHEDULED]: t('scheduled'),
|
||||||
[CampaignStatus.PAUSED]: t('paused'),
|
[CampaignStatus.PAUSED]: t('paused'),
|
||||||
[CampaignStatus.FINISHED]: t('finished'),
|
[CampaignStatus.FINISHED]: t('finished'),
|
||||||
[CampaignStatus.PAUSED]: t('paused'),
|
[CampaignStatus.PAUSED]: t('paused'),
|
||||||
[CampaignStatus.INACTIVE]: t('inactive'),
|
[CampaignStatus.INACTIVE]: t('inactive'),
|
||||||
[CampaignStatus.ACTIVE]: t('active'),
|
[CampaignStatus.ACTIVE]: t('active'),
|
||||||
[CampaignStatus.SENDING]: t('sending')
|
[CampaignStatus.SENDING]: t('sending')
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
campaignStatusLabels,
|
campaignStatusLabels,
|
||||||
campaignTypeLabels
|
campaignTypeLabels
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,35 +1,35 @@
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
import {Entity, Event} from '../../../../shared/triggers';
|
import {Entity, Event} from '../../../../shared/triggers';
|
||||||
|
|
||||||
export function getTriggerTypes(t) {
|
export function getTriggerTypes(t) {
|
||||||
|
|
||||||
const entityLabels = {
|
const entityLabels = {
|
||||||
[Entity.SUBSCRIPTION]: t('subscription'),
|
[Entity.SUBSCRIPTION]: t('subscription'),
|
||||||
[Entity.CAMPAIGN]: t('campaign')
|
[Entity.CAMPAIGN]: t('campaign')
|
||||||
};
|
};
|
||||||
|
|
||||||
const SubscriptionEvent = Event[Entity.SUBSCRIPTION];
|
const SubscriptionEvent = Event[Entity.SUBSCRIPTION];
|
||||||
const CampaignEvent = Event[Entity.CAMPAIGN];
|
const CampaignEvent = Event[Entity.CAMPAIGN];
|
||||||
|
|
||||||
const eventLabels = {
|
const eventLabels = {
|
||||||
[Entity.SUBSCRIPTION]: {
|
[Entity.SUBSCRIPTION]: {
|
||||||
[SubscriptionEvent.CREATED]: t('created'),
|
[SubscriptionEvent.CREATED]: t('created'),
|
||||||
[SubscriptionEvent.LATEST_OPEN]: t('latestOpen'),
|
[SubscriptionEvent.LATEST_OPEN]: t('latestOpen'),
|
||||||
[SubscriptionEvent.LATEST_CLICK]: t('latestClick')
|
[SubscriptionEvent.LATEST_CLICK]: t('latestClick')
|
||||||
},
|
},
|
||||||
[Entity.CAMPAIGN]: {
|
[Entity.CAMPAIGN]: {
|
||||||
[CampaignEvent.DELIVERED]: t('delivered'),
|
[CampaignEvent.DELIVERED]: t('delivered'),
|
||||||
[CampaignEvent.OPENED]: t('opened'),
|
[CampaignEvent.OPENED]: t('opened'),
|
||||||
[CampaignEvent.CLICKED]: t('clicked'),
|
[CampaignEvent.CLICKED]: t('clicked'),
|
||||||
[CampaignEvent.NOT_OPENED]: t('notOpened'),
|
[CampaignEvent.NOT_OPENED]: t('notOpened'),
|
||||||
[CampaignEvent.NOT_CLICKED]: t('notClicked')
|
[CampaignEvent.NOT_CLICKED]: t('notClicked')
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
entityLabels,
|
entityLabels,
|
||||||
eventLabels
|
eventLabels
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,32 +1,32 @@
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
import csrfToken from 'csrfToken';
|
import csrfToken from 'csrfToken';
|
||||||
import axios from 'axios';
|
import axios from 'axios';
|
||||||
import interoperableErrors from '../../../shared/interoperable-errors';
|
import interoperableErrors from '../../../shared/interoperable-errors';
|
||||||
|
|
||||||
const axiosInst = axios.create({
|
const axiosInst = axios.create({
|
||||||
headers: {
|
headers: {
|
||||||
'X-CSRF-TOKEN': csrfToken
|
'X-CSRF-TOKEN': csrfToken
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const axiosWrapper = {
|
const axiosWrapper = {
|
||||||
get: (...args) => axiosInst.get(...args).catch(error => { throw (error.response && interoperableErrors.deserialize(error.response.data)) || error }),
|
get: (...args) => axiosInst.get(...args).catch(error => { throw (error.response && interoperableErrors.deserialize(error.response.data)) || error }),
|
||||||
put: (...args) => axiosInst.put(...args).catch(error => { throw (error.response && interoperableErrors.deserialize(error.response.data)) || error }),
|
put: (...args) => axiosInst.put(...args).catch(error => { throw (error.response && interoperableErrors.deserialize(error.response.data)) || error }),
|
||||||
post: (...args) => axiosInst.post(...args).catch(error => { throw (error.response && interoperableErrors.deserialize(error.response.data)) || error }),
|
post: (...args) => axiosInst.post(...args).catch(error => { throw (error.response && interoperableErrors.deserialize(error.response.data)) || error }),
|
||||||
delete: (...args) => axiosInst.delete(...args).catch(error => { throw (error.response && interoperableErrors.deserialize(error.response.data)) || error })
|
delete: (...args) => axiosInst.delete(...args).catch(error => { throw (error.response && interoperableErrors.deserialize(error.response.data)) || error })
|
||||||
};
|
};
|
||||||
|
|
||||||
const HTTPMethod = {
|
const HTTPMethod = {
|
||||||
GET: axiosWrapper.get,
|
GET: axiosWrapper.get,
|
||||||
PUT: axiosWrapper.put,
|
PUT: axiosWrapper.put,
|
||||||
POST: axiosWrapper.post,
|
POST: axiosWrapper.post,
|
||||||
DELETE: axiosWrapper.delete
|
DELETE: axiosWrapper.delete
|
||||||
};
|
};
|
||||||
|
|
||||||
axiosWrapper.method = (method, ...args) => method(...args);
|
axiosWrapper.method = (method, ...args) => method(...args);
|
||||||
|
|
||||||
export default axiosWrapper;
|
export default axiosWrapper;
|
||||||
export {
|
export {
|
||||||
HTTPMethod
|
HTTPMethod
|
||||||
}
|
}
|
662
client/src/lib/bootstrap-components.js
vendored
662
client/src/lib/bootstrap-components.js
vendored
|
@ -1,331 +1,331 @@
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
import React, {Component} from 'react';
|
import React, {Component} from 'react';
|
||||||
import {withTranslation} from './i18n';
|
import {withTranslation} from './i18n';
|
||||||
import PropTypes
|
import PropTypes
|
||||||
from 'prop-types';
|
from 'prop-types';
|
||||||
import {
|
import {
|
||||||
withAsyncErrorHandler,
|
withAsyncErrorHandler,
|
||||||
withErrorHandling
|
withErrorHandling
|
||||||
} from './error-handling';
|
} from './error-handling';
|
||||||
import {withComponentMixins} from "./decorator-helpers";
|
import {withComponentMixins} from "./decorator-helpers";
|
||||||
|
|
||||||
@withComponentMixins([
|
@withComponentMixins([
|
||||||
withTranslation,
|
withTranslation,
|
||||||
withErrorHandling
|
withErrorHandling
|
||||||
])
|
])
|
||||||
export class DismissibleAlert extends Component {
|
export class DismissibleAlert extends Component {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
severity: PropTypes.string.isRequired,
|
severity: PropTypes.string.isRequired,
|
||||||
onCloseAsync: PropTypes.func
|
onCloseAsync: PropTypes.func
|
||||||
}
|
}
|
||||||
|
|
||||||
@withAsyncErrorHandler
|
@withAsyncErrorHandler
|
||||||
onClose() {
|
onClose() {
|
||||||
if (this.props.onCloseAsync) {
|
if (this.props.onCloseAsync) {
|
||||||
this.props.onCloseAsync();
|
this.props.onCloseAsync();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const t = this.props.t;
|
const t = this.props.t;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={`alert alert-${this.props.severity} alert-dismissible`} role="alert">
|
<div className={`alert alert-${this.props.severity} alert-dismissible`} role="alert">
|
||||||
<button type="button" className="close" aria-label={t('close')} onClick={::this.onClose}><span aria-hidden="true">×</span></button>
|
<button type="button" className="close" aria-label={t('close')} onClick={::this.onClose}><span aria-hidden="true">×</span></button>
|
||||||
{this.props.children}
|
{this.props.children}
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class Icon extends Component {
|
export class Icon extends Component {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
icon: PropTypes.string.isRequired,
|
icon: PropTypes.string.isRequired,
|
||||||
family: PropTypes.string,
|
family: PropTypes.string,
|
||||||
title: PropTypes.string,
|
title: PropTypes.string,
|
||||||
className: PropTypes.string
|
className: PropTypes.string
|
||||||
}
|
}
|
||||||
|
|
||||||
static defaultProps = {
|
static defaultProps = {
|
||||||
family: 'fas'
|
family: 'fas'
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const props = this.props;
|
const props = this.props;
|
||||||
|
|
||||||
if (props.family === 'fas' || props.family === 'far') {
|
if (props.family === 'fas' || props.family === 'far') {
|
||||||
return <i className={`${props.family} fa-${props.icon} ${props.className || ''}`} title={props.title}></i>;
|
return <i className={`${props.family} fa-${props.icon} ${props.className || ''}`} title={props.title}></i>;
|
||||||
} else {
|
} else {
|
||||||
console.error(`Icon font family ${props.family} not supported. (icon: ${props.icon}, title: ${props.title})`)
|
console.error(`Icon font family ${props.family} not supported. (icon: ${props.icon}, title: ${props.title})`)
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@withComponentMixins([
|
@withComponentMixins([
|
||||||
withErrorHandling
|
withErrorHandling
|
||||||
])
|
])
|
||||||
export class Button extends Component {
|
export class Button extends Component {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
onClickAsync: PropTypes.func,
|
onClickAsync: PropTypes.func,
|
||||||
label: PropTypes.string,
|
label: PropTypes.string,
|
||||||
icon: PropTypes.string,
|
icon: PropTypes.string,
|
||||||
iconTitle: PropTypes.string,
|
iconTitle: PropTypes.string,
|
||||||
className: PropTypes.string,
|
className: PropTypes.string,
|
||||||
title: PropTypes.string,
|
title: PropTypes.string,
|
||||||
type: PropTypes.string,
|
type: PropTypes.string,
|
||||||
disabled: PropTypes.bool
|
disabled: PropTypes.bool
|
||||||
}
|
}
|
||||||
|
|
||||||
@withAsyncErrorHandler
|
@withAsyncErrorHandler
|
||||||
async onClick(evt) {
|
async onClick(evt) {
|
||||||
if (this.props.onClickAsync) {
|
if (this.props.onClickAsync) {
|
||||||
evt.preventDefault();
|
evt.preventDefault();
|
||||||
await this.props.onClickAsync(evt);
|
await this.props.onClickAsync(evt);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const props = this.props;
|
const props = this.props;
|
||||||
|
|
||||||
let className = 'btn';
|
let className = 'btn';
|
||||||
if (props.className) {
|
if (props.className) {
|
||||||
className = className + ' ' + props.className;
|
className = className + ' ' + props.className;
|
||||||
}
|
}
|
||||||
|
|
||||||
let type = props.type || 'button';
|
let type = props.type || 'button';
|
||||||
|
|
||||||
let icon;
|
let icon;
|
||||||
if (props.icon) {
|
if (props.icon) {
|
||||||
icon = <Icon icon={props.icon} title={props.iconTitle}/>
|
icon = <Icon icon={props.icon} title={props.iconTitle}/>
|
||||||
}
|
}
|
||||||
|
|
||||||
let iconSpacer;
|
let iconSpacer;
|
||||||
if (props.icon && props.label) {
|
if (props.icon && props.label) {
|
||||||
iconSpacer = ' ';
|
iconSpacer = ' ';
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<button type={type} className={className} onClick={::this.onClick} title={this.props.title} disabled={this.props.disabled}>{icon}{iconSpacer}{props.label}</button>
|
<button type={type} className={className} onClick={::this.onClick} title={this.props.title} disabled={this.props.disabled}>{icon}{iconSpacer}{props.label}</button>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class ButtonDropdown extends Component {
|
export class ButtonDropdown extends Component {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
label: PropTypes.oneOfType([PropTypes.string, PropTypes.object]),
|
label: PropTypes.oneOfType([PropTypes.string, PropTypes.object]),
|
||||||
className: PropTypes.string,
|
className: PropTypes.string,
|
||||||
buttonClassName: PropTypes.string,
|
buttonClassName: PropTypes.string,
|
||||||
menuClassName: PropTypes.string
|
menuClassName: PropTypes.string
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const props = this.props;
|
const props = this.props;
|
||||||
|
|
||||||
const className = 'dropdown' + (props.className ? ' ' + props.className : '');
|
const className = 'dropdown' + (props.className ? ' ' + props.className : '');
|
||||||
const buttonClassName = 'btn dropdown-toggle' + (props.buttonClassName ? ' ' + props.buttonClassName : '');
|
const buttonClassName = 'btn dropdown-toggle' + (props.buttonClassName ? ' ' + props.buttonClassName : '');
|
||||||
const menuClassName = 'dropdown-menu' + (props.menuClassName ? ' ' + props.menuClassName : '');
|
const menuClassName = 'dropdown-menu' + (props.menuClassName ? ' ' + props.menuClassName : '');
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="dropdown" className={className}>
|
<div className="dropdown" className={className}>
|
||||||
<button type="button" className={buttonClassName} data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
|
<button type="button" className={buttonClassName} data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
|
||||||
{props.label}
|
{props.label}
|
||||||
</button>
|
</button>
|
||||||
<ul className={menuClassName}>
|
<ul className={menuClassName}>
|
||||||
{props.children}
|
{props.children}
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@withComponentMixins([
|
@withComponentMixins([
|
||||||
withErrorHandling
|
withErrorHandling
|
||||||
])
|
])
|
||||||
export class ActionLink extends Component {
|
export class ActionLink extends Component {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
onClickAsync: PropTypes.func,
|
onClickAsync: PropTypes.func,
|
||||||
className: PropTypes.string,
|
className: PropTypes.string,
|
||||||
href: PropTypes.string
|
href: PropTypes.string
|
||||||
}
|
}
|
||||||
|
|
||||||
@withAsyncErrorHandler
|
@withAsyncErrorHandler
|
||||||
async onClick(evt) {
|
async onClick(evt) {
|
||||||
if (this.props.onClickAsync) {
|
if (this.props.onClickAsync) {
|
||||||
evt.preventDefault();
|
evt.preventDefault();
|
||||||
evt.stopPropagation();
|
evt.stopPropagation();
|
||||||
|
|
||||||
await this.props.onClickAsync(evt);
|
await this.props.onClickAsync(evt);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const props = this.props;
|
const props = this.props;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<a href={props.href || ''} className={props.className} onClick={::this.onClick}>{props.children}</a>
|
<a href={props.href || ''} className={props.className} onClick={::this.onClick}>{props.children}</a>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
export class DropdownActionLink extends Component {
|
export class DropdownActionLink extends Component {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
onClickAsync: PropTypes.func,
|
onClickAsync: PropTypes.func,
|
||||||
className: PropTypes.string,
|
className: PropTypes.string,
|
||||||
disabled: PropTypes.bool
|
disabled: PropTypes.bool
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const props = this.props;
|
const props = this.props;
|
||||||
|
|
||||||
let clsName = "dropdown-item ";
|
let clsName = "dropdown-item ";
|
||||||
if (props.disabled) {
|
if (props.disabled) {
|
||||||
clsName += "disabled ";
|
clsName += "disabled ";
|
||||||
}
|
}
|
||||||
|
|
||||||
clsName += props.className;
|
clsName += props.className;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ActionLink className={clsName} onClickAsync={props.onClickAsync}>{props.children}</ActionLink>
|
<ActionLink className={clsName} onClickAsync={props.onClickAsync}>{props.children}</ActionLink>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
export class DropdownDivider extends Component {
|
export class DropdownDivider extends Component {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
className: PropTypes.string
|
className: PropTypes.string
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const props = this.props;
|
const props = this.props;
|
||||||
|
|
||||||
let className = 'dropdown-divider';
|
let className = 'dropdown-divider';
|
||||||
if (props.className) {
|
if (props.className) {
|
||||||
className = className + ' ' + props.className;
|
className = className + ' ' + props.className;
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={className}/>
|
<div className={className}/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@withComponentMixins([
|
@withComponentMixins([
|
||||||
withTranslation,
|
withTranslation,
|
||||||
withErrorHandling
|
withErrorHandling
|
||||||
])
|
])
|
||||||
export class ModalDialog extends Component {
|
export class ModalDialog extends Component {
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
|
|
||||||
const t = props.t;
|
const t = props.t;
|
||||||
|
|
||||||
this.state = {
|
this.state = {
|
||||||
buttons: this.props.buttons || [ { label: t('close'), className: 'btn-secondary', onClickAsync: null } ]
|
buttons: this.props.buttons || [ { label: t('close'), className: 'btn-secondary', onClickAsync: null } ]
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
title: PropTypes.string,
|
title: PropTypes.string,
|
||||||
onCloseAsync: PropTypes.func,
|
onCloseAsync: PropTypes.func,
|
||||||
onButtonClickAsync: PropTypes.func,
|
onButtonClickAsync: PropTypes.func,
|
||||||
buttons: PropTypes.array,
|
buttons: PropTypes.array,
|
||||||
hidden: PropTypes.bool,
|
hidden: PropTypes.bool,
|
||||||
className: PropTypes.string
|
className: PropTypes.string
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
this.props.hidden - this is the desired state of the modal
|
this.props.hidden - this is the desired state of the modal
|
||||||
this.hidden - this is the actual state of the modal - this is because there is no public API on Bootstrap modal to know whether the modal is shown or not
|
this.hidden - this is the actual state of the modal - this is because there is no public API on Bootstrap modal to know whether the modal is shown or not
|
||||||
*/
|
*/
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
const jqModal = jQuery(this.domModal);
|
const jqModal = jQuery(this.domModal);
|
||||||
|
|
||||||
jqModal.on('shown.bs.modal', () => jqModal.focus());
|
jqModal.on('shown.bs.modal', () => jqModal.focus());
|
||||||
jqModal.on('hide.bs.modal', ::this.onHide);
|
jqModal.on('hide.bs.modal', ::this.onHide);
|
||||||
|
|
||||||
this.hidden = this.props.hidden;
|
this.hidden = this.props.hidden;
|
||||||
jqModal.modal({
|
jqModal.modal({
|
||||||
show: !this.props.hidden
|
show: !this.props.hidden
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidUpdate() {
|
componentDidUpdate() {
|
||||||
if (this.props.hidden != this.hidden) {
|
if (this.props.hidden != this.hidden) {
|
||||||
const jqModal = jQuery(this.domModal);
|
const jqModal = jQuery(this.domModal);
|
||||||
this.hidden = this.props.hidden;
|
this.hidden = this.props.hidden;
|
||||||
jqModal.modal(this.props.hidden ? 'hide' : 'show');
|
jqModal.modal(this.props.hidden ? 'hide' : 'show');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillUnmount() {
|
componentWillUnmount() {
|
||||||
// We discard the modal in a hard way (without hiding it). Thus we have to take care of the backgrop too.
|
// We discard the modal in a hard way (without hiding it). Thus we have to take care of the backgrop too.
|
||||||
jQuery('.modal-backdrop').remove();
|
jQuery('.modal-backdrop').remove();
|
||||||
}
|
}
|
||||||
|
|
||||||
onHide(evt) {
|
onHide(evt) {
|
||||||
// Hide event is emited is both when hidden through user action or through API. We have to let the API
|
// Hide event is emited is both when hidden through user action or through API. We have to let the API
|
||||||
// calls through, otherwise the modal would never hide. The user actions, which change the desired state,
|
// calls through, otherwise the modal would never hide. The user actions, which change the desired state,
|
||||||
// are capture, converted to onClose callback and prevented. It's up to the parent to decide whether to
|
// are capture, converted to onClose callback and prevented. It's up to the parent to decide whether to
|
||||||
// hide the modal or not.
|
// hide the modal or not.
|
||||||
if (!this.props.hidden) {
|
if (!this.props.hidden) {
|
||||||
// noinspection JSIgnoredPromiseFromCall
|
// noinspection JSIgnoredPromiseFromCall
|
||||||
this.onClose();
|
this.onClose();
|
||||||
evt.preventDefault();
|
evt.preventDefault();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@withAsyncErrorHandler
|
@withAsyncErrorHandler
|
||||||
async onClose() {
|
async onClose() {
|
||||||
if (this.props.onCloseAsync) {
|
if (this.props.onCloseAsync) {
|
||||||
await this.props.onCloseAsync();
|
await this.props.onCloseAsync();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async onButtonClick(idx) {
|
async onButtonClick(idx) {
|
||||||
const buttonSpec = this.state.buttons[idx];
|
const buttonSpec = this.state.buttons[idx];
|
||||||
if (buttonSpec.onClickAsync) {
|
if (buttonSpec.onClickAsync) {
|
||||||
await buttonSpec.onClickAsync(idx);
|
await buttonSpec.onClickAsync(idx);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const props = this.props;
|
const props = this.props;
|
||||||
const t = props.t;
|
const t = props.t;
|
||||||
|
|
||||||
const buttons = [];
|
const buttons = [];
|
||||||
for (let idx = 0; idx < this.state.buttons.length; idx++) {
|
for (let idx = 0; idx < this.state.buttons.length; idx++) {
|
||||||
const buttonSpec = this.state.buttons[idx];
|
const buttonSpec = this.state.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 () => this.onButtonClick(idx)} />
|
||||||
buttons.push(button);
|
buttons.push(button);
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
ref={(domElem) => { this.domModal = domElem; }}
|
ref={(domElem) => { this.domModal = domElem; }}
|
||||||
className={'modal fade' + (props.className ? ' ' + props.className : '')}
|
className={'modal fade' + (props.className ? ' ' + props.className : '')}
|
||||||
tabIndex="-1" role="dialog" aria-labelledby="myModalLabel">
|
tabIndex="-1" role="dialog" aria-labelledby="myModalLabel">
|
||||||
|
|
||||||
<div className="modal-dialog" role="document">
|
<div className="modal-dialog" role="document">
|
||||||
<div className="modal-content">
|
<div className="modal-content">
|
||||||
<div className="modal-header">
|
<div className="modal-header">
|
||||||
<h4 className="modal-title">{this.props.title}</h4>
|
<h4 className="modal-title">{this.props.title}</h4>
|
||||||
<button type="button" className="close" aria-label={t('close')} onClick={::this.onClose}><span aria-hidden="true">×</span></button>
|
<button type="button" className="close" aria-label={t('close')} onClick={::this.onClose}><span aria-hidden="true">×</span></button>
|
||||||
</div>
|
</div>
|
||||||
<div className="modal-body">{this.props.children}</div>
|
<div className="modal-body">{this.props.children}</div>
|
||||||
<div className="modal-footer">
|
<div className="modal-footer">
|
||||||
{buttons}
|
{buttons}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,128 +1,128 @@
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
import React from "react";
|
import React from "react";
|
||||||
|
|
||||||
export function createComponentMixin(contexts, deps, decoratorFn) {
|
export function createComponentMixin(contexts, deps, decoratorFn) {
|
||||||
return {
|
return {
|
||||||
contexts,
|
contexts,
|
||||||
deps,
|
deps,
|
||||||
decoratorFn
|
decoratorFn
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function withComponentMixins(mixins, delegateFuns) {
|
export function withComponentMixins(mixins, delegateFuns) {
|
||||||
const mixinsClosure = new Set();
|
const mixinsClosure = new Set();
|
||||||
for (const mixin of mixins) {
|
for (const mixin of mixins) {
|
||||||
mixinsClosure.add(mixin);
|
mixinsClosure.add(mixin);
|
||||||
for (const dep of mixin.deps) {
|
for (const dep of mixin.deps) {
|
||||||
mixinsClosure.add(dep);
|
mixinsClosure.add(dep);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const contexts = new Map();
|
const contexts = new Map();
|
||||||
for (const mixin of mixinsClosure.values()) {
|
for (const mixin of mixinsClosure.values()) {
|
||||||
for (const ctx of mixin.contexts) {
|
for (const ctx of mixin.contexts) {
|
||||||
contexts.set(ctx.propName, ctx.context);
|
contexts.set(ctx.propName, ctx.context);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return TargetClass => {
|
return TargetClass => {
|
||||||
const ctors = [];
|
const ctors = [];
|
||||||
const mixinDelegateFuns = [];
|
const mixinDelegateFuns = [];
|
||||||
|
|
||||||
if (delegateFuns) {
|
if (delegateFuns) {
|
||||||
mixinDelegateFuns.push(...delegateFuns);
|
mixinDelegateFuns.push(...delegateFuns);
|
||||||
}
|
}
|
||||||
|
|
||||||
function TargetClassWithCtors(props) {
|
function TargetClassWithCtors(props) {
|
||||||
if (!new.target) {
|
if (!new.target) {
|
||||||
throw new TypeError();
|
throw new TypeError();
|
||||||
}
|
}
|
||||||
|
|
||||||
const self = Reflect.construct(TargetClass, [props], new.target);
|
const self = Reflect.construct(TargetClass, [props], new.target);
|
||||||
|
|
||||||
for (const ctor of ctors) {
|
for (const ctor of ctors) {
|
||||||
ctor(self, props);
|
ctor(self, props);
|
||||||
}
|
}
|
||||||
|
|
||||||
return self;
|
return self;
|
||||||
}
|
}
|
||||||
|
|
||||||
TargetClassWithCtors.prototype = TargetClass.prototype;
|
TargetClassWithCtors.prototype = TargetClass.prototype;
|
||||||
|
|
||||||
for (const attr in TargetClass) {
|
for (const attr in TargetClass) {
|
||||||
TargetClassWithCtors[attr] = TargetClass[attr];
|
TargetClassWithCtors[attr] = TargetClass[attr];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
class ComponentMixinsInner extends React.Component {
|
class ComponentMixinsInner extends React.Component {
|
||||||
render() {
|
render() {
|
||||||
const props = {
|
const props = {
|
||||||
...this.props,
|
...this.props,
|
||||||
ref: this.props._decoratorInnerInstanceRefFn
|
ref: this.props._decoratorInnerInstanceRefFn
|
||||||
};
|
};
|
||||||
delete props._decoratorInnerInstanceRefFn;
|
delete props._decoratorInnerInstanceRefFn;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<TargetClassWithCtors {...props}/>
|
<TargetClassWithCtors {...props}/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let DecoratedInner = ComponentMixinsInner;
|
let DecoratedInner = ComponentMixinsInner;
|
||||||
|
|
||||||
for (const mixin of mixinsClosure.values()) {
|
for (const mixin of mixinsClosure.values()) {
|
||||||
const res = mixin.decoratorFn(DecoratedInner, TargetClassWithCtors);
|
const res = mixin.decoratorFn(DecoratedInner, TargetClassWithCtors);
|
||||||
|
|
||||||
if (res.cls) {
|
if (res.cls) {
|
||||||
DecoratedInner = res.cls;
|
DecoratedInner = res.cls;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (res.ctor) {
|
if (res.ctor) {
|
||||||
ctors.push(res.ctor);
|
ctors.push(res.ctor);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (res.delegateFuns) {
|
if (res.delegateFuns) {
|
||||||
mixinDelegateFuns.push(...res.delegateFuns);
|
mixinDelegateFuns.push(...res.delegateFuns);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class ComponentMixinsOuter extends React.Component {
|
class ComponentMixinsOuter extends React.Component {
|
||||||
render() {
|
render() {
|
||||||
let innerFn = parentProps => {
|
let innerFn = parentProps => {
|
||||||
const props = {
|
const props = {
|
||||||
...parentProps,
|
...parentProps,
|
||||||
_decoratorInnerInstanceRefFn: node => this._decoratorInnerInstance = node
|
_decoratorInnerInstanceRefFn: node => this._decoratorInnerInstance = node
|
||||||
};
|
};
|
||||||
|
|
||||||
return <DecoratedInner {...props}/>
|
return <DecoratedInner {...props}/>
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const [propName, Context] of contexts.entries()) {
|
for (const [propName, Context] of contexts.entries()) {
|
||||||
const existingInnerFn = innerFn;
|
const existingInnerFn = innerFn;
|
||||||
innerFn = parentProps => (
|
innerFn = parentProps => (
|
||||||
<Context.Consumer>
|
<Context.Consumer>
|
||||||
{
|
{
|
||||||
value => existingInnerFn({
|
value => existingInnerFn({
|
||||||
...parentProps,
|
...parentProps,
|
||||||
[propName]: value
|
[propName]: value
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
</Context.Consumer>
|
</Context.Consumer>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return innerFn(this.props);
|
return innerFn(this.props);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const fun of mixinDelegateFuns) {
|
for (const fun of mixinDelegateFuns) {
|
||||||
ComponentMixinsOuter.prototype[fun] = function (...args) {
|
ComponentMixinsOuter.prototype[fun] = function (...args) {
|
||||||
return this._decoratorInnerInstance[fun](...args);
|
return this._decoratorInnerInstance[fun](...args);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return ComponentMixinsOuter;
|
return ComponentMixinsOuter;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,76 +1,76 @@
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import {createComponentMixin} from "./decorator-helpers";
|
import {createComponentMixin} from "./decorator-helpers";
|
||||||
|
|
||||||
function handleError(that, error) {
|
function handleError(that, error) {
|
||||||
let errorHandled;
|
let errorHandled;
|
||||||
if (that.errorHandler) {
|
if (that.errorHandler) {
|
||||||
errorHandled = that.errorHandler(error);
|
errorHandled = that.errorHandler(error);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!errorHandled && that.props.parentErrorHandler) {
|
if (!errorHandled && that.props.parentErrorHandler) {
|
||||||
errorHandled = handleError(that.props.parentErrorHandler, error);
|
errorHandled = handleError(that.props.parentErrorHandler, error);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!errorHandled) {
|
if (!errorHandled) {
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
|
|
||||||
return errorHandled;
|
return errorHandled;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const ParentErrorHandlerContext = React.createContext(null);
|
export const ParentErrorHandlerContext = React.createContext(null);
|
||||||
export const withErrorHandling = createComponentMixin([{context: ParentErrorHandlerContext, propName: 'parentErrorHandler'}], [], (TargetClass, InnerClass) => {
|
export const withErrorHandling = createComponentMixin([{context: ParentErrorHandlerContext, propName: 'parentErrorHandler'}], [], (TargetClass, InnerClass) => {
|
||||||
/* Example of use:
|
/* Example of use:
|
||||||
this.getFormValuesFromURL(....).catch(error => this.handleError(error));
|
this.getFormValuesFromURL(....).catch(error => this.handleError(error));
|
||||||
|
|
||||||
It's equivalent to:
|
It's equivalent to:
|
||||||
|
|
||||||
@withAsyncErrorHandler
|
@withAsyncErrorHandler
|
||||||
async loadFormValues() {
|
async loadFormValues() {
|
||||||
await this.getFormValuesFromURL(...);
|
await this.getFormValuesFromURL(...);
|
||||||
}
|
}
|
||||||
*/
|
*/
|
||||||
|
|
||||||
const originalRender = InnerClass.prototype.render;
|
const originalRender = InnerClass.prototype.render;
|
||||||
|
|
||||||
InnerClass.prototype.render = function() {
|
InnerClass.prototype.render = function() {
|
||||||
return (
|
return (
|
||||||
<ParentErrorHandlerContext.Provider value={this}>
|
<ParentErrorHandlerContext.Provider value={this}>
|
||||||
{originalRender.apply(this)}
|
{originalRender.apply(this)}
|
||||||
</ParentErrorHandlerContext.Provider>
|
</ParentErrorHandlerContext.Provider>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
InnerClass.prototype.handleError = function(error) {
|
InnerClass.prototype.handleError = function(error) {
|
||||||
handleError(this, error);
|
handleError(this, error);
|
||||||
};
|
};
|
||||||
|
|
||||||
return {};
|
return {};
|
||||||
});
|
});
|
||||||
|
|
||||||
export function withAsyncErrorHandler(target, name, descriptor) {
|
export function withAsyncErrorHandler(target, name, descriptor) {
|
||||||
let fn = descriptor.value;
|
let fn = descriptor.value;
|
||||||
|
|
||||||
descriptor.value = async function () {
|
descriptor.value = async function () {
|
||||||
try {
|
try {
|
||||||
await fn.apply(this, arguments)
|
await fn.apply(this, arguments)
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
handleError(this, error);
|
handleError(this, error);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
return descriptor;
|
return descriptor;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function wrapWithAsyncErrorHandler(self, fn) {
|
export function wrapWithAsyncErrorHandler(self, fn) {
|
||||||
return async function () {
|
return async function () {
|
||||||
try {
|
try {
|
||||||
await fn.apply(this, arguments)
|
await fn.apply(this, arguments)
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
handleError(self, error);
|
handleError(self, error);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
import ellipsize from "ellipsize";
|
import ellipsize from "ellipsize";
|
||||||
|
|
||||||
|
|
||||||
export function ellipsizeBreadcrumbLabel(label) {
|
export function ellipsizeBreadcrumbLabel(label) {
|
||||||
return ellipsize(label, 40)
|
return ellipsize(label, 40)
|
||||||
}
|
}
|
|
@ -1,75 +1,75 @@
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
import React, {Component} from 'react';
|
import React, {Component} from 'react';
|
||||||
import i18n
|
import i18n
|
||||||
from 'i18next';
|
from 'i18next';
|
||||||
import {withNamespaces} from "react-i18next";
|
import {withNamespaces} from "react-i18next";
|
||||||
import LanguageDetector
|
import LanguageDetector
|
||||||
from 'i18next-browser-languagedetector';
|
from 'i18next-browser-languagedetector';
|
||||||
import mailtrainConfig
|
import mailtrainConfig
|
||||||
from 'mailtrainConfig';
|
from 'mailtrainConfig';
|
||||||
|
|
||||||
import {convertToFake, getLang} from '../../../shared/langs';
|
import {convertToFake, getLang} from '../../../shared/langs';
|
||||||
import {createComponentMixin} from "./decorator-helpers";
|
import {createComponentMixin} from "./decorator-helpers";
|
||||||
|
|
||||||
import lang_en_US_common from "../../../locales/en-US/common";
|
import lang_en_US_common from "../../../locales/en-US/common";
|
||||||
import lang_es_ES_common from "../../../locales/es-ES/common";
|
import lang_es_ES_common from "../../../locales/es-ES/common";
|
||||||
|
|
||||||
|
|
||||||
const resourcesCommon = {
|
const resourcesCommon = {
|
||||||
'en-US': lang_en_US_common,
|
'en-US': lang_en_US_common,
|
||||||
'es-ES': lang_es_ES_common,
|
'es-ES': lang_es_ES_common,
|
||||||
'fk-FK': convertToFake(lang_en_US_common)
|
'fk-FK': convertToFake(lang_en_US_common)
|
||||||
};
|
};
|
||||||
|
|
||||||
const resources = {};
|
const resources = {};
|
||||||
for (const lng of mailtrainConfig.enabledLanguages) {
|
for (const lng of mailtrainConfig.enabledLanguages) {
|
||||||
const langDesc = getLang(lng);
|
const langDesc = getLang(lng);
|
||||||
resources[langDesc.longCode] = {
|
resources[langDesc.longCode] = {
|
||||||
common: resourcesCommon[langDesc.longCode]
|
common: resourcesCommon[langDesc.longCode]
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
i18n
|
i18n
|
||||||
.use(LanguageDetector)
|
.use(LanguageDetector)
|
||||||
.init({
|
.init({
|
||||||
resources,
|
resources,
|
||||||
|
|
||||||
fallbackLng: mailtrainConfig.defaultLanguage,
|
fallbackLng: mailtrainConfig.defaultLanguage,
|
||||||
defaultNS: 'common',
|
defaultNS: 'common',
|
||||||
|
|
||||||
interpolation: {
|
interpolation: {
|
||||||
escapeValue: false // not needed for react
|
escapeValue: false // not needed for react
|
||||||
},
|
},
|
||||||
|
|
||||||
react: {
|
react: {
|
||||||
wait: true
|
wait: true
|
||||||
},
|
},
|
||||||
|
|
||||||
detection: {
|
detection: {
|
||||||
order: ['querystring', 'cookie', 'localStorage', 'navigator'],
|
order: ['querystring', 'cookie', 'localStorage', 'navigator'],
|
||||||
lookupQuerystring: 'locale',
|
lookupQuerystring: 'locale',
|
||||||
lookupCookie: 'i18nextLng',
|
lookupCookie: 'i18nextLng',
|
||||||
lookupLocalStorage: 'i18nextLng',
|
lookupLocalStorage: 'i18nextLng',
|
||||||
caches: ['localStorage', 'cookie']
|
caches: ['localStorage', 'cookie']
|
||||||
},
|
},
|
||||||
|
|
||||||
whitelist: mailtrainConfig.enabledLanguages,
|
whitelist: mailtrainConfig.enabledLanguages,
|
||||||
load: 'currentOnly',
|
load: 'currentOnly',
|
||||||
|
|
||||||
debug: false
|
debug: false
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
export default i18n;
|
export default i18n;
|
||||||
|
|
||||||
|
|
||||||
export const withTranslation = createComponentMixin([], [], (TargetClass, InnerClass) => {
|
export const withTranslation = createComponentMixin([], [], (TargetClass, InnerClass) => {
|
||||||
return {
|
return {
|
||||||
cls: withNamespaces()(TargetClass)
|
cls: withNamespaces()(TargetClass)
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
export function tMark(key) {
|
export function tMark(key) {
|
||||||
return key;
|
return key;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,33 +1,33 @@
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
import React, {Component} from 'react';
|
import React, {Component} from 'react';
|
||||||
import {withTranslation} from './i18n';
|
import {withTranslation} from './i18n';
|
||||||
import {TreeTableSelect} from './form';
|
import {TreeTableSelect} from './form';
|
||||||
import {withComponentMixins} from "./decorator-helpers";
|
import {withComponentMixins} from "./decorator-helpers";
|
||||||
|
|
||||||
|
|
||||||
@withComponentMixins([
|
@withComponentMixins([
|
||||||
withTranslation
|
withTranslation
|
||||||
])
|
])
|
||||||
class NamespaceSelect extends Component {
|
class NamespaceSelect extends Component {
|
||||||
render() {
|
render() {
|
||||||
const t = this.props.t;
|
const t = this.props.t;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<TreeTableSelect id="namespace" label={t('namespace')} dataUrl="rest/namespaces-tree"/>
|
<TreeTableSelect id="namespace" label={t('namespace')} dataUrl="rest/namespaces-tree"/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function validateNamespace(t, state) {
|
function validateNamespace(t, state) {
|
||||||
if (!state.getIn(['namespace', 'value'])) {
|
if (!state.getIn(['namespace', 'value'])) {
|
||||||
state.setIn(['namespace', 'error'], t('namespacemustBeSelected'));
|
state.setIn(['namespace', 'error'], t('namespacemustBeSelected'));
|
||||||
} else {
|
} else {
|
||||||
state.setIn(['namespace', 'error'], null);
|
state.setIn(['namespace', 'error'], null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export {
|
export {
|
||||||
NamespaceSelect,
|
NamespaceSelect,
|
||||||
validateNamespace
|
validateNamespace
|
||||||
};
|
};
|
|
@ -1,146 +1,146 @@
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
import React
|
import React
|
||||||
from "react";
|
from "react";
|
||||||
import {withRouter} from "react-router";
|
import {withRouter} from "react-router";
|
||||||
import {withErrorHandling} from "./error-handling";
|
import {withErrorHandling} from "./error-handling";
|
||||||
import axios
|
import axios
|
||||||
from "../lib/axios";
|
from "../lib/axios";
|
||||||
import {getUrl} from "./urls";
|
import {getUrl} from "./urls";
|
||||||
import {createComponentMixin} from "./decorator-helpers";
|
import {createComponentMixin} from "./decorator-helpers";
|
||||||
|
|
||||||
export function needsResolve(route, nextRoute, match, nextMatch) {
|
export function needsResolve(route, nextRoute, match, nextMatch) {
|
||||||
const resolve = route.resolve;
|
const resolve = route.resolve;
|
||||||
const nextResolve = nextRoute.resolve;
|
const nextResolve = nextRoute.resolve;
|
||||||
|
|
||||||
// This compares whether two objects have the same content and returns TRUE if they don't
|
// This compares whether two objects have the same content and returns TRUE if they don't
|
||||||
if (Object.keys(resolve).length === Object.keys(nextResolve).length) {
|
if (Object.keys(resolve).length === Object.keys(nextResolve).length) {
|
||||||
for (const key in nextResolve) {
|
for (const key in nextResolve) {
|
||||||
if (!(key in resolve) ||
|
if (!(key in resolve) ||
|
||||||
resolve[key](match.params) !== nextResolve[key](nextMatch.params)) {
|
resolve[key](match.params) !== nextResolve[key](nextMatch.params)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function resolve(route, match) {
|
export async function resolve(route, match) {
|
||||||
const keys = Object.keys(route.resolve);
|
const keys = Object.keys(route.resolve);
|
||||||
|
|
||||||
const promises = keys.map(key => {
|
const promises = keys.map(key => {
|
||||||
const url = route.resolve[key](match.params);
|
const url = route.resolve[key](match.params);
|
||||||
if (url) {
|
if (url) {
|
||||||
return axios.get(getUrl(url));
|
return axios.get(getUrl(url));
|
||||||
} else {
|
} else {
|
||||||
return Promise.resolve({data: null});
|
return Promise.resolve({data: null});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
const resolvedArr = await Promise.all(promises);
|
const resolvedArr = await Promise.all(promises);
|
||||||
|
|
||||||
const resolved = {};
|
const resolved = {};
|
||||||
for (let idx = 0; idx < keys.length; idx++) {
|
for (let idx = 0; idx < keys.length; idx++) {
|
||||||
resolved[keys[idx]] = resolvedArr[idx].data;
|
resolved[keys[idx]] = resolvedArr[idx].data;
|
||||||
}
|
}
|
||||||
|
|
||||||
return resolved;
|
return resolved;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getRoutes(urlPrefix, resolve, parents, structure, navs, primaryMenuComponent, secondaryMenuComponent) {
|
export function getRoutes(urlPrefix, resolve, parents, structure, navs, primaryMenuComponent, secondaryMenuComponent) {
|
||||||
let routes = [];
|
let routes = [];
|
||||||
for (let routeKey in structure) {
|
for (let routeKey in structure) {
|
||||||
const entry = structure[routeKey];
|
const entry = structure[routeKey];
|
||||||
|
|
||||||
let path = urlPrefix + routeKey;
|
let path = urlPrefix + routeKey;
|
||||||
let pathWithParams = path;
|
let pathWithParams = path;
|
||||||
|
|
||||||
if (entry.extraParams) {
|
if (entry.extraParams) {
|
||||||
pathWithParams = pathWithParams + '/' + entry.extraParams.join('/');
|
pathWithParams = pathWithParams + '/' + entry.extraParams.join('/');
|
||||||
}
|
}
|
||||||
|
|
||||||
let entryResolve;
|
let entryResolve;
|
||||||
if (entry.resolve) {
|
if (entry.resolve) {
|
||||||
entryResolve = Object.assign({}, resolve, entry.resolve);
|
entryResolve = Object.assign({}, resolve, entry.resolve);
|
||||||
} else {
|
} else {
|
||||||
entryResolve = resolve;
|
entryResolve = resolve;
|
||||||
}
|
}
|
||||||
|
|
||||||
let navKeys;
|
let navKeys;
|
||||||
const entryNavs = [];
|
const entryNavs = [];
|
||||||
if (entry.navs) {
|
if (entry.navs) {
|
||||||
navKeys = Object.keys(entry.navs);
|
navKeys = Object.keys(entry.navs);
|
||||||
|
|
||||||
for (const navKey of navKeys) {
|
for (const navKey of navKeys) {
|
||||||
const nav = entry.navs[navKey];
|
const nav = entry.navs[navKey];
|
||||||
|
|
||||||
entryNavs.push({
|
entryNavs.push({
|
||||||
title: nav.title,
|
title: nav.title,
|
||||||
visible: nav.visible,
|
visible: nav.visible,
|
||||||
link: nav.link,
|
link: nav.link,
|
||||||
externalLink: nav.externalLink
|
externalLink: nav.externalLink
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const route = {
|
const route = {
|
||||||
path: (pathWithParams === '' ? '/' : pathWithParams),
|
path: (pathWithParams === '' ? '/' : pathWithParams),
|
||||||
panelComponent: entry.panelComponent,
|
panelComponent: entry.panelComponent,
|
||||||
panelRender: entry.panelRender,
|
panelRender: entry.panelRender,
|
||||||
primaryMenuComponent: entry.primaryMenuComponent || primaryMenuComponent,
|
primaryMenuComponent: entry.primaryMenuComponent || primaryMenuComponent,
|
||||||
secondaryMenuComponent: entry.secondaryMenuComponent || secondaryMenuComponent,
|
secondaryMenuComponent: entry.secondaryMenuComponent || secondaryMenuComponent,
|
||||||
title: entry.title,
|
title: entry.title,
|
||||||
link: entry.link,
|
link: entry.link,
|
||||||
panelInFullScreen: entry.panelInFullScreen,
|
panelInFullScreen: entry.panelInFullScreen,
|
||||||
insideIframe: entry.insideIframe,
|
insideIframe: entry.insideIframe,
|
||||||
resolve: entryResolve,
|
resolve: entryResolve,
|
||||||
parents,
|
parents,
|
||||||
navs: [...navs, ...entryNavs]
|
navs: [...navs, ...entryNavs]
|
||||||
};
|
};
|
||||||
|
|
||||||
routes.push(route);
|
routes.push(route);
|
||||||
|
|
||||||
const childrenParents = [...parents, route];
|
const childrenParents = [...parents, route];
|
||||||
|
|
||||||
if (entry.navs) {
|
if (entry.navs) {
|
||||||
for (let navKeyIdx = 0; navKeyIdx < navKeys.length; navKeyIdx++) {
|
for (let navKeyIdx = 0; navKeyIdx < navKeys.length; navKeyIdx++) {
|
||||||
const navKey = navKeys[navKeyIdx];
|
const navKey = navKeys[navKeyIdx];
|
||||||
const nav = entry.navs[navKey];
|
const nav = entry.navs[navKey];
|
||||||
|
|
||||||
const childNavs = [...entryNavs];
|
const childNavs = [...entryNavs];
|
||||||
childNavs[navKeyIdx] = Object.assign({}, childNavs[navKeyIdx], { active: true });
|
childNavs[navKeyIdx] = Object.assign({}, childNavs[navKeyIdx], { active: true });
|
||||||
|
|
||||||
routes = routes.concat(getRoutes(path + '/', entryResolve, childrenParents, { [navKey]: nav }, childNavs, route.primaryMenuComponent, route.secondaryMenuComponent));
|
routes = routes.concat(getRoutes(path + '/', entryResolve, childrenParents, { [navKey]: nav }, childNavs, route.primaryMenuComponent, route.secondaryMenuComponent));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (entry.children) {
|
if (entry.children) {
|
||||||
routes = routes.concat(getRoutes(path + '/', entryResolve, childrenParents, entry.children, entryNavs, route.primaryMenuComponent, route.secondaryMenuComponent));
|
routes = routes.concat(getRoutes(path + '/', entryResolve, childrenParents, entry.children, entryNavs, route.primaryMenuComponent, route.secondaryMenuComponent));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return routes;
|
return routes;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const SectionContentContext = React.createContext(null);
|
export const SectionContentContext = React.createContext(null);
|
||||||
export const withPageHelpers = createComponentMixin([{context: SectionContentContext, propName: 'sectionContent'}], [withErrorHandling], (TargetClass, InnerClass) => {
|
export const withPageHelpers = createComponentMixin([{context: SectionContentContext, propName: 'sectionContent'}], [withErrorHandling], (TargetClass, InnerClass) => {
|
||||||
InnerClass.prototype.setFlashMessage = function(severity, text) {
|
InnerClass.prototype.setFlashMessage = function(severity, text) {
|
||||||
return this.props.sectionContent.setFlashMessage(severity, text);
|
return this.props.sectionContent.setFlashMessage(severity, text);
|
||||||
};
|
};
|
||||||
|
|
||||||
InnerClass.prototype.navigateTo = function(path) {
|
InnerClass.prototype.navigateTo = function(path) {
|
||||||
return this.props.sectionContent.navigateTo(path);
|
return this.props.sectionContent.navigateTo(path);
|
||||||
}
|
}
|
||||||
|
|
||||||
InnerClass.prototype.navigateBack = function() {
|
InnerClass.prototype.navigateBack = function() {
|
||||||
return this.props.sectionContent.navigateBack();
|
return this.props.sectionContent.navigateBack();
|
||||||
}
|
}
|
||||||
|
|
||||||
InnerClass.prototype.navigateToWithFlashMessage = function(path, severity, text) {
|
InnerClass.prototype.navigateToWithFlashMessage = function(path, severity, text) {
|
||||||
return this.props.sectionContent.navigateToWithFlashMessage(path, severity, text);
|
return this.props.sectionContent.navigateToWithFlashMessage(path, severity, text);
|
||||||
}
|
}
|
||||||
|
|
||||||
return {};
|
return {};
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,12 +1,12 @@
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
import {getUrl} from "./urls";
|
import {getUrl} from "./urls";
|
||||||
import axios from "./axios";
|
import axios from "./axios";
|
||||||
|
|
||||||
async function checkPermissions(request) {
|
async function checkPermissions(request) {
|
||||||
return await axios.post(getUrl('rest/permissions-check'), request);
|
return await axios.post(getUrl('rest/permissions-check'), request);
|
||||||
}
|
}
|
||||||
|
|
||||||
export {
|
export {
|
||||||
checkPermissions
|
checkPermissions
|
||||||
}
|
}
|
|
@ -1,5 +1,5 @@
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
import {getUrl} from "./urls";
|
import {getUrl} from "./urls";
|
||||||
|
|
||||||
__webpack_public_path__ = getUrl('client/');
|
__webpack_public_path__ = getUrl('client/');
|
||||||
|
|
|
@ -1,90 +1,90 @@
|
||||||
$navbarHeight: 34px;
|
$navbarHeight: 34px;
|
||||||
$editorNormalHeight: 800px !default;
|
$editorNormalHeight: 800px !default;
|
||||||
|
|
||||||
.editor {
|
.editor {
|
||||||
.host {
|
.host {
|
||||||
@if $editorNormalHeight {
|
@if $editorNormalHeight {
|
||||||
height: $editorNormalHeight;
|
height: $editorNormalHeight;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.editorFullscreen {
|
.editorFullscreen {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
top: 0px;
|
top: 0px;
|
||||||
bottom: 0px;
|
bottom: 0px;
|
||||||
left: 0px;
|
left: 0px;
|
||||||
right: 0px;
|
right: 0px;
|
||||||
z-index: 1000;
|
z-index: 1000;
|
||||||
background: white;
|
background: white;
|
||||||
margin-top: $navbarHeight;
|
margin-top: $navbarHeight;
|
||||||
|
|
||||||
.navbar {
|
.navbar {
|
||||||
margin-top: -$navbarHeight;
|
margin-top: -$navbarHeight;
|
||||||
}
|
}
|
||||||
|
|
||||||
.host {
|
.host {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.navbar {
|
.navbar {
|
||||||
background: #f86c6b;
|
background: #f86c6b;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: $navbarHeight;
|
height: $navbarHeight;
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
}
|
}
|
||||||
|
|
||||||
.navbarLeft {
|
.navbarLeft {
|
||||||
.logo {
|
.logo {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
height: $navbarHeight;
|
height: $navbarHeight;
|
||||||
padding: 5px 0 5px 10px;
|
padding: 5px 0 5px 10px;
|
||||||
filter: brightness(0) invert(1);
|
filter: brightness(0) invert(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
.title {
|
.title {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
padding: 5px 0 5px 10px;
|
padding: 5px 0 5px 10px;
|
||||||
font-size: 18px;
|
font-size: 18px;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
float: left;
|
float: left;
|
||||||
color: white;
|
color: white;
|
||||||
height: $navbarHeight;
|
height: $navbarHeight;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.navbarRight {
|
.navbarRight {
|
||||||
.btn, .btnDisabled {
|
.btn, .btnDisabled {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
padding: 0px 15px;
|
padding: 0px 15px;
|
||||||
line-height: $navbarHeight;
|
line-height: $navbarHeight;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
font-family: sans-serif;
|
font-family: sans-serif;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
|
||||||
&, &:not([href]):not([tabindex]) { // This is to override reboot.scss in bootstrap
|
&, &:not([href]):not([tabindex]) { // This is to override reboot.scss in bootstrap
|
||||||
color: white;
|
color: white;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.btn:hover {
|
.btn:hover {
|
||||||
background-color: #c05454;
|
background-color: #c05454;
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
|
|
||||||
&, &:not([href]):not([tabindex]) { // This is to override reboot.scss in bootstrap
|
&, &:not([href]):not([tabindex]) { // This is to override reboot.scss in bootstrap
|
||||||
color: white;
|
color: white;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.btnDisabled {
|
.btnDisabled {
|
||||||
cursor: default;
|
cursor: default;
|
||||||
|
|
||||||
&, &:not([href]):not([tabindex]) { // This is to override reboot.scss in bootstrap
|
&, &:not([href]):not([tabindex]) { // This is to override reboot.scss in bootstrap
|
||||||
color: #621d1d;
|
color: #621d1d;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
$editorNormalHeight: false;
|
$editorNormalHeight: false;
|
||||||
@import "sandbox-common";
|
@import "sandbox-common";
|
||||||
|
|
||||||
.sandbox {
|
.sandbox {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
|
@ -1,35 +1,35 @@
|
||||||
@import "sandbox-common";
|
@import "sandbox-common";
|
||||||
|
|
||||||
.sandbox {
|
.sandbox {
|
||||||
}
|
}
|
||||||
|
|
||||||
.aceEditorWithPreview, .aceEditorWithoutPreview, .preview {
|
.aceEditorWithPreview, .aceEditorWithoutPreview, .preview {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.aceEditorWithPreview {
|
.aceEditorWithPreview {
|
||||||
border-right: #e8e8e8 solid 2px;
|
border-right: #e8e8e8 solid 2px;
|
||||||
width: 50%;
|
width: 50%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.aceEditorWithoutPreview {
|
.aceEditorWithoutPreview {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.preview {
|
.preview {
|
||||||
border-left: #e8e8e8 solid 2px;
|
border-left: #e8e8e8 solid 2px;
|
||||||
width: 50%;
|
width: 50%;
|
||||||
left: 50%;
|
left: 50%;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
|
||||||
iframe {
|
iframe {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
border: 0px none;
|
border: 0px none;
|
||||||
|
|
||||||
body {
|
body {
|
||||||
margin: 0px;
|
margin: 0px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -1,18 +1,18 @@
|
||||||
@import "sandbox-common";
|
@import "sandbox-common";
|
||||||
|
|
||||||
:global .grapesjs-body {
|
:global .grapesjs-body {
|
||||||
margin: 0px;
|
margin: 0px;
|
||||||
}
|
}
|
||||||
|
|
||||||
:global .gjs-editor-cont {
|
:global .gjs-editor-cont {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
}
|
}
|
||||||
|
|
||||||
:global .gjs-devices-c .gjs-devices {
|
:global .gjs-devices-c .gjs-devices {
|
||||||
padding-right: 15px;
|
padding-right: 15px;
|
||||||
}
|
}
|
||||||
|
|
||||||
:global .gjs-pn-devices-c, :global .gjs-pn-views {
|
:global .gjs-pn-devices-c, :global .gjs-pn-views {
|
||||||
padding: 4px;
|
padding: 4px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
@import "sandbox-common";
|
@import "sandbox-common";
|
||||||
|
|
||||||
:global .mo-standalone {
|
:global .mo-standalone {
|
||||||
top: 0px;
|
top: 0px;
|
||||||
bottom: 0px;
|
bottom: 0px;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,92 +1,92 @@
|
||||||
@import "../scss/variables.scss";
|
@import "../scss/variables.scss";
|
||||||
|
|
||||||
:global {
|
:global {
|
||||||
|
|
||||||
.mt-treetable-container .fancytree-container {
|
.mt-treetable-container .fancytree-container {
|
||||||
border: none;
|
border: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.mt-treetable-container span.fancytree-expander {
|
.mt-treetable-container span.fancytree-expander {
|
||||||
color: #333333;
|
color: #333333;
|
||||||
}
|
}
|
||||||
|
|
||||||
.mt-treetable-container.mt-treetable-inactivable>table.fancytree-ext-table.fancytree-container>tbody>tr.fancytree-active>td,
|
.mt-treetable-container.mt-treetable-inactivable>table.fancytree-ext-table.fancytree-container>tbody>tr.fancytree-active>td,
|
||||||
.mt-treetable-container.mt-treetable-inactivable>table.fancytree-ext-table.fancytree-container>tbody>tr.fancytree-active:hover>td,
|
.mt-treetable-container.mt-treetable-inactivable>table.fancytree-ext-table.fancytree-container>tbody>tr.fancytree-active:hover>td,
|
||||||
.mt-treetable-container.mt-treetable-inactivable .fancytree-container span.fancytree-node.fancytree-active span.fancytree-title,
|
.mt-treetable-container.mt-treetable-inactivable .fancytree-container span.fancytree-node.fancytree-active span.fancytree-title,
|
||||||
.mt-treetable-container.mt-treetable-inactivable .fancytree-container span.fancytree-node.fancytree-active span.fancytree-title:hover,
|
.mt-treetable-container.mt-treetable-inactivable .fancytree-container span.fancytree-node.fancytree-active span.fancytree-title:hover,
|
||||||
.mt-treetable-container.mt-treetable-inactivable .fancytree-container span.fancytree-node.fancytree-active:hover span.fancytree-title,
|
.mt-treetable-container.mt-treetable-inactivable .fancytree-container span.fancytree-node.fancytree-active:hover span.fancytree-title,
|
||||||
.mt-treetable-container.mt-treetable-inactivable .fancytree-container span.fancytree-node span.fancytree-title:hover {
|
.mt-treetable-container.mt-treetable-inactivable .fancytree-container span.fancytree-node span.fancytree-title:hover {
|
||||||
background-color: transparent;
|
background-color: transparent;
|
||||||
}
|
}
|
||||||
|
|
||||||
.mt-treetable-container.mt-treetable-inactivable .fancytree-container span.fancytree-node span.fancytree-title {
|
.mt-treetable-container.mt-treetable-inactivable .fancytree-container span.fancytree-node span.fancytree-title {
|
||||||
cursor: default;
|
cursor: default;
|
||||||
}
|
}
|
||||||
|
|
||||||
.mt-treetable-container.mt-treetable-inactivable .fancytree-container span.fancytree-node.fancytree-active span.fancytree-title,
|
.mt-treetable-container.mt-treetable-inactivable .fancytree-container span.fancytree-node.fancytree-active span.fancytree-title,
|
||||||
.mt-treetable-container.mt-treetable-inactivable .fancytree-container span.fancytree-node.fancytree-active span.fancytree-title:hover {
|
.mt-treetable-container.mt-treetable-inactivable .fancytree-container span.fancytree-node.fancytree-active span.fancytree-title:hover {
|
||||||
border-color: transparent;
|
border-color: transparent;
|
||||||
}
|
}
|
||||||
|
|
||||||
.mt-treetable-container.mt-treetable-inactivable>table.fancytree-ext-table.fancytree-container>tbody>tr.fancytree-active>td span.fancytree-title,
|
.mt-treetable-container.mt-treetable-inactivable>table.fancytree-ext-table.fancytree-container>tbody>tr.fancytree-active>td span.fancytree-title,
|
||||||
.mt-treetable-container.mt-treetable-inactivable>table.fancytree-ext-table.fancytree-container>tbody>tr.fancytree-active>td span.fancytree-expander,
|
.mt-treetable-container.mt-treetable-inactivable>table.fancytree-ext-table.fancytree-container>tbody>tr.fancytree-active>td span.fancytree-expander,
|
||||||
.mt-treetable-container.mt-treetable-inactivable .fancytree-container span.fancytree-node.fancytree-active span.fancytree-title,
|
.mt-treetable-container.mt-treetable-inactivable .fancytree-container span.fancytree-node.fancytree-active span.fancytree-title,
|
||||||
.mt-treetable-container.mt-treetable-inactivable .fancytree-container>tbody>tr.fancytree-active>td {
|
.mt-treetable-container.mt-treetable-inactivable .fancytree-container>tbody>tr.fancytree-active>td {
|
||||||
outline: 0px none;
|
outline: 0px none;
|
||||||
color: #333333;
|
color: #333333;
|
||||||
}
|
}
|
||||||
|
|
||||||
.mt-treetable-container span.fancytree-node span.fancytree-expander:hover {
|
.mt-treetable-container span.fancytree-node span.fancytree-expander:hover {
|
||||||
color: inherit;
|
color: inherit;
|
||||||
}
|
}
|
||||||
|
|
||||||
.mt-treetable-container {
|
.mt-treetable-container {
|
||||||
padding-top: 9px;
|
padding-top: 9px;
|
||||||
padding-bottom: 9px;
|
padding-bottom: 9px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.mt-treetable-container>table.fancytree-ext-table {
|
.mt-treetable-container>table.fancytree-ext-table {
|
||||||
margin-bottom: 0px;
|
margin-bottom: 0px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.mt-treetable-container.mt-treetable-noheader>.table>tbody>tr>td {
|
.mt-treetable-container.mt-treetable-noheader>.table>tbody>tr>td {
|
||||||
border-top: 0px none;
|
border-top: 0px none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.mt-treetable-container .mt-treetable-title {
|
.mt-treetable-container .mt-treetable-title {
|
||||||
min-width: 150px;
|
min-width: 150px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
.form-group .mt-treetable-container {
|
.form-group .mt-treetable-container {
|
||||||
border: $input-border-width solid $input-border-color;
|
border: $input-border-width solid $input-border-color;
|
||||||
border-radius: $input-border-radius;
|
border-radius: $input-border-radius;
|
||||||
padding-top: $input-padding-y;
|
padding-top: $input-padding-y;
|
||||||
padding-bottom: $input-padding-y;
|
padding-bottom: $input-padding-y;
|
||||||
|
|
||||||
-webkit-box-shadow: inset 0 1px 1px rgba(0,0,0,0.075);
|
-webkit-box-shadow: inset 0 1px 1px rgba(0,0,0,0.075);
|
||||||
box-shadow: inset 0 1px 1px rgba(0,0,0,0.075);
|
box-shadow: inset 0 1px 1px rgba(0,0,0,0.075);
|
||||||
-webkit-transition: border-color ease-in-out .15s,-webkit-box-shadow ease-in-out .15s;
|
-webkit-transition: border-color ease-in-out .15s,-webkit-box-shadow ease-in-out .15s;
|
||||||
-o-transition: border-color ease-in-out .15s,box-shadow ease-in-out .15s;
|
-o-transition: border-color ease-in-out .15s,box-shadow ease-in-out .15s;
|
||||||
transition: border-color ease-in-out .15s,box-shadow ease-in-out .15s;
|
transition: border-color ease-in-out .15s,box-shadow ease-in-out .15s;
|
||||||
}
|
}
|
||||||
|
|
||||||
.form-group .mt-treetable-container.is-valid {
|
.form-group .mt-treetable-container.is-valid {
|
||||||
border-color: $form-feedback-valid-color;
|
border-color: $form-feedback-valid-color;
|
||||||
-webkit-box-shadow: inset 0 1px 1px rgba(0,0,0,0.075);
|
-webkit-box-shadow: inset 0 1px 1px rgba(0,0,0,0.075);
|
||||||
box-shadow: inset 0 1px 1px rgba(0,0,0,0.075);
|
box-shadow: inset 0 1px 1px rgba(0,0,0,0.075);
|
||||||
}
|
}
|
||||||
|
|
||||||
.form-group .mt-treetable-container.is-invalid {
|
.form-group .mt-treetable-container.is-invalid {
|
||||||
border-color: $form-feedback-invalid-color;
|
border-color: $form-feedback-invalid-color;
|
||||||
-webkit-box-shadow: inset 0 1px 1px rgba(0,0,0,0.075);
|
-webkit-box-shadow: inset 0 1px 1px rgba(0,0,0,0.075);
|
||||||
box-shadow: inset 0 1px 1px rgba(0,0,0,0.075);
|
box-shadow: inset 0 1px 1px rgba(0,0,0,0.075);
|
||||||
}
|
}
|
||||||
|
|
||||||
.mt-treetable-container .table td {
|
.mt-treetable-container .table td {
|
||||||
padding-top: 3px;
|
padding-top: 3px;
|
||||||
padding-bottom: 3px;
|
padding-bottom: 3px;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,60 +1,60 @@
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
import {anonymousRestrictedAccessToken} from '../../../shared/urls';
|
import {anonymousRestrictedAccessToken} from '../../../shared/urls';
|
||||||
import {AppType} from '../../../shared/app';
|
import {AppType} from '../../../shared/app';
|
||||||
import mailtrainConfig from "mailtrainConfig";
|
import mailtrainConfig from "mailtrainConfig";
|
||||||
import i18n from './i18n';
|
import i18n from './i18n';
|
||||||
|
|
||||||
let restrictedAccessToken = anonymousRestrictedAccessToken;
|
let restrictedAccessToken = anonymousRestrictedAccessToken;
|
||||||
|
|
||||||
function setRestrictedAccessToken(token) {
|
function setRestrictedAccessToken(token) {
|
||||||
restrictedAccessToken = token;
|
restrictedAccessToken = token;
|
||||||
}
|
}
|
||||||
|
|
||||||
function getTrustedUrl(path) {
|
function getTrustedUrl(path) {
|
||||||
return mailtrainConfig.trustedUrlBase + (path || '');
|
return mailtrainConfig.trustedUrlBase + (path || '');
|
||||||
}
|
}
|
||||||
|
|
||||||
function getSandboxUrl(path, customRestrictedAccessToken) {
|
function getSandboxUrl(path, customRestrictedAccessToken) {
|
||||||
const localRestrictedAccessToken = customRestrictedAccessToken || restrictedAccessToken;
|
const localRestrictedAccessToken = customRestrictedAccessToken || restrictedAccessToken;
|
||||||
return mailtrainConfig.sandboxUrlBase + localRestrictedAccessToken + '/' + (path || '');
|
return mailtrainConfig.sandboxUrlBase + localRestrictedAccessToken + '/' + (path || '');
|
||||||
}
|
}
|
||||||
|
|
||||||
function getPublicUrl(path, opts) {
|
function getPublicUrl(path, opts) {
|
||||||
const url = new URL(path || '', mailtrainConfig.publicUrlBase);
|
const url = new URL(path || '', mailtrainConfig.publicUrlBase);
|
||||||
|
|
||||||
if (opts && opts.withLocale) {
|
if (opts && opts.withLocale) {
|
||||||
url.searchParams.append('locale', i18n.language);
|
url.searchParams.append('locale', i18n.language);
|
||||||
}
|
}
|
||||||
|
|
||||||
return url.toString();
|
return url.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
function getUrl(path) {
|
function getUrl(path) {
|
||||||
if (mailtrainConfig.appType === AppType.TRUSTED) {
|
if (mailtrainConfig.appType === AppType.TRUSTED) {
|
||||||
return getTrustedUrl(path);
|
return getTrustedUrl(path);
|
||||||
} else if (mailtrainConfig.appType === AppType.SANDBOXED) {
|
} else if (mailtrainConfig.appType === AppType.SANDBOXED) {
|
||||||
return getSandboxUrl(path);
|
return getSandboxUrl(path);
|
||||||
} else if (mailtrainConfig.appType === AppType.PUBLIC) {
|
} else if (mailtrainConfig.appType === AppType.PUBLIC) {
|
||||||
return getPublicUrl(path);
|
return getPublicUrl(path);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function getBaseDir() {
|
function getBaseDir() {
|
||||||
if (mailtrainConfig.appType === AppType.TRUSTED) {
|
if (mailtrainConfig.appType === AppType.TRUSTED) {
|
||||||
return mailtrainConfig.trustedUrlBaseDir;
|
return mailtrainConfig.trustedUrlBaseDir;
|
||||||
} else if (mailtrainConfig.appType === AppType.SANDBOXED) {
|
} else if (mailtrainConfig.appType === AppType.SANDBOXED) {
|
||||||
return mailtrainConfig.sandboxUrlBaseDir + restrictedAccessToken;
|
return mailtrainConfig.sandboxUrlBaseDir + restrictedAccessToken;
|
||||||
} else if (mailtrainConfig.appType === AppType.PUBLIC) {
|
} else if (mailtrainConfig.appType === AppType.PUBLIC) {
|
||||||
return mailtrainConfig.publicUrlBaseDir;
|
return mailtrainConfig.publicUrlBaseDir;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export {
|
export {
|
||||||
getTrustedUrl,
|
getTrustedUrl,
|
||||||
getSandboxUrl,
|
getSandboxUrl,
|
||||||
getPublicUrl,
|
getPublicUrl,
|
||||||
getUrl,
|
getUrl,
|
||||||
getBaseDir,
|
getBaseDir,
|
||||||
setRestrictedAccessToken
|
setRestrictedAccessToken
|
||||||
}
|
}
|
|
@ -1,55 +1,55 @@
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import {Fieldset, InputField} from "../../lib/form";
|
import {Fieldset, InputField} from "../../lib/form";
|
||||||
|
|
||||||
export function getFieldTypes(t) {
|
export function getFieldTypes(t) {
|
||||||
|
|
||||||
const fieldTypes = {
|
const fieldTypes = {
|
||||||
text: {
|
text: {
|
||||||
label: t('text'),
|
label: t('text'),
|
||||||
},
|
},
|
||||||
website: {
|
website: {
|
||||||
label: t('website'),
|
label: t('website'),
|
||||||
},
|
},
|
||||||
longtext: {
|
longtext: {
|
||||||
label: t('multilineText'),
|
label: t('multilineText'),
|
||||||
},
|
},
|
||||||
gpg: {
|
gpg: {
|
||||||
label: t('gpgPublicKey'),
|
label: t('gpgPublicKey'),
|
||||||
},
|
},
|
||||||
number: {
|
number: {
|
||||||
label: t('number'),
|
label: t('number'),
|
||||||
},
|
},
|
||||||
'checkbox-grouped': {
|
'checkbox-grouped': {
|
||||||
label: t('checkboxesFromOptionFields'),
|
label: t('checkboxesFromOptionFields'),
|
||||||
},
|
},
|
||||||
'radio-grouped': {
|
'radio-grouped': {
|
||||||
label: t('radioButtonsFromOptionFields')
|
label: t('radioButtonsFromOptionFields')
|
||||||
},
|
},
|
||||||
'dropdown-grouped': {
|
'dropdown-grouped': {
|
||||||
label: t('dropDownFromOptionFields')
|
label: t('dropDownFromOptionFields')
|
||||||
},
|
},
|
||||||
'radio-enum': {
|
'radio-enum': {
|
||||||
label: t('radioButtonsEnumerated')
|
label: t('radioButtonsEnumerated')
|
||||||
},
|
},
|
||||||
'dropdown-enum': {
|
'dropdown-enum': {
|
||||||
label: t('dropDownEnumerated')
|
label: t('dropDownEnumerated')
|
||||||
},
|
},
|
||||||
'date': {
|
'date': {
|
||||||
label: t('date')
|
label: t('date')
|
||||||
},
|
},
|
||||||
'birthday': {
|
'birthday': {
|
||||||
label: t('birthday')
|
label: t('birthday')
|
||||||
},
|
},
|
||||||
json: {
|
json: {
|
||||||
label: t('jsonValueForCustomRendering')
|
label: t('jsonValueForCustomRendering')
|
||||||
},
|
},
|
||||||
option: {
|
option: {
|
||||||
label: t('option')
|
label: t('option')
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
return fieldTypes;
|
return fieldTypes;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,11 +1,11 @@
|
||||||
$editorNormalHeight: 400px;
|
$editorNormalHeight: 400px;
|
||||||
@import "../../lib/sandbox-common";
|
@import "../../lib/sandbox-common";
|
||||||
|
|
||||||
.editor {
|
.editor {
|
||||||
margin-bottom: 15px;
|
margin-bottom: 15px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.host {
|
.host {
|
||||||
border: none;
|
border: none;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,46 +1,46 @@
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import {ImportSource, MappingType, ImportStatus, RunStatus} from '../../../../shared/imports';
|
import {ImportSource, MappingType, ImportStatus, RunStatus} from '../../../../shared/imports';
|
||||||
|
|
||||||
export function getImportLabels(t) {
|
export function getImportLabels(t) {
|
||||||
|
|
||||||
const importSourceLabels = {
|
const importSourceLabels = {
|
||||||
[ImportSource.CSV_FILE]: t('csvFile'),
|
[ImportSource.CSV_FILE]: t('csvFile'),
|
||||||
[ImportSource.LIST]: t('list'),
|
[ImportSource.LIST]: t('list'),
|
||||||
};
|
};
|
||||||
|
|
||||||
const importStatusLabels = {
|
const importStatusLabels = {
|
||||||
[ImportStatus.PREP_SCHEDULED]: t('created'),
|
[ImportStatus.PREP_SCHEDULED]: t('created'),
|
||||||
[ImportStatus.PREP_RUNNING]: t('preparing'),
|
[ImportStatus.PREP_RUNNING]: t('preparing'),
|
||||||
[ImportStatus.PREP_STOPPING]: t('stopping'),
|
[ImportStatus.PREP_STOPPING]: t('stopping'),
|
||||||
[ImportStatus.PREP_FINISHED]: t('ready'),
|
[ImportStatus.PREP_FINISHED]: t('ready'),
|
||||||
[ImportStatus.PREP_FAILED]: t('preparationFailed'),
|
[ImportStatus.PREP_FAILED]: t('preparationFailed'),
|
||||||
[ImportStatus.RUN_SCHEDULED]: t('scheduled'),
|
[ImportStatus.RUN_SCHEDULED]: t('scheduled'),
|
||||||
[ImportStatus.RUN_RUNNING]: t('running'),
|
[ImportStatus.RUN_RUNNING]: t('running'),
|
||||||
[ImportStatus.RUN_STOPPING]: t('stopping'),
|
[ImportStatus.RUN_STOPPING]: t('stopping'),
|
||||||
[ImportStatus.RUN_FINISHED]: t('finished'),
|
[ImportStatus.RUN_FINISHED]: t('finished'),
|
||||||
[ImportStatus.RUN_FAILED]: t('failed')
|
[ImportStatus.RUN_FAILED]: t('failed')
|
||||||
};
|
};
|
||||||
|
|
||||||
const runStatusLabels = {
|
const runStatusLabels = {
|
||||||
[RunStatus.SCHEDULED]: t('starting'),
|
[RunStatus.SCHEDULED]: t('starting'),
|
||||||
[RunStatus.RUNNING]: t('running'),
|
[RunStatus.RUNNING]: t('running'),
|
||||||
[RunStatus.STOPPING]: t('stopping'),
|
[RunStatus.STOPPING]: t('stopping'),
|
||||||
[RunStatus.FINISHED]: t('finished'),
|
[RunStatus.FINISHED]: t('finished'),
|
||||||
[RunStatus.FAILED]: t('failed')
|
[RunStatus.FAILED]: t('failed')
|
||||||
};
|
};
|
||||||
|
|
||||||
const mappingTypeLabels = {
|
const mappingTypeLabels = {
|
||||||
[MappingType.BASIC_SUBSCRIBE]: t('basicImportOfSubscribers'),
|
[MappingType.BASIC_SUBSCRIBE]: t('basicImportOfSubscribers'),
|
||||||
[MappingType.BASIC_UNSUBSCRIBE]: t('unsubscribeEmails'),
|
[MappingType.BASIC_UNSUBSCRIBE]: t('unsubscribeEmails'),
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
importStatusLabels,
|
importStatusLabels,
|
||||||
mappingTypeLabels,
|
mappingTypeLabels,
|
||||||
importSourceLabels,
|
importSourceLabels,
|
||||||
runStatusLabels
|
runStatusLabels
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,152 +1,152 @@
|
||||||
$desktopMinWidth: 768px;
|
$desktopMinWidth: 768px;
|
||||||
|
|
||||||
$mobileLeftPaneResidualWidth: 0px;
|
$mobileLeftPaneResidualWidth: 0px;
|
||||||
$mobileAnimationStartPosition: 100px;
|
$mobileAnimationStartPosition: 100px;
|
||||||
|
|
||||||
$desktopLeftPaneResidualWidth: 200px;
|
$desktopLeftPaneResidualWidth: 200px;
|
||||||
$desktopAnimationStartPosition: 300px;
|
$desktopAnimationStartPosition: 300px;
|
||||||
|
|
||||||
@mixin optionsHidden {
|
@mixin optionsHidden {
|
||||||
transform: translateX($mobileAnimationStartPosition);
|
transform: translateX($mobileAnimationStartPosition);
|
||||||
@media (min-width: $desktopMinWidth) {
|
@media (min-width: $desktopMinWidth) {
|
||||||
transform: translateX($desktopAnimationStartPosition);
|
transform: translateX($desktopAnimationStartPosition);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@mixin optionsVisible {
|
@mixin optionsVisible {
|
||||||
transform: translateX($mobileLeftPaneResidualWidth);
|
transform: translateX($mobileLeftPaneResidualWidth);
|
||||||
@media (min-width: $desktopMinWidth) {
|
@media (min-width: $desktopMinWidth) {
|
||||||
transform: translateX($desktopLeftPaneResidualWidth);
|
transform: translateX($desktopLeftPaneResidualWidth);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.toolbar {
|
.toolbar {
|
||||||
text-align: right;
|
text-align: right;
|
||||||
}
|
}
|
||||||
|
|
||||||
.ruleActionLink {
|
.ruleActionLink {
|
||||||
padding-right: 5px;
|
padding-right: 5px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.rulePane {
|
.rulePane {
|
||||||
position: relative;
|
position: relative;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
|
||||||
.leftPane {
|
.leftPane {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
margin-right: -100%;
|
margin-right: -100%;
|
||||||
|
|
||||||
.leftPaneInner {
|
.leftPaneInner {
|
||||||
.ruleTree {
|
.ruleTree {
|
||||||
background: #fbfbfb;
|
background: #fbfbfb;
|
||||||
border: #cfcfcf 1px solid;
|
border: #cfcfcf 1px solid;
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
padding: 10px 0px;
|
padding: 10px 0px;
|
||||||
margin-top: 15px;
|
margin-top: 15px;
|
||||||
margin-bottom: 30px;
|
margin-bottom: 30px;
|
||||||
|
|
||||||
// Without this, the placeholders when rearranging the tree are not shown
|
// Without this, the placeholders when rearranging the tree are not shown
|
||||||
position: relative;
|
position: relative;
|
||||||
z-index: 0;
|
z-index: 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.leftPaneOverlay {
|
.leftPaneOverlay {
|
||||||
display: none;
|
display: none;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
left: 0px;
|
left: 0px;
|
||||||
top: 0px;
|
top: 0px;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
z-index: 1;
|
z-index: 1;
|
||||||
|
|
||||||
width: $mobileLeftPaneResidualWidth;
|
width: $mobileLeftPaneResidualWidth;
|
||||||
@media (min-width: $desktopMinWidth) {
|
@media (min-width: $desktopMinWidth) {
|
||||||
width: $desktopLeftPaneResidualWidth;
|
width: $desktopLeftPaneResidualWidth;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.paneDivider {
|
.paneDivider {
|
||||||
display: block;
|
display: block;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
left: 0px;
|
left: 0px;
|
||||||
top: 0px;
|
top: 0px;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
background: url('./divider.png') repeat-y;
|
background: url('./divider.png') repeat-y;
|
||||||
|
|
||||||
@include optionsHidden;
|
@include optionsHidden;
|
||||||
|
|
||||||
padding-left: 50px;
|
padding-left: 50px;
|
||||||
z-index: 1;
|
z-index: 1;
|
||||||
|
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
visibility: hidden;
|
visibility: hidden;
|
||||||
|
|
||||||
.paneDividerSolidBackground {
|
.paneDividerSolidBackground {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
background: white;
|
background: white;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.rightPane {
|
.rightPane {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
vertical-align: top;
|
vertical-align: top;
|
||||||
z-index: 2;
|
z-index: 2;
|
||||||
position: relative;
|
position: relative;
|
||||||
|
|
||||||
@include optionsHidden;
|
@include optionsHidden;
|
||||||
|
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
visibility: hidden;
|
visibility: hidden;
|
||||||
|
|
||||||
.rightPaneInner {
|
.rightPaneInner {
|
||||||
margin-right: $mobileLeftPaneResidualWidth;
|
margin-right: $mobileLeftPaneResidualWidth;
|
||||||
@media (min-width: $desktopMinWidth) {
|
@media (min-width: $desktopMinWidth) {
|
||||||
margin-right: $desktopLeftPaneResidualWidth;
|
margin-right: $desktopLeftPaneResidualWidth;
|
||||||
}
|
}
|
||||||
|
|
||||||
.ruleOptions {
|
.ruleOptions {
|
||||||
margin-left: 60px;
|
margin-left: 60px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&.ruleOptionsVisible {
|
&.ruleOptionsVisible {
|
||||||
.leftPaneOverlay {
|
.leftPaneOverlay {
|
||||||
display: block;
|
display: block;
|
||||||
}
|
}
|
||||||
|
|
||||||
.paneDivider {
|
.paneDivider {
|
||||||
transition: transform 300ms ease-out, opacity 100ms ease-out;
|
transition: transform 300ms ease-out, opacity 100ms ease-out;
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
visibility: visible;
|
visibility: visible;
|
||||||
|
|
||||||
@include optionsVisible;
|
@include optionsVisible;
|
||||||
}
|
}
|
||||||
|
|
||||||
.rightPane {
|
.rightPane {
|
||||||
transition: transform 300ms ease-out, opacity 100ms ease-out;
|
transition: transform 300ms ease-out, opacity 100ms ease-out;
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
visibility: visible;
|
visibility: visible;
|
||||||
|
|
||||||
@include optionsVisible;
|
@include optionsVisible;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&.ruleOptionsHidden {
|
&.ruleOptionsHidden {
|
||||||
.paneDivider {
|
.paneDivider {
|
||||||
transition: visibility 0s linear 300ms, transform 300ms ease-in, opacity 100ms ease-in 200ms;
|
transition: visibility 0s linear 300ms, transform 300ms ease-in, opacity 100ms ease-in 200ms;
|
||||||
}
|
}
|
||||||
|
|
||||||
.rightPane {
|
.rightPane {
|
||||||
transition: visibility 0s linear 300ms, transform 300ms ease-in, opacity 100ms ease-in 200ms;
|
transition: visibility 0s linear 300ms, transform 300ms ease-in, opacity 100ms ease-in 200ms;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,459 +1,459 @@
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import {DatePicker, Dropdown, InputField} from "../../lib/form";
|
import {DatePicker, Dropdown, InputField} from "../../lib/form";
|
||||||
import { parseDate, parseBirthday, formatDate, formatBirthday, DateFormat, birthdayYear, getDateFormatString, getBirthdayFormatString } from '../../../../shared/date';
|
import { parseDate, parseBirthday, formatDate, formatBirthday, DateFormat, birthdayYear, getDateFormatString, getBirthdayFormatString } from '../../../../shared/date';
|
||||||
import { tMark } from "../../lib/i18n";
|
import { tMark } from "../../lib/i18n";
|
||||||
|
|
||||||
export function getRuleHelpers(t, fields) {
|
export function getRuleHelpers(t, fields) {
|
||||||
|
|
||||||
const ruleHelpers = {};
|
const ruleHelpers = {};
|
||||||
|
|
||||||
ruleHelpers.compositeRuleTypes = {
|
ruleHelpers.compositeRuleTypes = {
|
||||||
all: {
|
all: {
|
||||||
dropdownLabel: t('allRulesMustMatch'),
|
dropdownLabel: t('allRulesMustMatch'),
|
||||||
treeLabel: rule => t('allRulesMustMatch')
|
treeLabel: rule => t('allRulesMustMatch')
|
||||||
},
|
},
|
||||||
some: {
|
some: {
|
||||||
dropdownLabel: t('atLeastOneRuleMustMatch'),
|
dropdownLabel: t('atLeastOneRuleMustMatch'),
|
||||||
treeLabel: rule => t('atLeastOneRuleMustMatch')
|
treeLabel: rule => t('atLeastOneRuleMustMatch')
|
||||||
},
|
},
|
||||||
none: {
|
none: {
|
||||||
dropdownLabel: t('noRuleMayMatch'),
|
dropdownLabel: t('noRuleMayMatch'),
|
||||||
treeLabel: rule => t('noRuleMayMatch')
|
treeLabel: rule => t('noRuleMayMatch')
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
ruleHelpers.primitiveRuleTypes = {};
|
ruleHelpers.primitiveRuleTypes = {};
|
||||||
|
|
||||||
ruleHelpers.primitiveRuleTypes.text = {
|
ruleHelpers.primitiveRuleTypes.text = {
|
||||||
eq: {
|
eq: {
|
||||||
dropdownLabel: t('equalTo'),
|
dropdownLabel: t('equalTo'),
|
||||||
treeLabel: rule => t('valueInColumnColNameIsEqualToValue', {colName: ruleHelpers.getColumnName(rule.column), value: rule.value}),
|
treeLabel: rule => t('valueInColumnColNameIsEqualToValue', {colName: ruleHelpers.getColumnName(rule.column), value: rule.value}),
|
||||||
},
|
},
|
||||||
like: {
|
like: {
|
||||||
dropdownLabel: t('matchWithSqlLike'),
|
dropdownLabel: t('matchWithSqlLike'),
|
||||||
treeLabel: rule => t('valueInColumnColNameMatchesWithSqlLike', {colName: ruleHelpers.getColumnName(rule.column), value: rule.value}),
|
treeLabel: rule => t('valueInColumnColNameMatchesWithSqlLike', {colName: ruleHelpers.getColumnName(rule.column), value: rule.value}),
|
||||||
},
|
},
|
||||||
re: {
|
re: {
|
||||||
dropdownLabel: t('matchWithRegularExpressions'),
|
dropdownLabel: t('matchWithRegularExpressions'),
|
||||||
treeLabel: rule => t('valueInColumnColNameMatchesWithRegular', {colName: ruleHelpers.getColumnName(rule.column), value: rule.value}),
|
treeLabel: rule => t('valueInColumnColNameMatchesWithRegular', {colName: ruleHelpers.getColumnName(rule.column), value: rule.value}),
|
||||||
},
|
},
|
||||||
lt: {
|
lt: {
|
||||||
dropdownLabel: t('alphabeticallyBefore'),
|
dropdownLabel: t('alphabeticallyBefore'),
|
||||||
treeLabel: rule => t('valueInColumnColNameIsAlphabetically', {colName: ruleHelpers.getColumnName(rule.column), value: rule.value}),
|
treeLabel: rule => t('valueInColumnColNameIsAlphabetically', {colName: ruleHelpers.getColumnName(rule.column), value: rule.value}),
|
||||||
},
|
},
|
||||||
le: {
|
le: {
|
||||||
dropdownLabel: t('alphabeticallyBeforeOrEqualTo'),
|
dropdownLabel: t('alphabeticallyBeforeOrEqualTo'),
|
||||||
treeLabel: rule => t('valueInColumnColNameIsAlphabetically-1', {colName: ruleHelpers.getColumnName(rule.column), value: rule.value}),
|
treeLabel: rule => t('valueInColumnColNameIsAlphabetically-1', {colName: ruleHelpers.getColumnName(rule.column), value: rule.value}),
|
||||||
},
|
},
|
||||||
gt: {
|
gt: {
|
||||||
dropdownLabel: t('alphabeticallyAfter'),
|
dropdownLabel: t('alphabeticallyAfter'),
|
||||||
treeLabel: rule => t('valueInColumnColNameIsAlphabetically-2', {colName: ruleHelpers.getColumnName(rule.column), value: rule.value}),
|
treeLabel: rule => t('valueInColumnColNameIsAlphabetically-2', {colName: ruleHelpers.getColumnName(rule.column), value: rule.value}),
|
||||||
},
|
},
|
||||||
ge: {
|
ge: {
|
||||||
dropdownLabel: t('alphabeticallyAfterOrEqualTo'),
|
dropdownLabel: t('alphabeticallyAfterOrEqualTo'),
|
||||||
treeLabel: rule => t('valueInColumnColNameIsAlphabetically-3', {colName: ruleHelpers.getColumnName(rule.column), value: rule.value}),
|
treeLabel: rule => t('valueInColumnColNameIsAlphabetically-3', {colName: ruleHelpers.getColumnName(rule.column), value: rule.value}),
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
ruleHelpers.primitiveRuleTypes.website = {
|
ruleHelpers.primitiveRuleTypes.website = {
|
||||||
eq: {
|
eq: {
|
||||||
dropdownLabel: t('equalTo'),
|
dropdownLabel: t('equalTo'),
|
||||||
treeLabel: rule => t('valueInColumnColNameIsEqualToValue', {colName: ruleHelpers.getColumnName(rule.column), value: rule.value}),
|
treeLabel: rule => t('valueInColumnColNameIsEqualToValue', {colName: ruleHelpers.getColumnName(rule.column), value: rule.value}),
|
||||||
},
|
},
|
||||||
like: {
|
like: {
|
||||||
dropdownLabel: t('matchWithSqlLike'),
|
dropdownLabel: t('matchWithSqlLike'),
|
||||||
treeLabel: rule => t('valueInColumnColNameMatchesWithSqlLike', {colName: ruleHelpers.getColumnName(rule.column), value: rule.value}),
|
treeLabel: rule => t('valueInColumnColNameMatchesWithSqlLike', {colName: ruleHelpers.getColumnName(rule.column), value: rule.value}),
|
||||||
},
|
},
|
||||||
re: {
|
re: {
|
||||||
dropdownLabel: t('matchWithRegularExpressions'),
|
dropdownLabel: t('matchWithRegularExpressions'),
|
||||||
treeLabel: rule => t('valueInColumnColNameMatchesWithRegular', {colName: ruleHelpers.getColumnName(rule.column), value: rule.value}),
|
treeLabel: rule => t('valueInColumnColNameMatchesWithRegular', {colName: ruleHelpers.getColumnName(rule.column), value: rule.value}),
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
ruleHelpers.primitiveRuleTypes.number = {
|
ruleHelpers.primitiveRuleTypes.number = {
|
||||||
eq: {
|
eq: {
|
||||||
dropdownLabel: t('equalTo'),
|
dropdownLabel: t('equalTo'),
|
||||||
treeLabel: rule => t('valueInColumnColNameIsEqualToValue-1', {colName: ruleHelpers.getColumnName(rule.column), value: rule.value}),
|
treeLabel: rule => t('valueInColumnColNameIsEqualToValue-1', {colName: ruleHelpers.getColumnName(rule.column), value: rule.value}),
|
||||||
},
|
},
|
||||||
lt: {
|
lt: {
|
||||||
dropdownLabel: t('lessThan'),
|
dropdownLabel: t('lessThan'),
|
||||||
treeLabel: rule => t('valueInColumnColNameIsLessThanValue', {colName: ruleHelpers.getColumnName(rule.column), value: rule.value}),
|
treeLabel: rule => t('valueInColumnColNameIsLessThanValue', {colName: ruleHelpers.getColumnName(rule.column), value: rule.value}),
|
||||||
},
|
},
|
||||||
le: {
|
le: {
|
||||||
dropdownLabel: t('lessThanOrEqualTo'),
|
dropdownLabel: t('lessThanOrEqualTo'),
|
||||||
treeLabel: rule => t('valueInColumnColNameIsLessThanOrEqualTo', {colName: ruleHelpers.getColumnName(rule.column), value: rule.value}),
|
treeLabel: rule => t('valueInColumnColNameIsLessThanOrEqualTo', {colName: ruleHelpers.getColumnName(rule.column), value: rule.value}),
|
||||||
},
|
},
|
||||||
gt: {
|
gt: {
|
||||||
dropdownLabel: t('greaterThan'),
|
dropdownLabel: t('greaterThan'),
|
||||||
treeLabel: rule => t('valueInColumnColNameIsGreaterThanValue', {colName: ruleHelpers.getColumnName(rule.column), value: rule.value}),
|
treeLabel: rule => t('valueInColumnColNameIsGreaterThanValue', {colName: ruleHelpers.getColumnName(rule.column), value: rule.value}),
|
||||||
},
|
},
|
||||||
ge: {
|
ge: {
|
||||||
dropdownLabel: t('greaterThanOrEqualTo'),
|
dropdownLabel: t('greaterThanOrEqualTo'),
|
||||||
treeLabel: rule => t('valueInColumnColNameIsGreaterThanOrEqual', {colName: ruleHelpers.getColumnName(rule.column), value: rule.value}),
|
treeLabel: rule => t('valueInColumnColNameIsGreaterThanOrEqual', {colName: ruleHelpers.getColumnName(rule.column), value: rule.value}),
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// FXIME - the localization here is still wrong
|
// FXIME - the localization here is still wrong
|
||||||
function getRelativeDateTreeLabel(rule, variants) {
|
function getRelativeDateTreeLabel(rule, variants) {
|
||||||
if (rule.value === 0) {
|
if (rule.value === 0) {
|
||||||
return t(variants[0], {colName: ruleHelpers.getColumnName(rule.column)})
|
return t(variants[0], {colName: ruleHelpers.getColumnName(rule.column)})
|
||||||
} else if (rule.value > 0) {
|
} else if (rule.value > 0) {
|
||||||
return t(variants[1], {colName: ruleHelpers.getColumnName(rule.column), value: rule.value});
|
return t(variants[1], {colName: ruleHelpers.getColumnName(rule.column), value: rule.value});
|
||||||
} else {
|
} else {
|
||||||
return t(variants[2], {colName: ruleHelpers.getColumnName(rule.column), value: -rule.value});
|
return t(variants[2], {colName: ruleHelpers.getColumnName(rule.column), value: -rule.value});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ruleHelpers.primitiveRuleTypes.date = {
|
ruleHelpers.primitiveRuleTypes.date = {
|
||||||
eq: {
|
eq: {
|
||||||
dropdownLabel: t('on'),
|
dropdownLabel: t('on'),
|
||||||
treeLabel: rule => t('dateInColumnColNameIsValue', {colName: ruleHelpers.getColumnName(rule.column), value: formatDate(DateFormat.INTL, rule.value)}),
|
treeLabel: rule => t('dateInColumnColNameIsValue', {colName: ruleHelpers.getColumnName(rule.column), value: formatDate(DateFormat.INTL, rule.value)}),
|
||||||
},
|
},
|
||||||
lt: {
|
lt: {
|
||||||
dropdownLabel: t('before'),
|
dropdownLabel: t('before'),
|
||||||
treeLabel: rule => t('dateInColumnColNameIsBeforeValue', {colName: ruleHelpers.getColumnName(rule.column), value: formatDate(DateFormat.INTL, rule.value)}),
|
treeLabel: rule => t('dateInColumnColNameIsBeforeValue', {colName: ruleHelpers.getColumnName(rule.column), value: formatDate(DateFormat.INTL, rule.value)}),
|
||||||
},
|
},
|
||||||
le: {
|
le: {
|
||||||
dropdownLabel: t('beforeOrOn'),
|
dropdownLabel: t('beforeOrOn'),
|
||||||
treeLabel: rule => t('dateInColumnColNameIsBeforeOrOnValue', {colName: ruleHelpers.getColumnName(rule.column), value: formatDate(DateFormat.INTL, rule.value)}),
|
treeLabel: rule => t('dateInColumnColNameIsBeforeOrOnValue', {colName: ruleHelpers.getColumnName(rule.column), value: formatDate(DateFormat.INTL, rule.value)}),
|
||||||
},
|
},
|
||||||
gt: {
|
gt: {
|
||||||
dropdownLabel: t('after'),
|
dropdownLabel: t('after'),
|
||||||
treeLabel: rule => t('dateInColumnColNameIsAfterValue', {colName: ruleHelpers.getColumnName(rule.column), value: formatDate(DateFormat.INTL, rule.value)}),
|
treeLabel: rule => t('dateInColumnColNameIsAfterValue', {colName: ruleHelpers.getColumnName(rule.column), value: formatDate(DateFormat.INTL, rule.value)}),
|
||||||
},
|
},
|
||||||
ge: {
|
ge: {
|
||||||
dropdownLabel: t('afterOrOn'),
|
dropdownLabel: t('afterOrOn'),
|
||||||
treeLabel: rule => t('dateInColumnColNameIsAfterOrOnValue', {colName: ruleHelpers.getColumnName(rule.column), value: formatDate(DateFormat.INTL, rule.value)}),
|
treeLabel: rule => t('dateInColumnColNameIsAfterOrOnValue', {colName: ruleHelpers.getColumnName(rule.column), value: formatDate(DateFormat.INTL, rule.value)}),
|
||||||
},
|
},
|
||||||
eqTodayPlusDays: {
|
eqTodayPlusDays: {
|
||||||
dropdownLabel: t('onXthDayBeforeafterCurrentDate'),
|
dropdownLabel: t('onXthDayBeforeafterCurrentDate'),
|
||||||
treeLabel: rule => getRelativeDateTreeLabel(rule, [tMark('dateInColumnColNameIsTheCurrentDate'), tMark('dateInColumnColNameIsTheValuethDayAfter'), tMark('dateInColumnColNameIsTheValuethDayBefore')]),
|
treeLabel: rule => getRelativeDateTreeLabel(rule, [tMark('dateInColumnColNameIsTheCurrentDate'), tMark('dateInColumnColNameIsTheValuethDayAfter'), tMark('dateInColumnColNameIsTheValuethDayBefore')]),
|
||||||
},
|
},
|
||||||
ltTodayPlusDays: {
|
ltTodayPlusDays: {
|
||||||
dropdownLabel: t('beforeXthDayBeforeafterCurrentDate'),
|
dropdownLabel: t('beforeXthDayBeforeafterCurrentDate'),
|
||||||
treeLabel: rule => getRelativeDateTreeLabel(rule, [tMark('dateInColumnColNameIsBeforeTheCurrent'), tMark('dateInColumnColNameIsBeforeTheValuethDay'), tMark('dateInColumnColNameIsBeforeTheValuethDay-1')]),
|
treeLabel: rule => getRelativeDateTreeLabel(rule, [tMark('dateInColumnColNameIsBeforeTheCurrent'), tMark('dateInColumnColNameIsBeforeTheValuethDay'), tMark('dateInColumnColNameIsBeforeTheValuethDay-1')]),
|
||||||
},
|
},
|
||||||
leTodayPlusDays: {
|
leTodayPlusDays: {
|
||||||
dropdownLabel: t('beforeOrOnXthDayBeforeafterCurrentDate'),
|
dropdownLabel: t('beforeOrOnXthDayBeforeafterCurrentDate'),
|
||||||
treeLabel: rule => getRelativeDateTreeLabel(rule, [tMark('dateInColumnColNameIsBeforeOrOnThe'), tMark('dateInColumnColNameIsBeforeOrOnThe-1'), tMark('dateInColumnColNameIsBeforeOrOnThe-2')]),
|
treeLabel: rule => getRelativeDateTreeLabel(rule, [tMark('dateInColumnColNameIsBeforeOrOnThe'), tMark('dateInColumnColNameIsBeforeOrOnThe-1'), tMark('dateInColumnColNameIsBeforeOrOnThe-2')]),
|
||||||
},
|
},
|
||||||
gtTodayPlusDays: {
|
gtTodayPlusDays: {
|
||||||
dropdownLabel: t('afterXthDayBeforeafterCurrentDate'),
|
dropdownLabel: t('afterXthDayBeforeafterCurrentDate'),
|
||||||
treeLabel: rule => getRelativeDateTreeLabel(rule, [tMark('dateInColumnColNameIsAfterTheCurrentDate'), tMark('dateInColumnColNameIsAfterTheValuethDay'), tMark('dateInColumnColNameIsAfterTheValuethDay-1')]),
|
treeLabel: rule => getRelativeDateTreeLabel(rule, [tMark('dateInColumnColNameIsAfterTheCurrentDate'), tMark('dateInColumnColNameIsAfterTheValuethDay'), tMark('dateInColumnColNameIsAfterTheValuethDay-1')]),
|
||||||
},
|
},
|
||||||
geTodayPlusDays: {
|
geTodayPlusDays: {
|
||||||
dropdownLabel: t('afterOrOnXthDayBeforeafterCurrentDate'),
|
dropdownLabel: t('afterOrOnXthDayBeforeafterCurrentDate'),
|
||||||
treeLabel: rule => getRelativeDateTreeLabel(rule, [tMark('dateInColumnColNameIsAfterOrOnTheCurrent'), tMark('dateInColumnColNameIsAfterOrOnTheValueth'), tMark('dateInColumnColNameIsAfterOrOnTheValueth-1')]),
|
treeLabel: rule => getRelativeDateTreeLabel(rule, [tMark('dateInColumnColNameIsAfterOrOnTheCurrent'), tMark('dateInColumnColNameIsAfterOrOnTheValueth'), tMark('dateInColumnColNameIsAfterOrOnTheValueth-1')]),
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
ruleHelpers.primitiveRuleTypes.birthday = {
|
ruleHelpers.primitiveRuleTypes.birthday = {
|
||||||
eq: {
|
eq: {
|
||||||
dropdownLabel: t('on'),
|
dropdownLabel: t('on'),
|
||||||
treeLabel: rule => t('dateInColumnColNameIsValue', {colName: ruleHelpers.getColumnName(rule.column), value: formatBirthday(DateFormat.INTL, rule.value)}),
|
treeLabel: rule => t('dateInColumnColNameIsValue', {colName: ruleHelpers.getColumnName(rule.column), value: formatBirthday(DateFormat.INTL, rule.value)}),
|
||||||
},
|
},
|
||||||
lt: {
|
lt: {
|
||||||
dropdownLabel: t('before'),
|
dropdownLabel: t('before'),
|
||||||
treeLabel: rule => t('dateInColumnColNameIsBeforeValue', {colName: ruleHelpers.getColumnName(rule.column), value: formatBirthday(DateFormat.INTL, rule.value)}),
|
treeLabel: rule => t('dateInColumnColNameIsBeforeValue', {colName: ruleHelpers.getColumnName(rule.column), value: formatBirthday(DateFormat.INTL, rule.value)}),
|
||||||
},
|
},
|
||||||
le: {
|
le: {
|
||||||
dropdownLabel: t('beforeOrOn'),
|
dropdownLabel: t('beforeOrOn'),
|
||||||
treeLabel: rule => t('dateInColumnColNameIsBeforeOrOnValue', {colName: ruleHelpers.getColumnName(rule.column), value: formatBirthday(DateFormat.INTL, rule.value)}),
|
treeLabel: rule => t('dateInColumnColNameIsBeforeOrOnValue', {colName: ruleHelpers.getColumnName(rule.column), value: formatBirthday(DateFormat.INTL, rule.value)}),
|
||||||
},
|
},
|
||||||
gt: {
|
gt: {
|
||||||
dropdownLabel: t('after'),
|
dropdownLabel: t('after'),
|
||||||
treeLabel: rule => t('dateInColumnColNameIsAfterValue', {colName: ruleHelpers.getColumnName(rule.column), value: formatBirthday(DateFormat.INTL, rule.value)}),
|
treeLabel: rule => t('dateInColumnColNameIsAfterValue', {colName: ruleHelpers.getColumnName(rule.column), value: formatBirthday(DateFormat.INTL, rule.value)}),
|
||||||
},
|
},
|
||||||
ge: {
|
ge: {
|
||||||
dropdownLabel: t('afterOrOn'),
|
dropdownLabel: t('afterOrOn'),
|
||||||
treeLabel: rule => t('dateInColumnColNameIsAfterOrOnValue', {colName: ruleHelpers.getColumnName(rule.column), value: formatBirthday(DateFormat.INTL, rule.value)}),
|
treeLabel: rule => t('dateInColumnColNameIsAfterOrOnValue', {colName: ruleHelpers.getColumnName(rule.column), value: formatBirthday(DateFormat.INTL, rule.value)}),
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
ruleHelpers.primitiveRuleTypes.option = {
|
ruleHelpers.primitiveRuleTypes.option = {
|
||||||
isTrue: {
|
isTrue: {
|
||||||
dropdownLabel: t('isSelected'),
|
dropdownLabel: t('isSelected'),
|
||||||
treeLabel: rule => t('valueInColumnColNameIsSelected', {colName: ruleHelpers.getColumnName(rule.column)}),
|
treeLabel: rule => t('valueInColumnColNameIsSelected', {colName: ruleHelpers.getColumnName(rule.column)}),
|
||||||
},
|
},
|
||||||
isFalse: {
|
isFalse: {
|
||||||
dropdownLabel: t('isNotSelected'),
|
dropdownLabel: t('isNotSelected'),
|
||||||
treeLabel: rule => t('valueInColumnColNameIsNotSelected', {colName: ruleHelpers.getColumnName(rule.column)}),
|
treeLabel: rule => t('valueInColumnColNameIsNotSelected', {colName: ruleHelpers.getColumnName(rule.column)}),
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
ruleHelpers.primitiveRuleTypes['dropdown-enum'] = ruleHelpers.primitiveRuleTypes['radio-enum'] = {
|
ruleHelpers.primitiveRuleTypes['dropdown-enum'] = ruleHelpers.primitiveRuleTypes['radio-enum'] = {
|
||||||
eq: {
|
eq: {
|
||||||
dropdownLabel: t('keyEqualTo'),
|
dropdownLabel: t('keyEqualTo'),
|
||||||
treeLabel: rule => t('theSelectedKeyInColumnColNameIsEqualTo', {colName: ruleHelpers.getColumnName(rule.column), value: rule.value}),
|
treeLabel: rule => t('theSelectedKeyInColumnColNameIsEqualTo', {colName: ruleHelpers.getColumnName(rule.column), value: rule.value}),
|
||||||
},
|
},
|
||||||
like: {
|
like: {
|
||||||
dropdownLabel: t('keyMatchWithSqlLike'),
|
dropdownLabel: t('keyMatchWithSqlLike'),
|
||||||
treeLabel: rule => t('theSelectedKeyInColumnColNameMatchesWith', {colName: ruleHelpers.getColumnName(rule.column), value: rule.value}),
|
treeLabel: rule => t('theSelectedKeyInColumnColNameMatchesWith', {colName: ruleHelpers.getColumnName(rule.column), value: rule.value}),
|
||||||
},
|
},
|
||||||
re: {
|
re: {
|
||||||
dropdownLabel: t('keyMatchWithRegularExpressions'),
|
dropdownLabel: t('keyMatchWithRegularExpressions'),
|
||||||
treeLabel: rule => t('theSelectedKeyInColumnColNameMatchesWith-1', {colName: ruleHelpers.getColumnName(rule.column), value: rule.value}),
|
treeLabel: rule => t('theSelectedKeyInColumnColNameMatchesWith-1', {colName: ruleHelpers.getColumnName(rule.column), value: rule.value}),
|
||||||
},
|
},
|
||||||
lt: {
|
lt: {
|
||||||
dropdownLabel: t('keyAlphabeticallyBefore'),
|
dropdownLabel: t('keyAlphabeticallyBefore'),
|
||||||
treeLabel: rule => t('theSelectedKeyInColumnColNameIs', {colName: ruleHelpers.getColumnName(rule.column), value: rule.value}),
|
treeLabel: rule => t('theSelectedKeyInColumnColNameIs', {colName: ruleHelpers.getColumnName(rule.column), value: rule.value}),
|
||||||
},
|
},
|
||||||
le: {
|
le: {
|
||||||
dropdownLabel: t('keyAlphabeticallyBeforeOrEqualTo'),
|
dropdownLabel: t('keyAlphabeticallyBeforeOrEqualTo'),
|
||||||
treeLabel: rule => t('theSelectedKeyInColumnColNameIs-1', {colName: ruleHelpers.getColumnName(rule.column), value: rule.value}),
|
treeLabel: rule => t('theSelectedKeyInColumnColNameIs-1', {colName: ruleHelpers.getColumnName(rule.column), value: rule.value}),
|
||||||
},
|
},
|
||||||
gt: {
|
gt: {
|
||||||
dropdownLabel: t('keyAlphabeticallyAfter'),
|
dropdownLabel: t('keyAlphabeticallyAfter'),
|
||||||
treeLabel: rule => t('theSelectedKeyInColumnColNameIs-2', {colName: ruleHelpers.getColumnName(rule.column), value: rule.value}),
|
treeLabel: rule => t('theSelectedKeyInColumnColNameIs-2', {colName: ruleHelpers.getColumnName(rule.column), value: rule.value}),
|
||||||
},
|
},
|
||||||
ge: {
|
ge: {
|
||||||
dropdownLabel: t('keyAlphabeticallyAfterOrEqualTo'),
|
dropdownLabel: t('keyAlphabeticallyAfterOrEqualTo'),
|
||||||
treeLabel: rule => t('theSelectedKeyInColumnColNameIs-3', {colName: ruleHelpers.getColumnName(rule.column), value: rule.value}),
|
treeLabel: rule => t('theSelectedKeyInColumnColNameIs-3', {colName: ruleHelpers.getColumnName(rule.column), value: rule.value}),
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
const stringValueSettings = allowEmpty => ({
|
const stringValueSettings = allowEmpty => ({
|
||||||
getForm: () => <InputField id="value" label={t('value')} />,
|
getForm: () => <InputField id="value" label={t('value')} />,
|
||||||
getFormData: rule => ({
|
getFormData: rule => ({
|
||||||
value: rule.value
|
value: rule.value
|
||||||
}),
|
}),
|
||||||
assignRuleSettings: (rule, getter) => {
|
assignRuleSettings: (rule, getter) => {
|
||||||
rule.value = getter('value');
|
rule.value = getter('value');
|
||||||
},
|
},
|
||||||
validate: state => {
|
validate: state => {
|
||||||
if (!allowEmpty && !state.getIn(['value', 'value'])) {
|
if (!allowEmpty && !state.getIn(['value', 'value'])) {
|
||||||
state.setIn(['value', 'error'], t('valueMustNotBeEmpty'));
|
state.setIn(['value', 'error'], t('valueMustNotBeEmpty'));
|
||||||
} else {
|
} else {
|
||||||
state.setIn(['value', 'error'], null);
|
state.setIn(['value', 'error'], null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const numberValueSettings = {
|
const numberValueSettings = {
|
||||||
getForm: () => <InputField id="value" label={t('value')} />,
|
getForm: () => <InputField id="value" label={t('value')} />,
|
||||||
getFormData: rule => ({
|
getFormData: rule => ({
|
||||||
value: rule.value.toString()
|
value: rule.value.toString()
|
||||||
}),
|
}),
|
||||||
assignRuleSettings: (rule, getter) => {
|
assignRuleSettings: (rule, getter) => {
|
||||||
rule.value = parseInt(getter('value'));
|
rule.value = parseInt(getter('value'));
|
||||||
},
|
},
|
||||||
validate: state => {
|
validate: state => {
|
||||||
const value = state.getIn(['value', 'value']).trim();
|
const value = state.getIn(['value', 'value']).trim();
|
||||||
if (value === '') {
|
if (value === '') {
|
||||||
state.setIn(['value', 'error'], t('valueMustNotBeEmpty'));
|
state.setIn(['value', 'error'], t('valueMustNotBeEmpty'));
|
||||||
} else if (isNaN(value)) {
|
} else if (isNaN(value)) {
|
||||||
state.setIn(['value', 'error'], t('valueMustBeANumber'));
|
state.setIn(['value', 'error'], t('valueMustBeANumber'));
|
||||||
} else {
|
} else {
|
||||||
state.setIn(['value', 'error'], null);
|
state.setIn(['value', 'error'], null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const birthdayValueSettings = {
|
const birthdayValueSettings = {
|
||||||
getForm: () => <DatePicker id="birthday" label={t('date')} birthday />,
|
getForm: () => <DatePicker id="birthday" label={t('date')} birthday />,
|
||||||
getFormData: rule => ({
|
getFormData: rule => ({
|
||||||
birthday: formatBirthday(DateFormat.INTL, rule.value)
|
birthday: formatBirthday(DateFormat.INTL, rule.value)
|
||||||
}),
|
}),
|
||||||
assignRuleSettings: (rule, getter) => {
|
assignRuleSettings: (rule, getter) => {
|
||||||
rule.value = parseBirthday(DateFormat.INTL, getter('birthday')).toISOString();
|
rule.value = parseBirthday(DateFormat.INTL, getter('birthday')).toISOString();
|
||||||
},
|
},
|
||||||
validate: state => {
|
validate: state => {
|
||||||
const value = state.getIn(['birthday', 'value']);
|
const value = state.getIn(['birthday', 'value']);
|
||||||
const date = parseBirthday(DateFormat.INTL, value);
|
const date = parseBirthday(DateFormat.INTL, value);
|
||||||
if (!value) {
|
if (!value) {
|
||||||
state.setIn(['birthday', 'error'], t('dateMustNotBeEmpty'));
|
state.setIn(['birthday', 'error'], t('dateMustNotBeEmpty'));
|
||||||
} else if (!date) {
|
} else if (!date) {
|
||||||
state.setIn(['birthday', 'error'], t('dateIsInvalid'));
|
state.setIn(['birthday', 'error'], t('dateIsInvalid'));
|
||||||
} else {
|
} else {
|
||||||
state.setIn(['birthday', 'error'], null);
|
state.setIn(['birthday', 'error'], null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const dateValueSettings = {
|
const dateValueSettings = {
|
||||||
getForm: () => <DatePicker id="date" label={t('date')} />,
|
getForm: () => <DatePicker id="date" label={t('date')} />,
|
||||||
getFormData: rule => ({
|
getFormData: rule => ({
|
||||||
date: formatDate(DateFormat.INTL, rule.value)
|
date: formatDate(DateFormat.INTL, rule.value)
|
||||||
}),
|
}),
|
||||||
assignRuleSettings: (rule, getter) => {
|
assignRuleSettings: (rule, getter) => {
|
||||||
rule.value = parseDate(DateFormat.INTL, getter('date')).toISOString();
|
rule.value = parseDate(DateFormat.INTL, getter('date')).toISOString();
|
||||||
},
|
},
|
||||||
validate: state => {
|
validate: state => {
|
||||||
const value = state.getIn(['date', 'value']);
|
const value = state.getIn(['date', 'value']);
|
||||||
const date = parseDate(DateFormat.INTL, value);
|
const date = parseDate(DateFormat.INTL, value);
|
||||||
if (!value) {
|
if (!value) {
|
||||||
state.setIn(['date', 'error'], t('dateMustNotBeEmpty'));
|
state.setIn(['date', 'error'], t('dateMustNotBeEmpty'));
|
||||||
} else if (!date) {
|
} else if (!date) {
|
||||||
state.setIn(['date', 'error'], t('dateIsInvalid'));
|
state.setIn(['date', 'error'], t('dateIsInvalid'));
|
||||||
} else {
|
} else {
|
||||||
state.setIn(['date', 'error'], null);
|
state.setIn(['date', 'error'], null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const dateRelativeValueSettings = {
|
const dateRelativeValueSettings = {
|
||||||
getForm: () =>
|
getForm: () =>
|
||||||
<div>
|
<div>
|
||||||
<InputField id="daysValue" label={t('numberOfDays')}/>
|
<InputField id="daysValue" label={t('numberOfDays')}/>
|
||||||
<Dropdown id="direction" label={t('beforeAfter')} options={[
|
<Dropdown id="direction" label={t('beforeAfter')} options={[
|
||||||
{ key: 'before', label: t('beforeCurrentDate') },
|
{ key: 'before', label: t('beforeCurrentDate') },
|
||||||
{ key: 'after', label: t('afterCurrentDate') }
|
{ key: 'after', label: t('afterCurrentDate') }
|
||||||
]}/>
|
]}/>
|
||||||
</div>,
|
</div>,
|
||||||
getFormData: rule => ({
|
getFormData: rule => ({
|
||||||
daysValue: Math.abs(rule.value).toString(),
|
daysValue: Math.abs(rule.value).toString(),
|
||||||
direction: rule.value >= 0 ? 'after' : 'before'
|
direction: rule.value >= 0 ? 'after' : 'before'
|
||||||
}),
|
}),
|
||||||
assignRuleSettings: (rule, getter) => {
|
assignRuleSettings: (rule, getter) => {
|
||||||
const direction = getter('direction');
|
const direction = getter('direction');
|
||||||
rule.value = parseInt(getter('daysValue')) * (direction === 'before' ? -1 : 1);
|
rule.value = parseInt(getter('daysValue')) * (direction === 'before' ? -1 : 1);
|
||||||
},
|
},
|
||||||
validate: state => {
|
validate: state => {
|
||||||
const value = state.getIn(['daysValue', 'value']);
|
const value = state.getIn(['daysValue', 'value']);
|
||||||
if (!value) {
|
if (!value) {
|
||||||
state.setIn(['daysValue', 'error'], t('numberOfDaysMustNotBeEmpty'));
|
state.setIn(['daysValue', 'error'], t('numberOfDaysMustNotBeEmpty'));
|
||||||
} else if (isNaN(value)) {
|
} else if (isNaN(value)) {
|
||||||
state.setIn(['daysValue', 'error'], t('numberOfDaysMustBeANumber'));
|
state.setIn(['daysValue', 'error'], t('numberOfDaysMustBeANumber'));
|
||||||
} else {
|
} else {
|
||||||
state.setIn(['daysValue', 'error'], null);
|
state.setIn(['daysValue', 'error'], null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const optionValueSettings = {
|
const optionValueSettings = {
|
||||||
getForm: () => null,
|
getForm: () => null,
|
||||||
getFormData: rule => ({}),
|
getFormData: rule => ({}),
|
||||||
assignRuleSettings: (rule, getter) => {},
|
assignRuleSettings: (rule, getter) => {},
|
||||||
validate: state => {}
|
validate: state => {}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
function assignSettingsToRuleTypes(ruleTypes, keys, settings) {
|
function assignSettingsToRuleTypes(ruleTypes, keys, settings) {
|
||||||
for (const key of keys) {
|
for (const key of keys) {
|
||||||
Object.assign(ruleTypes[key], settings);
|
Object.assign(ruleTypes[key], settings);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
assignSettingsToRuleTypes(ruleHelpers.primitiveRuleTypes.text, ['eq', 'like', 're'], stringValueSettings(true));
|
assignSettingsToRuleTypes(ruleHelpers.primitiveRuleTypes.text, ['eq', 'like', 're'], stringValueSettings(true));
|
||||||
assignSettingsToRuleTypes(ruleHelpers.primitiveRuleTypes.text, ['lt', 'le', 'gt', 'ge'], stringValueSettings(false));
|
assignSettingsToRuleTypes(ruleHelpers.primitiveRuleTypes.text, ['lt', 'le', 'gt', 'ge'], stringValueSettings(false));
|
||||||
assignSettingsToRuleTypes(ruleHelpers.primitiveRuleTypes.website, ['eq', 'like', 're'], stringValueSettings(true));
|
assignSettingsToRuleTypes(ruleHelpers.primitiveRuleTypes.website, ['eq', 'like', 're'], stringValueSettings(true));
|
||||||
assignSettingsToRuleTypes(ruleHelpers.primitiveRuleTypes.number, ['eq', 'lt', 'le', 'gt', 'ge'], numberValueSettings);
|
assignSettingsToRuleTypes(ruleHelpers.primitiveRuleTypes.number, ['eq', 'lt', 'le', 'gt', 'ge'], numberValueSettings);
|
||||||
assignSettingsToRuleTypes(ruleHelpers.primitiveRuleTypes.birthday, ['eq', 'lt', 'le', 'gt', 'ge'], birthdayValueSettings);
|
assignSettingsToRuleTypes(ruleHelpers.primitiveRuleTypes.birthday, ['eq', 'lt', 'le', 'gt', 'ge'], birthdayValueSettings);
|
||||||
assignSettingsToRuleTypes(ruleHelpers.primitiveRuleTypes.date, ['eq', 'lt', 'le', 'gt', 'ge'], dateValueSettings);
|
assignSettingsToRuleTypes(ruleHelpers.primitiveRuleTypes.date, ['eq', 'lt', 'le', 'gt', 'ge'], dateValueSettings);
|
||||||
assignSettingsToRuleTypes(ruleHelpers.primitiveRuleTypes.date, ['eqTodayPlusDays', 'ltTodayPlusDays', 'leTodayPlusDays', 'gtTodayPlusDays', 'geTodayPlusDays'], dateRelativeValueSettings);
|
assignSettingsToRuleTypes(ruleHelpers.primitiveRuleTypes.date, ['eqTodayPlusDays', 'ltTodayPlusDays', 'leTodayPlusDays', 'gtTodayPlusDays', 'geTodayPlusDays'], dateRelativeValueSettings);
|
||||||
assignSettingsToRuleTypes(ruleHelpers.primitiveRuleTypes.option, ['isTrue', 'isFalse'], optionValueSettings);
|
assignSettingsToRuleTypes(ruleHelpers.primitiveRuleTypes.option, ['isTrue', 'isFalse'], optionValueSettings);
|
||||||
assignSettingsToRuleTypes(ruleHelpers.primitiveRuleTypes['dropdown-enum'], ['eq', 'like', 're'], stringValueSettings(true));
|
assignSettingsToRuleTypes(ruleHelpers.primitiveRuleTypes['dropdown-enum'], ['eq', 'like', 're'], stringValueSettings(true));
|
||||||
assignSettingsToRuleTypes(ruleHelpers.primitiveRuleTypes['dropdown-enum'], ['lt', 'le', 'gt', 'ge'], stringValueSettings(false));
|
assignSettingsToRuleTypes(ruleHelpers.primitiveRuleTypes['dropdown-enum'], ['lt', 'le', 'gt', 'ge'], stringValueSettings(false));
|
||||||
assignSettingsToRuleTypes(ruleHelpers.primitiveRuleTypes['radio-enum'], ['eq', 'like', 're'], stringValueSettings(true));
|
assignSettingsToRuleTypes(ruleHelpers.primitiveRuleTypes['radio-enum'], ['eq', 'like', 're'], stringValueSettings(true));
|
||||||
assignSettingsToRuleTypes(ruleHelpers.primitiveRuleTypes['radio-enum'], ['lt', 'le', 'gt', 'ge'], stringValueSettings(false));
|
assignSettingsToRuleTypes(ruleHelpers.primitiveRuleTypes['radio-enum'], ['lt', 'le', 'gt', 'ge'], stringValueSettings(false));
|
||||||
|
|
||||||
ruleHelpers.primitiveRuleTypesFormDataDefaults = {
|
ruleHelpers.primitiveRuleTypesFormDataDefaults = {
|
||||||
value: '',
|
value: '',
|
||||||
date: '',
|
date: '',
|
||||||
daysValue: '',
|
daysValue: '',
|
||||||
birthday: '',
|
birthday: '',
|
||||||
direction: 'before'
|
direction: 'before'
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
ruleHelpers.getCompositeRuleTypeOptions = () => {
|
ruleHelpers.getCompositeRuleTypeOptions = () => {
|
||||||
const order = ['all', 'some', 'none'];
|
const order = ['all', 'some', 'none'];
|
||||||
return order.map(key => ({ key, label: ruleHelpers.compositeRuleTypes[key].dropdownLabel }));
|
return order.map(key => ({ key, label: ruleHelpers.compositeRuleTypes[key].dropdownLabel }));
|
||||||
};
|
};
|
||||||
|
|
||||||
ruleHelpers.getPrimitiveRuleTypeOptions = columnType => {
|
ruleHelpers.getPrimitiveRuleTypeOptions = columnType => {
|
||||||
const order = {
|
const order = {
|
||||||
text: ['eq', 'like', 're', 'lt', 'le', 'gt', 'ge'],
|
text: ['eq', 'like', 're', 'lt', 'le', 'gt', 'ge'],
|
||||||
website: ['eq', 'like', 're'],
|
website: ['eq', 'like', 're'],
|
||||||
number: ['eq', 'lt', 'le', 'gt', 'ge'],
|
number: ['eq', 'lt', 'le', 'gt', 'ge'],
|
||||||
birthday: ['eq', 'lt', 'le', 'gt', 'ge'],
|
birthday: ['eq', 'lt', 'le', 'gt', 'ge'],
|
||||||
date: ['eq', 'lt', 'le', 'gt', 'ge', 'eqTodayPlusDays', 'ltTodayPlusDays', 'leTodayPlusDays', 'gtTodayPlusDays', 'geTodayPlusDays'],
|
date: ['eq', 'lt', 'le', 'gt', 'ge', 'eqTodayPlusDays', 'ltTodayPlusDays', 'leTodayPlusDays', 'gtTodayPlusDays', 'geTodayPlusDays'],
|
||||||
option: ['isTrue', 'isFalse'],
|
option: ['isTrue', 'isFalse'],
|
||||||
'dropdown-enum': ['eq', 'like', 're', 'lt', 'le', 'gt', 'ge'],
|
'dropdown-enum': ['eq', 'like', 're', 'lt', 'le', 'gt', 'ge'],
|
||||||
'radio-enum': ['eq', 'like', 're', 'lt', 'le', 'gt', 'ge']
|
'radio-enum': ['eq', 'like', 're', 'lt', 'le', 'gt', 'ge']
|
||||||
};
|
};
|
||||||
|
|
||||||
return order[columnType].map(key => ({ key, label: ruleHelpers.primitiveRuleTypes[columnType][key].dropdownLabel }));
|
return order[columnType].map(key => ({ key, label: ruleHelpers.primitiveRuleTypes[columnType][key].dropdownLabel }));
|
||||||
};
|
};
|
||||||
|
|
||||||
const predefColumns = [
|
const predefColumns = [
|
||||||
{
|
{
|
||||||
column: 'email',
|
column: 'email',
|
||||||
name: t('emailAddress-1'),
|
name: t('emailAddress-1'),
|
||||||
type: 'text',
|
type: 'text',
|
||||||
key: 'EMAIL'
|
key: 'EMAIL'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
column: 'opt_in_country',
|
column: 'opt_in_country',
|
||||||
name: t('signupCountry'),
|
name: t('signupCountry'),
|
||||||
type: 'text'
|
type: 'text'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
column: 'created',
|
column: 'created',
|
||||||
name: t('signUpDate'),
|
name: t('signUpDate'),
|
||||||
type: 'date'
|
type: 'date'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
column: 'latest_open',
|
column: 'latest_open',
|
||||||
name: t('latestOpen'),
|
name: t('latestOpen'),
|
||||||
type: 'date'
|
type: 'date'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
column: 'latest_click',
|
column: 'latest_click',
|
||||||
name: t('latestClick'),
|
name: t('latestClick'),
|
||||||
type: 'date'
|
type: 'date'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
column: 'is_test',
|
column: 'is_test',
|
||||||
name: t('Test user'),
|
name: t('Test user'),
|
||||||
type: 'option'
|
type: 'option'
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
ruleHelpers.fields = [
|
ruleHelpers.fields = [
|
||||||
...predefColumns,
|
...predefColumns,
|
||||||
...fields.filter(fld => fld.type in ruleHelpers.primitiveRuleTypes)
|
...fields.filter(fld => fld.type in ruleHelpers.primitiveRuleTypes)
|
||||||
];
|
];
|
||||||
|
|
||||||
ruleHelpers.fieldsByColumn = {};
|
ruleHelpers.fieldsByColumn = {};
|
||||||
for (const fld of ruleHelpers.fields) {
|
for (const fld of ruleHelpers.fields) {
|
||||||
ruleHelpers.fieldsByColumn[fld.column] = fld;
|
ruleHelpers.fieldsByColumn[fld.column] = fld;
|
||||||
}
|
}
|
||||||
|
|
||||||
ruleHelpers.getColumnType = column => {
|
ruleHelpers.getColumnType = column => {
|
||||||
const field = ruleHelpers.fieldsByColumn[column];
|
const field = ruleHelpers.fieldsByColumn[column];
|
||||||
if (field) {
|
if (field) {
|
||||||
return field.type;
|
return field.type;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
ruleHelpers.getColumnName = column => {
|
ruleHelpers.getColumnName = column => {
|
||||||
const field = ruleHelpers.fieldsByColumn[column];
|
const field = ruleHelpers.fieldsByColumn[column];
|
||||||
if (field) {
|
if (field) {
|
||||||
return field.name;
|
return field.name;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
ruleHelpers.getRuleTypeSettings = rule => {
|
ruleHelpers.getRuleTypeSettings = rule => {
|
||||||
if (ruleHelpers.isCompositeRuleType(rule.type)) {
|
if (ruleHelpers.isCompositeRuleType(rule.type)) {
|
||||||
return ruleHelpers.compositeRuleTypes[rule.type];
|
return ruleHelpers.compositeRuleTypes[rule.type];
|
||||||
} else {
|
} else {
|
||||||
const colType = ruleHelpers.getColumnType(rule.column);
|
const colType = ruleHelpers.getColumnType(rule.column);
|
||||||
|
|
||||||
if (colType) {
|
if (colType) {
|
||||||
if (rule.type in ruleHelpers.primitiveRuleTypes[colType]) {
|
if (rule.type in ruleHelpers.primitiveRuleTypes[colType]) {
|
||||||
return ruleHelpers.primitiveRuleTypes[colType][rule.type];
|
return ruleHelpers.primitiveRuleTypes[colType][rule.type];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
ruleHelpers.isCompositeRuleType = ruleType => ruleType in ruleHelpers.compositeRuleTypes;
|
ruleHelpers.isCompositeRuleType = ruleType => ruleType in ruleHelpers.compositeRuleTypes;
|
||||||
|
|
||||||
return ruleHelpers;
|
return ruleHelpers;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
.mapping {
|
.mapping {
|
||||||
margin-top: 30px;
|
margin-top: 30px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.erased {
|
.erased {
|
||||||
color: #808080;
|
color: #808080;
|
||||||
}
|
}
|
|
@ -1,212 +1,212 @@
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import {SubscriptionStatus} from "../../../../shared/lists";
|
import {SubscriptionStatus} from "../../../../shared/lists";
|
||||||
import {
|
import {
|
||||||
ACEEditor,
|
ACEEditor,
|
||||||
CheckBox,
|
CheckBox,
|
||||||
CheckBoxGroup,
|
CheckBoxGroup,
|
||||||
DatePicker,
|
DatePicker,
|
||||||
Dropdown,
|
Dropdown,
|
||||||
InputField,
|
InputField,
|
||||||
RadioGroup,
|
RadioGroup,
|
||||||
TextArea
|
TextArea
|
||||||
} from "../../lib/form";
|
} from "../../lib/form";
|
||||||
import {formatBirthday, formatDate, parseBirthday, parseDate} from "../../../../shared/date";
|
import {formatBirthday, formatDate, parseBirthday, parseDate} from "../../../../shared/date";
|
||||||
import {getFieldColumn} from '../../../../shared/lists';
|
import {getFieldColumn} from '../../../../shared/lists';
|
||||||
import 'brace/mode/json';
|
import 'brace/mode/json';
|
||||||
|
|
||||||
export function getSubscriptionStatusLabels(t) {
|
export function getSubscriptionStatusLabels(t) {
|
||||||
|
|
||||||
const subscriptionStatusLabels = {
|
const subscriptionStatusLabels = {
|
||||||
[SubscriptionStatus.SUBSCRIBED]: t('subscribed'),
|
[SubscriptionStatus.SUBSCRIBED]: t('subscribed'),
|
||||||
[SubscriptionStatus.UNSUBSCRIBED]: t('unubscribed'),
|
[SubscriptionStatus.UNSUBSCRIBED]: t('unubscribed'),
|
||||||
[SubscriptionStatus.BOUNCED]: t('bounced'),
|
[SubscriptionStatus.BOUNCED]: t('bounced'),
|
||||||
[SubscriptionStatus.COMPLAINED]: t('complained'),
|
[SubscriptionStatus.COMPLAINED]: t('complained'),
|
||||||
};
|
};
|
||||||
|
|
||||||
return subscriptionStatusLabels;
|
return subscriptionStatusLabels;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getFieldTypes(t) {
|
export function getFieldTypes(t) {
|
||||||
|
|
||||||
const groupedFieldTypes = {};
|
const groupedFieldTypes = {};
|
||||||
|
|
||||||
const stringFieldType = long => ({
|
const stringFieldType = long => ({
|
||||||
form: groupedField => long ? <TextArea key={getFieldColumn(groupedField)} id={getFieldColumn(groupedField)} label={groupedField.name}/> : <InputField key={getFieldColumn(groupedField)} id={getFieldColumn(groupedField)} label={groupedField.name}/>,
|
form: groupedField => long ? <TextArea key={getFieldColumn(groupedField)} id={getFieldColumn(groupedField)} label={groupedField.name}/> : <InputField key={getFieldColumn(groupedField)} id={getFieldColumn(groupedField)} label={groupedField.name}/>,
|
||||||
assignFormData: (groupedField, data) => {
|
assignFormData: (groupedField, data) => {
|
||||||
const value = data[getFieldColumn(groupedField)];
|
const value = data[getFieldColumn(groupedField)];
|
||||||
data[getFieldColumn(groupedField)] = value || '';
|
data[getFieldColumn(groupedField)] = value || '';
|
||||||
},
|
},
|
||||||
initFormData: (groupedField, data) => {
|
initFormData: (groupedField, data) => {
|
||||||
data[getFieldColumn(groupedField)] = '';
|
data[getFieldColumn(groupedField)] = '';
|
||||||
},
|
},
|
||||||
assignEntity: (groupedField, data) => {},
|
assignEntity: (groupedField, data) => {},
|
||||||
validate: (groupedField, state) => {},
|
validate: (groupedField, state) => {},
|
||||||
indexable: true
|
indexable: true
|
||||||
});
|
});
|
||||||
|
|
||||||
const numberFieldType = {
|
const numberFieldType = {
|
||||||
form: groupedField => <InputField key={getFieldColumn(groupedField)} id={getFieldColumn(groupedField)} label={groupedField.name}/>,
|
form: groupedField => <InputField key={getFieldColumn(groupedField)} id={getFieldColumn(groupedField)} label={groupedField.name}/>,
|
||||||
assignFormData: (groupedField, data) => {
|
assignFormData: (groupedField, data) => {
|
||||||
const value = data[getFieldColumn(groupedField)];
|
const value = data[getFieldColumn(groupedField)];
|
||||||
data[getFieldColumn(groupedField)] = value ? value.toString() : '';
|
data[getFieldColumn(groupedField)] = value ? value.toString() : '';
|
||||||
},
|
},
|
||||||
initFormData: (groupedField, data) => {
|
initFormData: (groupedField, data) => {
|
||||||
data[getFieldColumn(groupedField)] = '';
|
data[getFieldColumn(groupedField)] = '';
|
||||||
},
|
},
|
||||||
assignEntity: (groupedField, data) => {
|
assignEntity: (groupedField, data) => {
|
||||||
data[getFieldColumn(groupedField)] = parseInt(data[getFieldColumn(groupedField)]);
|
data[getFieldColumn(groupedField)] = parseInt(data[getFieldColumn(groupedField)]);
|
||||||
},
|
},
|
||||||
validate: (groupedField, state) => {
|
validate: (groupedField, state) => {
|
||||||
const value = state.getIn([getFieldColumn(groupedField), 'value']).trim();
|
const value = state.getIn([getFieldColumn(groupedField), 'value']).trim();
|
||||||
if (value !== '' && isNaN(value)) {
|
if (value !== '' && isNaN(value)) {
|
||||||
state.setIn([getFieldColumn(groupedField), 'error'], t('valueMustBeANumber'));
|
state.setIn([getFieldColumn(groupedField), 'error'], t('valueMustBeANumber'));
|
||||||
} else {
|
} else {
|
||||||
state.setIn([getFieldColumn(groupedField), 'error'], null);
|
state.setIn([getFieldColumn(groupedField), 'error'], null);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
indexable: true
|
indexable: true
|
||||||
};
|
};
|
||||||
|
|
||||||
const dateFieldType = {
|
const dateFieldType = {
|
||||||
form: groupedField => <DatePicker key={getFieldColumn(groupedField)} id={getFieldColumn(groupedField)} label={groupedField.name} dateFormat={groupedField.settings.dateFormat} />,
|
form: groupedField => <DatePicker key={getFieldColumn(groupedField)} id={getFieldColumn(groupedField)} label={groupedField.name} dateFormat={groupedField.settings.dateFormat} />,
|
||||||
assignFormData: (groupedField, data) => {
|
assignFormData: (groupedField, data) => {
|
||||||
const value = data[getFieldColumn(groupedField)];
|
const value = data[getFieldColumn(groupedField)];
|
||||||
data[getFieldColumn(groupedField)] = value ? formatDate(groupedField.settings.dateFormat, value) : '';
|
data[getFieldColumn(groupedField)] = value ? formatDate(groupedField.settings.dateFormat, value) : '';
|
||||||
},
|
},
|
||||||
initFormData: (groupedField, data) => {
|
initFormData: (groupedField, data) => {
|
||||||
data[getFieldColumn(groupedField)] = '';
|
data[getFieldColumn(groupedField)] = '';
|
||||||
},
|
},
|
||||||
assignEntity: (groupedField, data) => {
|
assignEntity: (groupedField, data) => {
|
||||||
const date = parseDate(groupedField.settings.dateFormat, data[getFieldColumn(groupedField)]);
|
const date = parseDate(groupedField.settings.dateFormat, data[getFieldColumn(groupedField)]);
|
||||||
data[getFieldColumn(groupedField)] = date;
|
data[getFieldColumn(groupedField)] = date;
|
||||||
},
|
},
|
||||||
validate: (groupedField, state) => {
|
validate: (groupedField, state) => {
|
||||||
const value = state.getIn([getFieldColumn(groupedField), 'value']);
|
const value = state.getIn([getFieldColumn(groupedField), 'value']);
|
||||||
const date = parseDate(groupedField.settings.dateFormat, value);
|
const date = parseDate(groupedField.settings.dateFormat, value);
|
||||||
if (value !== '' && !date) {
|
if (value !== '' && !date) {
|
||||||
state.setIn([getFieldColumn(groupedField), 'error'], t('dateIsInvalid'));
|
state.setIn([getFieldColumn(groupedField), 'error'], t('dateIsInvalid'));
|
||||||
} else {
|
} else {
|
||||||
state.setIn([getFieldColumn(groupedField), 'error'], null);
|
state.setIn([getFieldColumn(groupedField), 'error'], null);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
indexable: true
|
indexable: true
|
||||||
};
|
};
|
||||||
|
|
||||||
const birthdayFieldType = {
|
const birthdayFieldType = {
|
||||||
form: groupedField => <DatePicker key={getFieldColumn(groupedField)} id={getFieldColumn(groupedField)} label={groupedField.name} dateFormat={groupedField.settings.dateFormat} birthday />,
|
form: groupedField => <DatePicker key={getFieldColumn(groupedField)} id={getFieldColumn(groupedField)} label={groupedField.name} dateFormat={groupedField.settings.dateFormat} birthday />,
|
||||||
assignFormData: (groupedField, data) => {
|
assignFormData: (groupedField, data) => {
|
||||||
const value = data[getFieldColumn(groupedField)];
|
const value = data[getFieldColumn(groupedField)];
|
||||||
data[getFieldColumn(groupedField)] = value ? formatBirthday(groupedField.settings.dateFormat, value) : '';
|
data[getFieldColumn(groupedField)] = value ? formatBirthday(groupedField.settings.dateFormat, value) : '';
|
||||||
},
|
},
|
||||||
initFormData: (groupedField, data) => {
|
initFormData: (groupedField, data) => {
|
||||||
data[getFieldColumn(groupedField)] = '';
|
data[getFieldColumn(groupedField)] = '';
|
||||||
},
|
},
|
||||||
assignEntity: (groupedField, data) => {
|
assignEntity: (groupedField, data) => {
|
||||||
const date = parseBirthday(groupedField.settings.dateFormat, data[getFieldColumn(groupedField)]);
|
const date = parseBirthday(groupedField.settings.dateFormat, data[getFieldColumn(groupedField)]);
|
||||||
data[getFieldColumn(groupedField)] = date;
|
data[getFieldColumn(groupedField)] = date;
|
||||||
},
|
},
|
||||||
validate: (groupedField, state) => {
|
validate: (groupedField, state) => {
|
||||||
const value = state.getIn([getFieldColumn(groupedField), 'value']);
|
const value = state.getIn([getFieldColumn(groupedField), 'value']);
|
||||||
const date = parseBirthday(groupedField.settings.dateFormat, value);
|
const date = parseBirthday(groupedField.settings.dateFormat, value);
|
||||||
if (value !== '' && !date) {
|
if (value !== '' && !date) {
|
||||||
state.setIn([getFieldColumn(groupedField), 'error'], t('dateIsInvalid'));
|
state.setIn([getFieldColumn(groupedField), 'error'], t('dateIsInvalid'));
|
||||||
} else {
|
} else {
|
||||||
state.setIn([getFieldColumn(groupedField), 'error'], null);
|
state.setIn([getFieldColumn(groupedField), 'error'], null);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
indexable: true
|
indexable: true
|
||||||
};
|
};
|
||||||
|
|
||||||
const jsonFieldType = {
|
const jsonFieldType = {
|
||||||
form: groupedField => <ACEEditor key={getFieldColumn(groupedField)} id={getFieldColumn(groupedField)} label={groupedField.name} mode="json" height="300px"/>,
|
form: groupedField => <ACEEditor key={getFieldColumn(groupedField)} id={getFieldColumn(groupedField)} label={groupedField.name} mode="json" height="300px"/>,
|
||||||
assignFormData: (groupedField, data) => {
|
assignFormData: (groupedField, data) => {
|
||||||
const value = data[getFieldColumn(groupedField)];
|
const value = data[getFieldColumn(groupedField)];
|
||||||
data[getFieldColumn(groupedField)] = value || '';
|
data[getFieldColumn(groupedField)] = value || '';
|
||||||
},
|
},
|
||||||
initFormData: (groupedField, data) => {
|
initFormData: (groupedField, data) => {
|
||||||
data[getFieldColumn(groupedField)] = '';
|
data[getFieldColumn(groupedField)] = '';
|
||||||
},
|
},
|
||||||
assignEntity: (groupedField, data) => {},
|
assignEntity: (groupedField, data) => {},
|
||||||
validate: (groupedField, state) => {},
|
validate: (groupedField, state) => {},
|
||||||
indexable: false
|
indexable: false
|
||||||
};
|
};
|
||||||
|
|
||||||
const optionFieldType = {
|
const optionFieldType = {
|
||||||
form: groupedField => <CheckBox key={getFieldColumn(groupedField)} id={getFieldColumn(groupedField)} text={groupedField.settings.checkedLabel} label={groupedField.name}/>,
|
form: groupedField => <CheckBox key={getFieldColumn(groupedField)} id={getFieldColumn(groupedField)} text={groupedField.settings.checkedLabel} label={groupedField.name}/>,
|
||||||
assignFormData: (groupedField, data) => {
|
assignFormData: (groupedField, data) => {
|
||||||
const value = data[getFieldColumn(groupedField)];
|
const value = data[getFieldColumn(groupedField)];
|
||||||
data[getFieldColumn(groupedField)] = !!value;
|
data[getFieldColumn(groupedField)] = !!value;
|
||||||
},
|
},
|
||||||
initFormData: (groupedField, data) => {
|
initFormData: (groupedField, data) => {
|
||||||
data[getFieldColumn(groupedField)] = false;
|
data[getFieldColumn(groupedField)] = false;
|
||||||
},
|
},
|
||||||
assignEntity: (groupedField, data) => {},
|
assignEntity: (groupedField, data) => {},
|
||||||
validate: (groupedField, state) => {},
|
validate: (groupedField, state) => {},
|
||||||
indexable: true
|
indexable: true
|
||||||
};
|
};
|
||||||
|
|
||||||
const enumSingleFieldType = componentType => ({
|
const enumSingleFieldType = componentType => ({
|
||||||
form: groupedField => React.createElement(componentType, { key: getFieldColumn(groupedField), id: getFieldColumn(groupedField), label: groupedField.name, options: groupedField.settings.options }, null),
|
form: groupedField => React.createElement(componentType, { key: getFieldColumn(groupedField), id: getFieldColumn(groupedField), label: groupedField.name, options: groupedField.settings.options }, null),
|
||||||
assignFormData: (groupedField, data) => {
|
assignFormData: (groupedField, data) => {
|
||||||
if (data[getFieldColumn(groupedField)] === null) {
|
if (data[getFieldColumn(groupedField)] === null) {
|
||||||
if (groupedField.default_value) {
|
if (groupedField.default_value) {
|
||||||
data[getFieldColumn(groupedField)] = groupedField.default_value;
|
data[getFieldColumn(groupedField)] = groupedField.default_value;
|
||||||
} else if (groupedField.settings.options.length > 0) {
|
} else if (groupedField.settings.options.length > 0) {
|
||||||
data[getFieldColumn(groupedField)] = groupedField.settings.options[0].key;
|
data[getFieldColumn(groupedField)] = groupedField.settings.options[0].key;
|
||||||
} else {
|
} else {
|
||||||
data[getFieldColumn(groupedField)] = '';
|
data[getFieldColumn(groupedField)] = '';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
initFormData: (groupedField, data) => {
|
initFormData: (groupedField, data) => {
|
||||||
if (groupedField.default_value) {
|
if (groupedField.default_value) {
|
||||||
data[getFieldColumn(groupedField)] = groupedField.default_value;
|
data[getFieldColumn(groupedField)] = groupedField.default_value;
|
||||||
} else if (groupedField.settings.options.length > 0) {
|
} else if (groupedField.settings.options.length > 0) {
|
||||||
data[getFieldColumn(groupedField)] = groupedField.settings.options[0].key;
|
data[getFieldColumn(groupedField)] = groupedField.settings.options[0].key;
|
||||||
} else {
|
} else {
|
||||||
data[getFieldColumn(groupedField)] = '';
|
data[getFieldColumn(groupedField)] = '';
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
assignEntity: (groupedField, data) => {},
|
assignEntity: (groupedField, data) => {},
|
||||||
validate: (groupedField, state) => {},
|
validate: (groupedField, state) => {},
|
||||||
indexable: false
|
indexable: false
|
||||||
});
|
});
|
||||||
|
|
||||||
const enumMultipleFieldType = componentType => ({
|
const enumMultipleFieldType = componentType => ({
|
||||||
form: groupedField => React.createElement(componentType, { key: getFieldColumn(groupedField), id: getFieldColumn(groupedField), label: groupedField.name, options: groupedField.settings.options }, null),
|
form: groupedField => React.createElement(componentType, { key: getFieldColumn(groupedField), id: getFieldColumn(groupedField), label: groupedField.name, options: groupedField.settings.options }, null),
|
||||||
assignFormData: (groupedField, data) => {
|
assignFormData: (groupedField, data) => {
|
||||||
if (data[getFieldColumn(groupedField)] === null) {
|
if (data[getFieldColumn(groupedField)] === null) {
|
||||||
data[getFieldColumn(groupedField)] = [];
|
data[getFieldColumn(groupedField)] = [];
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
initFormData: (groupedField, data) => {
|
initFormData: (groupedField, data) => {
|
||||||
data[getFieldColumn(groupedField)] = [];
|
data[getFieldColumn(groupedField)] = [];
|
||||||
},
|
},
|
||||||
assignEntity: (groupedField, data) => {},
|
assignEntity: (groupedField, data) => {},
|
||||||
validate: (groupedField, state) => {},
|
validate: (groupedField, state) => {},
|
||||||
indexable: false
|
indexable: false
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
groupedFieldTypes.text = stringFieldType(false);
|
groupedFieldTypes.text = stringFieldType(false);
|
||||||
groupedFieldTypes.website = stringFieldType(false);
|
groupedFieldTypes.website = stringFieldType(false);
|
||||||
groupedFieldTypes.longtext = stringFieldType(true);
|
groupedFieldTypes.longtext = stringFieldType(true);
|
||||||
groupedFieldTypes.gpg = stringFieldType(true);
|
groupedFieldTypes.gpg = stringFieldType(true);
|
||||||
groupedFieldTypes.number = numberFieldType;
|
groupedFieldTypes.number = numberFieldType;
|
||||||
groupedFieldTypes.date = dateFieldType;
|
groupedFieldTypes.date = dateFieldType;
|
||||||
groupedFieldTypes.birthday = birthdayFieldType;
|
groupedFieldTypes.birthday = birthdayFieldType;
|
||||||
groupedFieldTypes.json = jsonFieldType;
|
groupedFieldTypes.json = jsonFieldType;
|
||||||
groupedFieldTypes.option = optionFieldType;
|
groupedFieldTypes.option = optionFieldType;
|
||||||
groupedFieldTypes['dropdown-enum'] = enumSingleFieldType(Dropdown);
|
groupedFieldTypes['dropdown-enum'] = enumSingleFieldType(Dropdown);
|
||||||
groupedFieldTypes['radio-enum'] = enumSingleFieldType(RadioGroup);
|
groupedFieldTypes['radio-enum'] = enumSingleFieldType(RadioGroup);
|
||||||
|
|
||||||
// Here we rely on the fact the model/groupedFields and model/subscriptions preprocess the groupedField info and subscription
|
// Here we rely on the fact the model/groupedFields and model/subscriptions preprocess the groupedField info and subscription
|
||||||
// such that the grouped entries behave the same as the enum entries
|
// such that the grouped entries behave the same as the enum entries
|
||||||
groupedFieldTypes['checkbox-grouped'] = enumMultipleFieldType(CheckBoxGroup);
|
groupedFieldTypes['checkbox-grouped'] = enumMultipleFieldType(CheckBoxGroup);
|
||||||
groupedFieldTypes['radio-grouped'] = enumSingleFieldType(RadioGroup);
|
groupedFieldTypes['radio-grouped'] = enumSingleFieldType(RadioGroup);
|
||||||
groupedFieldTypes['dropdown-grouped'] = enumSingleFieldType(Dropdown);
|
groupedFieldTypes['dropdown-grouped'] = enumSingleFieldType(Dropdown);
|
||||||
|
|
||||||
return groupedFieldTypes;
|
return groupedFieldTypes;
|
||||||
}
|
}
|
|
@ -1,337 +1,337 @@
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
import React from "react";
|
import React from "react";
|
||||||
|
|
||||||
import {MailerType, ZoneMTAType} from "../../../shared/send-configurations";
|
import {MailerType, ZoneMTAType} from "../../../shared/send-configurations";
|
||||||
import {
|
import {
|
||||||
CheckBox,
|
CheckBox,
|
||||||
Dropdown,
|
Dropdown,
|
||||||
Fieldset,
|
Fieldset,
|
||||||
InputField,
|
InputField,
|
||||||
TextArea
|
TextArea
|
||||||
} from "../lib/form";
|
} from "../lib/form";
|
||||||
import {Trans} from "react-i18next";
|
import {Trans} from "react-i18next";
|
||||||
import styles from "./styles.scss";
|
import styles from "./styles.scss";
|
||||||
import mailtrainConfig from 'mailtrainConfig';
|
import mailtrainConfig from 'mailtrainConfig';
|
||||||
|
|
||||||
export const mailerTypesOrder = [
|
export const mailerTypesOrder = [
|
||||||
MailerType.ZONE_MTA,
|
MailerType.ZONE_MTA,
|
||||||
MailerType.GENERIC_SMTP,
|
MailerType.GENERIC_SMTP,
|
||||||
MailerType.AWS_SES
|
MailerType.AWS_SES
|
||||||
];
|
];
|
||||||
|
|
||||||
export function getMailerTypes(t) {
|
export function getMailerTypes(t) {
|
||||||
const mailerTypes = {};
|
const mailerTypes = {};
|
||||||
|
|
||||||
function initFieldsIfMissing(mutStateData, mailerType) {
|
function initFieldsIfMissing(mutStateData, mailerType) {
|
||||||
const initVals = mailerTypes[mailerType].initData();
|
const initVals = mailerTypes[mailerType].initData();
|
||||||
|
|
||||||
for (const key in initVals) {
|
for (const key in initVals) {
|
||||||
if (!mutStateData.hasIn([key])) {
|
if (!mutStateData.hasIn([key])) {
|
||||||
mutStateData.setIn([key, 'value'], initVals[key]);
|
mutStateData.setIn([key, 'value'], initVals[key]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function clearBeforeSave(data) {
|
function clearBeforeSave(data) {
|
||||||
for (const mailerKey in mailerTypes) {
|
for (const mailerKey in mailerTypes) {
|
||||||
const initVals = mailerTypes[mailerKey].initData();
|
const initVals = mailerTypes[mailerKey].initData();
|
||||||
for (const fieldKey in initVals) {
|
for (const fieldKey in initVals) {
|
||||||
delete data[fieldKey];
|
delete data[fieldKey];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function validateNumber(state, field, label, emptyAllowed = false) {
|
function validateNumber(state, field, label, emptyAllowed = false) {
|
||||||
const value = state.getIn([field, 'value']);
|
const value = state.getIn([field, 'value']);
|
||||||
if (typeof value === 'string' && value.trim() === '' && !emptyAllowed) { // After load, the numerical values can be still numbers
|
if (typeof value === 'string' && value.trim() === '' && !emptyAllowed) { // After load, the numerical values can be still numbers
|
||||||
state.setIn([field, 'error'], t('labelMustNotBeEmpty', {label}));
|
state.setIn([field, 'error'], t('labelMustNotBeEmpty', {label}));
|
||||||
} else if (isNaN(value)) {
|
} else if (isNaN(value)) {
|
||||||
state.setIn([field, 'error'], t('labelMustBeANumber', {label}));
|
state.setIn([field, 'error'], t('labelMustBeANumber', {label}));
|
||||||
} else {
|
} else {
|
||||||
state.setIn([field, 'error'], null);
|
state.setIn([field, 'error'], null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function getInitCommon() {
|
function getInitCommon() {
|
||||||
return {
|
return {
|
||||||
maxConnections: '5',
|
maxConnections: '5',
|
||||||
throttling: '',
|
throttling: '',
|
||||||
logTransactions: false
|
logTransactions: false
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function getInitGenericSMTP() {
|
function getInitGenericSMTP() {
|
||||||
return {
|
return {
|
||||||
...getInitCommon(),
|
...getInitCommon(),
|
||||||
smtpHostname: '',
|
smtpHostname: '',
|
||||||
smtpPort: '',
|
smtpPort: '',
|
||||||
smtpEncryption: 'NONE',
|
smtpEncryption: 'NONE',
|
||||||
smtpUseAuth: false,
|
smtpUseAuth: false,
|
||||||
smtpUser: '',
|
smtpUser: '',
|
||||||
smtpPassword: '',
|
smtpPassword: '',
|
||||||
smtpAllowSelfSigned: false,
|
smtpAllowSelfSigned: false,
|
||||||
smtpMaxMessages: '100'
|
smtpMaxMessages: '100'
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function afterLoadCommon(data) {
|
function afterLoadCommon(data) {
|
||||||
data.maxConnections = data.mailer_settings.maxConnections;
|
data.maxConnections = data.mailer_settings.maxConnections;
|
||||||
data.throttling = data.mailer_settings.throttling || '';
|
data.throttling = data.mailer_settings.throttling || '';
|
||||||
data.logTransactions = data.mailer_settings.logTransactions;
|
data.logTransactions = data.mailer_settings.logTransactions;
|
||||||
}
|
}
|
||||||
|
|
||||||
function afterLoadGenericSMTP(data) {
|
function afterLoadGenericSMTP(data) {
|
||||||
afterLoadCommon(data);
|
afterLoadCommon(data);
|
||||||
data.smtpHostname = data.mailer_settings.hostname;
|
data.smtpHostname = data.mailer_settings.hostname;
|
||||||
data.smtpPort = data.mailer_settings.port || '';
|
data.smtpPort = data.mailer_settings.port || '';
|
||||||
data.smtpEncryption = data.mailer_settings.encryption;
|
data.smtpEncryption = data.mailer_settings.encryption;
|
||||||
data.smtpUseAuth = data.mailer_settings.useAuth;
|
data.smtpUseAuth = data.mailer_settings.useAuth;
|
||||||
data.smtpUser = data.mailer_settings.user;
|
data.smtpUser = data.mailer_settings.user;
|
||||||
data.smtpPassword = data.mailer_settings.password;
|
data.smtpPassword = data.mailer_settings.password;
|
||||||
data.smtpAllowSelfSigned = data.mailer_settings.allowSelfSigned;
|
data.smtpAllowSelfSigned = data.mailer_settings.allowSelfSigned;
|
||||||
data.smtpMaxMessages = data.mailer_settings.maxMessages;
|
data.smtpMaxMessages = data.mailer_settings.maxMessages;
|
||||||
}
|
}
|
||||||
|
|
||||||
function beforeSaveCommon(data) {
|
function beforeSaveCommon(data) {
|
||||||
data.mailer_settings = {};
|
data.mailer_settings = {};
|
||||||
data.mailer_settings.maxConnections = Number(data.maxConnections);
|
data.mailer_settings.maxConnections = Number(data.maxConnections);
|
||||||
data.mailer_settings.throttling = Number(data.throttling);
|
data.mailer_settings.throttling = Number(data.throttling);
|
||||||
data.mailer_settings.logTransactions = data.logTransactions;
|
data.mailer_settings.logTransactions = data.logTransactions;
|
||||||
}
|
}
|
||||||
|
|
||||||
function beforeSaveGenericSMTP(data, builtin = false) {
|
function beforeSaveGenericSMTP(data, builtin = false) {
|
||||||
beforeSaveCommon(data);
|
beforeSaveCommon(data);
|
||||||
|
|
||||||
if (!builtin) {
|
if (!builtin) {
|
||||||
data.mailer_settings.hostname = data.smtpHostname;
|
data.mailer_settings.hostname = data.smtpHostname;
|
||||||
data.mailer_settings.port = Number(data.smtpPort);
|
data.mailer_settings.port = Number(data.smtpPort);
|
||||||
data.mailer_settings.encryption = data.smtpEncryption;
|
data.mailer_settings.encryption = data.smtpEncryption;
|
||||||
data.mailer_settings.useAuth = data.smtpUseAuth;
|
data.mailer_settings.useAuth = data.smtpUseAuth;
|
||||||
data.mailer_settings.user = data.smtpUser;
|
data.mailer_settings.user = data.smtpUser;
|
||||||
data.mailer_settings.password = data.smtpPassword;
|
data.mailer_settings.password = data.smtpPassword;
|
||||||
data.mailer_settings.allowSelfSigned = data.smtpAllowSelfSigned;
|
data.mailer_settings.allowSelfSigned = data.smtpAllowSelfSigned;
|
||||||
}
|
}
|
||||||
|
|
||||||
data.mailer_settings.maxMessages = Number(data.smtpMaxMessages);
|
data.mailer_settings.maxMessages = Number(data.smtpMaxMessages);
|
||||||
}
|
}
|
||||||
|
|
||||||
function validateCommon(state) {
|
function validateCommon(state) {
|
||||||
validateNumber(state, 'maxConnections', 'Max connections');
|
validateNumber(state, 'maxConnections', 'Max connections');
|
||||||
validateNumber(state, 'throttling', 'Throttling', true);
|
validateNumber(state, 'throttling', 'Throttling', true);
|
||||||
}
|
}
|
||||||
|
|
||||||
function validateGenericSMTP(state) {
|
function validateGenericSMTP(state) {
|
||||||
validateCommon(state);
|
validateCommon(state);
|
||||||
validateNumber(state, 'smtpPort', 'Port', true);
|
validateNumber(state, 'smtpPort', 'Port', true);
|
||||||
validateNumber(state, 'smtpMaxMessages', 'Max messages');
|
validateNumber(state, 'smtpMaxMessages', 'Max messages');
|
||||||
}
|
}
|
||||||
|
|
||||||
const typeNames = {
|
const typeNames = {
|
||||||
[MailerType.GENERIC_SMTP]: t('genericSmtp'),
|
[MailerType.GENERIC_SMTP]: t('genericSmtp'),
|
||||||
[MailerType.ZONE_MTA]: t('zoneMta'),
|
[MailerType.ZONE_MTA]: t('zoneMta'),
|
||||||
[MailerType.AWS_SES]: t('amazonSes')
|
[MailerType.AWS_SES]: t('amazonSes')
|
||||||
};
|
};
|
||||||
|
|
||||||
const typeOptions = [
|
const typeOptions = [
|
||||||
{ key: MailerType.GENERIC_SMTP, label: typeNames[MailerType.GENERIC_SMTP]},
|
{ key: MailerType.GENERIC_SMTP, label: typeNames[MailerType.GENERIC_SMTP]},
|
||||||
{ key: MailerType.ZONE_MTA, label: typeNames[MailerType.ZONE_MTA]},
|
{ key: MailerType.ZONE_MTA, label: typeNames[MailerType.ZONE_MTA]},
|
||||||
{ key: MailerType.AWS_SES, label: typeNames[MailerType.AWS_SES]}
|
{ key: MailerType.AWS_SES, label: typeNames[MailerType.AWS_SES]}
|
||||||
];
|
];
|
||||||
|
|
||||||
const smtpEncryptionOptions = [
|
const smtpEncryptionOptions = [
|
||||||
{ key: 'NONE', label: t('doNotUseEncryption')},
|
{ key: 'NONE', label: t('doNotUseEncryption')},
|
||||||
{ key: 'TLS', label: t('useTls –UsuallySelectedForPort465')},
|
{ key: 'TLS', label: t('useTls –UsuallySelectedForPort465')},
|
||||||
{ key: 'STARTTLS', label: t('useStarttls –UsuallySelectedForPort587')}
|
{ key: 'STARTTLS', label: t('useStarttls –UsuallySelectedForPort587')}
|
||||||
];
|
];
|
||||||
|
|
||||||
const sesRegionOptions = [
|
const sesRegionOptions = [
|
||||||
{ key: 'us-east-1', label: t('useast1')},
|
{ key: 'us-east-1', label: t('useast1')},
|
||||||
{ key: 'us-west-2', label: t('uswest2')},
|
{ key: 'us-west-2', label: t('uswest2')},
|
||||||
{ key: 'eu-west-1', label: t('euwest1')}
|
{ key: 'eu-west-1', label: t('euwest1')}
|
||||||
];
|
];
|
||||||
|
|
||||||
const zoneMtaTypeOptions = [];
|
const zoneMtaTypeOptions = [];
|
||||||
|
|
||||||
if (mailtrainConfig.builtinZoneMTAEnabled) {
|
if (mailtrainConfig.builtinZoneMTAEnabled) {
|
||||||
zoneMtaTypeOptions.push({ key: ZoneMTAType.BUILTIN, label: t('builtinZoneMta')});
|
zoneMtaTypeOptions.push({ key: ZoneMTAType.BUILTIN, label: t('builtinZoneMta')});
|
||||||
}
|
}
|
||||||
zoneMtaTypeOptions.push({ key: ZoneMTAType.WITH_MAILTRAIN_HEADER_CONF, label: t('dynamicConfigurationOfDkimKeysViaZoneMt')});
|
zoneMtaTypeOptions.push({ key: ZoneMTAType.WITH_MAILTRAIN_HEADER_CONF, label: t('dynamicConfigurationOfDkimKeysViaZoneMt')});
|
||||||
zoneMtaTypeOptions.push({ key: ZoneMTAType.WITH_HTTP_CONF, label: t('dynamicConfigurationOfDkimKeysViaZoneMt-1')});
|
zoneMtaTypeOptions.push({ key: ZoneMTAType.WITH_HTTP_CONF, label: t('dynamicConfigurationOfDkimKeysViaZoneMt-1')});
|
||||||
zoneMtaTypeOptions.push({ key: ZoneMTAType.REGULAR, label: t('noDynamicConfigurationOfDkimKeys')});
|
zoneMtaTypeOptions.push({ key: ZoneMTAType.REGULAR, label: t('noDynamicConfigurationOfDkimKeys')});
|
||||||
|
|
||||||
mailerTypes[MailerType.GENERIC_SMTP] = {
|
mailerTypes[MailerType.GENERIC_SMTP] = {
|
||||||
typeName: typeNames[MailerType.GENERIC_SMTP],
|
typeName: typeNames[MailerType.GENERIC_SMTP],
|
||||||
getForm: owner =>
|
getForm: owner =>
|
||||||
<div>
|
<div>
|
||||||
<Fieldset label={t('mailerSettings')}>
|
<Fieldset label={t('mailerSettings')}>
|
||||||
<Dropdown id="mailer_type" label={t('mailerType')} options={typeOptions}/>
|
<Dropdown id="mailer_type" label={t('mailerType')} options={typeOptions}/>
|
||||||
<InputField id="smtpHostname" label={t('hostname')} placeholder={t('hostnameEgSmtpexamplecom')}/>
|
<InputField id="smtpHostname" label={t('hostname')} placeholder={t('hostnameEgSmtpexamplecom')}/>
|
||||||
<InputField id="smtpPort" label={t('port')} placeholder={t('portEg465AutodetectedIfLeftBlank')}/>
|
<InputField id="smtpPort" label={t('port')} placeholder={t('portEg465AutodetectedIfLeftBlank')}/>
|
||||||
<Dropdown id="smtpEncryption" label={t('encryption')} options={smtpEncryptionOptions}/>
|
<Dropdown id="smtpEncryption" label={t('encryption')} options={smtpEncryptionOptions}/>
|
||||||
<CheckBox id="smtpUseAuth" text={t('enableSmtpAuthentication')}/>
|
<CheckBox id="smtpUseAuth" text={t('enableSmtpAuthentication')}/>
|
||||||
{ owner.getFormValue('smtpUseAuth') &&
|
{ owner.getFormValue('smtpUseAuth') &&
|
||||||
<div>
|
<div>
|
||||||
<InputField id="smtpUser" label={t('username')} placeholder={t('usernameEgMyaccount@examplecom')}/>
|
<InputField id="smtpUser" label={t('username')} placeholder={t('usernameEgMyaccount@examplecom')}/>
|
||||||
<InputField id="smtpPassword" type="password" label={t('password')} placeholder={t('usernameEgMyaccount@examplecom')}/>
|
<InputField id="smtpPassword" type="password" label={t('password')} placeholder={t('usernameEgMyaccount@examplecom')}/>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
</Fieldset>
|
</Fieldset>
|
||||||
<Fieldset label={t('advancedMailerSettings')}>
|
<Fieldset label={t('advancedMailerSettings')}>
|
||||||
<CheckBox id="logTransactions" text={t('logSmtpTransactions')}/>
|
<CheckBox id="logTransactions" text={t('logSmtpTransactions')}/>
|
||||||
<CheckBox id="smtpAllowSelfSigned" text={t('allowSelfsignedCertificates')}/>
|
<CheckBox id="smtpAllowSelfSigned" text={t('allowSelfsignedCertificates')}/>
|
||||||
<InputField id="maxConnections" label={t('maxConnections')} placeholder={t('theCountOfMaxConnectionsEg10')} help={t('theCountOfMaximumSimultaneousConnections')}/>
|
<InputField id="maxConnections" label={t('maxConnections')} placeholder={t('theCountOfMaxConnectionsEg10')} help={t('theCountOfMaximumSimultaneousConnections')}/>
|
||||||
<InputField id="smtpMaxMessages" label={t('maxMessages')} placeholder={t('theCountOfMaxMessagesEg100')} help={t('theNumberOfMessagesToSendThroughASingle')}/>
|
<InputField id="smtpMaxMessages" label={t('maxMessages')} placeholder={t('theCountOfMaxMessagesEg100')} help={t('theNumberOfMessagesToSendThroughASingle')}/>
|
||||||
<InputField id="throttling" label={t('throttling')} placeholder={t('messagesPerHourEg1000')} help={t('maximumNumberOfMessagesToSendInAnHour')}/>
|
<InputField id="throttling" label={t('throttling')} placeholder={t('messagesPerHourEg1000')} help={t('maximumNumberOfMessagesToSendInAnHour')}/>
|
||||||
</Fieldset>
|
</Fieldset>
|
||||||
</div>,
|
</div>,
|
||||||
initData: () => ({
|
initData: () => ({
|
||||||
...getInitGenericSMTP()
|
...getInitGenericSMTP()
|
||||||
}),
|
}),
|
||||||
afterLoad: data => {
|
afterLoad: data => {
|
||||||
afterLoadGenericSMTP(data);
|
afterLoadGenericSMTP(data);
|
||||||
},
|
},
|
||||||
beforeSave: data => {
|
beforeSave: data => {
|
||||||
beforeSaveGenericSMTP(data);
|
beforeSaveGenericSMTP(data);
|
||||||
clearBeforeSave(data);
|
clearBeforeSave(data);
|
||||||
},
|
},
|
||||||
afterTypeChange: mutState => {
|
afterTypeChange: mutState => {
|
||||||
initFieldsIfMissing(mutState, MailerType.GENERIC_SMTP);
|
initFieldsIfMissing(mutState, MailerType.GENERIC_SMTP);
|
||||||
},
|
},
|
||||||
validate: state => {
|
validate: state => {
|
||||||
validateGenericSMTP(state);
|
validateGenericSMTP(state);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
mailerTypes[MailerType.ZONE_MTA] = {
|
mailerTypes[MailerType.ZONE_MTA] = {
|
||||||
typeName: typeNames[MailerType.ZONE_MTA],
|
typeName: typeNames[MailerType.ZONE_MTA],
|
||||||
getForm: owner => {
|
getForm: owner => {
|
||||||
const zoneMtaType = Number.parseInt(owner.getFormValue('zoneMtaType'));
|
const zoneMtaType = Number.parseInt(owner.getFormValue('zoneMtaType'));
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<Fieldset label={t('mailerSettings')}>
|
<Fieldset label={t('mailerSettings')}>
|
||||||
<Dropdown id="mailer_type" label={t('mailerType')} options={typeOptions}/>
|
<Dropdown id="mailer_type" label={t('mailerType')} options={typeOptions}/>
|
||||||
<Dropdown id="zoneMtaType" label={t('dynamicConfiguration')} options={zoneMtaTypeOptions}/>
|
<Dropdown id="zoneMtaType" label={t('dynamicConfiguration')} options={zoneMtaTypeOptions}/>
|
||||||
{(zoneMtaType === ZoneMTAType.REGULAR || zoneMtaType === ZoneMTAType.WITH_MAILTRAIN_HEADER_CONF || zoneMtaType === ZoneMTAType.WITH_HTTP_CONF) &&
|
{(zoneMtaType === ZoneMTAType.REGULAR || zoneMtaType === ZoneMTAType.WITH_MAILTRAIN_HEADER_CONF || zoneMtaType === ZoneMTAType.WITH_HTTP_CONF) &&
|
||||||
<div>
|
<div>
|
||||||
<InputField id="smtpHostname" label={t('hostname')} placeholder={t('hostnameEgSmtpexamplecom')}/>
|
<InputField id="smtpHostname" label={t('hostname')} placeholder={t('hostnameEgSmtpexamplecom')}/>
|
||||||
<InputField id="smtpPort" label={t('port')} placeholder={t('portEg465AutodetectedIfLeftBlank')}/>
|
<InputField id="smtpPort" label={t('port')} placeholder={t('portEg465AutodetectedIfLeftBlank')}/>
|
||||||
<Dropdown id="smtpEncryption" label={t('encryption')} options={smtpEncryptionOptions}/>
|
<Dropdown id="smtpEncryption" label={t('encryption')} options={smtpEncryptionOptions}/>
|
||||||
<CheckBox id="smtpUseAuth" text={t('enableSmtpAuthentication')}/>
|
<CheckBox id="smtpUseAuth" text={t('enableSmtpAuthentication')}/>
|
||||||
{ owner.getFormValue('smtpUseAuth') &&
|
{ owner.getFormValue('smtpUseAuth') &&
|
||||||
<div>
|
<div>
|
||||||
<InputField id="smtpUser" label={t('username')} placeholder={t('usernameEgMyaccount@examplecom')}/>
|
<InputField id="smtpUser" label={t('username')} placeholder={t('usernameEgMyaccount@examplecom')}/>
|
||||||
<InputField id="smtpPassword" type="password" label={t('password')} placeholder={t('usernameEgMyaccount@examplecom')}/>
|
<InputField id="smtpPassword" type="password" label={t('password')} placeholder={t('usernameEgMyaccount@examplecom')}/>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
</Fieldset>
|
</Fieldset>
|
||||||
{(zoneMtaType === ZoneMTAType.BUILTIN || zoneMtaType === ZoneMTAType.WITH_MAILTRAIN_HEADER_CONF || zoneMtaType === ZoneMTAType.WITH_HTTP_CONF) &&
|
{(zoneMtaType === ZoneMTAType.BUILTIN || zoneMtaType === ZoneMTAType.WITH_MAILTRAIN_HEADER_CONF || zoneMtaType === ZoneMTAType.WITH_HTTP_CONF) &&
|
||||||
<Fieldset label={t('dkimSigning')}>
|
<Fieldset label={t('dkimSigning')}>
|
||||||
<Trans i18nKey="ifYouAreUsingZoneMtaThenMailtrainCan"><p>If you are using ZoneMTA then Mailtrain can provide a DKIM key for signing all outgoing messages.</p></Trans>
|
<Trans i18nKey="ifYouAreUsingZoneMtaThenMailtrainCan"><p>If you are using ZoneMTA then Mailtrain can provide a DKIM key for signing all outgoing messages.</p></Trans>
|
||||||
<Trans i18nKey="doNotUseSensitiveKeysHereThePrivateKeyIs"><p className="text-warning">Do not use sensitive keys here. The private key is not encrypted in the database.</p></Trans>
|
<Trans i18nKey="doNotUseSensitiveKeysHereThePrivateKeyIs"><p className="text-warning">Do not use sensitive keys here. The private key is not encrypted in the database.</p></Trans>
|
||||||
{zoneMtaType === ZoneMTAType.WITH_HTTP_CONF &&
|
{zoneMtaType === ZoneMTAType.WITH_HTTP_CONF &&
|
||||||
<InputField id="dkimApiKey" label={t('zoneMtaDkimApiKey')} help={t('secretValueKnownToZoneMtaForRequesting')}/>
|
<InputField id="dkimApiKey" label={t('zoneMtaDkimApiKey')} help={t('secretValueKnownToZoneMtaForRequesting')}/>
|
||||||
}
|
}
|
||||||
<InputField id="dkimDomain" label={t('dkimDomain')} help={t('leaveBlankToUseTheSenderEmailAddress')}/>
|
<InputField id="dkimDomain" label={t('dkimDomain')} help={t('leaveBlankToUseTheSenderEmailAddress')}/>
|
||||||
<InputField id="dkimSelector" label={t('dkimKeySelector')} help={t('signingIsDisabledWithoutAValidSelector')}/>
|
<InputField id="dkimSelector" label={t('dkimKeySelector')} help={t('signingIsDisabledWithoutAValidSelector')}/>
|
||||||
<TextArea id="dkimPrivateKey" className={styles.dkimPrivateKey} label={t('dkimPrivateKey')} placeholder={t('beginsWithBeginRsaPrivateKey')} help={t('signingIsDisabledWithoutAValidPrivateKey')}/>
|
<TextArea id="dkimPrivateKey" className={styles.dkimPrivateKey} label={t('dkimPrivateKey')} placeholder={t('beginsWithBeginRsaPrivateKey')} help={t('signingIsDisabledWithoutAValidPrivateKey')}/>
|
||||||
</Fieldset>
|
</Fieldset>
|
||||||
}
|
}
|
||||||
<Fieldset label={t('advancedMailerSettings')}>
|
<Fieldset label={t('advancedMailerSettings')}>
|
||||||
<CheckBox id="logTransactions" text={t('logSmtpTransactions')}/>
|
<CheckBox id="logTransactions" text={t('logSmtpTransactions')}/>
|
||||||
<CheckBox id="smtpAllowSelfSigned" text={t('allowSelfsignedCertificates')}/>
|
<CheckBox id="smtpAllowSelfSigned" text={t('allowSelfsignedCertificates')}/>
|
||||||
<InputField id="maxConnections" label={t('maxConnections')} placeholder={t('theCountOfMaxConnectionsEg10')} help={t('theCountOfMaximumSimultaneousConnections')}/>
|
<InputField id="maxConnections" label={t('maxConnections')} placeholder={t('theCountOfMaxConnectionsEg10')} help={t('theCountOfMaximumSimultaneousConnections')}/>
|
||||||
<InputField id="smtpMaxMessages" label={t('maxMessages')} placeholder={t('theCountOfMaxMessagesEg100')} help={t('theNumberOfMessagesToSendThroughASingle')}/>
|
<InputField id="smtpMaxMessages" label={t('maxMessages')} placeholder={t('theCountOfMaxMessagesEg100')} help={t('theNumberOfMessagesToSendThroughASingle')}/>
|
||||||
<InputField id="throttling" label={t('throttling')} placeholder={t('messagesPerHourEg1000')} help={t('maximumNumberOfMessagesToSendInAnHour')}/>
|
<InputField id="throttling" label={t('throttling')} placeholder={t('messagesPerHourEg1000')} help={t('maximumNumberOfMessagesToSendInAnHour')}/>
|
||||||
</Fieldset>
|
</Fieldset>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
initData: () => ({
|
initData: () => ({
|
||||||
...getInitGenericSMTP(),
|
...getInitGenericSMTP(),
|
||||||
zoneMtaType: mailtrainConfig.builtinZoneMTAEnabled ? ZoneMTAType.BUILTIN : ZoneMTAType.REGULAR,
|
zoneMtaType: mailtrainConfig.builtinZoneMTAEnabled ? ZoneMTAType.BUILTIN : ZoneMTAType.REGULAR,
|
||||||
dkimApiKey: '',
|
dkimApiKey: '',
|
||||||
dkimDomain: '',
|
dkimDomain: '',
|
||||||
dkimSelector: '',
|
dkimSelector: '',
|
||||||
dkimPrivateKey: ''
|
dkimPrivateKey: ''
|
||||||
}),
|
}),
|
||||||
afterLoad: data => {
|
afterLoad: data => {
|
||||||
afterLoadGenericSMTP(data);
|
afterLoadGenericSMTP(data);
|
||||||
data.zoneMtaType = data.mailer_settings.zoneMtaType;
|
data.zoneMtaType = data.mailer_settings.zoneMtaType;
|
||||||
data.dkimApiKey = data.mailer_settings.dkimApiKey;
|
data.dkimApiKey = data.mailer_settings.dkimApiKey;
|
||||||
data.dkimDomain = data.mailer_settings.dkimDomain;
|
data.dkimDomain = data.mailer_settings.dkimDomain;
|
||||||
data.dkimSelector = data.mailer_settings.dkimSelector;
|
data.dkimSelector = data.mailer_settings.dkimSelector;
|
||||||
data.dkimPrivateKey = data.mailer_settings.dkimPrivateKey;
|
data.dkimPrivateKey = data.mailer_settings.dkimPrivateKey;
|
||||||
},
|
},
|
||||||
beforeSave: data => {
|
beforeSave: data => {
|
||||||
const zoneMtaType = Number.parseInt(data.zoneMtaType);
|
const zoneMtaType = Number.parseInt(data.zoneMtaType);
|
||||||
|
|
||||||
beforeSaveGenericSMTP(data, zoneMtaType === ZoneMTAType.BUILTIN);
|
beforeSaveGenericSMTP(data, zoneMtaType === ZoneMTAType.BUILTIN);
|
||||||
|
|
||||||
data.mailer_settings.zoneMtaType = zoneMtaType;
|
data.mailer_settings.zoneMtaType = zoneMtaType;
|
||||||
if (zoneMtaType === ZoneMTAType.BUILTIN || zoneMtaType === ZoneMTAType.WITH_HTTP_CONF || zoneMtaType === ZoneMTAType.WITH_MAILTRAIN_HEADER_CONF) {
|
if (zoneMtaType === ZoneMTAType.BUILTIN || zoneMtaType === ZoneMTAType.WITH_HTTP_CONF || zoneMtaType === ZoneMTAType.WITH_MAILTRAIN_HEADER_CONF) {
|
||||||
data.mailer_settings.dkimDomain = data.dkimDomain;
|
data.mailer_settings.dkimDomain = data.dkimDomain;
|
||||||
data.mailer_settings.dkimSelector = data.dkimSelector;
|
data.mailer_settings.dkimSelector = data.dkimSelector;
|
||||||
data.mailer_settings.dkimPrivateKey = data.dkimPrivateKey;
|
data.mailer_settings.dkimPrivateKey = data.dkimPrivateKey;
|
||||||
}
|
}
|
||||||
if (zoneMtaType === ZoneMTAType.WITH_HTTP_CONF) {
|
if (zoneMtaType === ZoneMTAType.WITH_HTTP_CONF) {
|
||||||
data.mailer_settings.dkimApiKey = data.dkimApiKey;
|
data.mailer_settings.dkimApiKey = data.dkimApiKey;
|
||||||
}
|
}
|
||||||
|
|
||||||
clearBeforeSave(data);
|
clearBeforeSave(data);
|
||||||
},
|
},
|
||||||
afterTypeChange: mutState => {
|
afterTypeChange: mutState => {
|
||||||
initFieldsIfMissing(mutState, MailerType.ZONE_MTA);
|
initFieldsIfMissing(mutState, MailerType.ZONE_MTA);
|
||||||
},
|
},
|
||||||
validate: state => {
|
validate: state => {
|
||||||
validateGenericSMTP(state);
|
validateGenericSMTP(state);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
mailerTypes[MailerType.AWS_SES] = {
|
mailerTypes[MailerType.AWS_SES] = {
|
||||||
typeName: typeNames[MailerType.AWS_SES],
|
typeName: typeNames[MailerType.AWS_SES],
|
||||||
getForm: owner =>
|
getForm: owner =>
|
||||||
<div>
|
<div>
|
||||||
<Fieldset label={t('mailerSettings')}>
|
<Fieldset label={t('mailerSettings')}>
|
||||||
<Dropdown id="mailer_type" label={t('mailerType')} options={typeOptions}/>
|
<Dropdown id="mailer_type" label={t('mailerType')} options={typeOptions}/>
|
||||||
<InputField id="sesKey" label={t('accessKey')} placeholder={t('awsAccessKeyId')}/>
|
<InputField id="sesKey" label={t('accessKey')} placeholder={t('awsAccessKeyId')}/>
|
||||||
<InputField id="sesSecret" label={t('port')} placeholder={t('awsSecretAccessKey')}/>
|
<InputField id="sesSecret" label={t('port')} placeholder={t('awsSecretAccessKey')}/>
|
||||||
<Dropdown id="sesRegion" label={t('region')} options={sesRegionOptions}/>
|
<Dropdown id="sesRegion" label={t('region')} options={sesRegionOptions}/>
|
||||||
</Fieldset>
|
</Fieldset>
|
||||||
<Fieldset label={t('advancedMailerSettings')}>
|
<Fieldset label={t('advancedMailerSettings')}>
|
||||||
<CheckBox id="logTransactions" text={t('logSmtpTransactions')}/>
|
<CheckBox id="logTransactions" text={t('logSmtpTransactions')}/>
|
||||||
<InputField id="maxConnections" label={t('maxConnections')} placeholder={t('theCountOfMaxConnectionsEg10')} help={t('theCountOfMaximumSimultaneousConnections')}/>
|
<InputField id="maxConnections" label={t('maxConnections')} placeholder={t('theCountOfMaxConnectionsEg10')} help={t('theCountOfMaximumSimultaneousConnections')}/>
|
||||||
<InputField id="throttling" label={t('throttling')} placeholder={t('messagesPerHourEg1000')} help={t('maximumNumberOfMessagesToSendInAnHour')}/>
|
<InputField id="throttling" label={t('throttling')} placeholder={t('messagesPerHourEg1000')} help={t('maximumNumberOfMessagesToSendInAnHour')}/>
|
||||||
</Fieldset>
|
</Fieldset>
|
||||||
</div>,
|
</div>,
|
||||||
initData: () => ({
|
initData: () => ({
|
||||||
...getInitCommon(),
|
...getInitCommon(),
|
||||||
sesKey: '',
|
sesKey: '',
|
||||||
sesSecret: '',
|
sesSecret: '',
|
||||||
sesRegion: ''
|
sesRegion: ''
|
||||||
}),
|
}),
|
||||||
afterLoad: data => {
|
afterLoad: data => {
|
||||||
afterLoadCommon(data);
|
afterLoadCommon(data);
|
||||||
data.sesKey = data.mailer_settings.key;
|
data.sesKey = data.mailer_settings.key;
|
||||||
data.sesSecret = data.mailer_settings.secret;
|
data.sesSecret = data.mailer_settings.secret;
|
||||||
data.sesRegion = data.mailer_settings.region;
|
data.sesRegion = data.mailer_settings.region;
|
||||||
},
|
},
|
||||||
beforeSave: data => {
|
beforeSave: data => {
|
||||||
beforeSaveCommon(data);
|
beforeSaveCommon(data);
|
||||||
data.mailer_settings.key = data.sesKey;
|
data.mailer_settings.key = data.sesKey;
|
||||||
data.mailer_settings.secret = data.sesSecret;
|
data.mailer_settings.secret = data.sesSecret;
|
||||||
data.mailer_settings.region = data.sesRegion;
|
data.mailer_settings.region = data.sesRegion;
|
||||||
clearBeforeSave(data);
|
clearBeforeSave(data);
|
||||||
},
|
},
|
||||||
afterTypeChange: mutState => {
|
afterTypeChange: mutState => {
|
||||||
initFieldsIfMissing(mutState, MailerType.AWS_SES);
|
initFieldsIfMissing(mutState, MailerType.AWS_SES);
|
||||||
},
|
},
|
||||||
validate: state => {
|
validate: state => {
|
||||||
validateCommon(state);
|
validateCommon(state);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
return mailerTypes;
|
return mailerTypes;
|
||||||
}
|
}
|
||||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -1,51 +1,51 @@
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import {ACEEditor} from "../../lib/form";
|
import {ACEEditor} from "../../lib/form";
|
||||||
import 'brace/mode/html'
|
import 'brace/mode/html'
|
||||||
import 'brace/mode/xml'
|
import 'brace/mode/xml'
|
||||||
|
|
||||||
export function getTemplateTypesOrder() {
|
export function getTemplateTypesOrder() {
|
||||||
return [/* 'mjml' , */ 'html'];
|
return [/* 'mjml' , */ 'html'];
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getTemplateTypes(t) {
|
export function getTemplateTypes(t) {
|
||||||
const templateTypes = {};
|
const templateTypes = {};
|
||||||
|
|
||||||
function clearBeforeSend(data) {
|
function clearBeforeSend(data) {
|
||||||
delete data.html;
|
delete data.html;
|
||||||
delete data.mjml;
|
delete data.mjml;
|
||||||
}
|
}
|
||||||
|
|
||||||
templateTypes.html = {
|
templateTypes.html = {
|
||||||
typeName: t('html'),
|
typeName: t('html'),
|
||||||
getForm: owner => <ACEEditor id="html" height="700px" mode="html" label={t('templateContent')}/>,
|
getForm: owner => <ACEEditor id="html" height="700px" mode="html" label={t('templateContent')}/>,
|
||||||
afterLoad: data => {
|
afterLoad: data => {
|
||||||
data.html = data.data.html;
|
data.html = data.data.html;
|
||||||
},
|
},
|
||||||
beforeSave: (data) => {
|
beforeSave: (data) => {
|
||||||
data.data = {
|
data.data = {
|
||||||
html: data.html
|
html: data.html
|
||||||
};
|
};
|
||||||
|
|
||||||
clearBeforeSend(data);
|
clearBeforeSend(data);
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
templateTypes.mjml = {
|
templateTypes.mjml = {
|
||||||
typeName: t('mjml'),
|
typeName: t('mjml'),
|
||||||
getForm: owner => <ACEEditor id="html" height="700px" mode="xml" label={t('templateContent')}/>,
|
getForm: owner => <ACEEditor id="html" height="700px" mode="xml" label={t('templateContent')}/>,
|
||||||
afterLoad: data => {
|
afterLoad: data => {
|
||||||
data.mjml = data.data.mjml;
|
data.mjml = data.data.mjml;
|
||||||
},
|
},
|
||||||
beforeSave: (data) => {
|
beforeSave: (data) => {
|
||||||
data.data = {
|
data.data = {
|
||||||
mjml: data.mjml
|
mjml: data.mjml
|
||||||
};
|
};
|
||||||
|
|
||||||
clearBeforeSend(data);
|
clearBeforeSend(data);
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
return templateTypes;
|
return templateTypes;
|
||||||
}
|
}
|
|
@ -1,2 +1,2 @@
|
||||||
The file icons-rtl.gif is assumed by skin-common.less. However skin-bootstrap does not come with icons.
|
The file icons-rtl.gif is assumed by skin-common.less. However skin-bootstrap does not come with icons.
|
||||||
To avoid errors with webpack an empty file has been provided.
|
To avoid errors with webpack an empty file has been provided.
|
4
client/static/mosaico/uploads/.gitignore
vendored
4
client/static/mosaico/uploads/.gitignore
vendored
|
@ -1,3 +1,3 @@
|
||||||
*
|
*
|
||||||
!.gitignore
|
!.gitignore
|
||||||
!README.md
|
!README.md
|
|
@ -1,7 +1,7 @@
|
||||||
@font-face {
|
@font-face {
|
||||||
font-family: 'Noto Sans';
|
font-family: 'Noto Sans';
|
||||||
font-style: normal;
|
font-style: normal;
|
||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
src: url('./noto-sans-400-normal.eot');
|
src: url('./noto-sans-400-normal.eot');
|
||||||
src: local('Noto Sans'), local('NotoSans'), url('./noto-sans-400-normal.eot#iefix') format('embedded-opentype'), url('./noto-sans-400-normal.woff') format('woff'), url('./noto-sans-400-normal.ttf') format('truetype');
|
src: local('Noto Sans'), local('NotoSans'), url('./noto-sans-400-normal.eot#iefix') format('embedded-opentype'), url('./noto-sans-400-normal.woff') format('woff'), url('./noto-sans-400-normal.ttf') format('truetype');
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,141 +1,141 @@
|
||||||
## Access Control
|
## Access Control
|
||||||
|
|
||||||
This document describes the key features and concepts of the current state of
|
This document describes the key features and concepts of the current state of
|
||||||
access control in Mailtrain.
|
access control in Mailtrain.
|
||||||
|
|
||||||
The current state provides user management and granular access control to reports
|
The current state provides user management and granular access control to reports
|
||||||
and report templates. The user management supports both local authentication and
|
and report templates. The user management supports both local authentication and
|
||||||
LDAP-based authentication.
|
LDAP-based authentication.
|
||||||
|
|
||||||
The access control has two abstractions levels: a high-level intended to be used through web UI,
|
The access control has two abstractions levels: a high-level intended to be used through web UI,
|
||||||
and low-level, intended to be configured once through the Mailtrain config file. The high-level
|
and low-level, intended to be configured once through the Mailtrain config file. The high-level
|
||||||
layer serves for providing access to variuous resources, while the low-level layer is meant
|
layer serves for providing access to variuous resources, while the low-level layer is meant
|
||||||
to define the access roles in Mailtrain to reflect an organisational or process hierarchy.
|
to define the access roles in Mailtrain to reflect an organisational or process hierarchy.
|
||||||
|
|
||||||
### High-level access management (through web UI)
|
### High-level access management (through web UI)
|
||||||
|
|
||||||
On the high abstraction level, which is accessible to users via the web-based UI, Mailtrain
|
On the high abstraction level, which is accessible to users via the web-based UI, Mailtrain
|
||||||
recognizes different entities (reports, report templates, etc.) and user roles that regulate
|
recognizes different entities (reports, report templates, etc.) and user roles that regulate
|
||||||
access to these entities (e.g. role "reporter" that allows viewing a report but prevents editing
|
access to these entities (e.g. role "reporter" that allows viewing a report but prevents editing
|
||||||
or deleting it). Access to entities is provided through so called "shares". A share is essentially
|
or deleting it). Access to entities is provided through so called "shares". A share is essentially
|
||||||
a triple: entity - role - user.
|
a triple: entity - role - user.
|
||||||
|
|
||||||
Mailtrain further features hierarchical namespaces. Every entity has to reside in a namespace
|
Mailtrain further features hierarchical namespaces. Every entity has to reside in a namespace
|
||||||
(in reality, the namespace itself is an entity to which access can be given).
|
(in reality, the namespace itself is an entity to which access can be given).
|
||||||
|
|
||||||
While sharing an entity with a user gives the user access to the particular entity (in the
|
While sharing an entity with a user gives the user access to the particular entity (in the
|
||||||
scope of the role), sharing a namespace amounts to giving access to all entities within
|
scope of the role), sharing a namespace amounts to giving access to all entities within
|
||||||
the namespace and transitively in all child namespaces. The role that regulates the access to the
|
the namespace and transitively in all child namespaces. The role that regulates the access to the
|
||||||
particular namespaces further determines the access to all different entity types that can
|
particular namespaces further determines the access to all different entity types that can
|
||||||
reside in the namespace.
|
reside in the namespace.
|
||||||
|
|
||||||
To simplify the management of permissions, every user is associated with one global role and
|
To simplify the management of permissions, every user is associated with one global role and
|
||||||
a namespace. The global role regulates access to global resources and operations (i.e. those
|
a namespace. The global role regulates access to global resources and operations (i.e. those
|
||||||
things that are not associated with any namespace). An example of such a global operation is
|
things that are not associated with any namespace). An example of such a global operation is
|
||||||
rebuilding the permission cache. Further, the global role determines a default share of the
|
rebuilding the permission cache. Further, the global role determines a default share of the
|
||||||
root namespace and the namespace of the user. For example, an administrator's global role may
|
root namespace and the namespace of the user. For example, an administrator's global role may
|
||||||
specify that a user get administrator's role in the root namespace, which effectively gives
|
specify that a user get administrator's role in the root namespace, which effectively gives
|
||||||
him/her access to everything.
|
him/her access to everything.
|
||||||
|
|
||||||
Mailtrain resets these default shares at start and also whenever permission cache is rebuilt
|
Mailtrain resets these default shares at start and also whenever permission cache is rebuilt
|
||||||
(essentially every time user, namespace or some entity is created or when share or user's
|
(essentially every time user, namespace or some entity is created or when share or user's
|
||||||
role is assigned). This effectively prevents deleting or overriding the default shares that
|
role is assigned). This effectively prevents deleting or overriding the default shares that
|
||||||
the user has through the global role.
|
the user has through the global role.
|
||||||
|
|
||||||
|
|
||||||
### Low-level access management (through config file)
|
### Low-level access management (through config file)
|
||||||
|
|
||||||
Internally, Mailtrain relies on fine-grained permissions, which are triplets:
|
Internally, Mailtrain relies on fine-grained permissions, which are triplets:
|
||||||
user - operation - entity (e.g. user id 1 - view - report id 2). These permissions are stored
|
user - operation - entity (e.g. user id 1 - view - report id 2). These permissions are stored
|
||||||
in a permission cache (in DB) and automatically generated at startup and whenever the permissions
|
in a permission cache (in DB) and automatically generated at startup and whenever the permissions
|
||||||
could have changed.
|
could have changed.
|
||||||
|
|
||||||
Mailtrain's config file defines the roles (available in the high-level access management) and
|
Mailtrain's config file defines the roles (available in the high-level access management) and
|
||||||
specifies the mapping of roles to operations.
|
specifies the mapping of roles to operations.
|
||||||
|
|
||||||
The roles are potentially different for each entity type/scope (currently global, namespace, report,
|
The roles are potentially different for each entity type/scope (currently global, namespace, report,
|
||||||
report template). Each role defines the permitted operations for the given entity type/scope.
|
report template). Each role defines the permitted operations for the given entity type/scope.
|
||||||
A namespace role further defines allowed operations for entity types within and under the
|
A namespace role further defines allowed operations for entity types within and under the
|
||||||
namespace.
|
namespace.
|
||||||
|
|
||||||
The following defines the role master for scope "global". This effectively means that in
|
The following defines the role master for scope "global". This effectively means that in
|
||||||
"Create/Edit User" form, the user can be given role "Master".
|
"Create/Edit User" form, the user can be given role "Master".
|
||||||
The role gives the permission to rebuild the permission cache.
|
The role gives the permission to rebuild the permission cache.
|
||||||
|
|
||||||
Further, it specifies that the
|
Further, it specifies that the
|
||||||
holder of the role will automatically be given access (share) to the root namespace in the
|
holder of the role will automatically be given access (share) to the root namespace in the
|
||||||
namespace role "master" (specified by ```rootNamespaceRole="master"```). This access to the root namespace is given irrespective of the namespace
|
namespace role "master" (specified by ```rootNamespaceRole="master"```). This access to the root namespace is given irrespective of the namespace
|
||||||
in which the user is created. This highlight the dual purpose of namespaces: a) they group
|
in which the user is created. This highlight the dual purpose of namespaces: a) they group
|
||||||
entities w.r.t. access management, b) they allow categorizing entities and users in a hierarchy
|
entities w.r.t. access management, b) they allow categorizing entities and users in a hierarchy
|
||||||
to potentially reflect the organisational or process hierarchy. The latter is especially useful for
|
to potentially reflect the organisational or process hierarchy. The latter is especially useful for
|
||||||
more enterprise applications where a single installation of Mailtrain serves a number of rather
|
more enterprise applications where a single installation of Mailtrain serves a number of rather
|
||||||
independent groups.
|
independent groups.
|
||||||
|
|
||||||
The global role defined below is also an admin role (denoted by the ```admin=true```), which means that user id 1 will always be reset to this role.
|
The global role defined below is also an admin role (denoted by the ```admin=true```), which means that user id 1 will always be reset to this role.
|
||||||
This serves as a kind of bootstrap that makes sure that there is always a user that can be
|
This serves as a kind of bootstrap that makes sure that there is always a user that can be
|
||||||
used to give access to other users.
|
used to give access to other users.
|
||||||
```
|
```
|
||||||
[roles.global.master]
|
[roles.global.master]
|
||||||
name="Master"
|
name="Master"
|
||||||
admin=true
|
admin=true
|
||||||
description="All permissions"
|
description="All permissions"
|
||||||
permissions=["rebuildPermissions"]
|
permissions=["rebuildPermissions"]
|
||||||
rootNamespaceRole="master"
|
rootNamespaceRole="master"
|
||||||
```
|
```
|
||||||
|
|
||||||
Another example for a global role is the following. This one is intended for regular users.
|
Another example for a global role is the following. This one is intended for regular users.
|
||||||
As such, it does not automatically give access to everything. Rather, it gives limited access
|
As such, it does not automatically give access to everything. Rather, it gives limited access
|
||||||
to entities under the namespace in which the user has been created. This is specified by the
|
to entities under the namespace in which the user has been created. This is specified by the
|
||||||
```ownNamespaceRole="editor"```
|
```ownNamespaceRole="editor"```
|
||||||
```
|
```
|
||||||
[roles.global.editor]
|
[roles.global.editor]
|
||||||
name="Editor"
|
name="Editor"
|
||||||
description="Anything under own namespace except operations related to sending and doing reports"
|
description="Anything under own namespace except operations related to sending and doing reports"
|
||||||
permissions=[]
|
permissions=[]
|
||||||
ownNamespaceRole="editor"
|
ownNamespaceRole="editor"
|
||||||
```
|
```
|
||||||
|
|
||||||
The roles for entities are defined in a similar fashion. The example below shows the definition
|
The roles for entities are defined in a similar fashion. The example below shows the definition
|
||||||
of the role "master" for "report" entities. It lists the operations that a user
|
of the role "master" for "report" entities. It lists the operations that a user
|
||||||
that has "master" access to a particular report can do with the report. Note that to get the
|
that has "master" access to a particular report can do with the report. Note that to get the
|
||||||
"master" access to a particular report through this role, the report would either have to be shared with the user
|
"master" access to a particular report through this role, the report would either have to be shared with the user
|
||||||
with role "master".
|
with role "master".
|
||||||
```
|
```
|
||||||
[roles.report.master]
|
[roles.report.master]
|
||||||
name="Master"
|
name="Master"
|
||||||
description="All permissions"
|
description="All permissions"
|
||||||
permissions=["view", "edit", "delete", "share", "execute", "viewContent", "viewOutput"]
|
permissions=["view", "edit", "delete", "share", "execute", "viewContent", "viewOutput"]
|
||||||
```
|
```
|
||||||
|
|
||||||
The same for the restricted role "editor" can look as follows.
|
The same for the restricted role "editor" can look as follows.
|
||||||
```
|
```
|
||||||
[roles.report.editor]
|
[roles.report.editor]
|
||||||
name="Editor"
|
name="Editor"
|
||||||
description="Anything under own namespace except operations related to sending and doing reports"
|
description="Anything under own namespace except operations related to sending and doing reports"
|
||||||
permissions=["view", "viewContent", "viewOutput"]
|
permissions=["view", "viewContent", "viewOutput"]
|
||||||
```
|
```
|
||||||
|
|
||||||
The following defines the role "master" for "namespace" entities. Similarly to the example above,
|
The following defines the role "master" for "namespace" entities. Similarly to the example above,
|
||||||
it lists operations that relate to a namespace. In particular all "create" operations pertain
|
it lists operations that relate to a namespace. In particular all "create" operations pertain
|
||||||
to a namespace rathen than to an entity, which at the time of creation does not exist yet.
|
to a namespace rathen than to an entity, which at the time of creation does not exist yet.
|
||||||
Additionally, the namespace roles define permissions to all entity types under the namespace
|
Additionally, the namespace roles define permissions to all entity types under the namespace
|
||||||
(including child namespaces).
|
(including child namespaces).
|
||||||
```
|
```
|
||||||
[roles.namespace.master]
|
[roles.namespace.master]
|
||||||
name="Master"
|
name="Master"
|
||||||
description="All permissions"
|
description="All permissions"
|
||||||
permissions=["view", "edit", "delete", "share", "createNamespace", "createReportTemplate", "createReport", "manageUsers"]
|
permissions=["view", "edit", "delete", "share", "createNamespace", "createReportTemplate", "createReport", "manageUsers"]
|
||||||
|
|
||||||
[roles.namespace.master.children]
|
[roles.namespace.master.children]
|
||||||
reportTemplate=["view", "edit", "delete", "share", "execute"]
|
reportTemplate=["view", "edit", "delete", "share", "execute"]
|
||||||
report=["view", "edit", "delete", "share", "execute", "viewContent", "viewOutput"]
|
report=["view", "edit", "delete", "share", "execute", "viewContent", "viewOutput"]
|
||||||
namespace=["view", "edit", "delete", "share", "createNamespace", "createReportTemplate", "createReport", "manageUsers"]
|
namespace=["view", "edit", "delete", "share", "createNamespace", "createReportTemplate", "createReport", "manageUsers"]
|
||||||
```
|
```
|
||||||
|
|
||||||
And the same for the more restricted role "editor".
|
And the same for the more restricted role "editor".
|
||||||
```
|
```
|
||||||
[roles.namespace.editor.children]
|
[roles.namespace.editor.children]
|
||||||
reportTemplate=[]
|
reportTemplate=[]
|
||||||
report=["view", "viewContent", "viewOutput"]
|
report=["view", "viewContent", "viewOutput"]
|
||||||
namespace=["view", "edit", "delete"]
|
namespace=["view", "edit", "delete"]
|
||||||
```
|
```
|
|
@ -1,363 +1,363 @@
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
// Processes statements like these:
|
// Processes statements like these:
|
||||||
// tUI(/*prefix:account*/'account.passwordChangeRequest', language)
|
// tUI(/*prefix:account*/'account.passwordChangeRequest', language)
|
||||||
// /*prefix:helpers*/<Trans i18nKey="userMessagesUnread" count={count}>Hello <strong title={t('nameTitle')}>{{name}}</strong>, you have {{count}} unread message. <Link to="/msgs">Go to messages</Link>.</Trans>
|
// /*prefix:helpers*/<Trans i18nKey="userMessagesUnread" count={count}>Hello <strong title={t('nameTitle')}>{{name}}</strong>, you have {{count}} unread message. <Link to="/msgs">Go to messages</Link>.</Trans>
|
||||||
|
|
||||||
const fs = require('fs');
|
const fs = require('fs');
|
||||||
const path = require('path');
|
const path = require('path');
|
||||||
const klawSync = require('klaw-sync');
|
const klawSync = require('klaw-sync');
|
||||||
const acorn = require("acorn");
|
const acorn = require("acorn");
|
||||||
const acornJsx = require("acorn-jsx");
|
const acornJsx = require("acorn-jsx");
|
||||||
const ellipsize = require('ellipsize');
|
const ellipsize = require('ellipsize');
|
||||||
const camelCase = require('camelcase');
|
const camelCase = require('camelcase');
|
||||||
const slugify = require('slugify');
|
const slugify = require('slugify');
|
||||||
const readline = require('readline');
|
const readline = require('readline');
|
||||||
|
|
||||||
const localeFile = 'en-US/common.json';
|
const localeFile = 'en-US/common.json';
|
||||||
const searchDirs = [
|
const searchDirs = [
|
||||||
'../client/src',
|
'../client/src',
|
||||||
'../server',
|
'../server',
|
||||||
'../shared'
|
'../shared'
|
||||||
];
|
];
|
||||||
|
|
||||||
function findAllVariantsByPrefixInDict(dict, keyPrefix) {
|
function findAllVariantsByPrefixInDict(dict, keyPrefix) {
|
||||||
const keyElems = keyPrefix.split('.');
|
const keyElems = keyPrefix.split('.');
|
||||||
|
|
||||||
for (const keyElem of keyElems.slice(0, -1)) {
|
for (const keyElem of keyElems.slice(0, -1)) {
|
||||||
if (dict[keyElem]) {
|
if (dict[keyElem]) {
|
||||||
if (typeof dict[keyElem] === 'string') {
|
if (typeof dict[keyElem] === 'string') {
|
||||||
return [];
|
return [];
|
||||||
} else {
|
} else {
|
||||||
dict = dict[keyElem];
|
dict = dict[keyElem];
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const prefix = keyElems[keyElems.length - 1];
|
const prefix = keyElems[keyElems.length - 1];
|
||||||
const res = [];
|
const res = [];
|
||||||
for (const key in dict) {
|
for (const key in dict) {
|
||||||
if (key.startsWith(prefix)) {
|
if (key.startsWith(prefix)) {
|
||||||
res.push(key.substring(prefix.length));
|
res.push(key.substring(prefix.length));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
function findInDict(dict, key) {
|
function findInDict(dict, key) {
|
||||||
const keyElems = key.split('.');
|
const keyElems = key.split('.');
|
||||||
|
|
||||||
for (const keyElem of keyElems.slice(0, -1)) {
|
for (const keyElem of keyElems.slice(0, -1)) {
|
||||||
if (dict[keyElem]) {
|
if (dict[keyElem]) {
|
||||||
if (typeof dict[keyElem] === 'string') {
|
if (typeof dict[keyElem] === 'string') {
|
||||||
return undefined;
|
return undefined;
|
||||||
} else {
|
} else {
|
||||||
dict = dict[keyElem];
|
dict = dict[keyElem];
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return dict[keyElems[keyElems.length - 1]];
|
return dict[keyElems[keyElems.length - 1]];
|
||||||
}
|
}
|
||||||
|
|
||||||
function setInDict(dict, key, value) {
|
function setInDict(dict, key, value) {
|
||||||
const keyElems = key.split('.');
|
const keyElems = key.split('.');
|
||||||
|
|
||||||
for (const keyElem of keyElems.slice(0, -1)) {
|
for (const keyElem of keyElems.slice(0, -1)) {
|
||||||
if (dict[keyElem]) {
|
if (dict[keyElem]) {
|
||||||
if (typeof dict[keyElem] === 'string') {
|
if (typeof dict[keyElem] === 'string') {
|
||||||
throw new Error(`Overlapping key ${key}`);
|
throw new Error(`Overlapping key ${key}`);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
dict[keyElem] = {}
|
dict[keyElem] = {}
|
||||||
}
|
}
|
||||||
|
|
||||||
dict = dict[keyElem];
|
dict = dict[keyElem];
|
||||||
}
|
}
|
||||||
|
|
||||||
dict[keyElems[keyElems.length - 1]] = value;
|
dict[keyElems[keyElems.length - 1]] = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
const assignedKeys = new Map();
|
const assignedKeys = new Map();
|
||||||
function getKeyFromValue(spec, value) {
|
function getKeyFromValue(spec, value) {
|
||||||
let key = value.replace(/<\/?[0-9]+>/g, ''); // Remove Trans markup
|
let key = value.replace(/<\/?[0-9]+>/g, ''); // Remove Trans markup
|
||||||
key = slugify(key, { replacement: ' ', remove: /[\\()"':.,;\/\[\]\{\}*+-]/g, lower: false });
|
key = slugify(key, { replacement: ' ', remove: /[\\()"':.,;\/\[\]\{\}*+-]/g, lower: false });
|
||||||
key = camelCase(key);
|
key = camelCase(key);
|
||||||
key = ellipsize(key, 40, {
|
key = ellipsize(key, 40, {
|
||||||
chars: [...Array(26)].map((_, i) => String.fromCharCode('A'.charCodeAt(0) + i)) /* This is an array of characters A-Z */,
|
chars: [...Array(26)].map((_, i) => String.fromCharCode('A'.charCodeAt(0) + i)) /* This is an array of characters A-Z */,
|
||||||
ellipse: ''
|
ellipse: ''
|
||||||
});
|
});
|
||||||
|
|
||||||
if (spec.prefix) {
|
if (spec.prefix) {
|
||||||
key = spec.prefix + '.' + key;
|
key = spec.prefix + '.' + key;
|
||||||
}
|
}
|
||||||
|
|
||||||
let idx = 0;
|
let idx = 0;
|
||||||
while (true) {
|
while (true) {
|
||||||
const keyExt = key + (idx ? '-' + idx : '')
|
const keyExt = key + (idx ? '-' + idx : '')
|
||||||
if (assignedKeys.has(keyExt)) {
|
if (assignedKeys.has(keyExt)) {
|
||||||
if (assignedKeys.get(keyExt) === value) {
|
if (assignedKeys.get(keyExt) === value) {
|
||||||
return keyExt;
|
return keyExt;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
assignedKeys.set(keyExt, value);
|
assignedKeys.set(keyExt, value);
|
||||||
return keyExt;
|
return keyExt;
|
||||||
}
|
}
|
||||||
|
|
||||||
idx++;
|
idx++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function allowedDirOrFile(item) {
|
function allowedDirOrFile(item) {
|
||||||
const pp = path.parse(item.path)
|
const pp = path.parse(item.path)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
(item.stats.isDirectory() &&
|
(item.stats.isDirectory() &&
|
||||||
pp.base !== 'node_modules'
|
pp.base !== 'node_modules'
|
||||||
) ||
|
) ||
|
||||||
(item.stats.isFile() &&
|
(item.stats.isFile() &&
|
||||||
( pp.ext === '.js' || pp.ext === '.jsx' || pp.ext === '.hbs')
|
( pp.ext === '.js' || pp.ext === '.jsx' || pp.ext === '.hbs')
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function parseSpec(specStr) {
|
function parseSpec(specStr) {
|
||||||
const spec = {};
|
const spec = {};
|
||||||
|
|
||||||
if (specStr) {
|
if (specStr) {
|
||||||
const entryMatcher = /([a-zA-Z]*)\s*:\s*(.*)/
|
const entryMatcher = /([a-zA-Z]*)\s*:\s*(.*)/
|
||||||
|
|
||||||
const entries = specStr.split(/\s*,\s*/);
|
const entries = specStr.split(/\s*,\s*/);
|
||||||
for (const entry of entries) {
|
for (const entry of entries) {
|
||||||
const elems = entry.match(entryMatcher);
|
const elems = entry.match(entryMatcher);
|
||||||
if (elems) {
|
if (elems) {
|
||||||
spec[elems[1]] = elems[2];
|
spec[elems[1]] = elems[2];
|
||||||
} else {
|
} else {
|
||||||
spec[entry] = true;
|
spec[entry] = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return spec;
|
return spec;
|
||||||
}
|
}
|
||||||
|
|
||||||
// see http://blog.stevenlevithan.com/archives/match-quoted-string
|
// see http://blog.stevenlevithan.com/archives/match-quoted-string
|
||||||
const tMatcher = /(^|[ {+(=.\[])((?:tUI|tLog|t|tMark)\s*\(\s*(?:\/\*(.*?)\*\/)?\s*)(["'])((?:(?!\4)[^\\]|\\.)*)(\4)/;
|
const tMatcher = /(^|[ {+(=.\[])((?:tUI|tLog|t|tMark)\s*\(\s*(?:\/\*(.*?)\*\/)?\s*)(["'])((?:(?!\4)[^\\]|\\.)*)(\4)/;
|
||||||
const jsxTransMatcher = /(\/\*(.*?)\*\/\s*)?(\<Trans[ >][\s\S]*?\<\/Trans\>)/;
|
const jsxTransMatcher = /(\/\*(.*?)\*\/\s*)?(\<Trans[ >][\s\S]*?\<\/Trans\>)/;
|
||||||
const hbsTranslateMatcher = /(\{\{!--(.*?)--\}\}\s*)?(\{\{#translate\}\})([\s\S]*?)(\{\{\/translate\}\})/;
|
const hbsTranslateMatcher = /(\{\{!--(.*?)--\}\}\s*)?(\{\{#translate\}\})([\s\S]*?)(\{\{\/translate\}\})/;
|
||||||
|
|
||||||
const jsxParser = acorn.Parser.extend(acornJsx());
|
const jsxParser = acorn.Parser.extend(acornJsx());
|
||||||
function parseJsxTrans(fragment) {
|
function parseJsxTrans(fragment) {
|
||||||
const match = fragment.match(jsxTransMatcher);
|
const match = fragment.match(jsxTransMatcher);
|
||||||
const spec = parseSpec(match[2]);
|
const spec = parseSpec(match[2]);
|
||||||
const jsxStr = match[3];
|
const jsxStr = match[3];
|
||||||
|
|
||||||
const jsxStrSmpl = jsxStr.replace('{::', '{ '); // Acorn does not handle bind (::) operator. So we just leave it out because we are not interested in the code anyway.
|
const jsxStrSmpl = jsxStr.replace('{::', '{ '); // Acorn does not handle bind (::) operator. So we just leave it out because we are not interested in the code anyway.
|
||||||
const ast = jsxParser.parse(jsxStrSmpl);
|
const ast = jsxParser.parse(jsxStrSmpl);
|
||||||
|
|
||||||
function convertChildren(children) {
|
function convertChildren(children) {
|
||||||
const entries = [];
|
const entries = [];
|
||||||
let childNo = 0;
|
let childNo = 0;
|
||||||
|
|
||||||
for (const child of children) {
|
for (const child of children) {
|
||||||
const type = child.type;
|
const type = child.type;
|
||||||
|
|
||||||
if (type === 'JSXText') {
|
if (type === 'JSXText') {
|
||||||
entries.push(child.value);
|
entries.push(child.value);
|
||||||
childNo++;
|
childNo++;
|
||||||
|
|
||||||
} else if (type === 'JSXElement') {
|
} else if (type === 'JSXElement') {
|
||||||
const inner = convertChildren(child.children);
|
const inner = convertChildren(child.children);
|
||||||
entries.push(`<${childNo}>${convertChildren(child.children)}</${childNo}>`);
|
entries.push(`<${childNo}>${convertChildren(child.children)}</${childNo}>`);
|
||||||
childNo++;
|
childNo++;
|
||||||
|
|
||||||
} else if (type === 'JSXExpressionContainer') {
|
} else if (type === 'JSXExpressionContainer') {
|
||||||
entries.push(jsxStr.substring(child.start, child.end));
|
entries.push(jsxStr.substring(child.start, child.end));
|
||||||
childNo++;
|
childNo++;
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
throw new Error('Unknown JSX node: ' + child);
|
throw new Error('Unknown JSX node: ' + child);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return entries.join('');
|
return entries.join('');
|
||||||
}
|
}
|
||||||
|
|
||||||
const expr = ast.body[0].expression;
|
const expr = ast.body[0].expression;
|
||||||
|
|
||||||
let originalKey;
|
let originalKey;
|
||||||
for (const attr of expr.openingElement.attributes) {
|
for (const attr of expr.openingElement.attributes) {
|
||||||
const name = attr.name.name;
|
const name = attr.name.name;
|
||||||
if (name === 'i18nKey') {
|
if (name === 'i18nKey') {
|
||||||
originalKey = attr.value.value;
|
originalKey = attr.value.value;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const convValue = convertChildren(expr.children);
|
const convValue = convertChildren(expr.children);
|
||||||
|
|
||||||
if (originalKey === undefined) {
|
if (originalKey === undefined) {
|
||||||
originalKey = convValue;
|
originalKey = convValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
let value;
|
let value;
|
||||||
const originalValue = findInDict(originalResDict, originalKey);
|
const originalValue = findInDict(originalResDict, originalKey);
|
||||||
|
|
||||||
if (originalValue === undefined) {
|
if (originalValue === undefined) {
|
||||||
value = convValue;
|
value = convValue;
|
||||||
} else {
|
} else {
|
||||||
value = originalValue;
|
value = originalValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
const key = getKeyFromValue(spec, value);
|
const key = getKeyFromValue(spec, value);
|
||||||
|
|
||||||
const replacement = `${match[1] || ''}<Trans i18nKey="${key}">${jsxStr.substring(expr.openingElement.end, expr.closingElement.start)}</Trans>`;
|
const replacement = `${match[1] || ''}<Trans i18nKey="${key}">${jsxStr.substring(expr.openingElement.end, expr.closingElement.start)}</Trans>`;
|
||||||
|
|
||||||
return { key, originalKey, value, replacement };
|
return { key, originalKey, value, replacement };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
function parseHbsTranslate(fragment) {
|
function parseHbsTranslate(fragment) {
|
||||||
const match = fragment.match(hbsTranslateMatcher);
|
const match = fragment.match(hbsTranslateMatcher);
|
||||||
const spec = parseSpec(match[2]);
|
const spec = parseSpec(match[2]);
|
||||||
const originalKey = match[4];
|
const originalKey = match[4];
|
||||||
|
|
||||||
let value;
|
let value;
|
||||||
const originalValue = findInDict(originalResDict, originalKey);
|
const originalValue = findInDict(originalResDict, originalKey);
|
||||||
|
|
||||||
if (originalValue === undefined) {
|
if (originalValue === undefined) {
|
||||||
value = originalKey;
|
value = originalKey;
|
||||||
} else {
|
} else {
|
||||||
value = originalValue;
|
value = originalValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
const key = getKeyFromValue(spec, value);
|
const key = getKeyFromValue(spec, value);
|
||||||
|
|
||||||
const replacement = `${match[1] || ''}${match[3]}${key}${match[5]}`;
|
const replacement = `${match[1] || ''}${match[3]}${key}${match[5]}`;
|
||||||
|
|
||||||
return { key, originalKey, value, replacement };
|
return { key, originalKey, value, replacement };
|
||||||
}
|
}
|
||||||
|
|
||||||
function parseT(fragment) {
|
function parseT(fragment) {
|
||||||
const match = fragment.match(tMatcher);
|
const match = fragment.match(tMatcher);
|
||||||
|
|
||||||
const originalKey = match[5];
|
const originalKey = match[5];
|
||||||
const spec = parseSpec(match[3]);
|
const spec = parseSpec(match[3]);
|
||||||
|
|
||||||
if (spec.ignore) {
|
if (spec.ignore) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
// console.log(`${fragment}`);
|
// console.log(`${fragment}`);
|
||||||
// console.log(` |${match[1]}|${match[2]}|${match[4]}|${match[5]}|${match[6]}| - ${JSON.stringify(spec)}`);
|
// console.log(` |${match[1]}|${match[2]}|${match[4]}|${match[5]}|${match[6]}| - ${JSON.stringify(spec)}`);
|
||||||
|
|
||||||
let value;
|
let value;
|
||||||
const originalValue = findInDict(originalResDict, originalKey);
|
const originalValue = findInDict(originalResDict, originalKey);
|
||||||
|
|
||||||
if (originalValue === undefined) {
|
if (originalValue === undefined) {
|
||||||
value = originalKey;
|
value = originalKey;
|
||||||
} else {
|
} else {
|
||||||
value = originalValue;
|
value = originalValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
const key = getKeyFromValue(spec, value);
|
const key = getKeyFromValue(spec, value);
|
||||||
|
|
||||||
const replacement = `${match[1]}${match[2]}${match[4]}${key}${match[6]}`;
|
const replacement = `${match[1]}${match[2]}${match[4]}${key}${match[6]}`;
|
||||||
|
|
||||||
return { key, originalKey, value, originalValue, replacement };
|
return { key, originalKey, value, originalValue, replacement };
|
||||||
}
|
}
|
||||||
|
|
||||||
const renamedKeys = new Map();
|
const renamedKeys = new Map();
|
||||||
const resDict = {};
|
const resDict = {};
|
||||||
let anyUpdatesToResDict = false;
|
let anyUpdatesToResDict = false;
|
||||||
|
|
||||||
function processFile(file) {
|
function processFile(file) {
|
||||||
let source = fs.readFileSync(file, 'utf8');
|
let source = fs.readFileSync(file, 'utf8');
|
||||||
let anyUpdates = false;
|
let anyUpdates = false;
|
||||||
|
|
||||||
function update(fragments, parseFun) {
|
function update(fragments, parseFun) {
|
||||||
if (fragments) {
|
if (fragments) {
|
||||||
for (const fragment of fragments) {
|
for (const fragment of fragments) {
|
||||||
const parseStruct = parseFun(fragment);
|
const parseStruct = parseFun(fragment);
|
||||||
if (parseStruct) {
|
if (parseStruct) {
|
||||||
const {key, originalKey, value, originalValue, replacement} = parseStruct;
|
const {key, originalKey, value, originalValue, replacement} = parseStruct;
|
||||||
// console.log(`${key} <- ${originalKey} | ${value} <- ${originalValue} | ${fragment} -> ${replacement}`);
|
// console.log(`${key} <- ${originalKey} | ${value} <- ${originalValue} | ${fragment} -> ${replacement}`);
|
||||||
|
|
||||||
source = source.split(fragment).join(replacement);
|
source = source.split(fragment).join(replacement);
|
||||||
setInDict(resDict, key, value);
|
setInDict(resDict, key, value);
|
||||||
|
|
||||||
const variants = originalKey ? findAllVariantsByPrefixInDict(originalResDict, originalKey + '_') : [];
|
const variants = originalKey ? findAllVariantsByPrefixInDict(originalResDict, originalKey + '_') : [];
|
||||||
for (const variant of variants) {
|
for (const variant of variants) {
|
||||||
setInDict(resDict, key + '_' + variant, findInDict(originalResDict, originalKey + '_' + variant));
|
setInDict(resDict, key + '_' + variant, findInDict(originalResDict, originalKey + '_' + variant));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (originalKey !== key) {
|
if (originalKey !== key) {
|
||||||
renamedKeys.set(originalKey, key);
|
renamedKeys.set(originalKey, key);
|
||||||
|
|
||||||
for (const variant of variants) {
|
for (const variant of variants) {
|
||||||
renamedKeys.set(originalKey + '_' + variant, key + '_' + variant);
|
renamedKeys.set(originalKey + '_' + variant, key + '_' + variant);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (originalKey !== key || originalValue !== value) {
|
if (originalKey !== key || originalValue !== value) {
|
||||||
anyUpdates = true;
|
anyUpdates = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const lines = source.split(/\r?\n/g);
|
const lines = source.split(/\r?\n/g);
|
||||||
for (const line of lines) {
|
for (const line of lines) {
|
||||||
const fragments = line.match(new RegExp(tMatcher, 'g'));
|
const fragments = line.match(new RegExp(tMatcher, 'g'));
|
||||||
update(fragments, parseT);
|
update(fragments, parseT);
|
||||||
}
|
}
|
||||||
|
|
||||||
const hbsFragments = source.match(new RegExp(hbsTranslateMatcher, 'g'));
|
const hbsFragments = source.match(new RegExp(hbsTranslateMatcher, 'g'));
|
||||||
update(hbsFragments, parseHbsTranslate);
|
update(hbsFragments, parseHbsTranslate);
|
||||||
|
|
||||||
const jsxFragments = source.match(new RegExp(jsxTransMatcher, 'g'));
|
const jsxFragments = source.match(new RegExp(jsxTransMatcher, 'g'));
|
||||||
update(jsxFragments, parseJsxTrans);
|
update(jsxFragments, parseJsxTrans);
|
||||||
|
|
||||||
if (anyUpdates) {
|
if (anyUpdates) {
|
||||||
console.log(`Updating ${file}`);
|
console.log(`Updating ${file}`);
|
||||||
fs.writeFileSync(file, source);
|
fs.writeFileSync(file, source);
|
||||||
|
|
||||||
anyUpdatesToResDict = true;
|
anyUpdatesToResDict = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const originalResDict = JSON.parse(fs.readFileSync(localeFile));
|
const originalResDict = JSON.parse(fs.readFileSync(localeFile));
|
||||||
|
|
||||||
function run() {
|
function run() {
|
||||||
for (const dir of searchDirs) {
|
for (const dir of searchDirs) {
|
||||||
const files = klawSync(dir, { nodir: true, filter: allowedDirOrFile })
|
const files = klawSync(dir, { nodir: true, filter: allowedDirOrFile })
|
||||||
|
|
||||||
for (const file of files) {
|
for (const file of files) {
|
||||||
processFile(file.path);
|
processFile(file.path);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (anyUpdatesToResDict) {
|
if (anyUpdatesToResDict) {
|
||||||
console.log(`Updating ${localeFile}`);
|
console.log(`Updating ${localeFile}`);
|
||||||
fs.writeFileSync(localeFile, JSON.stringify(resDict, null, 2));
|
fs.writeFileSync(localeFile, JSON.stringify(resDict, null, 2));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const rl = readline.createInterface({
|
const rl = readline.createInterface({
|
||||||
input: process.stdin,
|
input: process.stdin,
|
||||||
output: process.stdout
|
output: process.stdout
|
||||||
});
|
});
|
||||||
|
|
||||||
console.log('This script does modifications in the source tree. You should first commit all your files in git before proceeding.');
|
console.log('This script does modifications in the source tree. You should first commit all your files in git before proceeding.');
|
||||||
rl.question('To proceed type YES: ', (answer) => {
|
rl.question('To proceed type YES: ', (answer) => {
|
||||||
if (answer === 'YES') {
|
if (answer === 'YES') {
|
||||||
run();
|
run();
|
||||||
}
|
}
|
||||||
|
|
||||||
rl.close();
|
rl.close();
|
||||||
});
|
});
|
||||||
|
|
2
mvis/client/.gitignore
vendored
2
mvis/client/.gitignore
vendored
|
@ -1,2 +1,2 @@
|
||||||
/dist
|
/dist
|
||||||
/node_modules
|
/node_modules
|
4
mvis/server/.gitignore
vendored
4
mvis/server/.gitignore
vendored
|
@ -1,2 +1,2 @@
|
||||||
/node_modules
|
/node_modules
|
||||||
/config/development.yaml
|
/config/development.yaml
|
||||||
|
|
|
@ -1,77 +1,77 @@
|
||||||
mysql:
|
mysql:
|
||||||
host: localhost
|
host: localhost
|
||||||
user: mvis
|
user: mvis
|
||||||
password: mvis
|
password: mvis
|
||||||
database: mvis
|
database: mvis
|
||||||
|
|
||||||
mailtrain:
|
mailtrain:
|
||||||
url: http://localhost:3000/
|
url: http://localhost:3000/
|
||||||
namespaces:
|
namespaces:
|
||||||
campaigns: 2
|
campaigns: 2
|
||||||
userRole: mailtrainUser
|
userRole: mailtrainUser
|
||||||
|
|
||||||
www:
|
www:
|
||||||
# HTTP interface to listen on
|
# HTTP interface to listen on
|
||||||
host: 127.0.0.1
|
host: 127.0.0.1
|
||||||
# HTTP(S) port to listen on
|
# HTTP(S) port to listen on
|
||||||
trustedPort: 3010
|
trustedPort: 3010
|
||||||
trustedPortIsHttps: false
|
trustedPortIsHttps: false
|
||||||
sandboxPort: 3011
|
sandboxPort: 3011
|
||||||
sandboxPortIsHttps: false
|
sandboxPortIsHttps: false
|
||||||
apiPort: 3012
|
apiPort: 3012
|
||||||
apiPortIsHttps: false
|
apiPortIsHttps: false
|
||||||
|
|
||||||
trustedUrlBase: http://localhost:3010
|
trustedUrlBase: http://localhost:3010
|
||||||
sandboxUrlBase: http://localhost:3011
|
sandboxUrlBase: http://localhost:3011
|
||||||
|
|
||||||
|
|
||||||
roles:
|
roles:
|
||||||
global:
|
global:
|
||||||
mailtrainUser:
|
mailtrainUser:
|
||||||
name: "Mailtrain User"
|
name: "Mailtrain User"
|
||||||
admin: true
|
admin: true
|
||||||
description: "Limited permissions that allow only read-only access"
|
description: "Limited permissions that allow only read-only access"
|
||||||
permissions:
|
permissions:
|
||||||
|
|
||||||
namespace:
|
namespace:
|
||||||
mailtrainUser:
|
mailtrainUser:
|
||||||
name: "Mailtrain User"
|
name: "Mailtrain User"
|
||||||
description: "Limited permissions that allow only read-only access"
|
description: "Limited permissions that allow only read-only access"
|
||||||
permissions: ["view"]
|
permissions: ["view"]
|
||||||
children:
|
children:
|
||||||
namespace: ["view"]
|
namespace: ["view"]
|
||||||
template: ["view", "viewFiles", "execute"]
|
template: ["view", "viewFiles", "execute"]
|
||||||
workspace: ["view"]
|
workspace: ["view"]
|
||||||
panel: ["view"]
|
panel: ["view"]
|
||||||
signal: ["view", "query"]
|
signal: ["view", "query"]
|
||||||
signalSet: ["view", "query"]
|
signalSet: ["view", "query"]
|
||||||
|
|
||||||
# template:
|
# template:
|
||||||
# mailtrainUser:
|
# mailtrainUser:
|
||||||
# name: "Mailtrain User"
|
# name: "Mailtrain User"
|
||||||
# description: "Limited permissions that allow only read-only access"
|
# description: "Limited permissions that allow only read-only access"
|
||||||
# permissions: ["view", "viewFiles", "execute"]
|
# permissions: ["view", "viewFiles", "execute"]
|
||||||
#
|
#
|
||||||
# workspace:
|
# workspace:
|
||||||
# mailtrainUser:
|
# mailtrainUser:
|
||||||
# name: "Mailtrain User"
|
# name: "Mailtrain User"
|
||||||
# description: "Limited permissions that allow only read-only access"
|
# description: "Limited permissions that allow only read-only access"
|
||||||
# permissions: ["view"]
|
# permissions: ["view"]
|
||||||
#
|
#
|
||||||
# panel:
|
# panel:
|
||||||
# mailtrainUser:
|
# mailtrainUser:
|
||||||
# name: "Mailtrain User"
|
# name: "Mailtrain User"
|
||||||
# description: "Limited permissions that allow only read-only access"
|
# description: "Limited permissions that allow only read-only access"
|
||||||
# permissions: ["view"]
|
# permissions: ["view"]
|
||||||
#
|
#
|
||||||
# signal:
|
# signal:
|
||||||
# mailtrainUser:
|
# mailtrainUser:
|
||||||
# name: "Mailtrain User"
|
# name: "Mailtrain User"
|
||||||
# description: "Limited permissions that allow only read-only access"
|
# description: "Limited permissions that allow only read-only access"
|
||||||
# permissions: ["view", "query"]
|
# permissions: ["view", "query"]
|
||||||
#
|
#
|
||||||
# signalSet:
|
# signalSet:
|
||||||
# mailtrainUser:
|
# mailtrainUser:
|
||||||
# name: "Mailtrain User"
|
# name: "Mailtrain User"
|
||||||
# description: "Limited permissions that allow only read-only access"
|
# description: "Limited permissions that allow only read-only access"
|
||||||
# permissions: ["view", "query"]
|
# permissions: ["view", "query"]
|
||||||
|
|
4
mvis/test-embed/.gitignore
vendored
4
mvis/test-embed/.gitignore
vendored
|
@ -1,2 +1,2 @@
|
||||||
/dist
|
/dist
|
||||||
/node_modules
|
/node_modules
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
<div id="evif-panel" />
|
<div id="evif-panel" />
|
||||||
|
|
||||||
<script type="text/javascript" src="/ivis.js"></script>
|
<script type="text/javascript" src="/ivis.js"></script>
|
||||||
|
|
||||||
<script type="text/javascript">
|
<script type="text/javascript">
|
||||||
IVIS.embedPanel('evif-panel', '{{ivisSandboxUrlBase}}', {{panelId}}, '{{token}}');
|
IVIS.embedPanel('evif-panel', '{{ivisSandboxUrlBase}}', {{panelId}}, '{{token}}');
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
|
@ -1,32 +1,32 @@
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
function convertFileURLs(sourceCustom, fromEntityType, fromEntityId, toEntityType, toEntityId) {
|
function convertFileURLs(sourceCustom, fromEntityType, fromEntityId, toEntityType, toEntityId) {
|
||||||
|
|
||||||
function convertText(text) {
|
function convertText(text) {
|
||||||
if (text) {
|
if (text) {
|
||||||
const fromUrl = `/files/${fromEntityType}/file/${fromEntityId}`;
|
const fromUrl = `/files/${fromEntityType}/file/${fromEntityId}`;
|
||||||
const toUrl = `/files/${toEntityType}/file/${toEntityId}`;
|
const toUrl = `/files/${toEntityType}/file/${toEntityId}`;
|
||||||
|
|
||||||
const encodedFromUrl = encodeURIComponent(fromUrl);
|
const encodedFromUrl = encodeURIComponent(fromUrl);
|
||||||
const encodedToUrl = encodeURIComponent(toUrl);
|
const encodedToUrl = encodeURIComponent(toUrl);
|
||||||
|
|
||||||
text = text.split('[URL_BASE]' + fromUrl).join('[URL_BASE]' + toUrl);
|
text = text.split('[URL_BASE]' + fromUrl).join('[URL_BASE]' + toUrl);
|
||||||
text = text.split('[SANDBOX_URL_BASE]' + fromUrl).join('[SANDBOX_URL_BASE]' + toUrl);
|
text = text.split('[SANDBOX_URL_BASE]' + fromUrl).join('[SANDBOX_URL_BASE]' + toUrl);
|
||||||
text = text.split('[ENCODED_URL_BASE]' + encodedFromUrl).join('[ENCODED_URL_BASE]' + encodedToUrl);
|
text = text.split('[ENCODED_URL_BASE]' + encodedFromUrl).join('[ENCODED_URL_BASE]' + encodedToUrl);
|
||||||
text = text.split('[ENCODED_SANDBOX_URL_BASE]' + encodedFromUrl).join('[ENCODED_SANDBOX_URL_BASE]' + encodedToUrl);
|
text = text.split('[ENCODED_SANDBOX_URL_BASE]' + encodedFromUrl).join('[ENCODED_SANDBOX_URL_BASE]' + encodedToUrl);
|
||||||
}
|
}
|
||||||
|
|
||||||
return text;
|
return text;
|
||||||
}
|
}
|
||||||
|
|
||||||
sourceCustom.html = convertText(sourceCustom.html);
|
sourceCustom.html = convertText(sourceCustom.html);
|
||||||
sourceCustom.text = convertText(sourceCustom.text);
|
sourceCustom.text = convertText(sourceCustom.text);
|
||||||
|
|
||||||
if (sourceCustom.type === 'mosaico' || sourceCustom.type === 'mosaicoWithFsTemplate') {
|
if (sourceCustom.type === 'mosaico' || sourceCustom.type === 'mosaicoWithFsTemplate') {
|
||||||
sourceCustom.data.model = convertText(sourceCustom.data.model);
|
sourceCustom.data.model = convertText(sourceCustom.data.model);
|
||||||
sourceCustom.data.model = convertText(sourceCustom.data.model);
|
sourceCustom.data.model = convertText(sourceCustom.data.model);
|
||||||
sourceCustom.data.metadata = convertText(sourceCustom.data.metadata);
|
sourceCustom.data.metadata = convertText(sourceCustom.data.metadata);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports.convertFileURLs = convertFileURLs;
|
module.exports.convertFileURLs = convertFileURLs;
|
|
@ -1,151 +1,151 @@
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
const ReplacementBehavior = {
|
const ReplacementBehavior = {
|
||||||
NONE: 1,
|
NONE: 1,
|
||||||
REPLACE: 2,
|
REPLACE: 2,
|
||||||
RENAME: 3
|
RENAME: 3
|
||||||
};
|
};
|
||||||
|
|
||||||
const entityTypes = {
|
const entityTypes = {
|
||||||
namespace: {
|
namespace: {
|
||||||
entitiesTable: 'namespaces',
|
entitiesTable: 'namespaces',
|
||||||
sharesTable: 'shares_namespace',
|
sharesTable: 'shares_namespace',
|
||||||
permissionsTable: 'permissions_namespace',
|
permissionsTable: 'permissions_namespace',
|
||||||
clientLink: id => `/namespaces/${id}`
|
clientLink: id => `/namespaces/${id}`
|
||||||
},
|
},
|
||||||
list: {
|
list: {
|
||||||
entitiesTable: 'lists',
|
entitiesTable: 'lists',
|
||||||
sharesTable: 'shares_list',
|
sharesTable: 'shares_list',
|
||||||
permissionsTable: 'permissions_list',
|
permissionsTable: 'permissions_list',
|
||||||
clientLink: id => `/lists/${id}`
|
clientLink: id => `/lists/${id}`
|
||||||
},
|
},
|
||||||
customForm: {
|
customForm: {
|
||||||
entitiesTable: 'custom_forms',
|
entitiesTable: 'custom_forms',
|
||||||
sharesTable: 'shares_custom_form',
|
sharesTable: 'shares_custom_form',
|
||||||
permissionsTable: 'permissions_custom_form',
|
permissionsTable: 'permissions_custom_form',
|
||||||
clientLink: id => `/lists/forms/${id}`
|
clientLink: id => `/lists/forms/${id}`
|
||||||
},
|
},
|
||||||
campaign: {
|
campaign: {
|
||||||
entitiesTable: 'campaigns',
|
entitiesTable: 'campaigns',
|
||||||
sharesTable: 'shares_campaign',
|
sharesTable: 'shares_campaign',
|
||||||
permissionsTable: 'permissions_campaign',
|
permissionsTable: 'permissions_campaign',
|
||||||
dependentPermissions: {
|
dependentPermissions: {
|
||||||
extraColumns: ['parent'],
|
extraColumns: ['parent'],
|
||||||
getParent: entity => entity.parent
|
getParent: entity => entity.parent
|
||||||
},
|
},
|
||||||
files: {
|
files: {
|
||||||
file: {
|
file: {
|
||||||
table: 'files_campaign_file',
|
table: 'files_campaign_file',
|
||||||
permissions: {
|
permissions: {
|
||||||
view: 'viewFiles',
|
view: 'viewFiles',
|
||||||
manage: 'manageFiles'
|
manage: 'manageFiles'
|
||||||
},
|
},
|
||||||
defaultReplacementBehavior: ReplacementBehavior.REPLACE
|
defaultReplacementBehavior: ReplacementBehavior.REPLACE
|
||||||
},
|
},
|
||||||
attachment: {
|
attachment: {
|
||||||
table: 'files_campaign_attachment',
|
table: 'files_campaign_attachment',
|
||||||
permissions: {
|
permissions: {
|
||||||
view: 'viewAttachments',
|
view: 'viewAttachments',
|
||||||
manage: 'manageAttachments'
|
manage: 'manageAttachments'
|
||||||
},
|
},
|
||||||
defaultReplacementBehavior: ReplacementBehavior.NONE
|
defaultReplacementBehavior: ReplacementBehavior.NONE
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
clientLink: id => `/campaigns/${id}`
|
clientLink: id => `/campaigns/${id}`
|
||||||
},
|
},
|
||||||
template: {
|
template: {
|
||||||
entitiesTable: 'templates',
|
entitiesTable: 'templates',
|
||||||
sharesTable: 'shares_template',
|
sharesTable: 'shares_template',
|
||||||
permissionsTable: 'permissions_template',
|
permissionsTable: 'permissions_template',
|
||||||
files: {
|
files: {
|
||||||
file: {
|
file: {
|
||||||
table: 'files_template_file',
|
table: 'files_template_file',
|
||||||
permissions: {
|
permissions: {
|
||||||
view: 'viewFiles',
|
view: 'viewFiles',
|
||||||
manage: 'manageFiles'
|
manage: 'manageFiles'
|
||||||
},
|
},
|
||||||
defaultReplacementBehavior: ReplacementBehavior.REPLACE
|
defaultReplacementBehavior: ReplacementBehavior.REPLACE
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
clientLink: id => `/templates/${id}`
|
clientLink: id => `/templates/${id}`
|
||||||
},
|
},
|
||||||
sendConfiguration: {
|
sendConfiguration: {
|
||||||
entitiesTable: 'send_configurations',
|
entitiesTable: 'send_configurations',
|
||||||
sharesTable: 'shares_send_configuration',
|
sharesTable: 'shares_send_configuration',
|
||||||
permissionsTable: 'permissions_send_configuration',
|
permissionsTable: 'permissions_send_configuration',
|
||||||
clientLink: id => `/send-configurations/${id}`
|
clientLink: id => `/send-configurations/${id}`
|
||||||
},
|
},
|
||||||
report: {
|
report: {
|
||||||
entitiesTable: 'reports',
|
entitiesTable: 'reports',
|
||||||
sharesTable: 'shares_report',
|
sharesTable: 'shares_report',
|
||||||
permissionsTable: 'permissions_report',
|
permissionsTable: 'permissions_report',
|
||||||
clientLink: id => `/reports/${id}`
|
clientLink: id => `/reports/${id}`
|
||||||
},
|
},
|
||||||
reportTemplate: {
|
reportTemplate: {
|
||||||
entitiesTable: 'report_templates',
|
entitiesTable: 'report_templates',
|
||||||
sharesTable: 'shares_report_template',
|
sharesTable: 'shares_report_template',
|
||||||
permissionsTable: 'permissions_report_template',
|
permissionsTable: 'permissions_report_template',
|
||||||
clientLink: id => `/reports/templates/${id}`
|
clientLink: id => `/reports/templates/${id}`
|
||||||
},
|
},
|
||||||
mosaicoTemplate: {
|
mosaicoTemplate: {
|
||||||
entitiesTable: 'mosaico_templates',
|
entitiesTable: 'mosaico_templates',
|
||||||
sharesTable: 'shares_mosaico_template',
|
sharesTable: 'shares_mosaico_template',
|
||||||
permissionsTable: 'permissions_mosaico_template',
|
permissionsTable: 'permissions_mosaico_template',
|
||||||
files: {
|
files: {
|
||||||
file: {
|
file: {
|
||||||
table: 'files_mosaico_template_file',
|
table: 'files_mosaico_template_file',
|
||||||
permissions: {
|
permissions: {
|
||||||
view: 'viewFiles',
|
view: 'viewFiles',
|
||||||
manage: 'manageFiles'
|
manage: 'manageFiles'
|
||||||
},
|
},
|
||||||
defaultReplacementBehavior: ReplacementBehavior.REPLACE
|
defaultReplacementBehavior: ReplacementBehavior.REPLACE
|
||||||
},
|
},
|
||||||
block: {
|
block: {
|
||||||
table: 'files_mosaico_template_block',
|
table: 'files_mosaico_template_block',
|
||||||
permissions: {
|
permissions: {
|
||||||
view: 'viewFiles',
|
view: 'viewFiles',
|
||||||
manage: 'manageFiles'
|
manage: 'manageFiles'
|
||||||
},
|
},
|
||||||
defaultReplacementBehavior: ReplacementBehavior.REPLACE
|
defaultReplacementBehavior: ReplacementBehavior.REPLACE
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
clientLink: id => `/templates/mosaico/${id}`
|
clientLink: id => `/templates/mosaico/${id}`
|
||||||
},
|
},
|
||||||
user: {
|
user: {
|
||||||
entitiesTable: 'users',
|
entitiesTable: 'users',
|
||||||
clientLink: id => `/users/${id}`
|
clientLink: id => `/users/${id}`
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const entityTypesWithPermissions = {};
|
const entityTypesWithPermissions = {};
|
||||||
for (const key in entityTypes) {
|
for (const key in entityTypes) {
|
||||||
if (entityTypes[key].permissionsTable) {
|
if (entityTypes[key].permissionsTable) {
|
||||||
entityTypesWithPermissions[key] = entityTypes[key];
|
entityTypesWithPermissions[key] = entityTypes[key];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
function getEntityTypes() {
|
function getEntityTypes() {
|
||||||
return entityTypes;
|
return entityTypes;
|
||||||
}
|
}
|
||||||
|
|
||||||
function getEntityTypesWithPermissions() {
|
function getEntityTypesWithPermissions() {
|
||||||
return entityTypesWithPermissions;
|
return entityTypesWithPermissions;
|
||||||
}
|
}
|
||||||
|
|
||||||
function getEntityType(entityTypeId) {
|
function getEntityType(entityTypeId) {
|
||||||
const entityType = entityTypes[entityTypeId];
|
const entityType = entityTypes[entityTypeId];
|
||||||
|
|
||||||
if (!entityType) {
|
if (!entityType) {
|
||||||
throw new Error(`Unknown entity type ${entityTypeId}`);
|
throw new Error(`Unknown entity type ${entityTypeId}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
return entityType
|
return entityType
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
getEntityTypes,
|
getEntityTypes,
|
||||||
getEntityTypesWithPermissions,
|
getEntityTypesWithPermissions,
|
||||||
getEntityType,
|
getEntityType,
|
||||||
ReplacementBehavior
|
ReplacementBehavior
|
||||||
}
|
}
|
|
@ -1,8 +1,8 @@
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
const config = require('config');
|
const config = require('config');
|
||||||
const log = require('npmlog');
|
const log = require('npmlog');
|
||||||
|
|
||||||
log.level = config.log.level;
|
log.level = config.log.level;
|
||||||
|
|
||||||
module.exports = log;
|
module.exports = log;
|
|
@ -1,24 +1,24 @@
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
const { enforce } = require('./helpers');
|
const { enforce } = require('./helpers');
|
||||||
const interoperableErrors = require('../../shared/interoperable-errors');
|
const interoperableErrors = require('../../shared/interoperable-errors');
|
||||||
const shares = require('../models/shares');
|
const shares = require('../models/shares');
|
||||||
|
|
||||||
async function validateEntity(tx, entity) {
|
async function validateEntity(tx, entity) {
|
||||||
enforce(entity.namespace, 'Entity namespace not set');
|
enforce(entity.namespace, 'Entity namespace not set');
|
||||||
if (!await tx('namespaces').where('id', entity.namespace).first()) {
|
if (!await tx('namespaces').where('id', entity.namespace).first()) {
|
||||||
throw new interoperableErrors.NamespaceNotFoundError();
|
throw new interoperableErrors.NamespaceNotFoundError();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function validateMove(context, entity, existing, entityTypeId, createOperation, deleteOperation) {
|
async function validateMove(context, entity, existing, entityTypeId, createOperation, deleteOperation) {
|
||||||
if (existing.namespace !== entity.namespace) {
|
if (existing.namespace !== entity.namespace) {
|
||||||
await shares.enforceEntityPermission(context, 'namespace', entity.namespace, createOperation);
|
await shares.enforceEntityPermission(context, 'namespace', entity.namespace, createOperation);
|
||||||
await shares.enforceEntityPermission(context, entityTypeId, entity.id, deleteOperation);
|
await shares.enforceEntityPermission(context, entityTypeId, entity.id, deleteOperation);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
validateEntity,
|
validateEntity,
|
||||||
validateMove
|
validateMove
|
||||||
};
|
};
|
|
@ -1,15 +1,15 @@
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
const nodeify = require('nodeify');
|
const nodeify = require('nodeify');
|
||||||
|
|
||||||
module.exports.nodeifyPromise = nodeify;
|
module.exports.nodeifyPromise = nodeify;
|
||||||
|
|
||||||
module.exports.nodeifyFunction = (asyncFun) => {
|
module.exports.nodeifyFunction = (asyncFun) => {
|
||||||
return (...args) => {
|
return (...args) => {
|
||||||
const callback = args.pop();
|
const callback = args.pop();
|
||||||
|
|
||||||
const promise = asyncFun(...args);
|
const promise = asyncFun(...args);
|
||||||
|
|
||||||
return module.exports.nodeifyPromise(promise, callback);
|
return module.exports.nodeifyPromise(promise, callback);
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,72 +1,72 @@
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
const config = require('config');
|
const config = require('config');
|
||||||
const urllib = require('url');
|
const urllib = require('url');
|
||||||
const {anonymousRestrictedAccessToken} = require('../../shared/urls');
|
const {anonymousRestrictedAccessToken} = require('../../shared/urls');
|
||||||
const {getLangCodeFromExpressLocale} = require('./translate');
|
const {getLangCodeFromExpressLocale} = require('./translate');
|
||||||
|
|
||||||
function getTrustedUrlBase() {
|
function getTrustedUrlBase() {
|
||||||
return urllib.resolve(config.www.trustedUrlBase, '');
|
return urllib.resolve(config.www.trustedUrlBase, '');
|
||||||
}
|
}
|
||||||
|
|
||||||
function getSandboxUrlBase() {
|
function getSandboxUrlBase() {
|
||||||
return urllib.resolve(config.www.sandboxUrlBase, '');
|
return urllib.resolve(config.www.sandboxUrlBase, '');
|
||||||
}
|
}
|
||||||
|
|
||||||
function getPublicUrlBase() {
|
function getPublicUrlBase() {
|
||||||
return urllib.resolve(config.www.publicUrlBase, '');
|
return urllib.resolve(config.www.publicUrlBase, '');
|
||||||
}
|
}
|
||||||
|
|
||||||
function _getUrl(urlBase, path, opts) {
|
function _getUrl(urlBase, path, opts) {
|
||||||
const url = new URL(path || '', urlBase);
|
const url = new URL(path || '', urlBase);
|
||||||
|
|
||||||
if (opts && opts.locale) {
|
if (opts && opts.locale) {
|
||||||
url.searchParams.append('locale', getLangCodeFromExpressLocale(opts.locale));
|
url.searchParams.append('locale', getLangCodeFromExpressLocale(opts.locale));
|
||||||
}
|
}
|
||||||
|
|
||||||
return url.toString();
|
return url.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
function getTrustedUrl(path, opts) {
|
function getTrustedUrl(path, opts) {
|
||||||
return _getUrl(config.www.trustedUrlBase, path || '', opts);
|
return _getUrl(config.www.trustedUrlBase, path || '', opts);
|
||||||
}
|
}
|
||||||
|
|
||||||
function getSandboxUrl(path, context, opts) {
|
function getSandboxUrl(path, context, opts) {
|
||||||
if (context && context.user && context.user.restrictedAccessToken) {
|
if (context && context.user && context.user.restrictedAccessToken) {
|
||||||
return _getUrl(config.www.sandboxUrlBase, context.user.restrictedAccessToken + '/' + (path || ''), opts);
|
return _getUrl(config.www.sandboxUrlBase, context.user.restrictedAccessToken + '/' + (path || ''), opts);
|
||||||
} else {
|
} else {
|
||||||
return _getUrl(config.www.sandboxUrlBase, anonymousRestrictedAccessToken + '/' + (path || ''), opts);
|
return _getUrl(config.www.sandboxUrlBase, anonymousRestrictedAccessToken + '/' + (path || ''), opts);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function getPublicUrl(path, opts) {
|
function getPublicUrl(path, opts) {
|
||||||
return _getUrl(config.www.publicUrlBase, path || '', opts);
|
return _getUrl(config.www.publicUrlBase, path || '', opts);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
function getTrustedUrlBaseDir() {
|
function getTrustedUrlBaseDir() {
|
||||||
const mailtrainUrl = urllib.parse(config.www.trustedUrlBase);
|
const mailtrainUrl = urllib.parse(config.www.trustedUrlBase);
|
||||||
return mailtrainUrl.pathname;
|
return mailtrainUrl.pathname;
|
||||||
}
|
}
|
||||||
|
|
||||||
function getSandboxUrlBaseDir() {
|
function getSandboxUrlBaseDir() {
|
||||||
const mailtrainUrl = urllib.parse(config.www.sandboxUrlBase);
|
const mailtrainUrl = urllib.parse(config.www.sandboxUrlBase);
|
||||||
return mailtrainUrl.pathname;
|
return mailtrainUrl.pathname;
|
||||||
}
|
}
|
||||||
|
|
||||||
function getPublicUrlBaseDir() {
|
function getPublicUrlBaseDir() {
|
||||||
const mailtrainUrl = urllib.parse(config.www.publicUrlBase);
|
const mailtrainUrl = urllib.parse(config.www.publicUrlBase);
|
||||||
return mailtrainUrl.pathname;
|
return mailtrainUrl.pathname;
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
getTrustedUrl,
|
getTrustedUrl,
|
||||||
getSandboxUrl,
|
getSandboxUrl,
|
||||||
getPublicUrl,
|
getPublicUrl,
|
||||||
getTrustedUrlBase,
|
getTrustedUrlBase,
|
||||||
getSandboxUrlBase,
|
getSandboxUrlBase,
|
||||||
getPublicUrlBase,
|
getPublicUrlBase,
|
||||||
getTrustedUrlBaseDir,
|
getTrustedUrlBaseDir,
|
||||||
getSandboxUrlBaseDir,
|
getSandboxUrlBaseDir,
|
||||||
getPublicUrlBaseDir
|
getPublicUrlBaseDir
|
||||||
};
|
};
|
4
server/protected/reports/.gitignore
vendored
4
server/protected/reports/.gitignore
vendored
|
@ -1,3 +1,3 @@
|
||||||
*
|
*
|
||||||
!.gitignore
|
!.gitignore
|
||||||
!README.md
|
!README.md
|
|
@ -1,9 +1,9 @@
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
if (!process.env.NODE_CONFIG_DIR) {
|
if (!process.env.NODE_CONFIG_DIR) {
|
||||||
process.env.NODE_CONFIG_DIR = __dirname + '/../../config';
|
process.env.NODE_CONFIG_DIR = __dirname + '/../../config';
|
||||||
}
|
}
|
||||||
|
|
||||||
const config = require('server/setup/knex/config');
|
const config = require('server/setup/knex/config');
|
||||||
|
|
||||||
module.exports = config;
|
module.exports = config;
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
const config = require('./config');
|
const config = require('./config');
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
client: 'mysql',
|
client: 'mysql',
|
||||||
connection: config.mysql
|
connection: config.mysql
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,18 +1,18 @@
|
||||||
{{#if uaCode}}
|
{{#if uaCode}}
|
||||||
<script>
|
<script>
|
||||||
(function(i, s, o, g, r, a, m) {
|
(function(i, s, o, g, r, a, m) {
|
||||||
i['GoogleAnalyticsObject'] = r;
|
i['GoogleAnalyticsObject'] = r;
|
||||||
i[r] = i[r] || function() {
|
i[r] = i[r] || function() {
|
||||||
(i[r].q = i[r].q || []).push(arguments)
|
(i[r].q = i[r].q || []).push(arguments)
|
||||||
}, i[r].l = 1 * new Date();
|
}, i[r].l = 1 * new Date();
|
||||||
a = s.createElement(o),
|
a = s.createElement(o),
|
||||||
m = s.getElementsByTagName(o)[0];
|
m = s.getElementsByTagName(o)[0];
|
||||||
a.async = 1;
|
a.async = 1;
|
||||||
a.src = g;
|
a.src = g;
|
||||||
m.parentNode.insertBefore(a, m)
|
m.parentNode.insertBefore(a, m)
|
||||||
})(window, document, 'script', 'https://www.google-analytics.com/analytics.js', 'ga');
|
})(window, document, 'script', 'https://www.google-analytics.com/analytics.js', 'ga');
|
||||||
|
|
||||||
ga('create', '{{uaCode}}', 'auto');
|
ga('create', '{{uaCode}}', 'auto');
|
||||||
ga('send', 'pageview');
|
ga('send', 'pageview');
|
||||||
</script>
|
</script>
|
||||||
{{/if}}
|
{{/if}}
|
||||||
|
|
|
@ -1,78 +1,78 @@
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
const CampaignSource = {
|
const CampaignSource = {
|
||||||
MIN: 1,
|
MIN: 1,
|
||||||
|
|
||||||
TEMPLATE: 1,
|
TEMPLATE: 1,
|
||||||
CUSTOM: 2,
|
CUSTOM: 2,
|
||||||
CUSTOM_FROM_TEMPLATE: 3,
|
CUSTOM_FROM_TEMPLATE: 3,
|
||||||
CUSTOM_FROM_CAMPAIGN: 4,
|
CUSTOM_FROM_CAMPAIGN: 4,
|
||||||
URL: 5,
|
URL: 5,
|
||||||
|
|
||||||
MAX: 5
|
MAX: 5
|
||||||
};
|
};
|
||||||
|
|
||||||
const CampaignType = {
|
const CampaignType = {
|
||||||
MIN: 1,
|
MIN: 1,
|
||||||
|
|
||||||
REGULAR: 1,
|
REGULAR: 1,
|
||||||
RSS: 2,
|
RSS: 2,
|
||||||
RSS_ENTRY: 3,
|
RSS_ENTRY: 3,
|
||||||
TRIGGERED: 4,
|
TRIGGERED: 4,
|
||||||
|
|
||||||
MAX: 4
|
MAX: 4
|
||||||
};
|
};
|
||||||
|
|
||||||
const CampaignStatus = {
|
const CampaignStatus = {
|
||||||
MIN: 1,
|
MIN: 1,
|
||||||
|
|
||||||
// For campaign types: NORMAL, RSS_ENTRY
|
// For campaign types: NORMAL, RSS_ENTRY
|
||||||
IDLE: 1,
|
IDLE: 1,
|
||||||
SCHEDULED: 2,
|
SCHEDULED: 2,
|
||||||
FINISHED: 3,
|
FINISHED: 3,
|
||||||
PAUSED: 4,
|
PAUSED: 4,
|
||||||
|
|
||||||
// For campaign types: RSS, TRIGGERED
|
// For campaign types: RSS, TRIGGERED
|
||||||
INACTIVE: 5,
|
INACTIVE: 5,
|
||||||
ACTIVE: 6,
|
ACTIVE: 6,
|
||||||
|
|
||||||
// For campaign types: NORMAL, RSS_ENTRY
|
// For campaign types: NORMAL, RSS_ENTRY
|
||||||
SENDING: 7,
|
SENDING: 7,
|
||||||
|
|
||||||
MAX: 8
|
MAX: 8
|
||||||
};
|
};
|
||||||
|
|
||||||
const campaignOverridables = ['from_name', 'from_email', 'reply_to', 'subject'];
|
const campaignOverridables = ['from_name', 'from_email', 'reply_to', 'subject'];
|
||||||
|
|
||||||
function getSendConfigurationPermissionRequiredForSend(campaign, sendConfiguration) {
|
function getSendConfigurationPermissionRequiredForSend(campaign, sendConfiguration) {
|
||||||
let allowedOverride = false;
|
let allowedOverride = false;
|
||||||
let disallowedOverride = false;
|
let disallowedOverride = false;
|
||||||
|
|
||||||
for (const overridable of campaignOverridables) {
|
for (const overridable of campaignOverridables) {
|
||||||
if (campaign[overridable + '_override'] !== null) {
|
if (campaign[overridable + '_override'] !== null) {
|
||||||
if (sendConfiguration[overridable + '_overridable']) {
|
if (sendConfiguration[overridable + '_overridable']) {
|
||||||
allowedOverride = true;
|
allowedOverride = true;
|
||||||
} else {
|
} else {
|
||||||
disallowedOverride = true;
|
disallowedOverride = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let requiredPermission = 'sendWithoutOverrides';
|
let requiredPermission = 'sendWithoutOverrides';
|
||||||
if (allowedOverride) {
|
if (allowedOverride) {
|
||||||
requiredPermission = 'sendWithAllowedOverrides';
|
requiredPermission = 'sendWithAllowedOverrides';
|
||||||
}
|
}
|
||||||
if (disallowedOverride) {
|
if (disallowedOverride) {
|
||||||
requiredPermission = 'sendWithAnyOverrides';
|
requiredPermission = 'sendWithAnyOverrides';
|
||||||
}
|
}
|
||||||
|
|
||||||
return requiredPermission;
|
return requiredPermission;
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
CampaignSource,
|
CampaignSource,
|
||||||
CampaignType,
|
CampaignType,
|
||||||
CampaignStatus,
|
CampaignStatus,
|
||||||
campaignOverridables,
|
campaignOverridables,
|
||||||
getSendConfigurationPermissionRequiredForSend
|
getSendConfigurationPermissionRequiredForSend
|
||||||
};
|
};
|
148
shared/date.js
148
shared/date.js
|
@ -1,75 +1,75 @@
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
const moment = require('moment');
|
const moment = require('moment');
|
||||||
|
|
||||||
const birthdayYear = 2000;
|
const birthdayYear = 2000;
|
||||||
|
|
||||||
const DateFormat = {
|
const DateFormat = {
|
||||||
US: 'us',
|
US: 'us',
|
||||||
EU: 'eur',
|
EU: 'eur',
|
||||||
INTL: 'intl'
|
INTL: 'intl'
|
||||||
};
|
};
|
||||||
|
|
||||||
const dateFormatStrings = {
|
const dateFormatStrings = {
|
||||||
'us': 'MM/DD/YYYY',
|
'us': 'MM/DD/YYYY',
|
||||||
'eur': 'DD/MM/YYYY',
|
'eur': 'DD/MM/YYYY',
|
||||||
'intl': 'YYYY-MM-DD'
|
'intl': 'YYYY-MM-DD'
|
||||||
};
|
};
|
||||||
|
|
||||||
const birthdayFormatStrings = {
|
const birthdayFormatStrings = {
|
||||||
'us': 'MM/DD',
|
'us': 'MM/DD',
|
||||||
'eur': 'DD/MM',
|
'eur': 'DD/MM',
|
||||||
'intl': 'MM/DD'
|
'intl': 'MM/DD'
|
||||||
};
|
};
|
||||||
|
|
||||||
function parseDate(format, text) {
|
function parseDate(format, text) {
|
||||||
const date = moment.utc(text, dateFormatStrings[format]);
|
const date = moment.utc(text, dateFormatStrings[format]);
|
||||||
|
|
||||||
if (date.isValid()) {
|
if (date.isValid()) {
|
||||||
return date.toDate();
|
return date.toDate();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function parseBirthday(format, text) {
|
function parseBirthday(format, text) {
|
||||||
const fullDateStr = format === DateFormat.INTL ? birthdayYear + '-' + text : text + '-' + birthdayYear;
|
const fullDateStr = format === DateFormat.INTL ? birthdayYear + '-' + text : text + '-' + birthdayYear;
|
||||||
const date = moment.utc(fullDateStr, dateFormatStrings[format]);
|
const date = moment.utc(fullDateStr, dateFormatStrings[format]);
|
||||||
|
|
||||||
if (date.isValid()) {
|
if (date.isValid()) {
|
||||||
return date.toDate();
|
return date.toDate();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function formatDate(format, date) {
|
function formatDate(format, date) {
|
||||||
if (date === null) {
|
if (date === null) {
|
||||||
return '';
|
return '';
|
||||||
} else {
|
} else {
|
||||||
return moment.utc(date).format(dateFormatStrings[format]);
|
return moment.utc(date).format(dateFormatStrings[format]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function formatBirthday(format, date) {
|
function formatBirthday(format, date) {
|
||||||
if (date === null) {
|
if (date === null) {
|
||||||
return '';
|
return '';
|
||||||
} else {
|
} else {
|
||||||
return moment.utc(date).format(birthdayFormatStrings[format]);
|
return moment.utc(date).format(birthdayFormatStrings[format]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function getDateFormatString(format) {
|
function getDateFormatString(format) {
|
||||||
return dateFormatStrings[format];
|
return dateFormatStrings[format];
|
||||||
}
|
}
|
||||||
|
|
||||||
function getBirthdayFormatString(format) {
|
function getBirthdayFormatString(format) {
|
||||||
return birthdayFormatStrings[format];
|
return birthdayFormatStrings[format];
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
DateFormat,
|
DateFormat,
|
||||||
birthdayYear,
|
birthdayYear,
|
||||||
parseDate,
|
parseDate,
|
||||||
parseBirthday,
|
parseBirthday,
|
||||||
formatDate,
|
formatDate,
|
||||||
formatBirthday,
|
formatBirthday,
|
||||||
getDateFormatString,
|
getDateFormatString,
|
||||||
getBirthdayFormatString
|
getBirthdayFormatString
|
||||||
};
|
};
|
|
@ -1,82 +1,82 @@
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
const ImportSource = {
|
const ImportSource = {
|
||||||
MIN: 0,
|
MIN: 0,
|
||||||
|
|
||||||
CSV_FILE: 0,
|
CSV_FILE: 0,
|
||||||
LIST: 1,
|
LIST: 1,
|
||||||
|
|
||||||
MAX: 1
|
MAX: 1
|
||||||
};
|
};
|
||||||
|
|
||||||
const MappingType = {
|
const MappingType = {
|
||||||
MIN: 0,
|
MIN: 0,
|
||||||
|
|
||||||
BASIC_SUBSCRIBE: 0,
|
BASIC_SUBSCRIBE: 0,
|
||||||
BASIC_UNSUBSCRIBE: 1,
|
BASIC_UNSUBSCRIBE: 1,
|
||||||
|
|
||||||
MAX: 1
|
MAX: 1
|
||||||
};
|
};
|
||||||
|
|
||||||
const ImportStatus = {
|
const ImportStatus = {
|
||||||
PREP_SCHEDULED: 0,
|
PREP_SCHEDULED: 0,
|
||||||
PREP_RUNNING: 1,
|
PREP_RUNNING: 1,
|
||||||
PREP_STOPPING: 2,
|
PREP_STOPPING: 2,
|
||||||
PREP_FINISHED: 3,
|
PREP_FINISHED: 3,
|
||||||
PREP_FAILED: 4,
|
PREP_FAILED: 4,
|
||||||
|
|
||||||
RUN_SCHEDULED: 5,
|
RUN_SCHEDULED: 5,
|
||||||
RUN_RUNNING: 6,
|
RUN_RUNNING: 6,
|
||||||
RUN_STOPPING: 7,
|
RUN_STOPPING: 7,
|
||||||
RUN_FINISHED: 8,
|
RUN_FINISHED: 8,
|
||||||
RUN_FAILED: 9
|
RUN_FAILED: 9
|
||||||
};
|
};
|
||||||
|
|
||||||
const RunStatus = {
|
const RunStatus = {
|
||||||
SCHEDULED: 0,
|
SCHEDULED: 0,
|
||||||
RUNNING: 1,
|
RUNNING: 1,
|
||||||
STOPPING: 2,
|
STOPPING: 2,
|
||||||
FINISHED: 3,
|
FINISHED: 3,
|
||||||
FAILED: 4
|
FAILED: 4
|
||||||
};
|
};
|
||||||
|
|
||||||
function prepInProgress(status) {
|
function prepInProgress(status) {
|
||||||
return status === ImportStatus.PREP_SCHEDULED || status === ImportStatus.PREP_RUNNING || status === ImportStatus.PREP_STOPPING;
|
return status === ImportStatus.PREP_SCHEDULED || status === ImportStatus.PREP_RUNNING || status === ImportStatus.PREP_STOPPING;
|
||||||
}
|
}
|
||||||
|
|
||||||
function runInProgress(status) {
|
function runInProgress(status) {
|
||||||
return status === ImportStatus.RUN_SCHEDULED || status === ImportStatus.RUN_RUNNING || status === ImportStatus.RUN_STOPPING;
|
return status === ImportStatus.RUN_SCHEDULED || status === ImportStatus.RUN_RUNNING || status === ImportStatus.RUN_STOPPING;
|
||||||
}
|
}
|
||||||
|
|
||||||
function inProgress(status) {
|
function inProgress(status) {
|
||||||
return status === ImportStatus.PREP_SCHEDULED || status === ImportStatus.PREP_RUNNING || status === ImportStatus.PREP_STOPPING ||
|
return status === ImportStatus.PREP_SCHEDULED || status === ImportStatus.PREP_RUNNING || status === ImportStatus.PREP_STOPPING ||
|
||||||
status === ImportStatus.RUN_SCHEDULED || status === ImportStatus.RUN_RUNNING || status === ImportStatus.RUN_STOPPING;
|
status === ImportStatus.RUN_SCHEDULED || status === ImportStatus.RUN_RUNNING || status === ImportStatus.RUN_STOPPING;
|
||||||
}
|
}
|
||||||
|
|
||||||
function prepFinished(status) {
|
function prepFinished(status) {
|
||||||
return status === ImportStatus.PREP_FINISHED ||
|
return status === ImportStatus.PREP_FINISHED ||
|
||||||
status === ImportStatus.RUN_SCHEDULED || status === ImportStatus.RUN_RUNNING || status === ImportStatus.RUN_STOPPING ||
|
status === ImportStatus.RUN_SCHEDULED || status === ImportStatus.RUN_RUNNING || status === ImportStatus.RUN_STOPPING ||
|
||||||
status === ImportStatus.RUN_FINISHED || status === ImportStatus.RUN_FAILED;
|
status === ImportStatus.RUN_FINISHED || status === ImportStatus.RUN_FAILED;
|
||||||
}
|
}
|
||||||
|
|
||||||
function prepFinishedAndNotInProgress(status) {
|
function prepFinishedAndNotInProgress(status) {
|
||||||
return status === ImportStatus.PREP_FINISHED ||
|
return status === ImportStatus.PREP_FINISHED ||
|
||||||
status === ImportStatus.RUN_FINISHED || status === ImportStatus.RUN_FAILED;
|
status === ImportStatus.RUN_FINISHED || status === ImportStatus.RUN_FAILED;
|
||||||
}
|
}
|
||||||
|
|
||||||
function runStatusInProgress(status) {
|
function runStatusInProgress(status) {
|
||||||
return status === RunStatus.SCHEDULED || status === RunStatus.RUNNING || status === RunStatus.STOPPING;
|
return status === RunStatus.SCHEDULED || status === RunStatus.RUNNING || status === RunStatus.STOPPING;
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
ImportSource,
|
ImportSource,
|
||||||
MappingType,
|
MappingType,
|
||||||
ImportStatus,
|
ImportStatus,
|
||||||
RunStatus,
|
RunStatus,
|
||||||
prepInProgress,
|
prepInProgress,
|
||||||
runInProgress,
|
runInProgress,
|
||||||
prepFinished,
|
prepFinished,
|
||||||
prepFinishedAndNotInProgress,
|
prepFinishedAndNotInProgress,
|
||||||
inProgress,
|
inProgress,
|
||||||
runStatusInProgress
|
runStatusInProgress
|
||||||
};
|
};
|
|
@ -1,150 +1,150 @@
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
class InteroperableError extends Error {
|
class InteroperableError extends Error {
|
||||||
constructor(type, msg, data) {
|
constructor(type, msg, data) {
|
||||||
super(msg);
|
super(msg);
|
||||||
this.type = type;
|
this.type = type;
|
||||||
this.data = data;
|
this.data = data;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class NotLoggedInError extends InteroperableError {
|
class NotLoggedInError extends InteroperableError {
|
||||||
constructor(msg, data) {
|
constructor(msg, data) {
|
||||||
super('NotLoggedInError', msg, data);
|
super('NotLoggedInError', msg, data);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class ChangedError extends InteroperableError {
|
class ChangedError extends InteroperableError {
|
||||||
constructor(msg, data) {
|
constructor(msg, data) {
|
||||||
super('ChangedError', msg, data);
|
super('ChangedError', msg, data);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class NotFoundError extends InteroperableError {
|
class NotFoundError extends InteroperableError {
|
||||||
constructor(msg, data) {
|
constructor(msg, data) {
|
||||||
super('NotFoundError', msg || 'Not Found', data);
|
super('NotFoundError', msg || 'Not Found', data);
|
||||||
this.status = 404;
|
this.status = 404;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class LoopDetectedError extends InteroperableError {
|
class LoopDetectedError extends InteroperableError {
|
||||||
constructor(msg, data) {
|
constructor(msg, data) {
|
||||||
super('LoopDetectedError', msg, data);
|
super('LoopDetectedError', msg, data);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class DuplicitNameError extends InteroperableError {
|
class DuplicitNameError extends InteroperableError {
|
||||||
constructor(msg, data) {
|
constructor(msg, data) {
|
||||||
super('DuplicitNameError', msg, data);
|
super('DuplicitNameError', msg, data);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class DuplicitEmailError extends InteroperableError {
|
class DuplicitEmailError extends InteroperableError {
|
||||||
constructor(msg, data) {
|
constructor(msg, data) {
|
||||||
super('DuplicitEmailError', msg, data);
|
super('DuplicitEmailError', msg, data);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class DuplicitKeyError extends InteroperableError {
|
class DuplicitKeyError extends InteroperableError {
|
||||||
constructor(msg, data) {
|
constructor(msg, data) {
|
||||||
super('DuplicitKeyError', msg, data);
|
super('DuplicitKeyError', msg, data);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class IncorrectPasswordError extends InteroperableError {
|
class IncorrectPasswordError extends InteroperableError {
|
||||||
constructor(msg, data) {
|
constructor(msg, data) {
|
||||||
super('IncorrectPasswordError', msg, data);
|
super('IncorrectPasswordError', msg, data);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class InvalidTokenError extends InteroperableError {
|
class InvalidTokenError extends InteroperableError {
|
||||||
constructor(msg, data) {
|
constructor(msg, data) {
|
||||||
super('InvalidTokenError', msg, data);
|
super('InvalidTokenError', msg, data);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class DependencyNotFoundError extends InteroperableError {
|
class DependencyNotFoundError extends InteroperableError {
|
||||||
constructor(msg, data) {
|
constructor(msg, data) {
|
||||||
super('DependencyNotFoundError', msg, data);
|
super('DependencyNotFoundError', msg, data);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class NamespaceNotFoundError extends InteroperableError {
|
class NamespaceNotFoundError extends InteroperableError {
|
||||||
constructor(msg, data) {
|
constructor(msg, data) {
|
||||||
super('NamespaceNotFoundError', msg, data);
|
super('NamespaceNotFoundError', msg, data);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class PermissionDeniedError extends InteroperableError {
|
class PermissionDeniedError extends InteroperableError {
|
||||||
constructor(msg, data) {
|
constructor(msg, data) {
|
||||||
super('PermissionDeniedError', msg || 'Permission Denied', data);
|
super('PermissionDeniedError', msg || 'Permission Denied', data);
|
||||||
this.status = 403;
|
this.status = 403;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class InvalidConfirmationForSubscriptionError extends InteroperableError {
|
class InvalidConfirmationForSubscriptionError extends InteroperableError {
|
||||||
constructor(msg, data) {
|
constructor(msg, data) {
|
||||||
super('InvalidConfirmationForSubscriptionError', msg, data);
|
super('InvalidConfirmationForSubscriptionError', msg, data);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class InvalidConfirmationForAddressChangeError extends InteroperableError {
|
class InvalidConfirmationForAddressChangeError extends InteroperableError {
|
||||||
constructor(msg, data) {
|
constructor(msg, data) {
|
||||||
super('InvalidConfirmationForAddressChangeError', msg, data);
|
super('InvalidConfirmationForAddressChangeError', msg, data);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class InvalidConfirmationForUnsubscriptionError extends InteroperableError {
|
class InvalidConfirmationForUnsubscriptionError extends InteroperableError {
|
||||||
constructor(msg, data) {
|
constructor(msg, data) {
|
||||||
super('InvalidConfirmationForUnsubscriptionError', msg, data);
|
super('InvalidConfirmationForUnsubscriptionError', msg, data);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class DependencyPresentError extends InteroperableError {
|
class DependencyPresentError extends InteroperableError {
|
||||||
constructor(msg, data) {
|
constructor(msg, data) {
|
||||||
super('DependencyPresentError', msg, data);
|
super('DependencyPresentError', msg, data);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class InvalidStateError extends InteroperableError {
|
class InvalidStateError extends InteroperableError {
|
||||||
constructor(msg, data) {
|
constructor(msg, data) {
|
||||||
super('InvalidStateError', msg, data);
|
super('InvalidStateError', msg, data);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
const errorTypes = {
|
const errorTypes = {
|
||||||
InteroperableError,
|
InteroperableError,
|
||||||
NotLoggedInError,
|
NotLoggedInError,
|
||||||
ChangedError,
|
ChangedError,
|
||||||
NotFoundError,
|
NotFoundError,
|
||||||
LoopDetectedError,
|
LoopDetectedError,
|
||||||
DuplicitNameError,
|
DuplicitNameError,
|
||||||
DuplicitEmailError,
|
DuplicitEmailError,
|
||||||
DuplicitKeyError,
|
DuplicitKeyError,
|
||||||
IncorrectPasswordError,
|
IncorrectPasswordError,
|
||||||
InvalidTokenError,
|
InvalidTokenError,
|
||||||
DependencyNotFoundError,
|
DependencyNotFoundError,
|
||||||
NamespaceNotFoundError,
|
NamespaceNotFoundError,
|
||||||
PermissionDeniedError,
|
PermissionDeniedError,
|
||||||
InvalidConfirmationForSubscriptionError,
|
InvalidConfirmationForSubscriptionError,
|
||||||
InvalidConfirmationForAddressChangeError,
|
InvalidConfirmationForAddressChangeError,
|
||||||
InvalidConfirmationForUnsubscriptionError,
|
InvalidConfirmationForUnsubscriptionError,
|
||||||
DependencyPresentError,
|
DependencyPresentError,
|
||||||
InvalidStateError
|
InvalidStateError
|
||||||
};
|
};
|
||||||
|
|
||||||
function deserialize(errorObj) {
|
function deserialize(errorObj) {
|
||||||
if (errorObj.type) {
|
if (errorObj.type) {
|
||||||
const ctor = errorTypes[errorObj.type];
|
const ctor = errorTypes[errorObj.type];
|
||||||
if (ctor) {
|
if (ctor) {
|
||||||
return new ctor(errorObj.message, errorObj.data);
|
return new ctor(errorObj.message, errorObj.data);
|
||||||
} else {
|
} else {
|
||||||
console.log('Warning unknown type of interoperable error: ' + errorObj.type);
|
console.log('Warning unknown type of interoperable error: ' + errorObj.type);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = Object.assign({}, errorTypes, {
|
module.exports = Object.assign({}, errorTypes, {
|
||||||
deserialize
|
deserialize
|
||||||
});
|
});
|
130
shared/langs.js
130
shared/langs.js
|
@ -1,66 +1,66 @@
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
function convertToFake(dict) {
|
function convertToFake(dict) {
|
||||||
function convertValueToFakeLang(str) {
|
function convertValueToFakeLang(str) {
|
||||||
let from = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!@#$%^&*()-_=+\\|`~[{]};:'\",<.>/?";
|
let from = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!@#$%^&*()-_=+\\|`~[{]};:'\",<.>/?";
|
||||||
let to = "ɐqɔpǝɟƃɥıɾʞʅɯuodbɹsʇnʌʍxʎz∀ԐↃᗡƎℲ⅁HIſӼ⅂WNOԀÒᴚS⊥∩ɅMX⅄Z0123456789¡@#$%ᵥ⅋⁎()-_=+\\|,~[{]};:,„´<.>/¿";
|
let to = "ɐqɔpǝɟƃɥıɾʞʅɯuodbɹsʇnʌʍxʎz∀ԐↃᗡƎℲ⅁HIſӼ⅂WNOԀÒᴚS⊥∩ɅMX⅄Z0123456789¡@#$%ᵥ⅋⁎()-_=+\\|,~[{]};:,„´<.>/¿";
|
||||||
|
|
||||||
return str.replace(/(\{\{[^\}]+\}\}|%s)/g, '\x00\x04$1\x00').split('\x00').map(c => {
|
return str.replace(/(\{\{[^\}]+\}\}|%s)/g, '\x00\x04$1\x00').split('\x00').map(c => {
|
||||||
if (c.charAt(0) === '\x04') {
|
if (c.charAt(0) === '\x04') {
|
||||||
return c;
|
return c;
|
||||||
}
|
}
|
||||||
let r = '';
|
let r = '';
|
||||||
for (let i = 0, len = c.length; i < len; i++) {
|
for (let i = 0, len = c.length; i < len; i++) {
|
||||||
let pos = from.indexOf(c.charAt(i));
|
let pos = from.indexOf(c.charAt(i));
|
||||||
if (pos < 0) {
|
if (pos < 0) {
|
||||||
r += c.charAt(i);
|
r += c.charAt(i);
|
||||||
} else {
|
} else {
|
||||||
r += to.charAt(pos);
|
r += to.charAt(pos);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return r;
|
return r;
|
||||||
}).join('\x00').replace(/[\x00\x04]/g, '');
|
}).join('\x00').replace(/[\x00\x04]/g, '');
|
||||||
}
|
}
|
||||||
|
|
||||||
function _convertToFake(dict, fakeDict) {
|
function _convertToFake(dict, fakeDict) {
|
||||||
for (const key in dict) {
|
for (const key in dict) {
|
||||||
const val = dict[key];
|
const val = dict[key];
|
||||||
|
|
||||||
if (typeof val === 'string') {
|
if (typeof val === 'string') {
|
||||||
fakeDict[key] = convertValueToFakeLang(val);
|
fakeDict[key] = convertValueToFakeLang(val);
|
||||||
} else {
|
} else {
|
||||||
fakeDict[key] = _convertToFake(val, {});
|
fakeDict[key] = _convertToFake(val, {});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return fakeDict;
|
return fakeDict;
|
||||||
}
|
}
|
||||||
|
|
||||||
return _convertToFake(dict, {});
|
return _convertToFake(dict, {});
|
||||||
}
|
}
|
||||||
|
|
||||||
// The langugage labels below are intentionally not localized so that they are always native in the langugae of their speaker (regardless of the currently selected language)
|
// The langugage labels below are intentionally not localized so that they are always native in the langugae of their speaker (regardless of the currently selected language)
|
||||||
const langCodes = {
|
const langCodes = {
|
||||||
'en-US': {
|
'en-US': {
|
||||||
getShortLabel: t => 'EN',
|
getShortLabel: t => 'EN',
|
||||||
getLabel: t => 'English',
|
getLabel: t => 'English',
|
||||||
longCode: 'en-US'
|
longCode: 'en-US'
|
||||||
},
|
},
|
||||||
'es-ES': {
|
'es-ES': {
|
||||||
getShortLabel: t => 'ES',
|
getShortLabel: t => 'ES',
|
||||||
getLabel: t => 'Español',
|
getLabel: t => 'Español',
|
||||||
longCode: 'es-ES'
|
longCode: 'es-ES'
|
||||||
},
|
},
|
||||||
'fk-FK': {
|
'fk-FK': {
|
||||||
getShortLabel: t => 'FK',
|
getShortLabel: t => 'FK',
|
||||||
getLabel: t => 'Fake',
|
getLabel: t => 'Fake',
|
||||||
longCode: 'fk-FK'
|
longCode: 'fk-FK'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function getLang(lng) {
|
function getLang(lng) {
|
||||||
return langCodes[lng];
|
return langCodes[lng];
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports.convertToFake = convertToFake;
|
module.exports.convertToFake = convertToFake;
|
||||||
module.exports.getLang = getLang;
|
module.exports.getLang = getLang;
|
100
shared/lists.js
100
shared/lists.js
|
@ -1,51 +1,51 @@
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
const UnsubscriptionMode = {
|
const UnsubscriptionMode = {
|
||||||
MIN: 0,
|
MIN: 0,
|
||||||
|
|
||||||
ONE_STEP: 0,
|
ONE_STEP: 0,
|
||||||
ONE_STEP_WITH_FORM: 1,
|
ONE_STEP_WITH_FORM: 1,
|
||||||
TWO_STEP: 2,
|
TWO_STEP: 2,
|
||||||
TWO_STEP_WITH_FORM: 3,
|
TWO_STEP_WITH_FORM: 3,
|
||||||
MANUAL: 4,
|
MANUAL: 4,
|
||||||
|
|
||||||
MAX: 4
|
MAX: 4
|
||||||
};
|
};
|
||||||
|
|
||||||
const SubscriptionStatus = {
|
const SubscriptionStatus = {
|
||||||
MIN: 0,
|
MIN: 0,
|
||||||
|
|
||||||
SUBSCRIBED: 1,
|
SUBSCRIBED: 1,
|
||||||
UNSUBSCRIBED: 2,
|
UNSUBSCRIBED: 2,
|
||||||
BOUNCED: 3,
|
BOUNCED: 3,
|
||||||
COMPLAINED: 4,
|
COMPLAINED: 4,
|
||||||
|
|
||||||
MAX: 4
|
MAX: 4
|
||||||
};
|
};
|
||||||
|
|
||||||
const SubscriptionSource = {
|
const SubscriptionSource = {
|
||||||
ADMIN_FORM: -1,
|
ADMIN_FORM: -1,
|
||||||
SUBSCRIPTION_FORM: -2,
|
SUBSCRIPTION_FORM: -2,
|
||||||
API: -3,
|
API: -3,
|
||||||
NOT_IMPORTED_V1: -4,
|
NOT_IMPORTED_V1: -4,
|
||||||
IMPORTED_V1: -5,
|
IMPORTED_V1: -5,
|
||||||
ERASED: -6
|
ERASED: -6
|
||||||
};
|
};
|
||||||
|
|
||||||
const FieldWizard = {
|
const FieldWizard = {
|
||||||
NONE: 'none',
|
NONE: 'none',
|
||||||
NAME: 'full_name',
|
NAME: 'full_name',
|
||||||
FIRST_LAST_NAME: 'first_last_name'
|
FIRST_LAST_NAME: 'first_last_name'
|
||||||
}
|
}
|
||||||
|
|
||||||
function getFieldColumn(field) {
|
function getFieldColumn(field) {
|
||||||
return field.column || 'grouped_' + field.id;
|
return field.column || 'grouped_' + field.id;
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
UnsubscriptionMode,
|
UnsubscriptionMode,
|
||||||
SubscriptionStatus,
|
SubscriptionStatus,
|
||||||
SubscriptionSource,
|
SubscriptionSource,
|
||||||
FieldWizard,
|
FieldWizard,
|
||||||
getFieldColumn
|
getFieldColumn
|
||||||
};
|
};
|
File diff suppressed because it is too large
Load diff
|
@ -1,9 +1,9 @@
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
function getGlobalNamespaceId() {
|
function getGlobalNamespaceId() {
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
getGlobalNamespaceId
|
getGlobalNamespaceId
|
||||||
};
|
};
|
|
@ -1,37 +1,37 @@
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
const util = require('util');
|
const util = require('util');
|
||||||
const owaspPasswordStrengthTest = require('owasp-password-strength-test');
|
const owaspPasswordStrengthTest = require('owasp-password-strength-test');
|
||||||
|
|
||||||
function passwordValidator(t) {
|
function passwordValidator(t) {
|
||||||
const config = {
|
const config = {
|
||||||
allowPassphrases: true,
|
allowPassphrases: true,
|
||||||
maxLength: 128,
|
maxLength: 128,
|
||||||
minLength: 10,
|
minLength: 10,
|
||||||
minPhraseLength: 20,
|
minPhraseLength: 20,
|
||||||
minOptionalTestsToPass: 4
|
minOptionalTestsToPass: 4
|
||||||
};
|
};
|
||||||
|
|
||||||
if (t) {
|
if (t) {
|
||||||
config.translate = {
|
config.translate = {
|
||||||
minLength: function (minLength) {
|
minLength: function (minLength) {
|
||||||
return t('thePasswordMustBeAtLeastMinLength', { minLength });
|
return t('thePasswordMustBeAtLeastMinLength', { minLength });
|
||||||
},
|
},
|
||||||
maxLength: function (maxLength) {
|
maxLength: function (maxLength) {
|
||||||
return t('thePasswordMustBeFewerThanMaxLength', { maxLength });
|
return t('thePasswordMustBeFewerThanMaxLength', { maxLength });
|
||||||
},
|
},
|
||||||
repeat: t('thePasswordMayNotContainSequencesOfThree'),
|
repeat: t('thePasswordMayNotContainSequencesOfThree'),
|
||||||
lowercase: t('thePasswordMustContainAtLeastOne'),
|
lowercase: t('thePasswordMustContainAtLeastOne'),
|
||||||
uppercase: t('thePasswordMustContainAtLeastOne-1'),
|
uppercase: t('thePasswordMustContainAtLeastOne-1'),
|
||||||
number: t('thePasswordMustContainAtLeastOneNumber'),
|
number: t('thePasswordMustContainAtLeastOneNumber'),
|
||||||
special: t('thePasswordMustContainAtLeastOneSpecial')
|
special: t('thePasswordMustContainAtLeastOneSpecial')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const passwordValidator = owaspPasswordStrengthTest.create();
|
const passwordValidator = owaspPasswordStrengthTest.create();
|
||||||
passwordValidator.config(config);
|
passwordValidator.config(config);
|
||||||
|
|
||||||
return passwordValidator;
|
return passwordValidator;
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = passwordValidator;
|
module.exports = passwordValidator;
|
|
@ -1,16 +1,16 @@
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
const ReportState = {
|
const ReportState = {
|
||||||
MIN: 0,
|
MIN: 0,
|
||||||
|
|
||||||
SCHEDULED: 0,
|
SCHEDULED: 0,
|
||||||
PROCESSING: 1,
|
PROCESSING: 1,
|
||||||
FINISHED: 2,
|
FINISHED: 2,
|
||||||
FAILED: 3,
|
FAILED: 3,
|
||||||
|
|
||||||
MAX: 3
|
MAX: 3
|
||||||
};
|
};
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
ReportState
|
ReportState
|
||||||
};
|
};
|
|
@ -1,29 +1,29 @@
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
const MailerType = {
|
const MailerType = {
|
||||||
GENERIC_SMTP: 'generic_smtp',
|
GENERIC_SMTP: 'generic_smtp',
|
||||||
ZONE_MTA: 'zone_mta',
|
ZONE_MTA: 'zone_mta',
|
||||||
AWS_SES: 'aws_ses'
|
AWS_SES: 'aws_ses'
|
||||||
};
|
};
|
||||||
|
|
||||||
const ZoneMTAType = {
|
const ZoneMTAType = {
|
||||||
REGULAR: 0,
|
REGULAR: 0,
|
||||||
WITH_HTTP_CONF: 1,
|
WITH_HTTP_CONF: 1,
|
||||||
WITH_MAILTRAIN_HEADER_CONF: 2,
|
WITH_MAILTRAIN_HEADER_CONF: 2,
|
||||||
BUILTIN: 3
|
BUILTIN: 3
|
||||||
}
|
}
|
||||||
|
|
||||||
function getSystemSendConfigurationId() {
|
function getSystemSendConfigurationId() {
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
function getSystemSendConfigurationCid() {
|
function getSystemSendConfigurationCid() {
|
||||||
return 'system';
|
return 'system';
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
MailerType,
|
MailerType,
|
||||||
ZoneMTAType,
|
ZoneMTAType,
|
||||||
getSystemSendConfigurationId,
|
getSystemSendConfigurationId,
|
||||||
getSystemSendConfigurationCid
|
getSystemSendConfigurationCid
|
||||||
};
|
};
|
|
@ -1,62 +1,62 @@
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
function _getBases(trustedBaseUrl, sandboxBaseUrl, publicBaseUrl) {
|
function _getBases(trustedBaseUrl, sandboxBaseUrl, publicBaseUrl) {
|
||||||
if (trustedBaseUrl.endsWith('/')) {
|
if (trustedBaseUrl.endsWith('/')) {
|
||||||
trustedBaseUrl = trustedBaseUrl.substring(0, trustedBaseUrl.length - 1);
|
trustedBaseUrl = trustedBaseUrl.substring(0, trustedBaseUrl.length - 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (sandboxBaseUrl.endsWith('/')) {
|
if (sandboxBaseUrl.endsWith('/')) {
|
||||||
sandboxBaseUrl = sandboxBaseUrl.substring(0, sandboxBaseUrl.length - 1);
|
sandboxBaseUrl = sandboxBaseUrl.substring(0, sandboxBaseUrl.length - 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (publicBaseUrl.endsWith('/')) {
|
if (publicBaseUrl.endsWith('/')) {
|
||||||
publicBaseUrl = publicBaseUrl.substring(0, publicBaseUrl.length - 1);
|
publicBaseUrl = publicBaseUrl.substring(0, publicBaseUrl.length - 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
return {trustedBaseUrl, sandboxBaseUrl, publicBaseUrl};
|
return {trustedBaseUrl, sandboxBaseUrl, publicBaseUrl};
|
||||||
}
|
}
|
||||||
|
|
||||||
function getMergeTagsForBases(trustedBaseUrl, sandboxBaseUrl, publicBaseUrl) {
|
function getMergeTagsForBases(trustedBaseUrl, sandboxBaseUrl, publicBaseUrl) {
|
||||||
const bases = _getBases(trustedBaseUrl, sandboxBaseUrl, publicBaseUrl);
|
const bases = _getBases(trustedBaseUrl, sandboxBaseUrl, publicBaseUrl);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
URL_BASE: bases.publicBaseUrl,
|
URL_BASE: bases.publicBaseUrl,
|
||||||
TRUSTED_URL_BASE: bases.trustedBaseUrl,
|
TRUSTED_URL_BASE: bases.trustedBaseUrl,
|
||||||
SANDBOX_URL_BASE: bases.sandboxBaseUrl,
|
SANDBOX_URL_BASE: bases.sandboxBaseUrl,
|
||||||
ENCODED_URL_BASE: encodeURIComponent(bases.publicBaseUrl),
|
ENCODED_URL_BASE: encodeURIComponent(bases.publicBaseUrl),
|
||||||
ENCODED_TRUSTED_URL_BASE: encodeURIComponent(bases.trustedBaseUrl),
|
ENCODED_TRUSTED_URL_BASE: encodeURIComponent(bases.trustedBaseUrl),
|
||||||
ENCODED_SANDBOX_URL_BASE: encodeURIComponent(bases.sandboxBaseUrl)
|
ENCODED_SANDBOX_URL_BASE: encodeURIComponent(bases.sandboxBaseUrl)
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function base(text, trustedBaseUrl, sandboxBaseUrl, publicBaseUrl) {
|
function base(text, trustedBaseUrl, sandboxBaseUrl, publicBaseUrl) {
|
||||||
const bases = _getBases(trustedBaseUrl, sandboxBaseUrl, publicBaseUrl);
|
const bases = _getBases(trustedBaseUrl, sandboxBaseUrl, publicBaseUrl);
|
||||||
|
|
||||||
text = text.split('[URL_BASE]').join(bases.publicBaseUrl);
|
text = text.split('[URL_BASE]').join(bases.publicBaseUrl);
|
||||||
text = text.split('[TRUSTED_URL_BASE]').join(bases.trustedBaseUrl);
|
text = text.split('[TRUSTED_URL_BASE]').join(bases.trustedBaseUrl);
|
||||||
text = text.split('[SANDBOX_URL_BASE]').join(bases.sandboxBaseUrl);
|
text = text.split('[SANDBOX_URL_BASE]').join(bases.sandboxBaseUrl);
|
||||||
text = text.split('[ENCODED_URL_BASE]').join(encodeURIComponent(bases.publicBaseUrl));
|
text = text.split('[ENCODED_URL_BASE]').join(encodeURIComponent(bases.publicBaseUrl));
|
||||||
text = text.split('[ENCODED_TRUSTED_URL_BASE]').join(encodeURIComponent(bases.trustedBaseUrl));
|
text = text.split('[ENCODED_TRUSTED_URL_BASE]').join(encodeURIComponent(bases.trustedBaseUrl));
|
||||||
text = text.split('[ENCODED_SANDBOX_URL_BASE]').join(encodeURIComponent(bases.sandboxBaseUrl));
|
text = text.split('[ENCODED_SANDBOX_URL_BASE]').join(encodeURIComponent(bases.sandboxBaseUrl));
|
||||||
|
|
||||||
return text;
|
return text;
|
||||||
}
|
}
|
||||||
|
|
||||||
function unbase(text, trustedBaseUrl, sandboxBaseUrl, publicBaseUrl, treatAllAsPublic = false) {
|
function unbase(text, trustedBaseUrl, sandboxBaseUrl, publicBaseUrl, treatAllAsPublic = false) {
|
||||||
const bases = _getBases(trustedBaseUrl, sandboxBaseUrl, publicBaseUrl);
|
const bases = _getBases(trustedBaseUrl, sandboxBaseUrl, publicBaseUrl);
|
||||||
|
|
||||||
text = text.split(bases.publicBaseUrl).join('[URL_BASE]');
|
text = text.split(bases.publicBaseUrl).join('[URL_BASE]');
|
||||||
text = text.split(bases.trustedBaseUrl).join(treatAllAsPublic ? '[URL_BASE]' : '[TRUSTED_URL_BASE]');
|
text = text.split(bases.trustedBaseUrl).join(treatAllAsPublic ? '[URL_BASE]' : '[TRUSTED_URL_BASE]');
|
||||||
text = text.split(bases.sandboxBaseUrl).join(treatAllAsPublic ? '[URL_BASE]' : '[SANDBOX_URL_BASE]');
|
text = text.split(bases.sandboxBaseUrl).join(treatAllAsPublic ? '[URL_BASE]' : '[SANDBOX_URL_BASE]');
|
||||||
text = text.split(encodeURIComponent(bases.publicBaseUrl)).join('[ENCODED_URL_BASE]');
|
text = text.split(encodeURIComponent(bases.publicBaseUrl)).join('[ENCODED_URL_BASE]');
|
||||||
text = text.split(encodeURIComponent(bases.trustedBaseUrl)).join(treatAllAsPublic ? '[ENCODED_URL_BASE]' : '[ENCODED_TRUSTED_URL_BASE]');
|
text = text.split(encodeURIComponent(bases.trustedBaseUrl)).join(treatAllAsPublic ? '[ENCODED_URL_BASE]' : '[ENCODED_TRUSTED_URL_BASE]');
|
||||||
text = text.split(encodeURIComponent(bases.sandboxBaseUrl)).join(treatAllAsPublic ? '[ENCODED_URL_BASE]' : '[ENCODED_SANDBOX_URL_BASE]');
|
text = text.split(encodeURIComponent(bases.sandboxBaseUrl)).join(treatAllAsPublic ? '[ENCODED_URL_BASE]' : '[ENCODED_SANDBOX_URL_BASE]');
|
||||||
|
|
||||||
return text;
|
return text;
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
base,
|
base,
|
||||||
unbase,
|
unbase,
|
||||||
getMergeTagsForBases
|
getMergeTagsForBases
|
||||||
};
|
};
|
|
@ -1,48 +1,48 @@
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
const Entity = {
|
const Entity = {
|
||||||
SUBSCRIPTION: 'subscription',
|
SUBSCRIPTION: 'subscription',
|
||||||
CAMPAIGN: 'campaign'
|
CAMPAIGN: 'campaign'
|
||||||
};
|
};
|
||||||
|
|
||||||
const Event = {
|
const Event = {
|
||||||
[Entity.SUBSCRIPTION]: {
|
[Entity.SUBSCRIPTION]: {
|
||||||
CREATED: 'created',
|
CREATED: 'created',
|
||||||
LATEST_OPEN: 'latest_open',
|
LATEST_OPEN: 'latest_open',
|
||||||
LATEST_CLICK: 'latest_click'
|
LATEST_CLICK: 'latest_click'
|
||||||
},
|
},
|
||||||
[Entity.CAMPAIGN]: {
|
[Entity.CAMPAIGN]: {
|
||||||
DELIVERED: 'delivered',
|
DELIVERED: 'delivered',
|
||||||
OPENED: 'opened',
|
OPENED: 'opened',
|
||||||
CLICKED: 'clicked',
|
CLICKED: 'clicked',
|
||||||
NOT_OPENED: 'not_opened',
|
NOT_OPENED: 'not_opened',
|
||||||
NOT_CLICKED: 'not_clicked'
|
NOT_CLICKED: 'not_clicked'
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const EntityVals = {
|
const EntityVals = {
|
||||||
subscription: 'SUBSCRIPTION',
|
subscription: 'SUBSCRIPTION',
|
||||||
campaign: 'CAMPAIGN'
|
campaign: 'CAMPAIGN'
|
||||||
};
|
};
|
||||||
|
|
||||||
const EventVals = {
|
const EventVals = {
|
||||||
[Entity.SUBSCRIPTION]: {
|
[Entity.SUBSCRIPTION]: {
|
||||||
created: 'CREATED',
|
created: 'CREATED',
|
||||||
latest_open: 'LATEST_OPEN',
|
latest_open: 'LATEST_OPEN',
|
||||||
latest_click: 'LATEST_CLICK'
|
latest_click: 'LATEST_CLICK'
|
||||||
},
|
},
|
||||||
[Entity.CAMPAIGN]: {
|
[Entity.CAMPAIGN]: {
|
||||||
delivered: 'DELIVERED',
|
delivered: 'DELIVERED',
|
||||||
opened: 'OPENED',
|
opened: 'OPENED',
|
||||||
clicked: 'CLICKED',
|
clicked: 'CLICKED',
|
||||||
not_opened: 'NOT_OPENED',
|
not_opened: 'NOT_OPENED',
|
||||||
not_clicked: 'NOT_CLICKED'
|
not_clicked: 'NOT_CLICKED'
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
Entity,
|
Entity,
|
||||||
Event,
|
Event,
|
||||||
EntityVals,
|
EntityVals,
|
||||||
EventVals
|
EventVals
|
||||||
};
|
};
|
|
@ -1,7 +1,7 @@
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
const anonymousRestrictedAccessToken = 'anonymous';
|
const anonymousRestrictedAccessToken = 'anonymous';
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
anonymousRestrictedAccessToken
|
anonymousRestrictedAccessToken
|
||||||
};
|
};
|
|
@ -1,9 +1,9 @@
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
function getAdminId() {
|
function getAdminId() {
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
getAdminId
|
getAdminId
|
||||||
};
|
};
|
|
@ -1,9 +1,9 @@
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
function mergeTagValid(mergeTag) {
|
function mergeTagValid(mergeTag) {
|
||||||
return /^[A-Z][A-Z0-9_]*$/.test(mergeTag);
|
return /^[A-Z][A-Z0-9_]*$/.test(mergeTag);
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
mergeTagValid
|
mergeTagValid
|
||||||
};
|
};
|
Loading…
Add table
Add a link
Reference in a new issue