diff --git a/assets/js/src/form/fields/field.jsx b/assets/js/src/form/fields/field.jsx
index 5d7c1773c7..a1117d08ab 100644
--- a/assets/js/src/form/fields/field.jsx
+++ b/assets/js/src/form/fields/field.jsx
@@ -123,7 +123,10 @@ FormField.propTypes = {
field: PropTypes.shape({
name: PropTypes.string.isRequired,
values: PropTypes.object,
- tip: PropTypes.string,
+ tip: PropTypes.oneOfType([
+ PropTypes.array,
+ PropTypes.string,
+ ]),
label: PropTypes.string,
fields: PropTypes.array,
description: PropTypes.string,
diff --git a/assets/js/src/form/form.jsx b/assets/js/src/form/form.jsx
index 01043ebcf7..856978f560 100644
--- a/assets/js/src/form/form.jsx
+++ b/assets/js/src/form/form.jsx
@@ -26,6 +26,11 @@ class Form extends React.Component {
id: '',
onSubmit: undefined,
automationId: '',
+ messages: {
+ onUpdate: () => { /* no-op */ },
+ onCreate: () => { /* no-op */ },
+ },
+ endpoint: undefined,
};
state = {
@@ -67,6 +72,7 @@ class Form extends React.Component {
loadItem = (id) => {
this.setState({ loading: true });
+ if (!this.props.endpoint) return;
MailPoet.Ajax.post({
api_version: window.mailpoet_api_version,
endpoint: this.props.endpoint,
@@ -120,6 +126,8 @@ class Form extends React.Component {
item.id = this.props.params.id;
}
+ if (!this.props.endpoint) return;
+
MailPoet.Ajax.post({
api_version: window.mailpoet_api_version,
endpoint: this.props.endpoint,
@@ -257,14 +265,14 @@ Form.propTypes = {
}).isRequired,
item: PropTypes.object, // eslint-disable-line react/forbid-prop-types
errors: PropTypes.arrayOf(PropTypes.object),
- endpoint: PropTypes.string.isRequired,
+ endpoint: PropTypes.string,
fields: PropTypes.arrayOf(PropTypes.object),
messages: PropTypes.shape({
onUpdate: PropTypes.func,
onCreate: PropTypes.func,
}).isRequired,
loading: PropTypes.bool,
- children: PropTypes.element,
+ children: PropTypes.array, // eslint-disable-line react/forbid-prop-types
id: PropTypes.string,
automationId: PropTypes.string,
beforeFormContent: PropTypes.func,
diff --git a/assets/js/src/listing/bulk_actions.jsx b/assets/js/src/listing/bulk_actions.jsx
index 6687e77350..a23c07a6d1 100644
--- a/assets/js/src/listing/bulk_actions.jsx
+++ b/assets/js/src/listing/bulk_actions.jsx
@@ -1,5 +1,6 @@
import React from 'react';
import MailPoet from 'mailpoet';
+import PropTypes from 'prop-types';
class ListingBulkActions extends React.Component {
state = {
@@ -7,20 +8,16 @@ class ListingBulkActions extends React.Component {
extra: false,
};
- handleChangeAction = (e) => {
- this.setState({
- action: e.target.value,
- extra: false,
- }, () => {
- const action = this.getSelectedAction();
+ getSelectedAction = () => {
+ const selectedAction = this.action.value;
+ if (selectedAction.length > 0) {
+ const action = this.props.bulk_actions.filter(act => (act.name === selectedAction));
- // action on select callback
- if (action !== null && action.onSelect !== undefined) {
- this.setState({
- extra: action.onSelect(e),
- });
+ if (action.length > 0) {
+ return action[0];
}
- });
+ }
+ return null;
};
handleApplyAction = (e) => {
@@ -60,16 +57,20 @@ class ListingBulkActions extends React.Component {
});
};
- getSelectedAction = () => {
- const selectedAction = this.action.value;
- if (selectedAction.length > 0) {
- const action = this.props.bulk_actions.filter(act => (act.name === selectedAction));
+ handleChangeAction = (e) => {
+ this.setState({
+ action: e.target.value,
+ extra: false,
+ }, () => {
+ const action = this.getSelectedAction();
- if (action.length > 0) {
- return action[0];
+ // action on select callback
+ if (action !== null && action.onSelect !== undefined) {
+ this.setState({
+ extra: action.onSelect(e),
+ });
}
- }
- return null;
+ });
};
render() {
@@ -113,4 +114,14 @@ class ListingBulkActions extends React.Component {
}
}
+ListingBulkActions.propTypes = {
+ bulk_actions: PropTypes.arrayOf(PropTypes.object).isRequired,
+ selection: PropTypes.oneOfType([
+ PropTypes.string,
+ PropTypes.bool,
+ ]).isRequired,
+ selected_ids: PropTypes.arrayOf(PropTypes.number).isRequired,
+ onBulkAction: PropTypes.func.isRequired,
+};
+
export default ListingBulkActions;
diff --git a/assets/js/src/listing/filters.jsx b/assets/js/src/listing/filters.jsx
index d7e4bc2623..aeeea368c3 100644
--- a/assets/js/src/listing/filters.jsx
+++ b/assets/js/src/listing/filters.jsx
@@ -1,34 +1,9 @@
import React from 'react';
import jQuery from 'jquery';
import MailPoet from 'mailpoet';
+import PropTypes from 'prop-types';
class ListingFilters extends React.Component {
- handleFilterAction = () => {
- const filters = {};
- this.getAvailableFilters().forEach((filter, i) => {
- filters[this[`filter-${i}`].name] = this[`filter-${i}`].value;
- });
- if (this.props.onBeforeSelectFilter) {
- this.props.onBeforeSelectFilter(filters);
- }
- return this.props.onSelectFilter(filters);
- };
-
- handleEmptyTrash = () => {
- return this.props.onEmptyTrash();
- };
-
- getAvailableFilters = () => {
- const filters = this.props.filters;
- return Object.keys(filters).filter(filter => !(
- filters[filter].length === 0
- || (
- filters[filter].length === 1
- && !filters[filter][0].value
- )
- ));
- };
-
componentDidUpdate() {
const selectedFilters = this.props.filter;
this.getAvailableFilters().forEach(
@@ -42,6 +17,30 @@ class ListingFilters extends React.Component {
);
}
+ getAvailableFilters = () => {
+ const filters = this.props.filters;
+ return Object.keys(filters).filter(filter => !(
+ filters[filter].length === 0
+ || (
+ filters[filter].length === 1
+ && !filters[filter][0].value
+ )
+ ));
+ };
+
+ handleEmptyTrash = () => this.props.onEmptyTrash();
+
+ handleFilterAction = () => {
+ const filters = {};
+ this.getAvailableFilters().forEach((filter, i) => {
+ filters[this[`filter-${i}`].name] = this[`filter-${i}`].value;
+ });
+ if (this.props.onBeforeSelectFilter) {
+ this.props.onBeforeSelectFilter(filters);
+ }
+ return this.props.onSelectFilter(filters);
+ };
+
render() {
const filters = this.props.filters;
const availableFilters = this.getAvailableFilters()
@@ -96,4 +95,20 @@ class ListingFilters extends React.Component {
}
}
+ListingFilters.propTypes = {
+ filters: PropTypes.oneOfType([
+ PropTypes.object,
+ PropTypes.array,
+ ]).isRequired,
+ onEmptyTrash: PropTypes.func.isRequired,
+ onBeforeSelectFilter: PropTypes.func,
+ onSelectFilter: PropTypes.func.isRequired,
+ filter: PropTypes.objectOf(PropTypes.string).isRequired,
+ group: PropTypes.string.isRequired,
+};
+
+ListingFilters.defaultProps = {
+ onBeforeSelectFilter: undefined,
+};
+
export default ListingFilters;
diff --git a/assets/js/src/listing/header.jsx b/assets/js/src/listing/header.jsx
index 2da2a5f28e..4acb03de28 100644
--- a/assets/js/src/listing/header.jsx
+++ b/assets/js/src/listing/header.jsx
@@ -1,13 +1,10 @@
import MailPoet from 'mailpoet';
import React from 'react';
-import classNames from 'classnames';
+import PropTypes from 'prop-types';
+import ListingColumn from './listing_column.jsx';
class ListingHeader extends React.Component {
- handleSelectItems = () => {
- return this.props.onSelectItems(
- this.toggle.checked
- );
- };
+ handleSelectItems = () => this.props.onSelectItems(this.toggle.checked);
render() {
const columns = this.props.columns.map((column, index) => {
@@ -57,47 +54,23 @@ class ListingHeader extends React.Component {
}
}
-class ListingColumn extends React.Component {
- handleSort = () => {
- const sortBy = this.props.column.name;
- const sortOrder = (this.props.column.sorted === 'asc') ? 'desc' : 'asc';
- this.props.onSort(sortBy, sortOrder);
- };
+ListingHeader.propTypes = {
+ onSelectItems: PropTypes.func.isRequired,
+ onSort: PropTypes.func.isRequired,
+ columns: PropTypes.arrayOf(PropTypes.object),
+ sort_by: PropTypes.string,
+ sort_order: PropTypes.string,
+ is_selectable: PropTypes.bool.isRequired,
+ selection: PropTypes.oneOfType([
+ PropTypes.string,
+ PropTypes.bool,
+ ]).isRequired,
+};
- render() {
- const classes = classNames(
- 'manage-column',
- { 'column-primary': this.props.column.is_primary },
- { sortable: this.props.column.sortable },
- this.props.column.sorted,
- { sorted: (this.props.sort_by === this.props.column.name) }
- );
- let label;
-
- if (this.props.column.sortable === true) {
- label = (
-
- { this.props.column.label }
-
-
- );
- } else {
- label = this.props.column.label;
- }
- return (
-
{label} |
- );
- }
-}
+ListingHeader.defaultProps = {
+ columns: [],
+ sort_by: undefined,
+ sort_order: 'desc',
+};
module.exports = ListingHeader;
diff --git a/assets/js/src/listing/listing.jsx b/assets/js/src/listing/listing.jsx
index c757f27267..36210a1831 100644
--- a/assets/js/src/listing/listing.jsx
+++ b/assets/js/src/listing/listing.jsx
@@ -1,312 +1,76 @@
-import MailPoet from 'mailpoet';
import jQuery from 'jquery';
import React from 'react';
import createReactClass from 'create-react-class';
import _ from 'underscore';
-import { Link } from 'react-router';
import classNames from 'classnames';
+import PropTypes from 'prop-types';
+import MailPoet from 'mailpoet';
import ListingBulkActions from 'listing/bulk_actions.jsx';
import ListingHeader from 'listing/header.jsx';
import ListingPages from 'listing/pages.jsx';
import ListingSearch from 'listing/search.jsx';
import ListingGroups from 'listing/groups.jsx';
import ListingFilters from 'listing/filters.jsx';
+import ListingItems from 'listing/listing_items.jsx';
-class ListingItem extends React.Component {
- state = {
- expanded: false,
- };
-
- handleSelectItem = (e) => {
- this.props.onSelectItem(
- parseInt(e.target.value, 10),
- e.target.checked
- );
-
- return !e.target.checked;
- };
-
- handleRestoreItem = (id) => {
- this.props.onRestoreItem(id);
- };
-
- handleTrashItem = (id) => {
- this.props.onTrashItem(id);
- };
-
- handleDeleteItem = (id) => {
- this.props.onDeleteItem(id);
- };
-
- handleToggleItem = () => {
- this.setState({ expanded: !this.state.expanded });
- };
-
- render() {
- let checkbox = false;
-
- if (this.props.is_selectable === true) {
- checkbox = (
-
-
-
- |
- );
- }
-
- const customActions = this.props.item_actions;
- let itemActions = false;
-
- if (customActions.length > 0) {
- let isFirst = true;
- itemActions = customActions
- .filter(action => action.display === undefined || action.display(this.props.item))
- .map((action, index) => {
- let customAction = null;
-
- if (action.name === 'trash') {
- customAction = (
-
- {(!isFirst) ? ' | ' : ''}
- this.handleTrashItem(this.props.item.id)}
- >
- {MailPoet.I18n.t('moveToTrash')}
-
-
- );
- } else if (action.refresh) {
- customAction = (
-
- {(!isFirst) ? ' | ' : ''}
- { action.link(this.props.item) }
-
- );
- } else if (action.link) {
- customAction = (
-
- {(!isFirst) ? ' | ' : ''}
- { action.link(this.props.item) }
-
- );
- } else {
- customAction = (
-
- {(!isFirst) ? ' | ' : ''}
- action.onClick(this.props.item, this.props.onRefreshItems)
- : false
- }
- >{ action.label }
-
- );
- }
-
- if (customAction !== null && isFirst === true) {
- isFirst = false;
- }
-
- return customAction;
- });
- } else {
- itemActions = (
-
- {MailPoet.I18n.t('edit')}
-
- );
- }
-
- let actions;
-
- if (this.props.group === 'trash') {
- actions = (
-
-
-
-
- );
- } else {
- actions = (
-
-
- { itemActions }
-
-
-
- );
- }
-
- const rowClasses = classNames({ 'is-expanded': this.state.expanded });
-
- return (
-
- { checkbox }
- { this.props.onRenderItem(this.props.item, actions) }
-
- );
- }
-}
-
-class ListingItems extends React.Component {
- render() {
- if (this.props.items.length === 0) {
- let message;
- if (this.props.loading === true) {
- message = (this.props.messages.onLoadingItems
- && this.props.messages.onLoadingItems(this.props.group))
- || MailPoet.I18n.t('loadingItems');
- } else {
- message = (this.props.messages.onNoItemsFound
- && this.props.messages.onNoItemsFound(this.props.group))
- || MailPoet.I18n.t('noItemsFound');
- }
-
- return (
-
-
-
- {message}
- |
-
-
- );
- }
- const selectAllClasses = classNames(
- 'mailpoet_select_all',
- { mailpoet_hidden: (
- this.props.selection === false
- || (this.props.count <= this.props.limit)
- ),
- }
- );
-
- return (
-
-
-
- {
- (this.props.selection !== 'all')
- ? MailPoet.I18n.t('selectAllLabel')
- : MailPoet.I18n.t('selectedAllLabel').replace(
- '%d',
- this.props.count.toLocaleString()
- )
- }
-
- {
- (this.props.selection !== 'all')
- ? MailPoet.I18n.t('selectAllLink')
- : MailPoet.I18n.t('clearSelection')
- }
- |
-
-
- {this.props.items.map((item) => {
- const renderItem = item;
- renderItem.id = parseInt(item.id, 10);
- renderItem.selected = (this.props.selected_ids.indexOf(renderItem.id) !== -1);
- let key = `item-${renderItem.id}-${item.id}`;
- if (typeof this.props.getListingItemKey === 'function') {
- key = this.props.getListingItemKey(item);
- }
-
- return (
-
- );
- })}
-
- );
- }
-}
-
-const Listing = createReactClass({
+const Listing = createReactClass({ // eslint-disable-line react/prefer-es6-class
displayName: 'Listing',
+ /* eslint-disable react/require-default-props */
+ propTypes: {
+ limit: PropTypes.number,
+ sort_by: PropTypes.string,
+ sort_order: PropTypes.string,
+ params: PropTypes.object.isRequired, // eslint-disable-line react/forbid-prop-types
+ auto_refresh: PropTypes.bool,
+ location: PropTypes.shape({
+ pathname: PropTypes.string,
+ }),
+ base_url: PropTypes.string,
+ type: PropTypes.string,
+ endpoint: PropTypes.string.isRequired,
+ afterGetItems: PropTypes.func,
+ messages: PropTypes.shape({
+ onRestore: PropTypes.func,
+ onTrash: PropTypes.func,
+ onDelete: PropTypes.func,
+ }),
+ onRenderItem: PropTypes.func.isRequired,
+ columns: PropTypes.arrayOf(PropTypes.object),
+ bulk_actions: PropTypes.arrayOf(PropTypes.object),
+ item_actions: PropTypes.arrayOf(PropTypes.object),
+ search: PropTypes.bool,
+ groups: PropTypes.bool,
+ renderExtraActions: PropTypes.func,
+ onBeforeSelectFilter: PropTypes.func,
+ getListingItemKey: PropTypes.func,
+ },
+ /* eslint-enable react/require-default-props */
+
contextTypes: {
router: React.PropTypes.object.isRequired,
},
+ getDefaultProps: () => ({
+ limit: 10,
+ sort_by: null,
+ sort_order: undefined,
+ auto_refresh: false,
+ location: undefined,
+ base_url: '',
+ type: undefined,
+ afterGetItems: undefined,
+ messages: undefined,
+ columns: [],
+ bulk_actions: [],
+ item_actions: [],
+ search: false,
+ groups: false,
+ renderExtraActions: undefined,
+ onBeforeSelectFilter: undefined,
+ getListingItemKey: undefined,
+ }),
+
getInitialState: function getInitialState() {
return {
loading: false,
@@ -327,64 +91,25 @@ const Listing = createReactClass({
};
},
- getParam: function getParam(param) {
- const regex = /(.*)\[(.*)\]/;
- const matches = regex.exec(param);
- return [matches[1], matches[2]];
- },
+ componentDidMount: function componentDidMount() {
+ this.isComponentMounted = true;
+ const params = this.props.params || {};
+ this.initWithParams(params);
- initWithParams: function initWithParams(params) {
- const state = this.getInitialState();
- // check for url params
- if (params.splat) {
- params.splat.split('/').forEach((param) => {
- const [key, value] = this.getParam(param);
- const filters = {};
- switch (key) {
- case 'filter':
- value.split('&').forEach((pair) => {
- const [k, v] = pair.split('=');
- filters[k] = v;
- });
-
- state.filter = filters;
- break;
- default:
- state[key] = value;
- }
+ if (this.props.auto_refresh) {
+ jQuery(document).on('heartbeat-tick.mailpoet', () => {
+ this.getItems();
});
}
-
- // limit per page
- if (this.props.limit !== undefined) {
- state.limit = Math.abs(Number(this.props.limit));
- }
-
- // sort by
- if (state.sort_by === null && this.props.sort_by !== undefined) {
- state.sort_by = this.props.sort_by;
- }
-
- // sort order
- if (state.sort_order === null && this.props.sort_order !== undefined) {
- state.sort_order = this.props.sort_order;
- }
-
- this.setState(state, () => {
- this.getItems();
- });
},
- getParams: function getParams() {
- // get all route parameters (without the "splat")
- const params = _.omit(this.props.params, 'splat');
- // TODO:
- // find a way to set the "type" in the routes definition
- // so that it appears in `this.props.params`
- if (this.props.type) {
- params.type = this.props.type;
- }
- return params;
+ componentWillReceiveProps: function componentWillReceiveProps(nextProps) {
+ const params = nextProps.params || {};
+ this.initWithParams(params);
+ },
+
+ componentWillUnmount: function componentWillUnmount() {
+ this.isComponentMounted = false;
},
setParams: function setParams() {
@@ -451,25 +176,22 @@ const Listing = createReactClass({
return ret;
},
- componentDidMount: function componentDidMount() {
- this.isComponentMounted = true;
- const params = this.props.params || {};
- this.initWithParams(params);
-
- if (this.props.auto_refresh) {
- jQuery(document).on('heartbeat-tick.mailpoet', () => {
- this.getItems();
- });
+ getParams: function getParams() {
+ // get all route parameters (without the "splat")
+ const params = _.omit(this.props.params, 'splat');
+ // TODO:
+ // find a way to set the "type" in the routes definition
+ // so that it appears in `this.props.params`
+ if (this.props.type) {
+ params.type = this.props.type;
}
+ return params;
},
- componentWillUnmount: function componentWillUnmount() {
- this.isComponentMounted = false;
- },
-
- componentWillReceiveProps: function componentWillReceiveProps(nextProps) {
- const params = nextProps.params || {};
- this.initWithParams(params);
+ getParam: function getParam(param) {
+ const regex = /(.*)\[(.*)\]/;
+ const matches = regex.exec(param);
+ return [matches[1], matches[2]];
},
getItems: function getItems() {
@@ -525,6 +247,48 @@ const Listing = createReactClass({
});
},
+ initWithParams: function initWithParams(params) {
+ const state = this.getInitialState();
+ // check for url params
+ if (params.splat) {
+ params.splat.split('/').forEach((param) => {
+ const [key, value] = this.getParam(param);
+ const filters = {};
+ switch (key) {
+ case 'filter':
+ value.split('&').forEach((pair) => {
+ const [k, v] = pair.split('=');
+ filters[k] = v;
+ });
+
+ state.filter = filters;
+ break;
+ default:
+ state[key] = value;
+ }
+ });
+ }
+
+ // limit per page
+ if (this.props.limit !== undefined) {
+ state.limit = Math.abs(Number(this.props.limit));
+ }
+
+ // sort by
+ if (state.sort_by === null && this.props.sort_by !== undefined) {
+ state.sort_by = this.props.sort_by;
+ }
+
+ // sort order
+ if (state.sort_order === null && this.props.sort_order !== undefined) {
+ state.sort_order = this.props.sort_order;
+ }
+
+ this.setState(state, () => {
+ this.getItems();
+ });
+ },
+
handleRestoreItem: function handleRestoreItem(id) {
this.setState({
loading: true,
diff --git a/assets/js/src/listing/listing_column.jsx b/assets/js/src/listing/listing_column.jsx
new file mode 100644
index 0000000000..cfe86a994e
--- /dev/null
+++ b/assets/js/src/listing/listing_column.jsx
@@ -0,0 +1,68 @@
+import React from 'react';
+import classNames from 'classnames';
+import PropTypes from 'prop-types';
+
+class ListingColumn extends React.Component {
+ handleSort = () => {
+ const sortBy = this.props.column.name;
+ const sortOrder = (this.props.column.sorted === 'asc') ? 'desc' : 'asc';
+ this.props.onSort(sortBy, sortOrder);
+ };
+
+ render() {
+ const classes = classNames(
+ 'manage-column',
+ { 'column-primary': this.props.column.is_primary },
+ { sortable: this.props.column.sortable },
+ this.props.column.sorted,
+ { sorted: (this.props.sort_by === this.props.column.name) }
+ );
+ let label;
+
+ if (this.props.column.sortable === true) {
+ label = (
+
+ { this.props.column.label }
+
+
+ );
+ } else {
+ label = this.props.column.label;
+ }
+ return (
+ {label} |
+ );
+ }
+}
+
+ListingColumn.propTypes = {
+ column: PropTypes.shape({
+ name: PropTypes.string,
+ sorted: PropTypes.string,
+ is_primary: PropTypes.bool,
+ sortable: PropTypes.bool,
+ label: PropTypes.string,
+ width: PropTypes.oneOfType([
+ PropTypes.string,
+ PropTypes.number,
+ ]),
+ }).isRequired,
+ sort_by: PropTypes.string,
+ onSort: PropTypes.func.isRequired,
+};
+
+ListingColumn.defaultProps = {
+ sort_by: undefined,
+};
+
+module.exports = ListingColumn;
diff --git a/assets/js/src/listing/listing_item.jsx b/assets/js/src/listing/listing_item.jsx
new file mode 100644
index 0000000000..c1d479d4d4
--- /dev/null
+++ b/assets/js/src/listing/listing_item.jsx
@@ -0,0 +1,215 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+import MailPoet from 'mailpoet';
+import { Link } from 'react-router';
+import classNames from 'classnames';
+
+class ListingItem extends React.Component {
+ state = {
+ expanded: false,
+ };
+
+ handleSelectItem = (e) => {
+ this.props.onSelectItem(
+ parseInt(e.target.value, 10),
+ e.target.checked
+ );
+
+ return !e.target.checked;
+ };
+
+ handleRestoreItem = (id) => {
+ this.props.onRestoreItem(id);
+ };
+
+ handleTrashItem = (id) => {
+ this.props.onTrashItem(id);
+ };
+
+ handleDeleteItem = (id) => {
+ this.props.onDeleteItem(id);
+ };
+
+ handleToggleItem = () => {
+ this.setState({ expanded: !this.state.expanded });
+ };
+
+ render() {
+ let checkbox = false;
+
+ if (this.props.is_selectable === true) {
+ checkbox = (
+
+
+
+ |
+ );
+ }
+
+ const customActions = this.props.item_actions;
+ let itemActions = false;
+
+ if (customActions.length > 0) {
+ let isFirst = true;
+ itemActions = customActions
+ .filter(action => action.display === undefined || action.display(this.props.item))
+ .map((action, index) => {
+ let customAction = null;
+
+ if (action.name === 'trash') {
+ customAction = (
+
+ {(!isFirst) ? ' | ' : ''}
+ this.handleTrashItem(this.props.item.id)}
+ >
+ {MailPoet.I18n.t('moveToTrash')}
+
+
+ );
+ } else if (action.refresh) {
+ customAction = (
+
+ {(!isFirst) ? ' | ' : ''}
+ { action.link(this.props.item) }
+
+ );
+ } else if (action.link) {
+ customAction = (
+
+ {(!isFirst) ? ' | ' : ''}
+ { action.link(this.props.item) }
+
+ );
+ } else {
+ customAction = (
+
+ {(!isFirst) ? ' | ' : ''}
+ action.onClick(this.props.item, this.props.onRefreshItems)
+ : false
+ }
+ >{ action.label }
+
+ );
+ }
+
+ if (customAction !== null && isFirst === true) {
+ isFirst = false;
+ }
+
+ return customAction;
+ });
+ } else {
+ itemActions = (
+
+ {MailPoet.I18n.t('edit')}
+
+ );
+ }
+
+ let actions;
+
+ if (this.props.group === 'trash') {
+ actions = (
+
+
+
+
+ );
+ } else {
+ actions = (
+
+
+ { itemActions }
+
+
+
+ );
+ }
+
+ const rowClasses = classNames({ 'is-expanded': this.state.expanded });
+
+ return (
+
+ { checkbox }
+ { this.props.onRenderItem(this.props.item, actions) }
+
+ );
+ }
+}
+
+ListingItem.propTypes = {
+ onSelectItem: PropTypes.func.isRequired,
+ onRestoreItem: PropTypes.func.isRequired,
+ onTrashItem: PropTypes.func.isRequired,
+ onDeleteItem: PropTypes.func.isRequired,
+ is_selectable: PropTypes.bool.isRequired,
+ item: PropTypes.object.isRequired, // eslint-disable-line react/forbid-prop-types
+ columns: PropTypes.arrayOf(PropTypes.object).isRequired,
+ selection: PropTypes.oneOfType([
+ PropTypes.string,
+ PropTypes.number,
+ PropTypes.bool,
+ ]).isRequired,
+ item_actions: PropTypes.arrayOf(PropTypes.object).isRequired,
+ onRefreshItems: PropTypes.func.isRequired,
+ onRenderItem: PropTypes.func.isRequired,
+ group: PropTypes.string.isRequired,
+};
+
+module.exports = ListingItem;
diff --git a/assets/js/src/listing/listing_items.jsx b/assets/js/src/listing/listing_items.jsx
new file mode 100644
index 0000000000..ad95b51ce1
--- /dev/null
+++ b/assets/js/src/listing/listing_items.jsx
@@ -0,0 +1,140 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+import classNames from 'classnames';
+import MailPoet from 'mailpoet';
+import ListingItem from 'listing/listing_item.jsx';
+
+class ListingItems extends React.Component { // eslint-disable-line react/prefer-stateless-function, max-len
+ render() {
+ if (this.props.items.length === 0) {
+ let message;
+ if (this.props.loading === true) {
+ message = (this.props.messages.onLoadingItems
+ && this.props.messages.onLoadingItems(this.props.group))
+ || MailPoet.I18n.t('loadingItems');
+ } else {
+ message = (this.props.messages.onNoItemsFound
+ && this.props.messages.onNoItemsFound(this.props.group))
+ || MailPoet.I18n.t('noItemsFound');
+ }
+
+ return (
+
+
+
+ {message}
+ |
+
+
+ );
+ }
+ const selectAllClasses = classNames(
+ 'mailpoet_select_all',
+ { mailpoet_hidden: (
+ this.props.selection === false
+ || (this.props.count <= this.props.limit)
+ ),
+ }
+ );
+
+ return (
+
+
+
+ {
+ (this.props.selection !== 'all')
+ ? MailPoet.I18n.t('selectAllLabel')
+ : MailPoet.I18n.t('selectedAllLabel').replace(
+ '%d',
+ this.props.count.toLocaleString()
+ )
+ }
+
+ {
+ (this.props.selection !== 'all')
+ ? MailPoet.I18n.t('selectAllLink')
+ : MailPoet.I18n.t('clearSelection')
+ }
+ |
+
+
+ {this.props.items.map((item) => {
+ const renderItem = item;
+ renderItem.id = parseInt(item.id, 10);
+ renderItem.selected = (this.props.selected_ids.indexOf(renderItem.id) !== -1);
+ let key = `item-${renderItem.id}-${item.id}`;
+ if (typeof this.props.getListingItemKey === 'function') {
+ key = this.props.getListingItemKey(item);
+ }
+
+ return (
+
+ );
+ })}
+
+ );
+ }
+}
+
+ListingItems.propTypes = {
+ items: PropTypes.arrayOf(PropTypes.object).isRequired,
+ loading: PropTypes.bool.isRequired,
+ messages: PropTypes.shape({
+ onLoadingItems: PropTypes.func,
+ onNoItemsFound: PropTypes.func,
+ }).isRequired,
+ group: PropTypes.string.isRequired,
+ columns: PropTypes.arrayOf(PropTypes.object).isRequired,
+ is_selectable: PropTypes.bool.isRequired,
+ selection: PropTypes.oneOfType([
+ PropTypes.string,
+ PropTypes.number,
+ PropTypes.bool,
+ ]).isRequired,
+ count: PropTypes.number.isRequired,
+ limit: PropTypes.number.isRequired,
+ onSelectAll: PropTypes.func.isRequired,
+ selected_ids: PropTypes.arrayOf(PropTypes.number).isRequired,
+ getListingItemKey: PropTypes.func,
+ onSelectItem: PropTypes.func.isRequired,
+ onRenderItem: PropTypes.func.isRequired,
+ onDeleteItem: PropTypes.func.isRequired,
+ onRestoreItem: PropTypes.func.isRequired,
+ onTrashItem: PropTypes.func.isRequired,
+ onRefreshItems: PropTypes.func.isRequired,
+ item_actions: PropTypes.arrayOf(PropTypes.object).isRequired,
+
+};
+
+ListingItems.defaultProps = {
+ getListingItemKey: undefined,
+};
+
+module.exports = ListingItems;
diff --git a/assets/js/src/listing/pages.jsx b/assets/js/src/listing/pages.jsx
index ece96ebbc5..ccfdf29216 100644
--- a/assets/js/src/listing/pages.jsx
+++ b/assets/js/src/listing/pages.jsx
@@ -1,6 +1,7 @@
import React from 'react';
import classNames from 'classnames';
import MailPoet from 'mailpoet';
+import PropTypes from 'prop-types';
class ListingPages extends React.Component {
state = {
@@ -35,9 +36,7 @@ class ListingPages extends React.Component {
);
};
- constrainPage = (page) => {
- return Math.min(Math.max(1, Math.abs(Number(page))), this.getLastPage());
- };
+ getLastPage = () => Math.ceil(this.props.count / this.props.limit);
handleSetManualPage = (e) => {
if (e.which === 13) {
@@ -55,9 +54,7 @@ class ListingPages extends React.Component {
this.setPage(e.target.value);
};
- getLastPage = () => {
- return Math.ceil(this.props.count / this.props.limit);
- };
+ constrainPage = page => Math.min(Math.max(1, Math.abs(Number(page))), this.getLastPage());
render() {
if (this.props.count === 0) {
@@ -193,4 +190,14 @@ class ListingPages extends React.Component {
}
}
+ListingPages.propTypes = {
+ onSetPage: PropTypes.func.isRequired,
+ page: PropTypes.oneOfType([
+ PropTypes.number,
+ PropTypes.string,
+ ]).isRequired,
+ count: PropTypes.number.isRequired,
+ limit: PropTypes.number.isRequired,
+};
+
module.exports = ListingPages;