Obsoleting some old files

Transition to SPA-style client
Basis for Mosaico template editor
This commit is contained in:
Tomas Bures 2018-02-25 20:54:15 +01:00
parent 7750232716
commit c85f2d4440
942 changed files with 86311 additions and 967 deletions

24
client/src/Home.js Normal file
View file

@ -0,0 +1,24 @@
'use strict';
import React, {Component} from 'react';
import {translate} from 'react-i18next';
import { requiresAuthenticatedUser } from './lib/page';
@translate()
@requiresAuthenticatedUser
export default class List extends Component {
constructor(props) {
super(props);
}
render() {
const t = this.props.t;
return (
<div>
<h2>{t('Welcome to Mailtrain...')}</h2>
<div>TODO: some dashboard</div>
</div>
);
}
}

View file

@ -59,13 +59,11 @@ export default class Login extends Component {
this.setFormStatusMessage('info', t('Verifying credentials ...'));
const submitSuccessful = await this.validateAndSendFormValuesToURL(FormSendMethod.POST, '/rest/login');
/* FIXME, once we turn Mailtrain to single-page application, we should receive authenticated config (from client-helpers.js:getAuthenticatedConfig)
as part of login response. Then we should integrate it in the mailtrainConfig global variable. */
if (submitSuccessful) {
const nextUrl = qs.parse(this.props.location.search).next || '/';
/* FIXME, once we turn Mailtrain to single-page application, this should become navigateTo */
/* This ensures we get config for the authenticated user */
window.location = nextUrl;
} else {
this.setFormStatusMessage('warning', t('Please enter your credentials and try again.'));

View file

@ -14,7 +14,7 @@ import API from './API';
import mailtrainConfig from 'mailtrainConfig';
const getStructure = t => {
function getMenus(t) {
const subPaths = {
login: {
title: t('Sign in'),
@ -45,27 +45,16 @@ const getStructure = t => {
}
return {
'': {
title: t('Home'),
externalLink: '/',
children: {
account: {
title: t('Account'),
link: '/account',
panelComponent: Account,
'account': {
title: t('Account'),
link: '/account',
panelComponent: Account,
children: subPaths
}
}
children: subPaths
}
};
}
export default function() {
ReactDOM.render(
<I18nextProvider i18n={ i18n }><Section root='/account/login' structure={getStructure}/></I18nextProvider>,
document.getElementById('root')
);
export default {
getMenus
}

View file

@ -8,27 +8,16 @@ import i18n from "../lib/i18n";
import {Section} from "../lib/page";
import List from "./List";
const getStructure = t => {
function getMenus(t) {
return {
'': {
title: t('Home'),
externalLink: '/',
children: {
'blacklist': {
title: t('Blacklist'),
link: '/blacklist',
panelComponent: List,
}
}
'blacklist': {
title: t('Blacklist'),
link: '/blacklist',
panelComponent: List,
}
}
};
export default function() {
ReactDOM.render(
<I18nextProvider i18n={ i18n }><Section root='/blacklist' structure={getStructure}/></I18nextProvider>,
document.getElementById('root')
);
};
}
export default {
getMenus
}

View file

@ -217,6 +217,12 @@ class RouteContent extends Component {
return <Redirect to={link}/>;
} else {
const primaryMenuProps = {
location: this.props.location
};
const primaryMenuComponent = React.createElement(route.primaryMenuComponent, primaryMenuProps);
if (resolved) {
const compProps = {
match: this.props.match,
@ -233,17 +239,29 @@ class RouteContent extends Component {
return (
<div>
{primaryMenuComponent}
<div>
<SecondaryNavBar className="hidden-xs pull-right" route={route} params={params} resolved={resolved}/>
<Breadcrumb route={route} params={params} resolved={resolved}/>
<SecondaryNavBar className="visible-xs" route={route} params={params} resolved={resolved}/>
</div>
{this.props.flashMessage}
{panel}
<div className="container-fluid">
{this.props.flashMessage}
{panel}
</div>
</div>
);
} else {
return <div>{t('Loading...')}</div>;
return (
<div>
{primaryMenuComponent}
<div className="container-fluid">
{t('Loading...')}
</div>
</div>
);
}
}
}
@ -257,36 +275,11 @@ class SectionContent extends Component {
super(props);
this.state = {
flashMessageText: ''
}
this.historyUnlisten = props.history.listen((location, action) => {
this.closeFlashMessage();
})
// -------------------------------------------------------------------------------------------------------
/* FIXME - remove this once we migrate fully to React
This part transforms the flash notice rendered by the server to flash notice managed by React client.
It is used primarily for the login info, but there may be some other cases.
*/
const alrt = jQuery('.container>.alert');
alrt.find('button').remove();
const alrtText = alrt.text();
if (alrtText) {
this.state.flashMessageText = alrtText;
const severityRegex = /alert-([^ ]*)/;
const match = alrt.attr('class').match(severityRegex);
if (match) {
this.state.flashMessageSeverity = match[1];
}
}
alrt.remove();
// -------------------------------------------------------------------------------------------------------
}
static propTypes = {
@ -327,14 +320,13 @@ class SectionContent extends Component {
ensureAuthenticated() {
if (!mailtrainConfig.isAuthenticated) {
/* FIXME, once we turn Mailtrain to single-page application, this should become navigateTo */
window.location = '/account/login?next=' + encodeURIComponent(this.props.root);
this.navigateTo('/account/login?next=' + encodeURIComponent(window.location.pathname));
}
}
errorHandler(error) {
if (error instanceof interoperableErrors.NotLoggedInError) {
/* FIXME, once we turn Mailtrain to single-page application, this should become navigateTo */
window.location = '/account/login?next=' + encodeURIComponent(this.props.root);
this.navigateTo('/account/login?next=' + encodeURIComponent(window.location.pathname));
} else if (error.response && error.response.data && error.response.data.message) {
console.error(error);
this.navigateToWithFlashMessage(this.props.root, 'danger', error.response.data.message);

View file

@ -6,9 +6,9 @@ import { translate } from 'react-i18next';
import PropTypes from 'prop-types';
import jQuery from 'jquery';
import '../../public/jquery/jquery-ui-1.12.1.min.js';
import '../../public/fancytree/jquery.fancytree-all.min.js';
import '../../public/fancytree/skin-bootstrap/ui.fancytree.min.css';
import '../../vendor/jquery/jquery-ui-1.12.1.min.js';
import '../../vendor/fancytree/jquery.fancytree-all.min.js';
import '../../vendor/fancytree/skin-bootstrap/ui.fancytree.min.css';
import './tree.css';
import axios from './axios';

View file

@ -20,178 +20,168 @@ import SegmentsCUD from './segments/CUD';
import Share from '../shares/Share';
const getStructure = t => {
function getMenus(t) {
return {
'': {
title: t('Home'),
externalLink: '/',
'lists': {
title: t('Lists'),
link: '/lists',
panelComponent: ListsList,
children: {
'lists': {
title: t('Lists'),
link: '/lists',
panelComponent: ListsList,
children: {
':listId([0-9]+)': {
title: resolved => t('List "{{name}}"', {name: resolved.list.name}),
':listId([0-9]+)': {
title: resolved => t('List "{{name}}"', {name: resolved.list.name}),
resolve: {
list: params => `/rest/lists/${params.listId}`
},
link: params => `/lists/${params.listId}/subscriptions`,
navs: {
subscriptions: {
title: t('Subscribers'),
resolve: {
list: params => `/rest/lists/${params.listId}`
segments: params => `/rest/segments/${params.listId}`,
},
link: params => `/lists/${params.listId}/subscriptions`,
navs: {
subscriptions: {
title: t('Subscribers'),
resolve: {
segments: params => `/rest/segments/${params.listId}`,
},
link: params => `/lists/${params.listId}/subscriptions`,
visible: resolved => resolved.list.permissions.includes('viewSubscriptions'),
panelRender: props => <SubscriptionsList list={props.resolved.list} segments={props.resolved.segments} segmentId={qs.parse(props.location.search).segment} />,
children: {
':subscriptionId([0-9]+)': {
title: resolved => resolved.subscription.email,
resolve: {
subscription: params => `/rest/subscriptions/${params.listId}/${params.subscriptionId}`,
fieldsGrouped: params => `/rest/fields-grouped/${params.listId}`
},
link: params => `/lists/${params.listId}/subscriptions/${params.subscriptionId}/edit`,
navs: {
':action(edit|delete)': {
title: t('Edit'),
link: params => `/lists/${params.listId}/subscriptions/${params.subscriptionId}/edit`,
panelRender: props => <SubscriptionsCUD action={props.match.params.action} entity={props.resolved.subscription} list={props.resolved.list} fieldsGrouped={props.resolved.fieldsGrouped} />
}
}
},
create: {
title: t('Create'),
resolve: {
fieldsGrouped: params => `/rest/fields-grouped/${params.listId}`
},
panelRender: props => <SubscriptionsCUD action="create" list={props.resolved.list} fieldsGrouped={props.resolved.fieldsGrouped} />
}
} },
':action(edit|delete)': {
title: t('Edit'),
link: params => `/lists/${params.listId}/edit`,
visible: resolved => resolved.list.permissions.includes('edit'),
panelRender: props => <ListsCUD action={props.match.params.action} entity={props.resolved.list} />
},
fields: {
title: t('Fields'),
link: params => `/lists/${params.listId}/fields/`,
visible: resolved => resolved.list.permissions.includes('manageFields'),
panelRender: props => <FieldsList list={props.resolved.list} />,
children: {
':fieldId([0-9]+)': {
title: resolved => t('Field "{{name}}"', {name: resolved.field.name}),
resolve: {
field: params => `/rest/fields/${params.listId}/${params.fieldId}`,
fields: params => `/rest/fields/${params.listId}`
},
link: params => `/lists/${params.listId}/fields/${params.fieldId}/edit`,
navs: {
':action(edit|delete)': {
title: t('Edit'),
link: params => `/lists/${params.listId}/fields/${params.fieldId}/edit`,
panelRender: props => <FieldsCUD action={props.match.params.action} entity={props.resolved.field} list={props.resolved.list} fields={props.resolved.fields} />
}
}
},
create: {
title: t('Create'),
resolve: {
fields: params => `/rest/fields/${params.listId}`
},
panelRender: props => <FieldsCUD action="create" list={props.resolved.list} fields={props.resolved.fields} />
}
}
},
segments: {
title: t('Segments'),
link: params => `/lists/${params.listId}/segments`,
visible: resolved => resolved.list.permissions.includes('manageSegments'),
panelRender: props => <SegmentsList list={props.resolved.list} />,
children: {
':segmentId([0-9]+)': {
title: resolved => t('Segment "{{name}}"', {name: resolved.segment.name}),
resolve: {
segment: params => `/rest/segments/${params.listId}/${params.segmentId}`,
fields: params => `/rest/fields/${params.listId}`
},
link: params => `/lists/${params.listId}/segments/${params.segmentId}/edit`,
navs: {
':action(edit|delete)': {
title: t('Edit'),
link: params => `/lists/${params.listId}/segments/${params.segmentId}/edit`,
panelRender: props => <SegmentsCUD action={props.match.params.action} entity={props.resolved.segment} list={props.resolved.list} fields={props.resolved.fields} />
}
}
},
create: {
title: t('Create'),
resolve: {
fields: params => `/rest/fields/${params.listId}`
},
panelRender: props => <SegmentsCUD action="create" list={props.resolved.list} fields={props.resolved.fields} />
}
}
},
share: {
title: t('Share'),
link: params => `/lists/${params.listId}/share`,
visible: resolved => resolved.list.permissions.includes('share'),
panelRender: props => <Share title={t('Share')} entity={props.resolved.list} entityTypeId="list" />
}
}
},
create: {
title: t('Create'),
panelRender: props => <ListsCUD action="create" />
},
forms: {
title: t('Custom Forms'),
link: '/lists/forms',
panelComponent: FormsList,
visible: resolved => resolved.list.permissions.includes('viewSubscriptions'),
panelRender: props => <SubscriptionsList list={props.resolved.list} segments={props.resolved.segments} segmentId={qs.parse(props.location.search).segment} />,
children: {
':formsId([0-9]+)': {
title: resolved => t('Custom Forms "{{name}}"', {name: resolved.forms.name}),
':subscriptionId([0-9]+)': {
title: resolved => resolved.subscription.email,
resolve: {
forms: params => `/rest/forms/${params.formsId}`
subscription: params => `/rest/subscriptions/${params.listId}/${params.subscriptionId}`,
fieldsGrouped: params => `/rest/fields-grouped/${params.listId}`
},
link: params => `/lists/forms/${params.formsId}/edit`,
link: params => `/lists/${params.listId}/subscriptions/${params.subscriptionId}/edit`,
navs: {
':action(edit|delete)': {
title: t('Edit'),
link: params => `/lists/forms/${params.formsId}/edit`,
visible: resolved => resolved.forms.permissions.includes('edit'),
panelRender: props => <FormsCUD action={props.match.params.action} entity={props.resolved.forms} />
},
share: {
title: t('Share'),
link: params => `/lists/forms/${params.formsId}/share`,
visible: resolved => resolved.forms.permissions.includes('share'),
panelRender: props => <Share title={t('Share')} entity={props.resolved.forms} entityTypeId="customForm" />
link: params => `/lists/${params.listId}/subscriptions/${params.subscriptionId}/edit`,
panelRender: props => <SubscriptionsCUD action={props.match.params.action} entity={props.resolved.subscription} list={props.resolved.list} fieldsGrouped={props.resolved.fieldsGrouped} />
}
}
},
create: {
title: t('Create'),
panelRender: props => <FormsCUD action="create" />
resolve: {
fieldsGrouped: params => `/rest/fields-grouped/${params.listId}`
},
panelRender: props => <SubscriptionsCUD action="create" list={props.resolved.list} fieldsGrouped={props.resolved.fieldsGrouped} />
}
} },
':action(edit|delete)': {
title: t('Edit'),
link: params => `/lists/${params.listId}/edit`,
visible: resolved => resolved.list.permissions.includes('edit'),
panelRender: props => <ListsCUD action={props.match.params.action} entity={props.resolved.list} />
},
fields: {
title: t('Fields'),
link: params => `/lists/${params.listId}/fields/`,
visible: resolved => resolved.list.permissions.includes('manageFields'),
panelRender: props => <FieldsList list={props.resolved.list} />,
children: {
':fieldId([0-9]+)': {
title: resolved => t('Field "{{name}}"', {name: resolved.field.name}),
resolve: {
field: params => `/rest/fields/${params.listId}/${params.fieldId}`,
fields: params => `/rest/fields/${params.listId}`
},
link: params => `/lists/${params.listId}/fields/${params.fieldId}/edit`,
navs: {
':action(edit|delete)': {
title: t('Edit'),
link: params => `/lists/${params.listId}/fields/${params.fieldId}/edit`,
panelRender: props => <FieldsCUD action={props.match.params.action} entity={props.resolved.field} list={props.resolved.list} fields={props.resolved.fields} />
}
}
},
create: {
title: t('Create'),
resolve: {
fields: params => `/rest/fields/${params.listId}`
},
panelRender: props => <FieldsCUD action="create" list={props.resolved.list} fields={props.resolved.fields} />
}
}
},
segments: {
title: t('Segments'),
link: params => `/lists/${params.listId}/segments`,
visible: resolved => resolved.list.permissions.includes('manageSegments'),
panelRender: props => <SegmentsList list={props.resolved.list} />,
children: {
':segmentId([0-9]+)': {
title: resolved => t('Segment "{{name}}"', {name: resolved.segment.name}),
resolve: {
segment: params => `/rest/segments/${params.listId}/${params.segmentId}`,
fields: params => `/rest/fields/${params.listId}`
},
link: params => `/lists/${params.listId}/segments/${params.segmentId}/edit`,
navs: {
':action(edit|delete)': {
title: t('Edit'),
link: params => `/lists/${params.listId}/segments/${params.segmentId}/edit`,
panelRender: props => <SegmentsCUD action={props.match.params.action} entity={props.resolved.segment} list={props.resolved.list} fields={props.resolved.fields} />
}
}
},
create: {
title: t('Create'),
resolve: {
fields: params => `/rest/fields/${params.listId}`
},
panelRender: props => <SegmentsCUD action="create" list={props.resolved.list} fields={props.resolved.fields} />
}
}
},
share: {
title: t('Share'),
link: params => `/lists/${params.listId}/share`,
visible: resolved => resolved.list.permissions.includes('share'),
panelRender: props => <Share title={t('Share')} entity={props.resolved.list} entityTypeId="list" />
}
}
},
create: {
title: t('Create'),
panelRender: props => <ListsCUD action="create" />
},
forms: {
title: t('Custom Forms'),
link: '/lists/forms',
panelComponent: FormsList,
children: {
':formsId([0-9]+)': {
title: resolved => t('Custom Forms "{{name}}"', {name: resolved.forms.name}),
resolve: {
forms: params => `/rest/forms/${params.formsId}`
},
link: params => `/lists/forms/${params.formsId}/edit`,
navs: {
':action(edit|delete)': {
title: t('Edit'),
link: params => `/lists/forms/${params.formsId}/edit`,
visible: resolved => resolved.forms.permissions.includes('edit'),
panelRender: props => <FormsCUD action={props.match.params.action} entity={props.resolved.forms} />
},
share: {
title: t('Share'),
link: params => `/lists/forms/${params.formsId}/share`,
visible: resolved => resolved.forms.permissions.includes('share'),
panelRender: props => <Share title={t('Share')} entity={props.resolved.forms} entityTypeId="customForm" />
}
}
},
create: {
title: t('Create'),
panelRender: props => <FormsCUD action="create" />
}
}
}
}
}
}
};
export default function() {
ReactDOM.render(
<I18nextProvider i18n={ i18n }><Section root='/lists' structure={getStructure}/></I18nextProvider>,
document.getElementById('root')
);
};
}
export default {
getMenus
}

View file

@ -0,0 +1,75 @@
'use strict';
import React, {Component} from 'react';
import ReactDOM from 'react-dom';
import {
I18nextProvider,
translate
} from 'react-i18next';
import i18n from '../lib/i18n';
import PropTypes from "prop-types";
@translate()
class MosaicoEditor extends Component {
constructor(props) {
super(props);
}
static propTypes = {
//structure: PropTypes.oneOfType([PropTypes.object, PropTypes.func]).isRequired,
}
componentDidMount() {
const basePath = '/public/mosaico';
if (!Mosaico.isCompatible()) {
alert('Update your browser!');
return;
}
const plugins = window.mosaicoPlugins;
plugins.unshift(vm => {
// This is a fix for the use of hardcoded path in Mosaico
vm.logoPath = basePath + '/img/mosaico32.png'
});
const config = {
imgProcessorBackend: basePath+'/img/',
emailProcessorBackend: basePath+'/dl/',
titleToken: "MOSAICO Responsive Email Designer",
fileuploadConfig: {
url: basePath+'/upload/'
},
strings: window.mosaicoLanguageStrings
};
const metadata = undefined;
const model = undefined;
const template = basePath + '/templates/versafix-1/index.html';
Mosaico.start(config, template, metadata, model, plugins);
}
componentDidUpdate() {
}
render() {
return (
<div>
</div>
);
}
}
export default function() {
ReactDOM.render(
<I18nextProvider i18n={ i18n }><MosaicoEditor /></I18nextProvider>,
document.getElementById('root')
);
};

View file

@ -10,52 +10,44 @@ import CUD from './CUD';
import List from './List';
import Share from '../shares/Share';
const getStructure = t => ({
'': {
title: t('Home'),
externalLink: '/',
children: {
namespaces: {
title: t('Namespaces'),
link: '/namespaces',
panelComponent: List,
children: {
':namespaceId([0-9]+)': {
title: resolved => t('Namespace "{{name}}"', {name: resolved.namespace.name}),
resolve: {
namespace: params => `/rest/namespaces/${params.namespaceId}`
function getMenus(t) {
return {
namespaces: {
title: t('Namespaces'),
link: '/namespaces',
panelComponent: List,
children: {
':namespaceId([0-9]+)': {
title: resolved => t('Namespace "{{name}}"', {name: resolved.namespace.name}),
resolve: {
namespace: params => `/rest/namespaces/${params.namespaceId}`
},
link: params => `/namespaces/${params.namespaceId}/edit`,
navs: {
':action(edit|delete)': {
title: t('Edit'),
link: params => `/namespaces/${params.namespaceId}/edit`,
visible: resolved => resolved.namespace.permissions.includes('edit'),
panelRender: props => <CUD action={props.match.params.action} entity={props.resolved.namespace} />
},
link: params => `/namespaces/${params.namespaceId}/edit`,
navs: {
':action(edit|delete)': {
title: t('Edit'),
link: params => `/namespaces/${params.namespaceId}/edit`,
visible: resolved => resolved.namespace.permissions.includes('edit'),
panelRender: props => <CUD action={props.match.params.action} entity={props.resolved.namespace} />
},
share: {
title: t('Share'),
link: params => `/namespaces/${params.namespaceId}/share`,
visible: resolved => resolved.namespace.permissions.includes('share'),
panelRender: props => <Share title={t('Share')} entity={props.resolved.namespace} entityTypeId="namespace" />
}
share: {
title: t('Share'),
link: params => `/namespaces/${params.namespaceId}/share`,
visible: resolved => resolved.namespace.permissions.includes('share'),
panelRender: props => <Share title={t('Share')} entity={props.resolved.namespace} entityTypeId="namespace" />
}
},
create: {
title: t('Create'),
panelRender: props => <CUD action="create" />
},
}
}
},
create: {
title: t('Create'),
panelRender: props => <CUD action="create" />
},
}
}
}
});
export default function() {
ReactDOM.render(
<I18nextProvider i18n={ i18n }><Section root='/namespaces' structure={getStructure}/></I18nextProvider>,
document.getElementById('root')
);
};
}
export default {
getMenus
}

View file

@ -17,106 +17,93 @@ import { ReportState } from '../../../shared/reports';
import mailtrainConfig from 'mailtrainConfig';
const getStructure = t => {
const subPaths = {};
function getMenus(t) {
return {
'': {
title: t('Home'),
externalLink: '/',
'reports': {
title: t('Reports'),
link: '/reports',
panelComponent: ReportsList,
children: {
'reports': {
title: t('Reports'),
link: '/reports',
panelComponent: ReportsList,
children: {
':reportId([0-9]+)': {
title: resolved => t('Report "{{name}}"', {name: resolved.report.name}),
resolve: {
report: params => `/rest/reports/${params.reportId}`
},
':reportId([0-9]+)': {
title: resolved => t('Report "{{name}}"', {name: resolved.report.name}),
resolve: {
report: params => `/rest/reports/${params.reportId}`
},
link: params => `/reports/${params.reportId}/edit`,
navs: {
':action(edit|delete)': {
title: t('Edit'),
link: params => `/reports/${params.reportId}/edit`,
visible: resolved => resolved.report.permissions.includes('edit'),
panelRender: props => <ReportsCUD action={props.match.params.action} entity={props.resolved.report} />
},
view: {
title: t('View'),
link: params => `/reports/${params.reportId}/view`,
visible: resolved => resolved.report.permissions.includes('viewContent') && resolved.report.state === ReportState.FINISHED && resolved.report.mime_type === 'text/html',
panelRender: props => (<ReportsView {...props} />),
},
download: {
title: t('Download'),
externalLink: params => `/reports/${params.reportId}/download`,
visible: resolved => resolved.report.permissions.includes('viewContent') && resolved.report.state === ReportState.FINISHED && resolved.report.mime_type === 'text/csv'
},
output: {
title: t('Output'),
link: params => `/reports/${params.reportId}/output`,
visible: resolved => resolved.report.permissions.includes('viewOutput'),
panelRender: props => (<ReportsOutput {...props} />)
},
share: {
title: t('Share'),
link: params => `/reports/${params.reportId}/share`,
visible: resolved => resolved.report.permissions.includes('share'),
panelRender: props => <Share title={t('Share')} entity={props.resolved.report} entityTypeId="report" />
}
}
},
create: {
title: t('Create'),
panelRender: props => <ReportsCUD action="create" />
},
'templates': {
title: t('Templates'),
link: '/reports/templates',
panelComponent: ReportTemplatesList,
children: {
':templateId([0-9]+)': {
title: resolved => t('Template "{{name}}"', {name: resolved.template.name}),
resolve: {
template: params => `/rest/report-templates/${params.templateId}`
},
link: params => `/reports/templates/${params.templateId}/edit`,
navs: {
':action(edit|delete)': {
title: t('Edit'),
link: params => `/reports/${params.reportId}/edit`,
visible: resolved => resolved.report.permissions.includes('edit'),
panelRender: props => <ReportsCUD action={props.match.params.action} entity={props.resolved.report} />
},
view: {
title: t('View'),
link: params => `/reports/${params.reportId}/view`,
visible: resolved => resolved.report.permissions.includes('viewContent') && resolved.report.state === ReportState.FINISHED && resolved.report.mime_type === 'text/html',
panelRender: props => (<ReportsView {...props} />),
},
download: {
title: t('Download'),
externalLink: params => `/reports/${params.reportId}/download`,
visible: resolved => resolved.report.permissions.includes('viewContent') && resolved.report.state === ReportState.FINISHED && resolved.report.mime_type === 'text/csv'
},
output: {
title: t('Output'),
link: params => `/reports/${params.reportId}/output`,
visible: resolved => resolved.report.permissions.includes('viewOutput'),
panelRender: props => (<ReportsOutput {...props} />)
link: params => `/reports/templates/${params.templateId}/edit`,
visible: resolved => mailtrainConfig.globalPermissions.includes('createJavascriptWithROAccess') && resolved.template.permissions.includes('edit'),
panelRender: props => <ReportTemplatesCUD action={props.match.params.action} entity={props.resolved.template} />
},
share: {
title: t('Share'),
link: params => `/reports/${params.reportId}/share`,
visible: resolved => resolved.report.permissions.includes('share'),
panelRender: props => <Share title={t('Share')} entity={props.resolved.report} entityTypeId="report" />
link: params => `/reports/templates/${params.templateId}/share`,
visible: resolved => resolved.template.permissions.includes('share'),
panelRender: props => <Share title={t('Share')} entity={props.resolved.template} entityTypeId="reportTemplate" />
}
}
},
create: {
title: t('Create'),
panelRender: props => <ReportsCUD action="create" />
},
'templates': {
title: t('Templates'),
link: '/reports/templates',
panelComponent: ReportTemplatesList,
children: {
':templateId([0-9]+)': {
title: resolved => t('Template "{{name}}"', {name: resolved.template.name}),
resolve: {
template: params => `/rest/report-templates/${params.templateId}`
},
link: params => `/reports/templates/${params.templateId}/edit`,
navs: {
':action(edit|delete)': {
title: t('Edit'),
link: params => `/reports/templates/${params.templateId}/edit`,
visible: resolved => mailtrainConfig.globalPermissions.includes('createJavascriptWithROAccess') && resolved.template.permissions.includes('edit'),
panelRender: props => <ReportTemplatesCUD action={props.match.params.action} entity={props.resolved.template} />
},
share: {
title: t('Share'),
link: params => `/reports/templates/${params.templateId}/share`,
visible: resolved => resolved.template.permissions.includes('share'),
panelRender: props => <Share title={t('Share')} entity={props.resolved.template} entityTypeId="reportTemplate" />
}
}
},
create: {
title: t('Create'),
extraParams: [':wizard?'],
panelRender: props => <ReportTemplatesCUD action="create" wizard={props.match.params.wizard} />
}
}
extraParams: [':wizard?'],
panelRender: props => <ReportTemplatesCUD action="create" wizard={props.match.params.wizard} />
}
}
}
}
}
}
};
export default function() {
ReactDOM.render(
<I18nextProvider i18n={ i18n }><Section root='/reports' structure={getStructure}/></I18nextProvider>,
document.getElementById('root')
);
};
};
}
export default {
getMenus
}

166
client/src/root.js Normal file
View file

@ -0,0 +1,166 @@
'use strict';
import React, {Component} from 'react';
import ReactDOM from 'react-dom';
import {
I18nextProvider,
translate
} from 'react-i18next';
import i18n from './lib/i18n';
import account from './account/root';
import blacklist from './blacklist/root';
import lists from './lists/root';
import namespaces from './namespaces/root';
import reports from './reports/root';
import templates from './templates/root';
import users from './users/root';
import {Section} from "./lib/page";
import mailtrainConfig from 'mailtrainConfig';
import Home from "./Home";
import {
ActionLink,
Icon
} from "./lib/bootstrap-components";
import {Link} from "react-router-dom";
import axios from './lib/axios';
@translate()
class Root extends Component {
constructor(props) {
super(props);
const t = props.t;
const self = this;
const topLevelMenuKeys = ['lists', 'templates', 'reports'];
class MainMenu extends Component {
render() {
const path = this.props.location.pathname;
const topLevelMenu = [];
const topLevelItems = self.structure[''].children;
for (const entryKey of topLevelMenuKeys) {
const entry = topLevelItems[entryKey];
const link = entry.link || entry.externalLink;
if (link && path.startsWith(link)) {
topLevelMenu.push(<li key={entryKey} className="active"><Link to={link}>{entry.title} <span className="sr-only">{t('(current)')}</span></Link></li>);
} else {
topLevelMenu.push(<li key={entryKey}><Link to={link}>{entry.title}</Link></li>);
}
}
return (
<nav className="navbar navbar-default navbar-static-top">
<div className="container-fluid">
<div className="navbar-header">
<button type="button" className="navbar-toggle collapsed" data-toggle="collapse" data-target="#bs-example-navbar-collapse-1" aria-expanded="false">
<span className="sr-only">{t('Toggle navigation')}</span>
<span className="icon-bar"></span>
<span className="icon-bar"></span>
<span className="icon-bar"></span>
</button>
<Link className="navbar-brand" to="/"><i className="glyphicon glyphicon-envelope"></i> Mailtrain</Link>
</div>
{mailtrainConfig.isAuthenticated &&
<div className="collapse navbar-collapse" id="bs-example-navbar-collapse-1">
<ul className="nav navbar-nav">
{topLevelMenu}
<li className="dropdown">
<a href="#" className="dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false">{t('Administration')}<span className="caret"></span></a>
<ul className="dropdown-menu">
<li>
<Link to="/users"><Icon icon='cog'/> {t('Users')}</Link>
</li>
<li>
<Link to="/namespaces"><Icon icon='cog'/> {t('Namespaces')}</Link>
</li>
<li>
<Link to="/settings"><Icon icon='cog'/> {t('Settings')}</Link>
</li>
<li>
<Link to="/blacklist"><Icon icon='ban-circle'/> {t('Blacklist')}</Link>
</li>
<li>
<Link to="/account/api"><Icon icon='retweet'/> {t('API')}</Link>
</li>
</ul>
</li>
</ul>
<ul className="nav navbar-nav navbar-right">
<li className="dropdown">
<a href="#" className="dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false">
<span className="glyphicon glyphicon-user" aria-hidden="true"></span> {mailtrainConfig.user.username}<span className="caret"></span>
</a>
<ul className="dropdown-menu">
<li>
<Link to="/account"><Icon icon='user'/> {t('Account')}</Link>
</li>
<li>
<ActionLink onClickAsync={::self.logout}><Icon icon='log-out'/> {t('Log out')}</ActionLink>
</li>
</ul>
</li>
</ul>
</div>
}
</div>
</nav>
);
}
}
this.structure = {
'': {
title: t('Home'),
link: '/',
panelComponent: Home,
primaryMenuComponent: MainMenu,
children: {
...lists.getMenus(t),
...reports.getMenus(t),
...templates.getMenus(t),
...namespaces.getMenus(t),
...users.getMenus(t),
...blacklist.getMenus(t),
...account.getMenus(t),
}
}
};
}
async logout() {
await axios.post('/rest/logout');
window.location = '/';
}
render() {
const t = this.props.t;
return (
<div>
<Section root='/' structure={this.structure}/>
<footer className="footer">
<div className="container-fluid">
<p className="text-muted">&copy; 2018 <a href="https://mailtrain.org">Mailtrain.org</a>, <a href="mailto:info@mailtrain.org">info@mailtrain.org</a>. <a href="https://github.com/Mailtrain-org/mailtrain">{t('Source on GitHub')}</a></p>
</div>
</footer>
</div>
);
}
}
export default function() {
ReactDOM.render(<I18nextProvider i18n={ i18n }><Root/></I18nextProvider>,document.getElementById('root'));
};

View file

@ -167,6 +167,7 @@ export default class CUD extends Component {
{this.state.showMergeTagReference &&
<div style={{marginTop: '15px'}}>
<Trans><p>Merge tags are tags that are replaced before sending out the message. The format of the merge tag is the following: <code>[TAG_NAME]</code> or <code>[TAG_NAME/fallback]</code> where <code>fallback</code> is an optional text value used when <code>TAG_NAME</code> is empty.</p></Trans>
<Trans><p>You can use any of the standard merge tags below. In addition to that every custom field has its own merge tag. Check the fields of the list you are going to send to.</p></Trans>
<table className="table table-bordered table-condensed table-striped">
<thead>
<tr>
@ -211,30 +212,6 @@ export default class CUD extends Component {
<Trans>Email address</Trans>
</td>
</tr>
<tr>
<th scope="row">
[FIRST_NAME]
</th>
<td>
<Trans>First name</Trans>
</td>
</tr>
<tr>
<th scope="row">
[LAST_NAME]
</th>
<td>
<Trans>Last name</Trans>
</td>
</tr>
<tr>
<th scope="row">
[FULL_NAME]
</th>
<td>
<Trans>Full name (first and last name combined)</Trans>
</td>
</tr>
<tr>
<th scope="row">
[SUBSCRIPTION_ID]
@ -261,8 +238,6 @@ export default class CUD extends Component {
</tr>
</tbody>
</table>
<Trans><p>In addition to that any custom field can have its own merge tag.</p></Trans>
</div>}
</AlignedRow>

View file

@ -11,54 +11,43 @@ import TemplatesList from './List';
import Share from '../shares/Share';
const getStructure = t => {
function getMenus(t) {
return {
'': {
title: t('Home'),
externalLink: '/',
'templates': {
title: t('Templates'),
link: '/templates',
panelComponent: TemplatesList,
children: {
'templates': {
title: t('Templates'),
link: '/templates',
panelComponent: TemplatesList,
children: {
':templateId([0-9]+)': {
title: resolved => t('Template "{{name}}"', {name: resolved.template.name}),
resolve: {
template: params => `/rest/templates/${params.templateId}`
},
':templateId([0-9]+)': {
title: resolved => t('Template "{{name}}"', {name: resolved.template.name}),
resolve: {
template: params => `/rest/templates/${params.templateId}`
},
link: params => `/templates/${params.templateId}/edit`,
navs: {
':action(edit|delete)': {
title: t('Edit'),
link: params => `/templates/${params.templateId}/edit`,
navs: {
':action(edit|delete)': {
title: t('Edit'),
link: params => `/templates/${params.templateId}/edit`,
visible: resolved => resolved.template.permissions.includes('edit'),
panelRender: props => <TemplatesCUD action={props.match.params.action} entity={props.resolved.template} />
},
share: {
title: t('Share'),
link: params => `/templates/${params.templateId}/share`,
visible: resolved => resolved.template.permissions.includes('share'),
panelRender: props => <Share title={t('Share')} entity={props.resolved.template} entityTypeId="template" />
}
}
visible: resolved => resolved.template.permissions.includes('edit'),
panelRender: props => <TemplatesCUD action={props.match.params.action} entity={props.resolved.template} />
},
create: {
title: t('Create'),
panelRender: props => <TemplatesCUD action="create" />
share: {
title: t('Share'),
link: params => `/templates/${params.templateId}/share`,
visible: resolved => resolved.template.permissions.includes('share'),
panelRender: props => <Share title={t('Share')} entity={props.resolved.template} entityTypeId="template" />
}
}
},
create: {
title: t('Create'),
panelRender: props => <TemplatesCUD action="create" />
}
}
}
}
};
export default function() {
ReactDOM.render(
<I18nextProvider i18n={ i18n }><Section root='/templates' structure={getStructure}/></I18nextProvider>,
document.getElementById('root')
);
};
};
}
export default {
getMenus
}

View file

@ -10,52 +10,41 @@ import CUD from './CUD';
import List from './List';
import UserShares from '../shares/UserShares';
const getStructure = t => {
function getMenus(t) {
return {
'': {
title: t('Home'),
externalLink: '/',
'users': {
title: t('Users'),
link: '/users',
panelComponent: List,
children: {
users: {
title: t('Users'),
link: '/users',
panelComponent: List,
children: {
':userId([0-9]+)': {
title: resolved => t('User "{{name}}"', {name: resolved.user.name}),
resolve: {
user: params => `/rest/users/${params.userId}`
},
':userId([0-9]+)': {
title: resolved => t('User "{{name}}"', {name: resolved.user.name}),
resolve: {
user: params => `/rest/users/${params.userId}`
},
link: params => `/users/${params.userId}/edit`,
navs: {
':action(edit|delete)': {
title: t('Edit'),
link: params => `/users/${params.userId}/edit`,
navs: {
':action(edit|delete)': {
title: t('Edit'),
link: params => `/users/${params.userId}/edit`,
panelRender: props => <CUD action={props.match.params.action} entity={props.resolved.user} />
},
shares: {
title: t('Shares'),
link: params => `/users/${params.userId}/shares`,
panelRender: props => <UserShares user={props.resolved.user} />
}
}
},
create: {
title: t('Create'),
panelRender: props => <CUD action="create" />
panelRender: props => <CUD action={props.match.params.action} entity={props.resolved.user} />
},
shares: {
title: t('Shares'),
link: params => `/users/${params.userId}/shares`,
panelRender: props => <UserShares user={props.resolved.user} />
}
}
}
},
create: {
title: t('Create'),
panelRender: props => <CUD action="create" />
},
}
}
}
};
};
export default function() {
ReactDOM.render(
<I18nextProvider i18n={ i18n }><Section root='/users' structure={getStructure}/></I18nextProvider>,
document.getElementById('root')
);
export default {
getMenus
}