- listing component
- subscribers listing
- newsletters listing
This commit is contained in:
Jonathan Labreuille
2015-09-01 16:21:25 +02:00
parent e0ef01d9f8
commit 27883a232a
20 changed files with 778 additions and 563 deletions

View File

@@ -6,4 +6,4 @@
right: 0.5em
top: 0.5em
color: #999
text-decoration: none
text-decoration: none !important

View File

@@ -31,6 +31,10 @@ define('bulk_actions', ['react'], function(React) {
return null;
},
render: function() {
if(this.props.actions.length === 0) {
return null;
}
return (
<div className="alignleft actions bulkactions">
<label

View File

@@ -0,0 +1,10 @@
define('filters', ['react'], function(React) {
var ListingFilters = React.createClass({
render: function() {
return null;
}
});
return ListingFilters;
});

View File

@@ -0,0 +1,36 @@
define('groups', ['react', 'classnames'], function(React, classNames) {
var ListingGroups = React.createClass({
handleSelect: function(group) {
return this.props.onSelect(group);
},
render: function() {
var count = this.props.groups.length;
var groups = this.props.groups.map(function(group, index) {
var classes = classNames(
{ 'current' : (group.name === this.props.selected) }
);
return (
<li key={index}>
<a
href="javascript:;"
className={classes}
onClick={this.handleSelect.bind(this, group.name)} >
{group.label}
<span className="count">({ group.count })</span>
</a>{(index < (count - 1)) ? ' | ' : ''}
</li>
);
}.bind(this));
return (
<ul className="subsubsub">
{ groups }
</ul>
);
}
});
return ListingGroups;
});

View File

@@ -19,14 +19,15 @@ define('header', ['react', 'classnames'], function(React, classNames) {
);
},
render: function() {
var columns = this.props.columns.map(function(column) {
return (
<ListingColumn
onSort={this.props.onSort}
sort_by={this.props.sort_by}
key={column.name}
column={column} />
);
var columns = this.props.columns.map(function(column, index) {
column.is_primary = (index === 0);
return (
<ListingColumn
onSort={this.props.onSort}
sort_by={this.props.sort_by}
key={ 'column-' + index }
column={column} />
);
}.bind(this));
return (
@@ -55,11 +56,11 @@ define('header', ['react', 'classnames'], function(React, classNames) {
render: function() {
var 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) }
);
var label;
if(this.props.column.sortable === true) {

View File

@@ -0,0 +1,262 @@
define(
'listing',
[
'mailpoet',
'jquery',
'react',
'classnames',
'listing/bulk_actions.jsx',
'listing/header.jsx',
'listing/pages.jsx',
'listing/search.jsx',
'listing/groups.jsx',
'listing/filters.jsx'
],
function(
MailPoet,
jQuery,
React,
classNames,
ListingBulkActions,
ListingHeader,
ListingPages,
ListingSearch,
ListingGroups,
ListingFilters
) {
var ListingItem = React.createClass({
handleSelect: function(e) {
var is_checked = jQuery(e.target).is(':checked');
this.props.onSelect(
parseInt(e.target.value, 10),
is_checked
);
return !e.target.checked;
},
render: function() {
return (
<tr>
<th className="check-column" scope="row">
<label className="screen-reader-text">
{ 'Select ' + this.props.item.email }</label>
<input
type="checkbox"
defaultValue={ this.props.item.id }
defaultChecked={ this.props.item.selected }
onChange={ this.handleSelect } />
</th>
{ this.props.onRenderItem(this.props.item) }
</tr>
);
}
});
var ListingItems = React.createClass({
render: function() {
if(this.props.items.length === 0) {
return (
<tbody>
<td
colSpan={this.props.columns.length + 1}
className="colspanchange">
{
(this.props.loading === true)
? MailPoetI18n.loading
: MailPoetI18n.noRecordFound
}
</td>
</tbody>
);
} else {
return (
<tbody>
{this.props.items.map(function(item) {
item.selected = (this.props.selected.indexOf(item.id) !== -1);
return (
<ListingItem
columns={ this.props.columns }
onSelect={ this.props.onSelect }
onRenderItem={ this.props.onRenderItem }
key={ 'item-' + item.id }
item={ item } />
);
}.bind(this))}
</tbody>
);
}
}
});
var Listing = React.createClass({
getInitialState: function() {
return {
loading: false,
search: '',
page: 1,
count: 0,
limit: 10,
sort_by: 'id',
sort_order: 'desc',
items: [],
groups: [],
group: 'all',
filters: [],
selected: []
};
},
componentDidMount: function() {
this.getItems();
},
getItems: function() {
this.setState({ loading: true });
this.props.items.bind(null, this)();
},
handleSearch: function(search) {
this.setState({
search: search
}, function() {
this.getItems();
}.bind(this));
},
handleSort: function(sort_by, sort_order = 'asc') {
this.setState({
sort_by: sort_by,
sort_order: sort_order
}, function() {
this.getItems();
}.bind(this));
},
handleSelect: function(id, is_checked) {
var selected = this.state.selected;
if(is_checked) {
selected = jQuery.merge(selected, [ id ]);
} else {
selected.splice(selected.indexOf(id), 1);
}
this.setState({
selected: selected
});
},
handleGroup: function(group) {
// reset search
jQuery('#search_input').val('');
this.setState({
group: group,
filters: [],
selected: [],
search: '',
page: 1
}, function() {
this.getItems();
}.bind(this));
},
handleSelectAll: function(is_checked) {
if(is_checked === false) {
this.setState({ selected: [] });
} else {
var selected = this.state.items.map(function(item) {
return ~~item.id;
});
this.setState({
selected: selected
});
}
},
handleSetPage: function(page) {
this.setState({ page: page }, function() {
this.getItems();
}.bind(this));
},
handleRenderItem: function(item) {
return this.props.onRenderItem(item);
},
render: function() {
var items = this.state.items,
sort_by = this.state.sort_by,
sort_order = this.state.sort_order;
// set sortable columns
columns = this.props.columns.map(function(column) {
column.sorted = (column.name === sort_by) ? sort_order : false;
return column;
});
var tableClasses = classNames(
'wp-list-table',
'widefat',
'fixed',
'striped',
{ 'mailpoet_listing_loading': this.state.loading }
);
return (
<div>
<ListingGroups
groups={ this.state.groups }
selected={ this.state.group }
onSelect={ this.handleGroup } />
<ListingSearch
onSearch={ this.handleSearch }
search={ this.state.search } />
<div className="tablenav top clearfix">
<ListingBulkActions
actions={ this.props.actions }
selected={ this.state.selected } />
<ListingFilters filters={ this.state.filters } />
<ListingPages
count={ this.state.count }
page={ this.state.page }
limit={ this.state.limit }
onSetPage={ this.handleSetPage } />
</div>
<table className={ tableClasses }>
<thead>
<ListingHeader
onSort={ this.handleSort }
onSelectAll={ this.handleSelectAll }
sort_by={ this.state.sort_by }
sort_order={ this.state.sort_order }
columns={ this.props.columns } />
</thead>
<ListingItems
onRenderItem={ this.handleRenderItem }
columns={ this.props.columns }
selected={ this.state.selected }
onSelect={ this.handleSelect }
loading= { this.state.loading }
items={ items } />
<tfoot>
<ListingHeader
onSort={ this.handleSort }
onSelectAll={ this.handleSelectAll }
sort_by={ this.state.sort_by }
sort_order={ this.state.sort_order }
columns={ this.props.columns } />
</tfoot>
</table>
<div className="tablenav bottom">
<ListingBulkActions
actions={ this.props.actions }
selected={ this.state.selected } />
<ListingPages
count={ this.state.count }
page={ this.state.page }
limit={ this.state.limit }
onSetPage={ this.handleSetPage } />
</div>
</div>
);
}
});
return Listing;
}
);

View File

@@ -20,11 +20,13 @@ define('search', ['react'], function(React) {
</label>
<input
type="search"
id="search_input"
ref="search"
name="s"
defaultValue={this.props.search} />
<input
type="submit"
defaultValue="Search"
defaultValue={MailPoetI18n.searchLabel}
className="button" />
</p>
</form>

View File

@@ -3,78 +3,99 @@ define(
[
'react',
'jquery',
'mailpoet'
'mailpoet',
'listing/listing.jsx',
'classnames'
],
function(
React,
jQuery,
MailPoet
MailPoet,
Listing,
classNames
) {
var Newsletter = React.createClass({
send: function(e) {
e.preventDefault();
MailPoet.Ajax.post({
endpoint: 'newsletters',
action: 'send',
data: this.props.newsletter.id,
onSuccess: function(response) {
alert('Sent!');
},
onError: function(response) {
alert('Cannot send. Set the settings and add some subscribers!');
}
})
var columns = [
{
name: 'subject',
label: 'Subject',
sortable: true
},
render: function() {
return (
<div className="newsletter">
<p className="subject">
{this.props.newsletter.subject} - <a href="" onClick={this.send}>
Send
</a>
</p>
</div>
);
{
name: 'created_at',
label: 'Created on',
sortable: true
},
{
name: 'updated_at',
label: 'Last modified on',
sortable: true
}
});
];
var actions = [
];
var List = React.createClass({
load: function() {
getItems: function(listing) {
MailPoet.Ajax.post({
endpoint: 'newsletters',
action: 'get',
data: {},
data: {
offset: (listing.state.page - 1) * listing.state.limit,
limit: listing.state.limit,
group: listing.state.group,
search: listing.state.search,
sort_by: listing.state.sort_by,
sort_order: listing.state.sort_order
},
onSuccess: function(response) {
this.setState({data: response});
}.bind(this)
if(listing.isMounted()) {
listing.setState({
items: response.items || [],
filters: response.filters || [],
groups: response.groups || [],
count: response.count || 0,
loading: false
});
}
}.bind(listing)
});
},
renderItem: function(newsletter) {
var rowClasses = classNames(
'manage-column',
'column-primary',
'has-row-actions'
);
getInitialState: function() {
return {data: []};
},
componentDidMount: function() {
this.load();
setInterval(this.load, 2000);
},
render: function() {
var nodes = this.state.data.map(function (newsletter) {
return (
<Newsletter key={newsletter.id} newsletter={newsletter} />
);
});
return (
<div className="newslettersList">
<h1>Newsletters</h1>
{nodes}
<div>
<td className={ rowClasses }>
<strong>
<a>{ newsletter.subject }</a>
</strong>
</td>
<td className="column-date" data-colname="Subscribed on">
<abbr>{ newsletter.created_at }</abbr>
</td>
<td className="column-date" data-colname="Last modified on">
<abbr>{ newsletter.updated_at }</abbr>
</td>
</div>
);
},
render: function() {
return (
<Listing
onRenderItem={this.renderItem}
items={this.getItems}
columns={columns}
actions={actions} />
);
}
});
return List;
});
}
);

View File

@@ -22,16 +22,15 @@ define(
render: function() {
return (
<div>
<header>
<ul>
<li>
<Link to="list">Newsletters</Link>
</li>
<li>
<Link to="form">New</Link>
</li>
</ul>
</header>
<h1>
{ MailPoetI18n.pageTitle }
<span>
<Link className="add-new-h2" to="list">Newsletters</Link>
</span>
<span>
<Link className="add-new-h2" to="form">New newsletter</Link>
</span>
</h1>
<RouteHandler/>
</div>
@@ -49,8 +48,11 @@ define(
var hook = document.getElementById('newsletters');
if (hook) {
Router.run(routes, function(Handler) {
React.render(<Handler/>, hook);
Router.run(routes, function(Handler, state) {
React.render(
<Handler params={state.params} query={state.query} />,
hook
);
});
}
});

View File

@@ -1,69 +0,0 @@
define('subscribers', ['react', 'jquery', 'mailpoet'], function(React, jQuery, MailPoet) {
var data = [
{
first_name: "John",
last_name: "Mailer",
email: 'john@mailpoet.com'
},
{
first_name: "Mark",
last_name: "Trailer",
email: 'mark@mailpoet.com'
}
];
var Subscriber = React.createClass({
render: function() {
return (
<div className="subscriber">
<h3 className="name">
{this.props.subscriber.first_name} {this.props.subscriber.last_name}
</h3>
{this.props.subscriber.email}
</div>
);
}
});
var SubscribersList = React.createClass({
load: function() {
MailPoet.Ajax.post({
endpoint: 'subscribers',
action: 'get',
data: {},
onSuccess: function(response) {
this.setState({data: response});
}.bind(this)
});
},
getInitialState: function() {
return {data: []};
},
componentDidMount: function() {
this.load();
setInterval(this.load, this.props.pollInterval);
},
render: function() {
var nodes = this.state.data.map(function (subscriber) {
return (
<Subscriber key={subscriber.id} subscriber={subscriber} />
);
});
return (
<div className="subscribersList">
{nodes}
</div>
);
}
});
var element = jQuery('#mailpoet_subscribers');
if(element.length > 0) {
React.render(
<SubscribersList data={data} pollInterval={2000} />,
element[0]
);
}
});

View File

@@ -0,0 +1,179 @@
define(
'list',
[
'react',
'jquery',
'mailpoet',
'listing/listing.jsx',
'classnames'
],
function(
React,
jQuery,
MailPoet,
Listing,
classNames
) {
var columns = [
{
name: 'email',
label: 'Email',
sortable: true
},
{
name: 'first_name',
label: 'Firstname',
sortable: true
},
{
name: 'last_name',
label: 'Lastname',
sortable: true
},
{
name: 'status',
label: 'Status',
sortable: true
},
{
name: 'created_at',
label: 'Subscribed on',
sortable: true
},
{
name: 'updated_at',
label: 'Last modified on',
sortable: true
},
];
var actions = [
{
name: 'move',
label: 'Move to list...',
onSelect: function(e) {
// display list selector
jQuery(e.target).after(
'<select id="bulk_action_list">'+
'<option value="">Select a list</option>'+
'<option value="1">List #1</option>'+
'<option value="2">List #2</option>'+
'<option value="3">List #3</option>'+
'</select>'
);
},
onApply: function(selected) {
var list = jQuery('#bulk_action_list').val();
MailPoet.Ajax.post({
endpoint: 'subscribers',
action: 'move',
data: {
selected: selected,
list: list
}
});
}
},
{
name: 'add',
label: 'Add to list...'
},
{
name: 'remove',
label: 'Remove from list...'
}
];
var List = React.createClass({
getItems: function(listing) {
MailPoet.Ajax.post({
endpoint: 'subscribers',
action: 'get',
data: {
offset: (listing.state.page - 1) * listing.state.limit,
limit: listing.state.limit,
group: listing.state.group,
search: listing.state.search,
sort_by: listing.state.sort_by,
sort_order: listing.state.sort_order
},
onSuccess: function(response) {
if(listing.isMounted()) {
listing.setState({
items: response.items || [],
filters: response.filters || [],
groups: response.groups || [],
count: response.count || 0,
loading: false
});
}
}.bind(listing)
});
},
renderItem: function(subscriber) {
var rowClasses = classNames(
'manage-column',
'column-primary',
'has-row-actions'
);
var status;
switch(parseInt(subscriber.status, 10)) {
case 1:
status = 'Subscribed';
break;
case 0:
status = 'Unconfirmed';
break;
case -1:
status = 'Unsubscribed';
break;
}
return (
<div>
<td className={ rowClasses }>
<strong>
<a>{ subscriber.email }</a>
</strong>
<button className="toggle-row" type="button">
<span className="screen-reader-text">Show more details</span>
</button>
</td>
<td className="column" data-colname="First name">
{ subscriber.first_name }
</td>
<td className="column" data-colname="Last name">
{ subscriber.last_name }
</td>
<td className="column" data-colname="Status">
{ status }
</td>
<td className="column-date" data-colname="Subscribed on">
<abbr>{ subscriber.created_at }</abbr>
</td>
<td className="column-date" data-colname="Last modified on">
<abbr>{ subscriber.updated_at }</abbr>
</td>
</div>
);
},
render: function() {
return (
<Listing
onRenderItem={this.renderItem}
items={this.getItems}
columns={columns}
actions={actions} />
);
}
});
return List;
}
);

View File

@@ -1,399 +0,0 @@
define(
'listing',
[
'mailpoet',
'jquery',
'react',
'classnames',
'listing/bulk_actions.jsx',
'listing/header.jsx',
'listing/pages.jsx',
'listing/search.jsx'
],
function(
MailPoet,
jQuery,
React,
classNames,
ListingBulkActions,
ListingHeader,
ListingPages,
ListingSearch
) {
var ListingGroups = React.createClass({
render: function() {
return (
<ul className="subsubsub">
<li>
<a className="current">
All
<span className="count">({ this.props.count })</span>
</a>&nbsp;|&nbsp;
</li>
<li>
<a>
Subscribed
<span className="count">(0)</span>
</a>
</li>
</ul>
);
}
});
var ListingFilters = React.createClass({
render: function() {
return (
<div></div>
);
}
});
var ListSelector = React.createClass({
getList: function(e) {
e.preventDefault();
MailPoet.Modal.popup({
title: 'Bulk action',
template: '',
onInit: function(modal) {
var target = modal.getContentContainer();
React.render(
<ListSelector />,
target[0]
);
}
})
},
render: function() {
return (
<div>
<select >
<option>Select a list</option>
</select>
<a onClick={this.getList}>Test me</a>
</div>
);
}
});
var ListingItem = React.createClass({
handleSelect: function(e) {
var is_checked = jQuery(e.target).is(':checked');
this.props.onSelect(
parseInt(e.target.value, 10),
is_checked
);
return !e.target.checked;
},
render: function() {
var rowClasses = classNames(
'title',
'column-title',
'has-row-actions',
'column-primary',
'page-title'
);
return (
<tr>
<th className="check-column" scope="row">
<label className="screen-reader-text">
{ 'Select ' + this.props.item.email }</label>
<input
type="checkbox"
defaultValue={ this.props.item.id }
defaultChecked={ this.props.item.selected }
onChange={ this.handleSelect } />
</th>
<td className={rowClasses}>
<strong>
<a className="row-title">{ this.props.item.email }</a>
</strong>
</td>
<td>
{ this.props.item.first_name }
</td>
<td>
{ this.props.item.last_name }
</td>
<td className="date column-date">
<abbr title="">{ this.props.item.created_at }</abbr>
</td>
<td className="date column-date">
<abbr title="">{ this.props.item.updated_at }</abbr>
</td>
</tr>
);
}
});
var ListingItems = React.createClass({
render: function() {
if(this.props.items.length === 0) {
return (
<tbody>
<td
colSpan={this.props.columns.length + 1}
className="colspanchange">No subscribers found.</td>
</tbody>
);
} else {
return (
<tbody>
{this.props.items.map(function(item) {
item.selected = (this.props.selected.indexOf(item.id) !== -1);
return (
<ListingItem
columns={this.props.columns}
onSelect={this.props.onSelect}
key={item.id}
item={item} />
);
}.bind(this))}
</tbody>
);
}
}
});
var Listing = React.createClass({
getInitialState: function() {
return {
loading: false,
search: '',
page: 1,
count: 0,
limit: 10,
sort_by: 'email',
sort_order: 'asc',
items: [],
selected: []
};
},
componentDidMount: function() {
this.getItems();
},
getItems: function() {
this.setState({ loading: true });
MailPoet.Ajax.post({
endpoint: 'subscribers',
action: 'get',
data: {
offset: (this.state.page - 1) * this.state.limit,
limit: this.state.limit,
search: this.state.search,
sort_by: this.state.sort_by,
sort_order: this.state.sort_order
},
onSuccess: function(response) {
if(this.isMounted()) {
this.setState({
items: response.items,
count: response.count,
loading: false
});
}
}.bind(this)
});
},
handleSearch: function(search) {
this.setState({ search: search }, function() {
this.getItems();
}.bind(this));
},
handleSort: function(sort_by, sort_order = 'asc') {
this.setState({
sort_by: sort_by,
sort_order: sort_order
}, function() {
this.getItems();
}.bind(this));
},
handleSelect: function(id, is_checked) {
var selected = this.state.selected;
if(is_checked) {
selected = jQuery.merge(selected, [ id ]);
} else {
selected.splice(selected.indexOf(id), 1);
}
this.setState({
selected: selected
});
},
handleSelectAll: function(is_checked) {
if(is_checked === false) {
this.setState({ selected: [] });
} else {
var selected = this.state.items.map(function(item) {
return ~~item.id;
});
this.setState({
selected: selected
});
}
},
handleSetPage: function(page) {
this.setState({ page: page }, function() {
this.getItems();
}.bind(this));
},
render: function() {
var items = this.state.items,
sort_by = this.state.sort_by,
sort_order = this.state.sort_order;
// set sortable columns
columns = columns.map(function(column) {
column.sorted = (column.name === sort_by) ? sort_order : false;
return column;
});
var tableClasses = classNames(
'wp-list-table',
'widefat',
'fixed',
'striped',
{ 'mailpoet_listing_loading': this.state.loading }
);
return (
<div>
<ListingGroups count={this.state.count} />
<ListingSearch
onSearch={this.handleSearch}
search={this.state.search} />
<div className="tablenav top clearfix">
<ListingBulkActions
actions={this.props.actions}
selected={this.state.selected} />
<ListingFilters />
<ListingPages
count={this.state.count}
page={this.state.page}
limit={this.state.limit}
onSetPage={this.handleSetPage} />
</div>
<table className={tableClasses}>
<thead>
<ListingHeader
onSort={this.handleSort}
onSelectAll={this.handleSelectAll}
sort_by={this.state.sort_by}
sort_order={this.state.sort_order}
columns={this.props.columns} />
</thead>
<ListingItems
columns={this.props.columns}
selected={this.state.selected}
onSelect={this.handleSelect}
items={items} />
<tfoot>
<ListingHeader
onSort={this.handleSort}
onSelectAll={this.handleSelectAll}
sort_by={this.state.sort_by}
sort_order={this.state.sort_order}
columns={this.props.columns} />
</tfoot>
</table>
<div className="tablenav bottom">
<ListingBulkActions
actions={this.props.actions}
selected={this.state.selected} />
<ListingPages
count={this.state.count}
page={this.state.page}
limit={this.state.limit}
onSetPage={this.handleSetPage} />
</div>
</div>
);
}
});
var columns = [
{
name: 'email',
label: 'Email',
sortable: true
},
{
name: 'first_name',
label: 'Firstname',
sortable: true
},
{
name: 'last_name',
label: 'Lastname',
sortable: true
},
{
name: 'created_at',
label: 'Subscribed on',
sortable: true
},
{
name: 'updated_at',
label: 'Last modified on',
sortable: true
},
];
var actions = [
{
name: 'move',
label: 'Move to...',
onSelect: function(e) {
// display list selector
jQuery(e.target).after(
'<select id="bulk_action_list">'+
'<option value="">Select a list</option>'+
'<option value="1">List #1</option>'+
'<option value="2">List #2</option>'+
'<option value="3">List #3</option>'+
'</select>'
);
},
onApply: function(selected) {
var list = jQuery('#bulk_action_list').val();
MailPoet.Ajax.post({
endpoint: 'subscribers',
action: 'move',
data: {
selected: selected,
list: list
}
});
}
},
{
name: 'add',
label: 'Add to...'
},
{
name: 'remove',
label: 'Remove from...'
}
];
var element = jQuery('#mailpoet_subscribers_listing');
if(element.length > 0) {
React.render(
<Listing columns={columns} actions={actions} />,
element[0]
);
}
}
);

View File

@@ -0,0 +1,52 @@
define(
'subscribers',
[
'react',
'react-router',
'subscribers/list.jsx'
],
function(
React,
Router,
List
) {
var DefaultRoute = Router.DefaultRoute;
var Link = Router.Link;
var Route = Router.Route;
var RouteHandler = Router.RouteHandler;
var App = React.createClass({
render: function() {
return (
<div>
<h1>
{ MailPoetI18n.pageTitle }
<span>
<Link className="add-new-h2" to="list">Subscribers</Link>
</span>
</h1>
<RouteHandler/>
</div>
);
}
});
var routes = (
<Route name="app" path="/" handler={App}>
<Route name="list" handler={List} />
<DefaultRoute handler={List} />
</Route>
);
var hook = document.getElementById('subscribers');
if(hook) {
Router.run(routes, function(Handler, state) {
React.render(
<Handler params={state.params} query={state.query} />,
hook
);
});
}
});

View File

@@ -5,6 +5,9 @@ if (!defined('ABSPATH')) exit;
class Subscriber extends Model {
public static $_table = MP_SUBSCRIBERS_TABLE;
const STATE_SUBSCRIBED = 1;
const STATE_UNCONFIRMED = 0;
const STATE_UNSUBSCRIBED = -1;
function __construct() {
parent::__construct();

View File

@@ -1,7 +1,6 @@
<?php
namespace MailPoet\Router;
use \MailPoet\Models\Newsletter;
use \MailPoet\Models\Subscriber;
use \MailPoet\Mailer\Bridge;
if(!defined('ABSPATH')) exit;
@@ -10,7 +9,51 @@ class Newsletters {
function __construct() {
}
function get() {
function get($data = array()) {
// pagination
$offset = (isset($data['offset']) ? (int)$data['offset'] : 0);
$limit = (isset($data['limit']) ? (int)$data['limit'] : 50);
// searching
$search = (isset($data['search']) ? $data['search'] : null);
// sorting
$sort_by = (isset($data['sort_by']) ? $data['sort_by'] : 'id');
$sort_order = (isset($data['sort_order']) ? $data['sort_order'] : 'asc');
// grouping
$group = (isset($data['group']) ? $data['group'] : null);
$groups = array(
array(
'name' => 'all',
'label' => __('All'),
'count' => Newsletter::count()
)
);
// instantiate subscriber collection
$collection = Newsletter::{'order_by_'.$sort_order}($sort_by);
// handle search
if($search !== null) {
$collection->where_like('subject', '%'.$search.'%');
}
// handle filters
$filters = array();
// return result
$collection = array(
'count' => $collection->count(),
'filters' => $filters,
'groups' => $groups,
'items' => $collection
->offset($offset)
->limit($limit)
->find_array()
);
wp_send_json($collection);
}
function getAll() {
$collection = Newsletter::find_array();
wp_send_json($collection);
}
@@ -37,7 +80,7 @@ class Newsletters {
function send($id) {
$newsletter = Newsletter::find_one($id)->as_array();
$subscribers = Subscriber::find_array();
$subscribers = Newsletter::find_array();
$mailer = new Bridge($newsletter, $subscribers);
wp_send_json($mailer->send());
}

View File

@@ -9,14 +9,58 @@ class Subscribers {
}
function get($data = array()) {
// pagination
$offset = (isset($data['offset']) ? (int)$data['offset'] : 0);
$limit = (isset($data['limit']) ? (int)$data['limit'] : 50);
// searching
$search = (isset($data['search']) ? $data['search'] : null);
// sorting
$sort_by = (isset($data['sort_by']) ? $data['sort_by'] : 'id');
$sort_order = (isset($data['sort_order']) ? $data['sort_order'] : 'desc');
$sort_order = (isset($data['sort_order']) ? $data['sort_order'] : 'asc');
// grouping
$group = (isset($data['group']) ? $data['group'] : null);
$groups = array(
array(
'name' => 'all',
'label' => __('All'),
'count' => Subscriber::count()
),
array(
'name' => 'subscribed',
'label' => __('Subscribed'),
'count' => Subscriber::where('status', Subscriber::STATE_SUBSCRIBED)->count()
),
array(
'name' => 'unconfirmed',
'label' => __('Unconfirmed'),
'count' => Subscriber::where('status', Subscriber::STATE_UNCONFIRMED)->count()
),
array(
'name' => 'unsubscribed',
'label' => __('Unsubscribed'),
'count' => Subscriber::where('status', Subscriber::STATE_UNSUBSCRIBED)->count()
)
);
// instantiate subscriber collection
$collection = Subscriber::{'order_by_'.$sort_order}($sort_by);
// handle group
switch($group) {
case 'subscribed':
$collection = $collection->where('status', Subscriber::STATE_SUBSCRIBED);
break;
case 'unconfirmed':
$collection = $collection->where('status', Subscriber::STATE_UNCONFIRMED);
break;
case 'unsubscribed':
$collection = $collection->where('status', Subscriber::STATE_UNSUBSCRIBED);
break;
}
// handle search
if($search !== null) {
$collection->where_raw(
'(`email` LIKE ? OR `first_name` LIKE ? OR `last_name` LIKE ?)',
@@ -24,14 +68,14 @@ class Subscribers {
);
}
// filters
$filters = array(
);
// handle filters
$filters = array();
// return result
$collection = array(
'count' => $collection->count(),
'filters' => $filters,
'groups' => $groups,
'items' => $collection
->offset($offset)
->limit($limit)

View File

@@ -5,20 +5,20 @@ class i18n extends \Twig_Extension {
private $_text_domain;
public function __construct($text_domain) {
function __construct($text_domain) {
// set text domain
$this->_text_domain = $text_domain;
}
public function getName() {
function getName() {
return 'i18n';
}
public function getFunctions() {
function getFunctions() {
// twig custom functions
$twig_functions = array();
// list of WP functions to map
$functions = array('__', '_n');
$functions = array('localize', '__', '_n');
foreach($functions as $function) {
$twig_functions[] = new \Twig_SimpleFunction(
@@ -30,13 +30,27 @@ class i18n extends \Twig_Extension {
return $twig_functions;
}
public function __() {
function localize() {
$args = func_get_args();
$translations = array_shift($args);
$output = array();
$output[] = '<script type="text/javascript">';
$output[] = ' var MailPoetI18n = MailPoetI18n || {}';
foreach($translations as $key => $translation) {
$output[] = 'MailPoetI18n[\''.$key.'\'] = "'.$translation.'";';
}
$output[] = '</script>';
return join("\n", $output);
}
function __() {
$args = func_get_args();
return call_user_func_array('__', $this->setTextDomain($args));
}
public function _n() {
function _n() {
$args = func_get_args();
return call_user_func_array('_n', $this->setTextDomain($args));

View File

@@ -2,4 +2,11 @@
<% block content %>
<div id="newsletters"></div>
<%= localize({
'pageTitle': __('Newsletters'),
'searchLabel': __('Search'),
'loading': __('Loading newsletters...'),
'noRecordFound': __('No newsletters found.')
}) %>
<% endblock %>

View File

@@ -1,6 +1,12 @@
<% extends 'layout.html' %>
<% block content %>
<h1><%= __('Subscribers') %></h1>
<div id="mailpoet_subscribers_listing"></div>
<div id="subscribers"></div>
<%= localize({
'pageTitle': __('Subscribers'),
'searchLabel': __('Search'),
'loading': __('Loading subscribers...'),
'noRecordFound': __('No subscribers found.')
}) %>
<% endblock %>

View File

@@ -60,12 +60,9 @@ config.push(_.extend({}, baseConfig, {
vendor: ['handlebars', 'handlebars_helpers'],
mailpoet: ['mailpoet', 'ajax', 'modal', 'notice'],
admin: [
'subscribers/listing.jsx',
'settings.jsx',
'subscribers.jsx',
'newsletters/newsletters.jsx',
'newsletters/list.jsx',
'newsletters/form.jsx'
'subscribers/subscribers.jsx',
'newsletters/newsletters.jsx'
],
newsletter_editor: [
'underscore',